migrate organizations from clerk to in-house
This commit is contained in:
@@ -18,25 +18,21 @@ export default function LayoutOrganizationSelector({
|
||||
const router = useRouter();
|
||||
|
||||
const organization = organizations.find(
|
||||
(item) => item.slug === params.organizationSlug
|
||||
(item) => item.id === params.organizationSlug
|
||||
);
|
||||
|
||||
if (!organization) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Combobox
|
||||
className="w-full"
|
||||
placeholder="Select organization"
|
||||
icon={Building}
|
||||
value={organization.slug}
|
||||
value={organization?.id}
|
||||
items={
|
||||
organizations
|
||||
.filter((item) => item.slug)
|
||||
.filter((item) => item.id)
|
||||
.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.slug,
|
||||
value: item.id,
|
||||
})) ?? []
|
||||
}
|
||||
onChange={(value) => {
|
||||
|
||||
@@ -27,7 +27,7 @@ export default async function AppLayout({
|
||||
getDashboardsByProjectId(projectId),
|
||||
]);
|
||||
|
||||
if (!organizations.find((item) => item.slug === organizationSlug)) {
|
||||
if (!organizations.find((item) => item.id === organizationSlug)) {
|
||||
return (
|
||||
<FullPageEmptyState title="Not found" className="min-h-screen">
|
||||
The organization you were looking for could not be found.
|
||||
|
||||
@@ -53,6 +53,9 @@ export default function CreateInvite({ projects }: Props) {
|
||||
closeSheet();
|
||||
router.refresh();
|
||||
},
|
||||
onError() {
|
||||
toast.error('Failed to invite user');
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { Dot } from '@/components/dot';
|
||||
import { TooltipComplete } from '@/components/tooltip-complete';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -20,9 +19,9 @@ import {
|
||||
} from '@/components/ui/table';
|
||||
import { Widget, WidgetHead } from '@/components/widget';
|
||||
import { api } from '@/trpc/client';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { MoreHorizontalIcon } from 'lucide-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { pathOr } from 'ramda';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import type { IServiceInvite, IServiceProject } from '@openpanel/db';
|
||||
@@ -44,10 +43,9 @@ const Invites = ({ invites, projects }: Props) => {
|
||||
<Table className="mini">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Mail</TableHead>
|
||||
<TableHead>Role</TableHead>
|
||||
<TableHead>Created</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead>Access</TableHead>
|
||||
<TableHead>More</TableHead>
|
||||
</TableRow>
|
||||
@@ -66,18 +64,9 @@ interface ItemProps extends IServiceInvite {
|
||||
projects: IServiceProject[];
|
||||
}
|
||||
|
||||
function Item({
|
||||
id,
|
||||
email,
|
||||
role,
|
||||
createdAt,
|
||||
projects,
|
||||
publicMetadata,
|
||||
status,
|
||||
organizationId,
|
||||
}: ItemProps) {
|
||||
function Item({ id, email, role, createdAt, projects, meta }: ItemProps) {
|
||||
const router = useRouter();
|
||||
const access = (publicMetadata?.access ?? []) as string[];
|
||||
const access = pathOr<string[]>([], ['access'], meta);
|
||||
const revoke = api.organization.revokeInvite.useMutation({
|
||||
onSuccess() {
|
||||
toast.success(`Invite for ${email} revoked`);
|
||||
@@ -96,17 +85,6 @@ function Item({
|
||||
{new Date(createdAt).toLocaleDateString()}
|
||||
</TooltipComplete>
|
||||
</TableCell>
|
||||
<TableCell className="flex items-center gap-2 capitalize">
|
||||
<Dot
|
||||
className={cn(
|
||||
status === 'accepted' && 'bg-emerald-600',
|
||||
status === 'revoked' && 'bg-red-600',
|
||||
status === 'pending' && 'bg-orange-600'
|
||||
)}
|
||||
animated={status === 'pending'}
|
||||
/>
|
||||
{status}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{access.map((id) => {
|
||||
const project = projects.find((p) => p.id === id);
|
||||
@@ -136,7 +114,7 @@ function Item({
|
||||
<DropdownMenuItem
|
||||
className="text-destructive"
|
||||
onClick={() => {
|
||||
revoke.mutate({ organizationId, invitationId: id });
|
||||
revoke.mutate({ memberId: id });
|
||||
}}
|
||||
>
|
||||
Revoke invite
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { TooltipComplete } from '@/components/tooltip-complete';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ComboboxAdvanced } from '@/components/ui/combobox-advanced';
|
||||
import {
|
||||
@@ -62,10 +63,10 @@ interface ItemProps extends IServiceMember {
|
||||
|
||||
function Item({
|
||||
id,
|
||||
name,
|
||||
user,
|
||||
role,
|
||||
organizationId,
|
||||
createdAt,
|
||||
organization,
|
||||
projects,
|
||||
access: prevAccess,
|
||||
}: ItemProps) {
|
||||
@@ -73,22 +74,35 @@ function Item({
|
||||
const mutation = api.organization.updateMemberAccess.useMutation();
|
||||
const revoke = api.organization.removeMember.useMutation({
|
||||
onSuccess() {
|
||||
toast.success(`${name} has been removed from the organization`);
|
||||
toast.success(
|
||||
`${user?.firstName} has been removed from the organization`
|
||||
);
|
||||
router.refresh();
|
||||
},
|
||||
onError() {
|
||||
toast.error(`Failed to remove ${name} from the organization`);
|
||||
toast.error(`Failed to remove ${user?.firstName} from the organization`);
|
||||
},
|
||||
});
|
||||
const [access, setAccess] = useState<string[]>(
|
||||
prevAccess.map((item) => item.projectId)
|
||||
);
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TableRow key={id}>
|
||||
<TableCell className="font-medium">{name}</TableCell>
|
||||
<TableCell className="font-medium">
|
||||
<div>{[user?.firstName, user?.lastName].filter(Boolean).join(' ')}</div>
|
||||
<div className="text-sm text-muted-foreground">{user?.email}</div>
|
||||
</TableCell>
|
||||
<TableCell>{role}</TableCell>
|
||||
<TableCell>{new Date(createdAt).toLocaleString()}</TableCell>
|
||||
<TableCell>
|
||||
<TooltipComplete content={new Date(createdAt).toLocaleString()}>
|
||||
{new Date(createdAt).toLocaleDateString()}
|
||||
</TooltipComplete>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<ComboboxAdvanced
|
||||
placeholder="Restrict access to projects"
|
||||
@@ -96,8 +110,8 @@ function Item({
|
||||
onChange={(newAccess) => {
|
||||
setAccess(newAccess);
|
||||
mutation.mutate({
|
||||
userId: id!,
|
||||
organizationSlug: organization.slug,
|
||||
userId: user.id,
|
||||
organizationSlug: organizationId,
|
||||
access: newAccess as string[],
|
||||
});
|
||||
}}
|
||||
@@ -116,7 +130,7 @@ function Item({
|
||||
<DropdownMenuItem
|
||||
className="text-destructive"
|
||||
onClick={() => {
|
||||
revoke.mutate({ organizationId: organization.id, userId: id! });
|
||||
revoke.mutate({ organizationId: organizationId, userId: id });
|
||||
}}
|
||||
>
|
||||
Remove member
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import PageLayout from '@/app/(app)/[organizationSlug]/[projectId]/page-layout';
|
||||
import { FullPageEmptyState } from '@/components/full-page-empty-state';
|
||||
import { auth, clerkClient } from '@clerk/nextjs/server';
|
||||
import { auth } from '@clerk/nextjs/server';
|
||||
import { ShieldAlertIcon } from 'lucide-react';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
import { getOrganizationBySlug } from '@openpanel/db';
|
||||
import { db } from '@openpanel/db';
|
||||
|
||||
import EditOrganization from './edit-organization';
|
||||
import InvitesServer from './invites';
|
||||
@@ -19,25 +19,30 @@ interface PageProps {
|
||||
export default async function Page({
|
||||
params: { organizationSlug },
|
||||
}: PageProps) {
|
||||
const organization = await getOrganizationBySlug(organizationSlug);
|
||||
const session = auth();
|
||||
const memberships = await clerkClient.users.getOrganizationMembershipList({
|
||||
userId: session.userId!,
|
||||
const organization = await db.organization.findUnique({
|
||||
where: {
|
||||
id: organizationSlug,
|
||||
members: {
|
||||
some: {
|
||||
userId: session.userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
members: {
|
||||
select: {
|
||||
role: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!organization) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const member = memberships.data.find(
|
||||
(membership) => membership.organization.id === organization.id
|
||||
);
|
||||
|
||||
if (!member) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const hasAccess = member.role === 'org:admin';
|
||||
const hasAccess = organization.members[0]?.role === 'org:admin';
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -20,9 +20,7 @@ export default async function Page({
|
||||
getCurrentProjects(organizationSlug),
|
||||
]);
|
||||
|
||||
const organization = organizations.find(
|
||||
(org) => org.slug === organizationSlug
|
||||
);
|
||||
const organization = organizations.find((org) => org.id === organizationSlug);
|
||||
|
||||
if (!organization) {
|
||||
return (
|
||||
|
||||
@@ -6,7 +6,7 @@ export default async function Page() {
|
||||
const organizations = await getCurrentOrganizations();
|
||||
|
||||
if (organizations.length > 0) {
|
||||
return redirect(`/${organizations[0]?.slug}`);
|
||||
return redirect(`/${organizations[0]?.id}`);
|
||||
}
|
||||
|
||||
return redirect('/onboarding');
|
||||
|
||||
Reference in New Issue
Block a user