diff --git a/actions/createNewLeaflet.ts b/actions/createNewLeaflet.ts index 29e2f840..afbb8b2e 100644 --- a/actions/createNewLeaflet.ts +++ b/actions/createNewLeaflet.ts @@ -21,10 +21,14 @@ export async function createNewLeaflet({ pageType, redirectUser, firstBlockType, + welcomeModal, + addToHome, }: { pageType: "canvas" | "doc"; redirectUser: boolean; firstBlockType?: "h1" | "text"; + welcomeModal?: boolean; + addToHome?: boolean; }) { let auth_token = (await cookies()).get("auth_token")?.value; const client = await pool.connect(); @@ -157,6 +161,9 @@ export async function createNewLeaflet({ }); client.release(); - if (redirectUser) redirect(`/${permissionToken.id}?focusFirstBlock`); + if (redirectUser) + redirect( + `/${permissionToken.id}?focusFirstBlock${welcomeModal ? "&welcomeModal" : ""}${addToHome ? "&addToHome" : ""}`, + ); return permissionToken.id; } diff --git a/app/[leaflet_id]/AddToHomeEffect.tsx b/app/[leaflet_id]/AddToHomeEffect.tsx new file mode 100644 index 00000000..d3d9927a --- /dev/null +++ b/app/[leaflet_id]/AddToHomeEffect.tsx @@ -0,0 +1,41 @@ +"use client"; + +import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { useEffect, useRef } from "react"; +import { mutate as swrMutate } from "swr"; +import { addDocToHome } from "app/(home-pages)/(writer)/home/storage"; +import { useIdentityData } from "components/IdentityProvider"; +import { useReplicache } from "src/replicache"; + +export function useAddToHomeParam() { + return useSearchParams().has("addToHome"); +} + +export function AddToHomeEffect() { + let searchParams = useSearchParams(); + let pathname = usePathname(); + let router = useRouter(); + let shouldAdd = searchParams.has("addToHome"); + let { permission_token } = useReplicache(); + let { identity } = useIdentityData(); + let ran = useRef(false); + + useEffect(() => { + if (!shouldAdd || ran.current) return; + ran.current = true; + + // Authed users are added to their homepage server-side in createNewLeaflet. + // Here we only need to handle the unauthed case via localStorage. + if (!identity) { + addDocToHome(permission_token); + swrMutate("leaflets"); + } + + let params = new URLSearchParams(searchParams.toString()); + params.delete("addToHome"); + let qs = params.toString(); + router.replace(qs ? `${pathname}?${qs}` : pathname); + }, [shouldAdd, identity, permission_token, router, pathname, searchParams]); + + return null; +} diff --git a/app/[leaflet_id]/Footer.tsx b/app/[leaflet_id]/Footer.tsx index 89a39eff..e48ad8d3 100644 --- a/app/[leaflet_id]/Footer.tsx +++ b/app/[leaflet_id]/Footer.tsx @@ -21,6 +21,7 @@ import { block } from "sharp"; import { PostSettings } from "components/PostSettings"; import useSWR from "swr"; import { getHomeDocs } from "app/(home-pages)/(writer)/home/storage"; +import { useAddToHomeParam } from "./AddToHomeEffect"; export function hasBlockToolbar(blockType: string | null | undefined) { return ( @@ -43,11 +44,14 @@ export function LeafletFooter(props: { entityID: string }) { }); let blockType = useEntity(focusedBlock?.entityID || null, "block/type")?.data .value; - let isOnHome = identity - ? !!identity.permission_token_on_homepage.find( - (pth) => pth.permission_tokens.id === permission_token.id, - ) - : !!localLeaflets.find((f) => f.token.id === permission_token.id); + let addingToHome = useAddToHomeParam(); + let isOnHome = + addingToHome || + (identity + ? !!identity.permission_token_on_homepage.find( + (pth) => pth.permission_tokens.id === permission_token.id, + ) + : !!localLeaflets.find((f) => f.token.id === permission_token.id)); let isOwnerOfPub = !!pub?.publications && !!identity?.atp_did && diff --git a/app/[leaflet_id]/Leaflet.tsx b/app/[leaflet_id]/Leaflet.tsx index 190de7bd..08dcfc83 100644 --- a/app/[leaflet_id]/Leaflet.tsx +++ b/app/[leaflet_id]/Leaflet.tsx @@ -12,6 +12,8 @@ import { EntitySetProvider } from "components/EntitySetProvider"; import { UpdateLeafletTitle } from "components/utils/UpdateLeafletTitle"; import { useUIState } from "src/useUIState"; import { LeafletLayout } from "components/LeafletLayout"; +import { WelcomeModal } from "./WelcomeModal"; +import { AddToHomeEffect } from "./AddToHomeEffect"; export function Leaflet(props: { token: PermissionToken; @@ -37,6 +39,8 @@ export function Leaflet(props: { > + + {/* we need the padding bottom here because if we don't have it the mobile footer will cut off... the dropshadow on the page... the padding is compensated by a negative top margin in mobile footer */} diff --git a/app/[leaflet_id]/Sidebar.tsx b/app/[leaflet_id]/Sidebar.tsx index 26582bf0..8402185c 100644 --- a/app/[leaflet_id]/Sidebar.tsx +++ b/app/[leaflet_id]/Sidebar.tsx @@ -18,6 +18,7 @@ import { useIdentityData } from "components/IdentityProvider"; import { useReplicache } from "src/replicache"; import useSWR from "swr"; import { getHomeDocs } from "app/(home-pages)/(writer)/home/storage"; +import { useAddToHomeParam } from "./AddToHomeEffect"; export function LeafletSidebar() { let entity_set = useEntitySetContext(); @@ -28,11 +29,14 @@ export function LeafletSidebar() { let { data: localLeaflets } = useSWR("leaflets", () => getHomeDocs(), { fallbackData: [], }); - let isOnHome = identity - ? !!identity.permission_token_on_homepage.find( - (pth) => pth.permission_tokens.id === permission_token.id, - ) - : !!localLeaflets.find((f) => f.token.id === permission_token.id); + let addingToHome = useAddToHomeParam(); + let isOnHome = + addingToHome || + (identity + ? !!identity.permission_token_on_homepage.find( + (pth) => pth.permission_tokens.id === permission_token.id, + ) + : !!localLeaflets.find((f) => f.token.id === permission_token.id)); let isOwnerOfPub = !!pub?.publications && !!identity?.atp_did && diff --git a/app/[leaflet_id]/WelcomeModal.tsx b/app/[leaflet_id]/WelcomeModal.tsx new file mode 100644 index 00000000..60ab0993 --- /dev/null +++ b/app/[leaflet_id]/WelcomeModal.tsx @@ -0,0 +1,28 @@ +"use client"; + +import { Modal } from "components/Modal"; +import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { useState } from "react"; + +export function WelcomeModal() { + let searchParams = useSearchParams(); + let pathname = usePathname(); + let router = useRouter(); + let [open, setOpen] = useState(searchParams.has("welcomeModal")); + + let handleOpenChange = (next: boolean) => { + setOpen(next); + if (!next) { + let params = new URLSearchParams(searchParams.toString()); + params.delete("welcomeModal"); + let qs = params.toString(); + router.replace(qs ? `${pathname}?${qs}` : pathname); + } + }; + + return ( + +

Placeholder welcome content.

+
+ ); +} diff --git a/app/about/LandingCTA.tsx b/app/about/LandingCTA.tsx index 8ef74d27..51fc248f 100644 --- a/app/about/LandingCTA.tsx +++ b/app/about/LandingCTA.tsx @@ -20,7 +20,7 @@ export function LandingCTA() { onOpenChange={setLoginOpen} redirectRoute="/home" /> - + Start Writing diff --git a/app/about/Pricing.tsx b/app/about/Pricing.tsx index 40a2ca35..4dc94974 100644 --- a/app/about/Pricing.tsx +++ b/app/about/Pricing.tsx @@ -40,12 +40,14 @@ export const Pricing = () => {

Basic Leaflet is free for everyone

- - Start Writing - + + + Start Writing + +
    diff --git a/app/new/route.ts b/app/new/route.ts index 1a917326..6148842d 100644 --- a/app/new/route.ts +++ b/app/new/route.ts @@ -4,6 +4,13 @@ export const preferredRegion = ["sfo1"]; export const dynamic = "force-dynamic"; export const fetchCache = "force-no-store"; -export async function GET() { - await createNewLeaflet({ pageType: "doc", redirectUser: true }); +export async function GET(request: Request) { + let url = new URL(request.url); + let welcomeModal = url.searchParams.has("welcomeModal"); + await createNewLeaflet({ + pageType: "doc", + redirectUser: true, + welcomeModal, + addToHome: true, + }); }