diff --git a/prisma/schema.prisma b/prisma/schema.prisma index c8b6536..09b781d 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -18,6 +18,7 @@ model User { displayName String? avatarUrl String @default("") role String @default("viewer") + activeOrgId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@ -77,6 +78,7 @@ model Organization { type String @default("church") timezone String @default("America/Chicago") settings Json? + allowedDomains String[] @default([]) onboardingComplete Boolean @default(false) onboardingStep Int @default(0) createdAt DateTime @default(now()) diff --git a/src/app/(auth)/signup/page.tsx b/src/app/(auth)/signup/page.tsx index d761b1a..6f0f44b 100644 --- a/src/app/(auth)/signup/page.tsx +++ b/src/app/(auth)/signup/page.tsx @@ -90,7 +90,7 @@ function SignupForm() {

{token ? "Complete your account setup" - : "You need an invitation to sign up"} + : "Get started with Echo OCR"}

diff --git a/src/app/(auth)/workspace-setup/page.tsx b/src/app/(auth)/workspace-setup/page.tsx new file mode 100644 index 0000000..bf6ec59 --- /dev/null +++ b/src/app/(auth)/workspace-setup/page.tsx @@ -0,0 +1,88 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { useSession } from "next-auth/react"; +import { ScanLine, Building2, Zap, Loader2 } from "lucide-react"; +import { Button } from "@/components/ui/button"; + +export default function WorkspaceSetupPage() { + const router = useRouter(); + const { update: updateSession } = useSession(); + const [loading, setLoading] = useState<"create" | "skip" | null>(null); + + async function handleCreate() { + router.push("/onboarding"); + } + + async function handleSkip() { + setLoading("skip"); + try { + const res = await fetch("/api/org/create-personal", { method: "POST" }); + if (!res.ok) { + const data = await res.json(); + console.error("Failed to create personal workspace:", data.error); + setLoading(null); + return; + } + await updateSession(); + router.push("/"); + } catch (err) { + console.error("Failed to create personal workspace:", err); + setLoading(null); + } + } + + return ( +
+
+
+ +
+

+ Set up your workspace +

+

+ Create a workspace for your team, or skip to get started with a personal one. +

+
+ +
+ + + +
+
+ ); +} diff --git a/src/app/(dashboard)/settings/organization/page.tsx b/src/app/(dashboard)/settings/organization/page.tsx index bd22232..9278420 100644 --- a/src/app/(dashboard)/settings/organization/page.tsx +++ b/src/app/(dashboard)/settings/organization/page.tsx @@ -3,7 +3,7 @@ import * as React from "react"; import { useSession } from "next-auth/react"; import { toast } from "sonner"; -import { Loader2, Save, Building2, Globe, AlertTriangle } from "lucide-react"; +import { Loader2, Save, Building2, Globe, AlertTriangle, X, Mail } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -23,6 +23,7 @@ import { } from "@/components/ui/select"; const ORG_TYPES = [ + { value: "personal", label: "Personal" }, { value: "church", label: "Church" }, { value: "ministry", label: "Ministry" }, { value: "nonprofit", label: "Nonprofit" }, @@ -35,6 +36,7 @@ type OrgData = { slug: string; type: string; timezone: string; + allowedDomains: string[]; }; export default function OrganizationSettingsPage() { @@ -42,6 +44,7 @@ export default function OrganizationSettingsPage() { const [org, setOrg] = React.useState(null); const [loading, setLoading] = React.useState(true); const [saving, setSaving] = React.useState(false); + const [domainInput, setDomainInput] = React.useState(""); React.useEffect(() => { fetch("/api/org") @@ -64,6 +67,7 @@ export default function OrganizationSettingsPage() { name: org.name, type: org.type, timezone: org.timezone, + allowedDomains: org.allowedDomains ?? [], }), }); if (res.ok) { @@ -154,6 +158,98 @@ export default function OrganizationSettingsPage() { + + + + + Allowed Email Domains + + + Users who sign up with an email at these domains will automatically join this workspace. + + + + {org.allowedDomains?.length > 0 && ( +
+ {org.allowedDomains.map((domain) => ( + + {domain} + + + ))} +
+ )} +
+ setDomainInput(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") { + e.preventDefault(); + const val = domainInput.trim().toLowerCase(); + if (val && !org.allowedDomains?.includes(val)) { + setOrg((prev) => + prev + ? { + ...prev, + allowedDomains: [...(prev.allowedDomains ?? []), val], + } + : prev + ); + setDomainInput(""); + } + } + }} + /> + +
+

+ Type a domain and press Enter or click Add. Changes are saved when you click Save Changes. +

+
+
+