feature(auth): replace clerk.com with custom auth (#103)
* feature(auth): replace clerk.com with custom auth * minor fixes * remove notification preferences * decrease live events interval fix(api): cookies.. # Conflicts: # .gitignore # apps/api/src/index.ts # apps/dashboard/src/app/providers.tsx # packages/trpc/src/trpc.ts
This commit is contained in:
committed by
Carl-Gerhard Lindesvärd
parent
f28802b1c2
commit
d31d9924a5
@@ -24,14 +24,14 @@ import { usePathname, useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
|
||||
import type {
|
||||
getCurrentOrganizations,
|
||||
getOrganizations,
|
||||
getProjectsByOrganizationId,
|
||||
} from '@openpanel/db';
|
||||
import Link from 'next/link';
|
||||
|
||||
interface LayoutProjectSelectorProps {
|
||||
projects: Awaited<ReturnType<typeof getProjectsByOrganizationId>>;
|
||||
organizations?: Awaited<ReturnType<typeof getCurrentOrganizations>>;
|
||||
organizations?: Awaited<ReturnType<typeof getOrganizations>>;
|
||||
align?: 'start' | 'end';
|
||||
}
|
||||
export default function LayoutProjectSelector({
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { FullPageEmptyState } from '@/components/full-page-empty-state';
|
||||
|
||||
import {
|
||||
getCurrentOrganizations,
|
||||
getCurrentProjects,
|
||||
getDashboardsByProjectId,
|
||||
getOrganizations,
|
||||
getProjects,
|
||||
} from '@openpanel/db';
|
||||
|
||||
import { auth } from '@openpanel/auth/nextjs';
|
||||
import LayoutContent from './layout-content';
|
||||
import { LayoutSidebar } from './layout-sidebar';
|
||||
import SideEffects from './side-effects';
|
||||
@@ -22,9 +23,10 @@ export default async function AppLayout({
|
||||
children,
|
||||
params: { organizationSlug: organizationId, projectId },
|
||||
}: AppLayoutProps) {
|
||||
const { userId } = await auth();
|
||||
const [organizations, projects, dashboards] = await Promise.all([
|
||||
getCurrentOrganizations(),
|
||||
getCurrentProjects(organizationId),
|
||||
getOrganizations(userId),
|
||||
getProjects({ organizationId, userId }),
|
||||
getDashboardsByProjectId(projectId),
|
||||
]);
|
||||
|
||||
|
||||
@@ -48,101 +48,137 @@ export default function CreateInvite({ projects }: Props) {
|
||||
|
||||
const mutation = api.organization.inviteUser.useMutation({
|
||||
onSuccess() {
|
||||
toast('User invited!', {
|
||||
description: 'The user has been invited to the organization.',
|
||||
});
|
||||
toast.success('User has been invited');
|
||||
reset();
|
||||
closeSheet();
|
||||
router.refresh();
|
||||
},
|
||||
onError() {
|
||||
toast.error('Failed to invite user');
|
||||
onError(error) {
|
||||
toast.error('Failed to invite user', {
|
||||
description: error.message,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Sheet>
|
||||
<Sheet onOpenChange={() => mutation.reset()}>
|
||||
<SheetTrigger asChild>
|
||||
<Button icon={PlusIcon}>Invite user</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent>
|
||||
<SheetHeader>
|
||||
<div>
|
||||
<SheetTitle>Invite a user</SheetTitle>
|
||||
<SheetDescription>
|
||||
Invite users to your organization. They will recieve an email will
|
||||
instructions.
|
||||
</SheetDescription>
|
||||
{mutation.isSuccess ? (
|
||||
<SheetContent>
|
||||
<SheetHeader>
|
||||
<SheetTitle>User has been invited</SheetTitle>
|
||||
</SheetHeader>
|
||||
<div className="prose">
|
||||
{mutation.data.type === 'is_member' ? (
|
||||
<>
|
||||
<p>
|
||||
Since the user already has an account we have added him/her to
|
||||
your organization. This means you will not see this user in
|
||||
the list of invites.
|
||||
</p>
|
||||
<p>We have also notified the user by email about this.</p>
|
||||
</>
|
||||
) : (
|
||||
<p>
|
||||
We have sent an email with instructions to join the
|
||||
organization.
|
||||
</p>
|
||||
)}
|
||||
<div className="row gap-4 mt-8">
|
||||
<Button onClick={() => mutation.reset()}>
|
||||
Invite another user
|
||||
</Button>
|
||||
<Button variant="outline" onClick={() => closeSheet()}>
|
||||
Close
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</SheetHeader>
|
||||
<form
|
||||
onSubmit={handleSubmit((values) => mutation.mutate(values))}
|
||||
className="flex flex-col gap-8"
|
||||
>
|
||||
<InputWithLabel
|
||||
className="w-full max-w-sm"
|
||||
label="Email"
|
||||
error={formState.errors.email?.message}
|
||||
placeholder="Who do you want to invite?"
|
||||
{...register('email')}
|
||||
/>
|
||||
<div>
|
||||
<Label>What role?</Label>
|
||||
</SheetContent>
|
||||
) : (
|
||||
<SheetContent>
|
||||
<SheetHeader>
|
||||
<div>
|
||||
<SheetTitle>Invite a user</SheetTitle>
|
||||
<SheetDescription>
|
||||
Invite users to your organization. They will recieve an email
|
||||
will instructions.
|
||||
</SheetDescription>
|
||||
</div>
|
||||
</SheetHeader>
|
||||
<form
|
||||
onSubmit={handleSubmit((values) => mutation.mutate(values))}
|
||||
className="flex flex-col gap-8"
|
||||
>
|
||||
<InputWithLabel
|
||||
className="w-full max-w-sm"
|
||||
label="Email"
|
||||
error={formState.errors.email?.message}
|
||||
placeholder="Who do you want to invite?"
|
||||
{...register('email')}
|
||||
/>
|
||||
<div>
|
||||
<Label>What role?</Label>
|
||||
<Controller
|
||||
name="role"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<RadioGroup
|
||||
defaultValue={field.value}
|
||||
onChange={field.onChange}
|
||||
ref={field.ref}
|
||||
onBlur={field.onBlur}
|
||||
className="flex gap-4"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="org:member" id="member" />
|
||||
<Label className="mb-0" htmlFor="member">
|
||||
Member
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="org:admin" id="admin" />
|
||||
<Label className="mb-0" htmlFor="admin">
|
||||
Admin
|
||||
</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Controller
|
||||
name="role"
|
||||
name="access"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<RadioGroup
|
||||
defaultValue={field.value}
|
||||
onChange={field.onChange}
|
||||
ref={field.ref}
|
||||
onBlur={field.onBlur}
|
||||
className="flex gap-4"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="org:member" id="member" />
|
||||
<Label className="mb-0" htmlFor="member">
|
||||
Member
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="org:admin" id="admin" />
|
||||
<Label className="mb-0" htmlFor="admin">
|
||||
Admin
|
||||
</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
<div>
|
||||
<Label>Restrict access</Label>
|
||||
<ComboboxAdvanced
|
||||
placeholder="Restrict access to projects"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
items={projects.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))}
|
||||
/>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
Leave empty to give access to all projects
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Controller
|
||||
name="access"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<div>
|
||||
<Label>Restrict access</Label>
|
||||
<ComboboxAdvanced
|
||||
placeholder="Restrict access to projects"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
items={projects.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))}
|
||||
/>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
Leave empty to give access to all projects
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<SheetFooter>
|
||||
<Button icon={SendIcon} type="submit" loading={mutation.isLoading}>
|
||||
Invite user
|
||||
</Button>
|
||||
</SheetFooter>
|
||||
</form>
|
||||
</SheetContent>
|
||||
<SheetFooter>
|
||||
<Button
|
||||
icon={SendIcon}
|
||||
type="submit"
|
||||
loading={mutation.isLoading}
|
||||
>
|
||||
Invite user
|
||||
</Button>
|
||||
</SheetFooter>
|
||||
</form>
|
||||
</SheetContent>
|
||||
)}
|
||||
</Sheet>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { FullPageEmptyState } from '@/components/full-page-empty-state';
|
||||
import { PageTabs, PageTabsLink } from '@/components/page-tabs';
|
||||
import { Padding } from '@/components/ui/padding';
|
||||
import { auth } from '@clerk/nextjs/server';
|
||||
import { ShieldAlertIcon } from 'lucide-react';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { parseAsStringEnum } from 'nuqs/server';
|
||||
|
||||
import { auth } from '@openpanel/auth/nextjs';
|
||||
import { db } from '@openpanel/db';
|
||||
|
||||
import EditOrganization from './edit-organization';
|
||||
@@ -26,7 +26,7 @@ export default async function Page({
|
||||
const tab = parseAsStringEnum(['org', 'members', 'invites'])
|
||||
.withDefault('org')
|
||||
.parseServerSide(searchParams.tab);
|
||||
const session = auth();
|
||||
const session = await auth();
|
||||
const organization = await db.organization.findUnique({
|
||||
where: {
|
||||
id: organizationId,
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { Padding } from '@/components/ui/padding';
|
||||
import { auth } from '@clerk/nextjs/server';
|
||||
|
||||
import { auth } from '@openpanel/auth/nextjs';
|
||||
import { getUserById } from '@openpanel/db';
|
||||
|
||||
import EditProfile from './edit-profile';
|
||||
|
||||
export default async function Page() {
|
||||
const { userId } = auth();
|
||||
const { userId } = await auth();
|
||||
const profile = await getUserById(userId!);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,39 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { pushModal, useOnPushModal } from '@/modals';
|
||||
import { useUser } from '@clerk/nextjs';
|
||||
import { differenceInDays } from 'date-fns';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { useOpenPanel } from '@openpanel/nextjs';
|
||||
|
||||
export default function SideEffects() {
|
||||
const op = useOpenPanel();
|
||||
const { user } = useUser();
|
||||
const accountAgeInDays = differenceInDays(
|
||||
new Date(),
|
||||
user?.createdAt || new Date(),
|
||||
);
|
||||
useOnPushModal('Testimonial', (open) => {
|
||||
if (!open) {
|
||||
user?.update({
|
||||
unsafeMetadata: {
|
||||
...user.unsafeMetadata,
|
||||
testimonial: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const showTestimonial =
|
||||
user && !user.unsafeMetadata.testimonial && accountAgeInDays > 7;
|
||||
|
||||
useEffect(() => {
|
||||
if (showTestimonial) {
|
||||
pushModal('Testimonial');
|
||||
op.track('testimonials_shown');
|
||||
}
|
||||
}, [showTestimonial]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@ import ProjectCard from '@/components/projects/project-card';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
import SettingsToggle from '@/components/settings-toggle';
|
||||
import { getCurrentOrganizations, getCurrentProjects } from '@openpanel/db';
|
||||
import { auth } from '@openpanel/auth/nextjs';
|
||||
import { getOrganizations, getProjects } from '@openpanel/db';
|
||||
import LayoutProjectSelector from './[projectId]/layout-project-selector';
|
||||
|
||||
interface PageProps {
|
||||
@@ -16,9 +17,10 @@ interface PageProps {
|
||||
export default async function Page({
|
||||
params: { organizationSlug: organizationId },
|
||||
}: PageProps) {
|
||||
const { userId } = await auth();
|
||||
const [organizations, projects] = await Promise.all([
|
||||
getCurrentOrganizations(),
|
||||
getCurrentProjects(organizationId),
|
||||
getOrganizations(userId),
|
||||
getProjects({ organizationId, userId }),
|
||||
]);
|
||||
|
||||
const organization = organizations.find((org) => org.id === organizationId);
|
||||
@@ -32,7 +34,7 @@ export default async function Page({
|
||||
}
|
||||
|
||||
if (projects.length === 0) {
|
||||
return redirect('/onboarding');
|
||||
return redirect('/onboarding/project');
|
||||
}
|
||||
|
||||
if (projects.length === 1 && projects[0]) {
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
import { getCurrentOrganizations } from '@openpanel/db';
|
||||
import { auth } from '@openpanel/auth/nextjs';
|
||||
import { getOrganizations } from '@openpanel/db';
|
||||
|
||||
export default async function Page() {
|
||||
const organizations = await getCurrentOrganizations();
|
||||
const { userId } = await auth();
|
||||
const organizations = await getOrganizations(userId);
|
||||
|
||||
if (organizations.length > 0) {
|
||||
return redirect(`/${organizations[0]?.id}`);
|
||||
}
|
||||
|
||||
return redirect('/onboarding');
|
||||
return redirect('/onboarding/project');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user