diff --git a/apps/dashboard/app/(app)/settings/team/client.tsx b/apps/dashboard/app/(app)/settings/team/client.tsx
new file mode 100644
index 0000000000..43dc72dba3
--- /dev/null
+++ b/apps/dashboard/app/(app)/settings/team/client.tsx
@@ -0,0 +1,342 @@
+"use client";
+import { Badge } from "@/components/ui/badge";
+import { Empty } from "@unkey/ui";
+import { Button } from "@unkey/ui";
+import type React from "react";
+import { useState } from "react";
+import { InviteButton } from "./invite";
+
+import Confirm from "@/components/dashboard/confirm";
+import { PageHeader } from "@/components/dashboard/page-header";
+import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
+import { useAuth, useClerk, useOrganization } from "@clerk/nextjs";
+
+import { Loading } from "@/components/dashboard/loading";
+import { Navbar as SubMenu } from "@/components/dashboard/navbar";
+import { Navigation } from "@/components/navigation/navigation";
+import { PageContent } from "@/components/page-content";
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { toast } from "@/components/ui/toaster";
+import type { MembershipRole } from "@clerk/types";
+import { Gear } from "@unkey/icons";
+import Link from "next/link";
+import { navigation } from "../constants";
+
+type Member = {
+ id: string;
+ name: string;
+ image: string;
+ role: MembershipRole;
+ email?: string;
+};
+
+export default function TeamPage({ team }: { team: boolean }) {
+ const { user, organization } = useClerk();
+
+ if (organization && !team) {
+ return (
+
+
} />
+
+
+
+
+ Invites are not available on the Free tier
+
+ Please upgrade your workspace to a paid plan to enable invites.
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+ if (!organization) {
+ return (
+
+
} />
+
+
+
+
+ Invites are not available on the Free tier
+
+ Please create a workspace and upgrade to the pro tier.
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+ const isAdmin =
+ user?.organizationMemberships.find((m) => m.organization.id === organization.id)?.role ===
+ "admin";
+
+ type Tab = "members" | "invitations";
+ const [tab, setTab] = useState("members");
+
+ const actions: React.ReactNode[] = [];
+
+ if (isAdmin) {
+ actions.push(
+ ,
+ );
+ }
+
+ if (isAdmin) {
+ actions.push();
+ }
+
+ return (
+
+
} name="Settings" />
+
+
+
+
+
+ {tab === "members" ?
:
}
+
+
+
+ );
+}
+
+const Members: React.FC = () => {
+ const { user } = useClerk();
+
+ const { isLoaded, membershipList, membership, organization } = useOrganization({
+ membershipList: { limit: 20, offset: 0 },
+ });
+
+ if (!isLoaded) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
+ Member
+ Role
+
+ {/*/ empty */}
+
+
+
+ {membershipList?.map(({ id, role, publicUserData }) => (
+
+
+
+
+
+ {publicUserData.identifier.slice(0, 2)}
+
+
+ {`${
+ publicUserData.firstName ? publicUserData.firstName : publicUserData.identifier
+ } ${publicUserData.lastName ? publicUserData.lastName : ""}`}
+
+ {publicUserData.firstName ? publicUserData.identifier : ""}
+
+
+
+
+
+
+
+
+ {membership?.role === "admin" && publicUserData.userId !== user?.id ? (
+ {
+ if (publicUserData.userId) {
+ organization?.removeMember(publicUserData.userId);
+ }
+ }}
+ trigger={}
+ />
+ ) : null}
+
+
+ ))}
+
+
+ );
+};
+
+const Invitations: React.FC = () => {
+ const { isLoaded, invitationList } = useOrganization({
+ invitationList: { limit: 20, offset: 0 },
+ });
+
+ if (!isLoaded) {
+ return (
+
+ );
+ }
+
+ if (!invitationList || invitationList.length === 0) {
+ return (
+
+ No pending invitations
+ Invite members to your team
+
+
+ );
+ }
+
+ return (
+
+
+
+ Email
+ Status
+
+ {/*/ empty */}
+
+
+
+ {invitationList?.map((invitation) => (
+
+
+ {invitation.emailAddress}
+
+
+
+
+
+
+
+
+
+ ))}
+
+
+ );
+};
+
+const RoleSwitcher: React.FC<{
+ member: { id: string; role: Member["role"] };
+}> = ({ member }) => {
+ const [role, setRole] = useState(member.role);
+ const [isLoading, setLoading] = useState(false);
+ const { organization, membership } = useOrganization();
+ const { userId } = useAuth();
+ async function updateRole(role: Member["role"]) {
+ try {
+ setLoading(true);
+ if (!organization) {
+ return;
+ }
+ await organization?.updateMember({ userId: member.id, role });
+
+ setRole(role);
+ toast.success("Role updated");
+ } catch (err) {
+ console.error(err);
+ toast.error((err as Error).message);
+ } finally {
+ setLoading(false);
+ }
+ }
+
+ if (!membership) {
+ return null;
+ }
+
+ if (membership.role === "admin") {
+ return (
+
+ );
+ }
+
+ return {role === "admin" ? "Admin" : "Member"};
+};
+
+const StatusBadge: React.FC<{ status: "pending" | "accepted" | "revoked" }> = ({ status }) => {
+ switch (status) {
+ case "pending":
+ return Pending;
+ case "accepted":
+ return Accepted;
+ case "revoked":
+ return Revoked;
+
+ default:
+ return null;
+ }
+};
diff --git a/apps/dashboard/app/(app)/settings/team/page.tsx b/apps/dashboard/app/(app)/settings/team/page.tsx
index ef79641e87..a6ba4597ac 100644
--- a/apps/dashboard/app/(app)/settings/team/page.tsx
+++ b/apps/dashboard/app/(app)/settings/team/page.tsx
@@ -1,317 +1,22 @@
-"use client";
-import { Badge } from "@/components/ui/badge";
-import { Empty } from "@unkey/ui";
-import { Button } from "@unkey/ui";
-import type React from "react";
-import { useState } from "react";
-import { InviteButton } from "./invite";
-
-import Confirm from "@/components/dashboard/confirm";
-import { PageHeader } from "@/components/dashboard/page-header";
-import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
-import {
- Table,
- TableBody,
- TableCell,
- TableHead,
- TableHeader,
- TableRow,
-} from "@/components/ui/table";
-import { useAuth, useClerk, useOrganization } from "@clerk/nextjs";
-
-import { Loading } from "@/components/dashboard/loading";
-import { Navbar as SubMenu } from "@/components/dashboard/navbar";
-import { Navigation } from "@/components/navigation/navigation";
-import { PageContent } from "@/components/page-content";
-import {
- Select,
- SelectContent,
- SelectGroup,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import { toast } from "@/components/ui/toaster";
-import type { MembershipRole } from "@clerk/types";
-import { Gear } from "@unkey/icons";
-import Link from "next/link";
-import { navigation } from "../constants";
-
-type Member = {
- id: string;
- name: string;
- image: string;
- role: MembershipRole;
- email?: string;
-};
-
-export default function TeamPage() {
- const { user, organization } = useClerk();
-
- if (!organization) {
- return (
-
-
} />
-
-
-
-
- This is a personal account
- You can only manage teams in paid workspaces.
-
-
-
-
-
-
-
-
-
- );
- }
-
- const isAdmin =
- user?.organizationMemberships.find((m) => m.organization.id === organization.id)?.role ===
- "admin";
-
- type Tab = "members" | "invitations";
- const [tab, setTab] = useState("members");
-
- const actions: React.ReactNode[] = [];
-
- if (isAdmin) {
- actions.push(
- ,
- );
- }
+import { getTenantId } from "@/lib/auth";
+import { db } from "@/lib/db";
+import TeamPage from "./client";
+
+export const revalidate = 0;
+
+export default async function SettingsKeysPage() {
+ const tenantId = getTenantId();
+ const ws = await db.query.workspaces.findFirst({
+ where: (table, { and, eq, isNull }) =>
+ and(eq(table.tenantId, tenantId), isNull(table.deletedAtM)),
+ with: { quota: true },
+ });
- if (isAdmin) {
- actions.push();
- }
+ const team = ws?.quota?.team ?? false;
return (
-
} name="Settings" />
-
-
-
-
-
- {tab === "members" ?
:
}
-
-
+
);
}
-
-const Members: React.FC = () => {
- const { user } = useClerk();
-
- const { isLoaded, membershipList, membership, organization } = useOrganization({
- membershipList: { limit: 20, offset: 0 },
- });
-
- if (!isLoaded) {
- return (
-
- );
- }
-
- return (
-
-
-
- Member
- Role
-
- {/*/ empty */}
-
-
-
- {membershipList?.map(({ id, role, publicUserData }) => (
-
-
-
-
-
- {publicUserData.identifier.slice(0, 2)}
-
-
- {`${
- publicUserData.firstName ? publicUserData.firstName : publicUserData.identifier
- } ${publicUserData.lastName ? publicUserData.lastName : ""}`}
-
- {publicUserData.firstName ? publicUserData.identifier : ""}
-
-
-
-
-
-
-
-
- {membership?.role === "admin" && publicUserData.userId !== user?.id ? (
- {
- if (publicUserData.userId) {
- organization?.removeMember(publicUserData.userId);
- }
- }}
- trigger={}
- />
- ) : null}
-
-
- ))}
-
-
- );
-};
-
-const Invitations: React.FC = () => {
- const { isLoaded, invitationList } = useOrganization({
- invitationList: { limit: 20, offset: 0 },
- });
-
- if (!isLoaded) {
- return (
-
- );
- }
-
- if (!invitationList || invitationList.length === 0) {
- return (
-
- No pending invitations
- Invite members to your team
-
-
- );
- }
-
- return (
-
-
-
- Email
- Status
-
- {/*/ empty */}
-
-
-
- {invitationList?.map((invitation) => (
-
-
- {invitation.emailAddress}
-
-
-
-
-
-
-
-
-
- ))}
-
-
- );
-};
-
-const RoleSwitcher: React.FC<{
- member: { id: string; role: Member["role"] };
-}> = ({ member }) => {
- const [role, setRole] = useState(member.role);
- const [isLoading, setLoading] = useState(false);
- const { organization, membership } = useOrganization();
- const { userId } = useAuth();
- async function updateRole(role: Member["role"]) {
- try {
- setLoading(true);
- if (!organization) {
- return;
- }
- await organization?.updateMember({ userId: member.id, role });
-
- setRole(role);
- toast.success("Role updated");
- } catch (err) {
- console.error(err);
- toast.error((err as Error).message);
- } finally {
- setLoading(false);
- }
- }
-
- if (!membership) {
- return null;
- }
-
- if (membership.role === "admin") {
- return (
-
- );
- }
-
- return {role === "admin" ? "Admin" : "Member"};
-};
-
-const StatusBadge: React.FC<{ status: "pending" | "accepted" | "revoked" }> = ({ status }) => {
- switch (status) {
- case "pending":
- return Pending;
- case "accepted":
- return Accepted;
- case "revoked":
- return Revoked;
-
- default:
- return null;
- }
-};