import { useRouter, useSearchParams } from "next/navigation"; import { t } from "@lingui/core/macro"; import { AnimatePresence, motion } from "framer-motion"; import { useEffect, useMemo, useState } from "react"; import { HiArrowLeft, HiArrowPath, HiArrowRight, HiCheck, HiEllipsisVertical, HiInformationCircle, HiLockClosed, } from "react-icons/hi2"; import { authClient } from "@kan/auth/client"; import { generateUID } from "@kan/shared/utils"; import Button from "~/components/Button"; import Input from "~/components/Input"; import LoadingSpinner from "~/components/LoadingSpinner"; import Toggle from "~/components/Toggle"; import { Tooltip } from "~/components/Tooltip"; import { useDebounce } from "~/hooks/useDebounce"; import { usePopup } from "~/providers/popup"; import { api } from "~/utils/api"; function slugify(value: string) { return value .toLowerCase() .trim() .replace(/[^a-z0-9\s-]/g, "") .replace(/\s+/g, "-") .replace(/-+/g, "-") .slice(0, 60); } export default function WorkspaceNameView() { const router = useRouter(); const searchParams = useSearchParams(); const plan = searchParams.get("plan") ?? "solo"; const billing = searchParams.get("billing") ?? "annual"; const { showPopup } = usePopup(); const [isProToggle, setIsProToggle] = useState(plan === "pro"); const [name, setName] = useState(""); const [slug, setSlug] = useState(""); const [description, setDescription] = useState(""); const generatedUid = useMemo(() => generateUID(), []); const [debouncedSlug] = useDebounce(slug, 500); const isTyping = slug !== debouncedSlug; const slugAvailability = api.workspace.checkSlugAvailability.useQuery( { workspaceSlug: debouncedSlug }, { enabled: isProToggle && debouncedSlug.length >= 3 && !isTyping }, ); const isSlugAvailable = slugAvailability.data?.isAvailable && !slugAvailability.data?.isReserved; const isSlugTaken = slugAvailability.data?.isAvailable === false && !slugAvailability.data?.isReserved; const isSlugReserved = slugAvailability.data?.isReserved === true; const slugError = isSlugTaken ? t`This URL has already been taken` : isSlugReserved ? t`This URL is reserved` : undefined; const { data: session } = authClient.useSession(); const { data: user } = api.user.getUser.useQuery(undefined, { enabled: !!session?.user, }); const utils = api.useUtils(); const previewSlug = isProToggle ? slugify(slug) || slugify(name) || "your-workspace" : "your-workspace"; const createWorkspace = api.workspace.create.useMutation({ onSuccess: async (workspace) => { if (!workspace.publicId) return; localStorage.setItem("workspacePublicId", workspace.publicId); localStorage.removeItem("onboardingWorkspaceSlug"); void utils.workspace.all.invalidate(); if (plan !== "solo") { try { const response = await fetch("/api/stripe/create_checkout_session", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ workspacePublicId: workspace.publicId, cancelUrl: "/boards", successUrl: "/boards", billing, }), }); const data = await response.json(); const url = (data as { url: string }).url; if (url) { window.location.href = url; return; } } catch { // fall through } } router.push("/boards"); }, onError: () => { showPopup({ header: t`Unable to create workspace`, message: t`Please try again later, or contact customer support.`, icon: "error", }); }, }); const handleContinue = () => { if (!name.trim()) return; createWorkspace.mutate({ name: name.trim() }); }; useEffect(() => { document.getElementById("workspace-name-input")?.focus(); }, []); const displayName = user?.name ?? session?.user.name ?? ""; const BOARDS = [t`Roadmap`, t`Engineering`, t`Marketing`]; const [visibleCount, setVisibleCount] = useState(1); useEffect(() => { const timers = BOARDS.slice(1).map((_, i) => setTimeout(() => setVisibleCount(i + 2), (i + 1) * 2000), ); return () => timers.forEach(clearTimeout); }, []); return (
{t`You can always change these later in settings.`}
{name || t`Your workspace`}
{description || t`Your workspace description`}
{/* Boards area */}kan.bn