diff --git a/src/app/create-org/page.tsx b/src/app/create-org/page.tsx new file mode 100644 index 00000000..4e4446ac --- /dev/null +++ b/src/app/create-org/page.tsx @@ -0,0 +1,23 @@ +"use client"; + +import { CreateOrg } from "@/components/organization/create-org"; +import { toast } from "sonner"; +import { useRouter } from "next/navigation"; +import { authClient } from "@/lib/auth-client"; + +export default function CreateOrgPage() { + const router = useRouter(); + const { useActiveOrganization } = authClient; + const { data: activeOrganization } = useActiveOrganization(); + + const onSuccess = () => { + toast.success("Organization created successfully"); + router.push(`/${activeOrganization?.slug}/localhost/query`); + }; + + return ( +
+ +
+ ); +} diff --git a/src/app/onboarding/create-org/page.tsx b/src/app/onboarding/create-org/page.tsx new file mode 100644 index 00000000..ea19fd45 --- /dev/null +++ b/src/app/onboarding/create-org/page.tsx @@ -0,0 +1,25 @@ +"use client"; + +import { toast } from "sonner"; +import { useRouter } from "next/navigation"; +import { CreateOrg } from "@/components/organization/create-org"; +import { authClient } from "@/lib/auth-client"; + +export default function OnboardingCreateOrgPage() { + const { useActiveOrganization } = authClient; + const { data: activeOrganization } = useActiveOrganization(); + const router = useRouter(); + + const onSuccess = () => { + toast.success("Organization created successfully"); + router.push(`/${activeOrganization?.slug}/localhost/query`); + + // TODO + // router.push("/onboarding/pricing"); + }; + return ( +
+ +
+ ); +} diff --git a/src/app/onboarding/page.tsx b/src/app/onboarding/pricing/page.tsx similarity index 78% rename from src/app/onboarding/page.tsx rename to src/app/onboarding/pricing/page.tsx index b296e896..6be04a64 100644 --- a/src/app/onboarding/page.tsx +++ b/src/app/onboarding/pricing/page.tsx @@ -1,12 +1,8 @@ "use client"; -import { useSearchParams } from "next/navigation"; import OnboardingPricing from "@/components/onboarding/pricing"; export default function OnboardingPage() { - const searchParams = useSearchParams(); - const step = searchParams.get("step"); - return (
diff --git a/src/app/settings/org/create-org.tsx b/src/app/settings/org/create-org.tsx deleted file mode 100644 index 763ce58f..00000000 --- a/src/app/settings/org/create-org.tsx +++ /dev/null @@ -1,79 +0,0 @@ -"use client"; - -import { Button } from "@/components/ui/button"; -import { - Card, - CardHeader, - CardTitle, - CardDescription, - CardContent, - CardFooter, -} from "@/components/ui/card"; -import { Input } from "@/components/ui/input"; -import { authClient } from "@/lib/auth-client"; -import { Loader2 } from "lucide-react"; -import { useState } from "react"; -import { toast } from "sonner"; - -export const CreateOrg = () => { - const { organization, useSession } = authClient; - const { data: session } = useSession(); - const [orgName, setOrgName] = useState(""); - const [isLoading, setIsLoading] = useState(false); - - const handleCreateOrg = async () => { - setIsLoading(true); - await organization.create( - { - name: orgName, - slug: orgName, - userId: session?.user.id, - metadata: { - createdBy: session?.user.id, - }, - }, - { - onSuccess: () => { - toast.success("Organization created successfully"); - setIsLoading(false); - }, - onError: (error) => { - toast.error(`Failed to create organization: ${error.error.message}`); - console.error(error); - setIsLoading(false); - }, - }, - ); - }; - - return ( - - - Create New Organization - - Create a new organization and become its owner - - - - setOrgName(e.target.value)} - /> - - - - - - ); -}; diff --git a/src/app/settings/org/org-setting-form.tsx b/src/app/settings/org/org-setting-form.tsx index a945bd61..6e4e25ab 100644 --- a/src/app/settings/org/org-setting-form.tsx +++ b/src/app/settings/org/org-setting-form.tsx @@ -36,6 +36,7 @@ import { } from "@/components/ui/select"; import { Member, Invitation, Organization } from "better-auth/plugins"; import { User } from "better-auth"; +import { useRouter } from "next/navigation"; interface Role { value: "owner" | "admin" | "member"; @@ -74,7 +75,13 @@ export const OrgSettingForm = ({ activeOrganization, user, }: OrgSettingFormProps) => { - const { organization } = authClient; + const router = useRouter(); + const { organization, useListOrganizations } = authClient; + const { data: allOrganizations } = useListOrganizations(); + + const isLastOrganization = !!( + allOrganizations && allOrganizations.length <= 1 + ); const [email, setEmail] = useState(""); const [orgName, setOrgName] = useState(""); @@ -208,14 +215,33 @@ export const OrgSettingForm = ({ }; const handleDeleteOrg = async () => { + if (isLastOrganization) { + toast.error( + "Cannot delete your last organization. Create a new organization before deleting this one.", + ); + return; + } setIsDeletingOrg(true); await organization.delete( { organizationId: activeOrganization?.id, }, { - onSuccess: () => { + onSuccess: async () => { toast.success("Organization deleted successfully"); + + const remainingOrgs = allOrganizations?.filter( + (org) => org.id !== activeOrganization?.id, + ); + + if (!remainingOrgs || remainingOrgs.length === 0) return; + + await organization.setActive({ + organizationId: remainingOrgs[0].id, + }); + toast.info(`Switched to ${remainingOrgs[0].name}`); + router.refresh(); + setIsDeletingOrg(false); }, onError: (error) => { @@ -534,28 +560,6 @@ export const OrgSettingForm = ({ } - {/* Create New Organization */} - - - Create New Organization - - Create a new organization and become its owner - - - -
- setOrgName(e.target.value)} - /> - -
-
-
- {/* Danger Zone */} { @@ -573,14 +577,16 @@ export const OrgSettingForm = ({ Delete Organization

- Permanently delete this organization and all its data + {isLastOrganization + ? "You must have at least one organization. Create another organization before deleting this one." + : "Permanently delete this organization and all its data"}

+ + +
+ +
+ + Or + +
+
+

Join existing organization

+

+ Please contact an administrator of the existing
+ organization and request an invitation. +

+
+ + ); +}; diff --git a/src/stores/organization-store.ts b/src/stores/organization-store.ts new file mode 100644 index 00000000..41f4ed0a --- /dev/null +++ b/src/stores/organization-store.ts @@ -0,0 +1,21 @@ +import { create } from "zustand"; +import { persist } from "zustand/middleware"; + +interface OrganizationStore { + lastActiveOrgId: string | null; + setLastActiveOrgId: (orgId: string) => void; + clearLastActiveOrgId: () => void; +} + +export const useOrganizationStore = create()( + persist( + (set) => ({ + lastActiveOrgId: null, + setLastActiveOrgId: (orgId) => set({ lastActiveOrgId: orgId }), + clearLastActiveOrgId: () => set({ lastActiveOrgId: null }), + }), + { + name: "organization-storage", + }, + ), +);