"use client"; import { CheckIcon, ChevronDownIcon, ChevronRightIcon, ClockIcon, FileTextIcon, LockIcon, LogInIcon } from "lucide-react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import { type ReactNode, useEffect, useId, useState } from "react"; import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; import { Menu, MenuGroup, MenuGroupLabel, MenuItem, MenuPopup, MenuSeparator, MenuTrigger } from "@/components/ui/menu"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; export type AccessStatus = "denied" | "pending" | "granted"; export interface AccessPerson { name: string; email: string; initials: string; } export interface RequestAccessProps { workspace?: string; space?: string; doc?: string; owner?: AccessPerson; user?: AccessPerson; otherAccounts?: string[]; resendAfter?: number; status?: AccessStatus; defaultStatus?: AccessStatus; onStatusChange?: (status: AccessStatus) => void; onRequest?: (note: string) => void; onResend?: () => void; onCancel?: () => void; onSwitchAccount?: (email: string | null) => void; children?: ReactNode; className?: string; } const ease = [0.23, 1, 0.32, 1] as const; function Mark() { return ( ); } function Initials({ person, className }: { person: AccessPerson; className?: string }) { return ( {person.initials} ); } function RollingDigit({ digit }: { digit: string }) { const reduce = useReducedMotion(); return ( {digit} ); } function Countdown({ seconds }: { seconds: number }) { const text = `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, "0")}`; return ( {text.split("").map((c, i) => (c === ":" ? : : ))} ); } function DrawnCheck() { const reduce = useReducedMotion(); return ( ); } function ago(ms: number) { const s = Math.floor(ms / 1000); if (s < 45) return "just now"; const m = Math.round(s / 60); return m === 1 ? "1 min ago" : `${m} min ago`; } export function RequestAccess({ workspace = "Ridgeline", space = "Strategy", doc = "Q3 roadmap", owner = { name: "Mara Okafor", email: "mara@ridgeline.app", initials: "MO" }, user = { name: "Ada Lindqvist", email: "ada@ridgeline.app", initials: "AL" }, otherAccounts = [], resendAfter = 60, status: statusProp, defaultStatus = "denied", onStatusChange, onRequest, onResend, onCancel, onSwitchAccount, children, className, }: RequestAccessProps) { const reduce = useReducedMotion(); const uid = useId(); const [inner, setInner] = useState(defaultStatus); const status = statusProp ?? inner; const [email, setEmail] = useState(user.email); const [note, setNote] = useState(""); const [sending, setSending] = useState(false); const [sentAt, setSentAt] = useState(null); const [now, setNow] = useState(0); const ownerFirst = owner.name.split(" ")[0]; const setStatus = (next: AccessStatus) => { setInner(next); onStatusChange?.(next); }; useEffect(() => { if (status !== "pending") return; setNow(Date.now()); const t = setInterval(() => setNow(Date.now()), 1000); return () => clearInterval(t); }, [status]); useEffect(() => { if (!sending) return; const t = setTimeout(() => { setSending(false); setSentAt(Date.now()); setStatus("pending"); }, 700); return () => clearTimeout(t); }, [sending]); const request = () => { setSending(true); onRequest?.(note.trim()); }; const resend = () => { setSentAt(Date.now()); setNow(Date.now()); onResend?.(); }; const cancel = () => { setSentAt(null); setStatus("denied"); onCancel?.(); }; const switchTo = (next: string | null) => { if (next) setEmail(next); onSwitchAccount?.(next); }; const elapsed = sentAt && now ? Math.max(0, now - sentAt) : 0; const wait = sentAt ? Math.max(0, resendAfter - Math.floor(elapsed / 1000)) : resendAfter; const sentTime = sentAt ? new Intl.DateTimeFormat(undefined, { hour: "numeric", minute: "2-digit" }).format(sentAt) : ""; const swap = reduce ? { opacity: 0 } : { opacity: 0, y: 8, filter: "blur(4px)" }; return (
{status === "granted" ? ( {ownerFirst} gave you access.{" "} You can view and comment on this doc. {children} ) : ( {status === "denied" ? (

You don’t have access to {doc}

This doc is private to the {space} team. Ask {ownerFirst} for access and you’ll get an email when they reply.

You’re signed in as {email} }> Switch account Switch Accounts {[user.email, ...otherAccounts].map((a) => ( switchTo(a)}> {a} {a === email && } ))} switchTo(null)}> Sign in with another account