diff --git a/apps/web/package.json b/apps/web/package.json index 073c7567..ff507081 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -12,6 +12,7 @@ "with-env": "dotenv -e ../../.env -c --" }, "dependencies": { + "@clerk/nextjs": "^4.29.6", "@clickhouse/client": "^0.2.9", "@hookform/resolvers": "^3.3.2", "@mixan/common": "workspace:^", @@ -45,7 +46,7 @@ "hamburger-react": "^2.5.0", "lodash.debounce": "^4.0.8", "lottie-react": "^2.4.0", - "lucide-react": "^0.286.0", + "lucide-react": "^0.323.0", "mathjs": "^12.3.0", "mitt": "^3.0.1", "next": "~14.0.4", diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/dashboards/[dashboardId]/page.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/dashboards/[dashboardId]/page.tsx index 7740aeab..9f639d4a 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/dashboards/[dashboardId]/page.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/dashboards/[dashboardId]/page.tsx @@ -1,10 +1,10 @@ import PageLayout from '@/app/(app)/page-layout'; -import { getSession } from '@/server/auth'; import { createRecentDashboard, getDashboardById, } from '@/server/services/dashboard.service'; import { getReportsByDashboardId } from '@/server/services/reports.service'; +import { auth } from '@clerk/nextjs'; import { revalidateTag } from 'next/cache'; import { ListReports } from './list-reports'; @@ -20,10 +20,10 @@ interface PageProps { export default async function Page({ params: { organizationId, projectId, dashboardId }, }: PageProps) { - const session = await getSession(); + const { userId } = auth(); + const dashboard = await getDashboardById(dashboardId); const reports = await getReportsByDashboardId(dashboardId); - const userId = session?.user.id; if (userId && dashboard) { await createRecentDashboard({ userId, @@ -35,7 +35,7 @@ export default async function Page({ } return ( - + ); diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/dashboards/page.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/dashboards/page.tsx index 77e7f4ba..07033f02 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/dashboards/page.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/dashboards/page.tsx @@ -6,18 +6,15 @@ import { ListDashboards } from './list-dashboards'; interface PageProps { params: { - organizationId: string; projectId: string; }; } -export default async function Page({ - params: { organizationId, projectId }, -}: PageProps) { +export default async function Page({ params: { projectId } }: PageProps) { const dashboards = await getDashboardsByProjectId(projectId); return ( - + diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/events/page.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/events/page.tsx index 9c8c2b49..8a411603 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/events/page.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/events/page.tsx @@ -4,15 +4,12 @@ import { ListEvents } from './list-events'; interface PageProps { params: { - organizationId: string; projectId: string; }; } -export default function Page({ - params: { organizationId, projectId }, -}: PageProps) { +export default function Page({ params: { projectId } }: PageProps) { return ( - + ); diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/page.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/page.tsx index f89ef128..cd2214a8 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/page.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/page.tsx @@ -2,16 +2,9 @@ import PageLayout from '@/app/(app)/page-layout'; import OverviewMetrics from './overview-metrics'; -interface PageProps { - params: { - organizationId: string; - projectId: string; - }; -} - -export default function Page({ params: { organizationId } }: PageProps) { +export default function Page() { return ( - + ); diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/profiles/[profileId]/page.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/profiles/[profileId]/page.tsx index ce7fdf29..f8737aa1 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/profiles/[profileId]/page.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/profiles/[profileId]/page.tsx @@ -1,7 +1,6 @@ import PageLayout from '@/app/(app)/page-layout'; import { ListProperties } from '@/components/events/ListProperties'; import { ProfileAvatar } from '@/components/profiles/ProfileAvatar'; -import { Avatar } from '@/components/ui/avatar'; import { Widget, WidgetBody, WidgetHead } from '@/components/Widget'; import { getProfileById, @@ -14,14 +13,13 @@ import ListProfileEvents from './list-profile-events'; interface PageProps { params: { - organizationId: string; projectId: string; profileId: string; }; } export default async function Page({ - params: { organizationId, projectId, profileId }, + params: { projectId, profileId }, }: PageProps) { const profile = await getProfileById(profileId); const profiles = ( @@ -35,7 +33,6 @@ export default async function Page({ {getProfileName(profile)} } - organizationId={organizationId} >
diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/profiles/page.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/profiles/page.tsx index dcb0c41d..3d5b9745 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/profiles/page.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/profiles/page.tsx @@ -12,7 +12,7 @@ export default function Page({ params: { organizationId, projectId }, }: PageProps) { return ( - + ); diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/reports/[reportId]/page.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/reports/[reportId]/page.tsx index e2674c05..3acb571d 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/reports/[reportId]/page.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/reports/[reportId]/page.tsx @@ -6,15 +6,12 @@ import ReportEditor from '../report-editor'; interface PageProps { params: { - organizationId: string; projectId: string; reportId: string; }; } -export default async function Page({ - params: { organizationId, reportId }, -}: PageProps) { +export default async function Page({ params: { reportId } }: PageProps) { const report = await getReportById(reportId); return (
} - organizationId={organizationId} > diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/reports/page.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/reports/page.tsx index 2b3308ce..9d496720 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/reports/page.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/reports/page.tsx @@ -19,7 +19,6 @@ export default function Page({ params: { organizationId } }: PageProps) {
} - organizationId={organizationId} >
diff --git a/apps/web/src/app/(app)/[organizationId]/page.tsx b/apps/web/src/app/(app)/[organizationId]/page.tsx index 850eba57..e507c37c 100644 --- a/apps/web/src/app/(app)/[organizationId]/page.tsx +++ b/apps/web/src/app/(app)/[organizationId]/page.tsx @@ -1,6 +1,8 @@ import { getProjectWithMostEvents } from '@/server/services/project.service'; import { redirect } from 'next/navigation'; +import PageLayout from '../page-layout'; + interface PageProps { params: { organizationId: string; @@ -14,5 +16,11 @@ export default async function Page({ params: { organizationId } }: PageProps) { return redirect(`/${organizationId}/${project.id}`); } - return null; + return ( + +
+

Create your first project

+
+
+ ); } diff --git a/apps/web/src/app/(app)/[organizationId]/settings/clients/page.tsx b/apps/web/src/app/(app)/[organizationId]/settings/clients/page.tsx index 497f900c..3cfed300 100644 --- a/apps/web/src/app/(app)/[organizationId]/settings/clients/page.tsx +++ b/apps/web/src/app/(app)/[organizationId]/settings/clients/page.tsx @@ -13,7 +13,7 @@ export default async function Page({ params: { organizationId } }: PageProps) { const clients = await getClientsByOrganizationId(organizationId); return ( - + ); diff --git a/apps/web/src/app/(app)/[organizationId]/settings/organization/edit-organization.tsx b/apps/web/src/app/(app)/[organizationId]/settings/organization/edit-organization.tsx index c5671767..2e0e06c6 100644 --- a/apps/web/src/app/(app)/[organizationId]/settings/organization/edit-organization.tsx +++ b/apps/web/src/app/(app)/[organizationId]/settings/organization/edit-organization.tsx @@ -5,7 +5,7 @@ import { InputWithLabel } from '@/components/forms/InputWithLabel'; import { Button } from '@/components/ui/button'; import { toast } from '@/components/ui/use-toast'; import { Widget, WidgetBody, WidgetHead } from '@/components/Widget'; -import type { getOrganizationById } from '@/server/services/organization.service'; +import type { getOrganizationBySlug } from '@/server/services/organization.service'; import { useRouter } from 'next/navigation'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; @@ -17,7 +17,7 @@ const validator = z.object({ type IForm = z.infer; interface EditOrganizationProps { - organization: Awaited>; + organization: Awaited>; } export default function EditOrganization({ organization, @@ -64,10 +64,3 @@ export default function EditOrganization({ ); } - -// - -// diff --git a/apps/web/src/app/(app)/[organizationId]/settings/organization/page.tsx b/apps/web/src/app/(app)/[organizationId]/settings/organization/page.tsx index f70e44a6..a5d1cfa0 100644 --- a/apps/web/src/app/(app)/[organizationId]/settings/organization/page.tsx +++ b/apps/web/src/app/(app)/[organizationId]/settings/organization/page.tsx @@ -1,6 +1,5 @@ import PageLayout from '@/app/(app)/page-layout'; -import { getOrganizationById } from '@/server/services/organization.service'; -import { getInvitesByOrganizationId } from '@/server/services/user.service'; +import { getOrganizationBySlug } from '@/server/services/organization.service'; import EditOrganization from './edit-organization'; import InvitedUsers from './invited-users'; @@ -12,11 +11,11 @@ interface PageProps { } export default async function Page({ params: { organizationId } }: PageProps) { - const organization = await getOrganizationById(organizationId); - const invites = await getInvitesByOrganizationId(organizationId); + const organization = await getOrganizationBySlug(organizationId); + const invites = []; return ( - +
diff --git a/apps/web/src/app/(app)/[organizationId]/settings/profile/edit-profile.tsx b/apps/web/src/app/(app)/[organizationId]/settings/profile/edit-profile.tsx index 600f1791..a3b7ded3 100644 --- a/apps/web/src/app/(app)/[organizationId]/settings/profile/edit-profile.tsx +++ b/apps/web/src/app/(app)/[organizationId]/settings/profile/edit-profile.tsx @@ -1,15 +1,10 @@ 'use client'; import { api, handleError } from '@/app/_trpc/client'; -import { ContentHeader, ContentSection } from '@/components/Content'; -import { InputError } from '@/components/forms/InputError'; import { InputWithLabel } from '@/components/forms/InputWithLabel'; import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; import { toast } from '@/components/ui/use-toast'; import { Widget, WidgetBody, WidgetHead } from '@/components/Widget'; -import type { getOrganizationById } from '@/server/services/organization.service'; -import type { getProfileById } from '@/server/services/profile.service'; import type { getUserById } from '@/server/services/user.service'; import { zodResolver } from '@hookform/resolvers/zod'; import { useRouter } from 'next/navigation'; @@ -17,7 +12,8 @@ import { useForm } from 'react-hook-form'; import { z } from 'zod'; const validator = z.object({ - name: z.string().min(2), + firstName: z.string().min(2), + lastName: z.string().min(2), email: z.string().email(), }); @@ -31,7 +27,8 @@ export default function EditProfile({ profile }: EditProfileProps) { const { register, handleSubmit, reset, formState } = useForm({ resolver: zodResolver(validator), defaultValues: { - name: profile.name ?? '', + firstName: profile.firstName ?? '', + lastName: profile.lastName ?? '', email: profile.email ?? '', }, }); @@ -63,12 +60,19 @@ export default function EditProfile({ profile }: EditProfileProps) { + +
diff --git a/apps/web/src/app/(app)/[organizationId]/settings/projects/page.tsx b/apps/web/src/app/(app)/[organizationId]/settings/projects/page.tsx index b80e357f..6e57d49c 100644 --- a/apps/web/src/app/(app)/[organizationId]/settings/projects/page.tsx +++ b/apps/web/src/app/(app)/[organizationId]/settings/projects/page.tsx @@ -1,5 +1,5 @@ import PageLayout from '@/app/(app)/page-layout'; -import { getProjectsByOrganizationId } from '@/server/services/project.service'; +import { getProjectsByOrganizationSlug } from '@/server/services/project.service'; import ListProjects from './list-projects'; @@ -10,10 +10,10 @@ interface PageProps { } export default async function Page({ params: { organizationId } }: PageProps) { - const projects = await getProjectsByOrganizationId(organizationId); + const projects = await getProjectsByOrganizationSlug(organizationId); return ( - + ); diff --git a/apps/web/src/app/(app)/layout-menu.tsx b/apps/web/src/app/(app)/layout-menu.tsx index 68cd01e8..f17d1f5d 100644 --- a/apps/web/src/app/(app)/layout-menu.tsx +++ b/apps/web/src/app/(app)/layout-menu.tsx @@ -134,7 +134,7 @@ export default function LayoutMenu({
} - href={`/${item.organization_id}/${item.project_id}/${item.dashboard_id}`} + href={`/${item.organization_slug}/${item.project_id}/${item.dashboard_id}`} /> ))}
diff --git a/apps/web/src/app/(app)/layout-organization-selector.tsx b/apps/web/src/app/(app)/layout-organization-selector.tsx index fae56d4a..a6b914a6 100644 --- a/apps/web/src/app/(app)/layout-organization-selector.tsx +++ b/apps/web/src/app/(app)/layout-organization-selector.tsx @@ -14,7 +14,7 @@ export default function LayoutOrganizationSelector({ const params = useAppParams(); const organization = organizations.find( - (item) => item.id === params.organizationId + (item) => item.slug === params.organizationId ); if (!organization) { diff --git a/apps/web/src/app/(app)/layout-project-selector.tsx b/apps/web/src/app/(app)/layout-project-selector.tsx index 5b83d3c8..2e1c00ec 100644 --- a/apps/web/src/app/(app)/layout-project-selector.tsx +++ b/apps/web/src/app/(app)/layout-project-selector.tsx @@ -1,20 +1,18 @@ 'use client'; import { Combobox } from '@/components/ui/combobox'; -import type { getProjectsByOrganizationId } from '@/server/services/project.service'; -import { useParams, usePathname, useRouter } from 'next/navigation'; +import { useAppParams } from '@/hooks/useAppParams'; +import type { getCurrentProjects } from '@/server/services/project.service'; +import { usePathname, useRouter } from 'next/navigation'; interface LayoutProjectSelectorProps { - projects: Awaited>; - organizationId: string | null; + projects: Awaited>; } export default function LayoutProjectSelector({ projects, - organizationId, }: LayoutProjectSelectorProps) { const router = useRouter(); - const params = useParams<{ projectId: string }>(); - const projectId = params?.projectId ? params.projectId : null; + const { organizationId, projectId } = useAppParams(); const pathname = usePathname() || ''; return ( @@ -27,7 +25,7 @@ export default function LayoutProjectSelector({ // we know its safe to just replace the current projectId // since the rest of the url is to a static page // e.g. /[organizationId]/[projectId]/events - if (params && projectId && Object.keys(params).length === 2) { + if (organizationId && projectId) { router.push(pathname.replace(projectId, value)); } else { router.push(`/${organizationId}/${value}`); diff --git a/apps/web/src/app/(app)/layout-sidebar.tsx b/apps/web/src/app/(app)/layout-sidebar.tsx index 2bc74c3a..47ab3501 100644 --- a/apps/web/src/app/(app)/layout-sidebar.tsx +++ b/apps/web/src/app/(app)/layout-sidebar.tsx @@ -2,7 +2,6 @@ import { useEffect, useState } from 'react'; import { Logo } from '@/components/Logo'; -import type { IServiceRecentDashboards } from '@/server/services/dashboard.service'; import type { IServiceOrganization } from '@/server/services/organization.service'; import { cn } from '@/utils/cn'; import { Rotate as Hamburger } from 'hamburger-react'; @@ -13,15 +12,11 @@ import LayoutMenu from './layout-menu'; import LayoutOrganizationSelector from './layout-organization-selector'; interface LayoutSidebarProps { - recentDashboards: IServiceRecentDashboards; organizations: IServiceOrganization[]; } -export function LayoutSidebar({ - organizations, - recentDashboards, -}: LayoutSidebarProps) { +export function LayoutSidebar({ organizations }: LayoutSidebarProps) { const [active, setActive] = useState(false); - const fallbackProjectId = recentDashboards[0]?.project_id ?? null; + const fallbackProjectId = null; const pathname = usePathname(); useEffect(() => { setActive(false); @@ -54,7 +49,7 @@ export function LayoutSidebar({
{/* Placeholder for LayoutOrganizationSelector */} diff --git a/apps/web/src/app/(app)/layout.tsx b/apps/web/src/app/(app)/layout.tsx index c62299ab..8b305657 100644 --- a/apps/web/src/app/(app)/layout.tsx +++ b/apps/web/src/app/(app)/layout.tsx @@ -1,8 +1,5 @@ -import { getSession } from '@/server/auth'; -import { getRecentDashboardsByUserId } from '@/server/services/dashboard.service'; import { getOrganizations } from '@/server/services/organization.service'; -import Auth from '../auth'; import { LayoutSidebar } from './layout-sidebar'; interface AppLayoutProps { @@ -10,19 +7,11 @@ interface AppLayoutProps { } export default async function AppLayout({ children }: AppLayoutProps) { - const session = await getSession(); const organizations = await getOrganizations(); - const recentDashboards = session?.user.id - ? await getRecentDashboardsByUserId(session?.user.id) - : []; - - if (!session) { - return ; - } return (
- +
{children}
); diff --git a/apps/web/src/app/(app)/page-layout.tsx b/apps/web/src/app/(app)/page-layout.tsx index b1cd9329..38ae8a3c 100644 --- a/apps/web/src/app/(app)/page-layout.tsx +++ b/apps/web/src/app/(app)/page-layout.tsx @@ -1,32 +1,21 @@ -import { getProjectsByOrganizationId } from '@/server/services/project.service'; +import { getCurrentProjects } from '@/server/services/project.service'; import LayoutProjectSelector from './layout-project-selector'; interface PageLayoutProps { children: React.ReactNode; title: React.ReactNode; - organizationId: string | null; } -export default async function PageLayout({ - children, - title, - organizationId, -}: PageLayoutProps) { - const projects = organizationId - ? await getProjectsByOrganizationId(organizationId) - : []; +export default async function PageLayout({ children, title }: PageLayoutProps) { + const projects = await getCurrentProjects(); + return ( <>
{title}
- {projects.length > 0 && ( - - )} + {projects.length > 0 && }
{children}
diff --git a/apps/web/src/app/(app)/page.tsx b/apps/web/src/app/(app)/page.tsx index 5fb88bda..2be0f2ac 100644 --- a/apps/web/src/app/(app)/page.tsx +++ b/apps/web/src/app/(app)/page.tsx @@ -1,26 +1,13 @@ -import { ModalWrapper } from '@/modals'; -import { ModalContent, ModalHeader } from '@/modals/Modal/Container'; +import { getOrganizations } from '@/server/services/organization.service'; +import { CreateOrganization } from '@clerk/nextjs'; import { redirect } from 'next/navigation'; -import { db } from '@mixan/db'; - -import { ListOrganizations } from './list-organizations'; - export default async function Page() { - const organizations = await db.organization.findMany(); + const organizations = await getOrganizations(); - if (organizations.length === 1 && organizations[0]?.id) { - redirect(`/${organizations[0].id}`); + if (organizations.length === 0) { + return ; } - return ( -
- - - - - - -
- ); + return redirect(`/${organizations[0]?.slug}`); } diff --git a/apps/web/src/app/api/cookie/route.ts b/apps/web/src/app/api/cookie/route.ts deleted file mode 100644 index 521c496c..00000000 --- a/apps/web/src/app/api/cookie/route.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { cookies } from 'next/headers'; -import { NextResponse } from 'next/server'; - -export const dynamic = 'force-dynamic'; // defaults to auto -export function GET(req: Request) { - const qwe = new URL(req.url); - const item = qwe.searchParams.entries(); - const { - value: [key, value], - } = item.next(); - - if (key && value) { - cookies().set(`@mixan-${key}`, JSON.stringify(value), { - httpOnly: true, - expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365), - }); - } - - return NextResponse.json({ key, value }); -} diff --git a/apps/web/src/app/api/setup/route.ts b/apps/web/src/app/api/setup/route.ts deleted file mode 100644 index a4209391..00000000 --- a/apps/web/src/app/api/setup/route.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { randomUUID } from 'crypto'; -import { db, getId } from '@/server/db'; -import { hashPassword } from '@/server/services/hash.service'; -import { NextResponse } from 'next/server'; - -const userName = 'demo'; -const userPassword = 'demo'; -const userEmail = 'demo@demo.com'; -const organizationName = 'Demo Org'; -const projectName = 'Demo Project'; - -export async function GET() { - try { - const counts = await db.$transaction([ - db.user.count(), - db.organization.count(), - db.project.count(), - db.client.count(), - ]); - - if (counts.some((count) => count > 0)) { - return NextResponse.json({ message: 'Already setup' }); - } - - const organization = await db.organization.create({ - data: { - id: await getId('organization', organizationName), - name: organizationName, - }, - }); - - const user = await db.user.create({ - data: { - name: userName, - password: await hashPassword(userPassword), - email: userEmail, - organization_id: organization.id, - }, - }); - - const project = await db.project.create({ - data: { - id: await getId('project', projectName), - name: projectName, - organization_id: organization.id, - }, - }); - const secret = randomUUID(); - const client = await db.client.create({ - data: { - name: `${projectName} Client`, - project_id: project.id, - organization_id: organization.id, - secret: await hashPassword(secret), - }, - }); - - return NextResponse.json({ - clientId: client.id, - clientSecret: secret, - user, - }); - } catch (error) { - return NextResponse.json({ error: 'Failed to setup' }); - } -} diff --git a/apps/web/src/app/api/trpc/[trpc]/route.ts b/apps/web/src/app/api/trpc/[trpc]/route.ts index 18568f6d..6c605f5c 100644 --- a/apps/web/src/app/api/trpc/[trpc]/route.ts +++ b/apps/web/src/app/api/trpc/[trpc]/route.ts @@ -1,5 +1,5 @@ import { appRouter } from '@/server/api/root'; -import { getSession } from '@/server/auth'; +import { auth } from '@clerk/nextjs'; import { fetchRequestHandler } from '@trpc/server/adapters/fetch'; const handler = (req: Request) => @@ -7,10 +7,9 @@ const handler = (req: Request) => endpoint: '/api/trpc', req, router: appRouter, - createContext: async () => { - const session = await getSession(); + createContext: () => { return { - session, + session: auth(), }; }, }); diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index c5a25da5..c5df0a0a 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -4,8 +4,6 @@ import Providers from './providers'; import '@/styles/globals.css'; -import { getSession } from '@/server/auth'; - export const metadata = {}; export const viewport = { @@ -20,14 +18,12 @@ export default async function RootLayout({ }: { children: React.ReactNode; }) { - const session = await getSession(); - return ( - {children} + {children} ); diff --git a/apps/web/src/app/providers.tsx b/apps/web/src/app/providers.tsx index 10e449c7..9e401e08 100644 --- a/apps/web/src/app/providers.tsx +++ b/apps/web/src/app/providers.tsx @@ -7,20 +7,13 @@ import { TooltipProvider } from '@/components/ui/tooltip'; import { ModalProvider } from '@/modals'; import type { AppStore } from '@/redux'; import makeStore from '@/redux'; +import { ClerkProvider } from '@clerk/nextjs'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { httpLink } from '@trpc/client'; -import type { Session } from 'next-auth'; -import { SessionProvider } from 'next-auth/react'; import { Provider as ReduxProvider } from 'react-redux'; import superjson from 'superjson'; -export default function Providers({ - children, - session, -}: { - children: React.ReactNode; - session: Session | null; -}) { +export default function Providers({ children }: { children: React.ReactNode }) { const [queryClient] = useState( () => new QueryClient({ @@ -51,7 +44,7 @@ export default function Providers({ } return ( - + @@ -63,6 +56,6 @@ export default function Providers({ - + ); } diff --git a/apps/web/src/components/user/ChangePassword.tsx b/apps/web/src/components/user/ChangePassword.tsx deleted file mode 100644 index 0230bcea..00000000 --- a/apps/web/src/components/user/ChangePassword.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { api, handleError } from '@/app/_trpc/client'; -import { ContentHeader, ContentSection } from '@/components/Content'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { toast } from '@/components/ui/use-toast'; -import { zodResolver } from '@hookform/resolvers/zod'; -import { useForm } from 'react-hook-form'; -import { z } from 'zod'; - -import { InputError } from '../forms/InputError'; - -const validator = z - .object({ - oldPassword: z.string().min(1), - password: z.string().min(8), - confirmPassword: z.string().min(8), - }) - .superRefine(({ confirmPassword, password }, ctx) => { - if (confirmPassword !== password) { - ctx.addIssue({ - path: ['confirmPassword'], - code: 'custom', - message: 'The passwords did not match', - }); - } - }); - -type IForm = z.infer; - -export function ChangePassword() { - const mutation = api.user.changePassword.useMutation({ - onSuccess() { - toast({ - title: 'Success', - description: 'You have updated your password', - }); - }, - onError: handleError, - }); - - const { register, handleSubmit, formState } = useForm({ - resolver: zodResolver(validator), - defaultValues: { - oldPassword: '', - password: '', - confirmPassword: '', - }, - }); - - return ( -
{ - mutation.mutate(values); - })} - className="flex flex-col divide-y divide-border" - > - - - - } - > - - - } - > - - - } - > - - -
- ); -} diff --git a/apps/web/src/middleware.ts b/apps/web/src/middleware.ts index 346243b1..f88d2f15 100644 --- a/apps/web/src/middleware.ts +++ b/apps/web/src/middleware.ts @@ -1,24 +1,12 @@ -import type { NextRequest } from 'next/server'; -import { NextResponse } from 'next/server'; +import { authMiddleware } from '@clerk/nextjs'; -export const config = { matcher: ['/api/sdk/:path*'] }; +// This example protects all routes including api/trpc routes +// Please edit this to allow other routes to be public as needed. +// See https://clerk.com/docs/references/nextjs/auth-middleware for more information about configuring your Middleware +export default authMiddleware({ + publicRoutes: [], +}); -export const cors = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'POST, PUT, OPTIONS', - 'Access-Control-Allow-Headers': - 'Content-Type, Authorization, Mixan-Client-Id, Mixan-Client-Secret', +export const config = { + matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api|trpc)(.*)'], }; - -export function middleware(request: NextRequest) { - const response = NextResponse.next(); - - if (request.method === 'OPTIONS') { - return NextResponse.json({}, { headers: cors }); - } - - Object.entries(cors).forEach(([key, value]) => { - response.headers.append(key, value); - }); - return response; -} diff --git a/apps/web/src/server/api/routers/client.ts b/apps/web/src/server/api/routers/client.ts index 69660333..1bde6c86 100644 --- a/apps/web/src/server/api/routers/client.ts +++ b/apps/web/src/server/api/routers/client.ts @@ -14,7 +14,7 @@ export const clientRouter = createTRPCRouter({ .query(async ({ input: { organizationId } }) => { return db.client.findMany({ where: { - organization_id: organizationId, + organization_slug: organizationId, }, include: { project: true, @@ -66,7 +66,7 @@ export const clientRouter = createTRPCRouter({ const secret = randomUUID(); const client = await db.client.create({ data: { - organization_id: input.organizationId, + organization_slug: input.organizationId, project_id: input.projectId, name: input.name, secret: input.withCors ? null : await hashPassword(secret), diff --git a/apps/web/src/server/api/routers/dashboard.ts b/apps/web/src/server/api/routers/dashboard.ts index 7ae5cdf7..c493301a 100644 --- a/apps/web/src/server/api/routers/dashboard.ts +++ b/apps/web/src/server/api/routers/dashboard.ts @@ -44,7 +44,7 @@ export const dashboardRouter = createTRPCRouter({ return db.dashboard.findMany({ where: { project: { - organization_id: input.organizationId, + organization_slug: input.organizationId, }, }, include: { diff --git a/apps/web/src/server/api/routers/organization.ts b/apps/web/src/server/api/routers/organization.ts index 99a0cca1..d77cc1ee 100644 --- a/apps/web/src/server/api/routers/organization.ts +++ b/apps/web/src/server/api/routers/organization.ts @@ -1,31 +1,16 @@ import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc'; -import { db } from '@/server/db'; -import { getOrganizationById } from '@/server/services/organization.service'; +import { + getCurrentOrganization, + getOrganizationBySlug, +} from '@/server/services/organization.service'; +import { clerkClient } from '@clerk/nextjs'; import { z } from 'zod'; export const organizationRouter = createTRPCRouter({ - list: protectedProcedure.query(({ ctx }) => { - return db.organization.findMany({ - where: { - users: { - some: { - id: ctx.session.user.id, - }, - }, - }, - }); - }), - first: protectedProcedure.query(({ ctx }) => { - return db.organization.findFirst({ - where: { - users: { - some: { - id: ctx.session.user.id, - }, - }, - }, - }); + list: protectedProcedure.query(() => { + return clerkClient.organizations.getOrganizationList(); }), + first: protectedProcedure.query(() => getCurrentOrganization()), get: protectedProcedure .input( z.object({ @@ -33,7 +18,7 @@ export const organizationRouter = createTRPCRouter({ }) ) .query(({ input }) => { - return getOrganizationById(input.id); + return getOrganizationBySlug(input.id); }), update: protectedProcedure .input( @@ -43,13 +28,8 @@ export const organizationRouter = createTRPCRouter({ }) ) .mutation(({ input }) => { - return db.organization.update({ - where: { - id: input.id, - }, - data: { - name: input.name, - }, + return clerkClient.organizations.updateOrganization(input.id, { + name: input.name, }); }), }); diff --git a/apps/web/src/server/api/routers/project.ts b/apps/web/src/server/api/routers/project.ts index 5665842b..9b8f87d3 100644 --- a/apps/web/src/server/api/routers/project.ts +++ b/apps/web/src/server/api/routers/project.ts @@ -15,7 +15,7 @@ export const projectRouter = createTRPCRouter({ return db.project.findMany({ where: { - organization_id: organizationId, + organization_slug: organizationId, }, }); }), @@ -60,7 +60,7 @@ export const projectRouter = createTRPCRouter({ return db.project.create({ data: { id: await getId('project', input.name), - organization_id: input.organizationId, + organization_slug: input.organizationId, name: input.name, }, }); diff --git a/apps/web/src/server/api/routers/user.ts b/apps/web/src/server/api/routers/user.ts index 5fad162b..3357ae6d 100644 --- a/apps/web/src/server/api/routers/user.ts +++ b/apps/web/src/server/api/routers/user.ts @@ -1,6 +1,8 @@ import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc'; import { db } from '@/server/db'; import { hashPassword, verifyPassword } from '@/server/services/hash.service'; +import { transformUser } from '@/server/services/user.service'; +import { clerkClient } from '@clerk/nextjs'; import { z } from 'zod'; export const userRouter = createTRPCRouter({ @@ -14,47 +16,17 @@ export const userRouter = createTRPCRouter({ update: protectedProcedure .input( z.object({ - name: z.string(), - email: z.string(), + firstName: z.string(), + lastName: z.string(), }) ) .mutation(({ input, ctx }) => { - return db.user.update({ - where: { - id: ctx.session.user.id, - }, - data: { - name: input.name, - email: input.email, - }, - }); - }), - changePassword: protectedProcedure - .input( - z.object({ - password: z.string(), - oldPassword: z.string(), - }) - ) - .mutation(async ({ input, ctx }) => { - const user = await db.user.findUniqueOrThrow({ - where: { - id: ctx.session.user.id, - }, - }); - - if (!(await verifyPassword(input.oldPassword, user.password))) { - throw new Error('Old password is incorrect'); - } - - return db.user.update({ - where: { - id: ctx.session.user.id, - }, - data: { - password: await hashPassword(input.password), - }, - }); + return clerkClient.users + .updateUser(ctx.session.userId, { + firstName: input.firstName, + lastName: input.lastName, + }) + .then(transformUser); }), invite: protectedProcedure .input( diff --git a/apps/web/src/server/api/trpc.ts b/apps/web/src/server/api/trpc.ts index b5949653..c27d3b5a 100644 --- a/apps/web/src/server/api/trpc.ts +++ b/apps/web/src/server/api/trpc.ts @@ -1,71 +1,13 @@ -/** - * YOU PROBABLY DON'T NEED TO EDIT THIS FILE, UNLESS: - * 1. You want to modify request context (see Part 1). - * 2. You want to create a new middleware or type of procedure (see Part 3). - * - * TL;DR - This is where all the tRPC server stuff is created and plugged in. The pieces you will - * need to use are documented accordingly near the end. - */ - -import { getSession } from '@/server/auth'; +import type { auth } from '@clerk/nextjs'; import { initTRPC, TRPCError } from '@trpc/server'; -import type { CreateNextContextOptions } from '@trpc/server/adapters/next'; -import type { Session } from 'next-auth'; import superjson from 'superjson'; import { ZodError } from 'zod'; -/** - * 1. CONTEXT - * - * This section defines the "contexts" that are available in the backend API. - * - * These allow you to access things when processing a request, like the database, the session, etc. - */ - interface CreateContextOptions { - session: Session | null; + session: ReturnType | null; } -/** - * This helper generates the "internals" for a tRPC context. If you need to use it, you can export - * it from here. - * - * Examples of things you may need it for: - * - testing, so we don't have to mock Next.js' req/res - * - tRPC's `createSSGHelpers`, where we don't have req/res - * - * @see https://create.t3.gg/en/usage/trpc#-serverapitrpcts - */ -export const createInnerTRPCContext = (opts: CreateContextOptions) => { - return { - session: opts.session, - }; -}; - -/** - * This is the actual context you will use in your router. It will be used to process every request - * that goes through your tRPC endpoint. - * - * @see https://trpc.io/docs/context - */ -export const createTRPCContext = async (opts: CreateNextContextOptions) => { - // Get the session from the server using the getServerSession wrapper function - const session = await getSession(); - - return createInnerTRPCContext({ - session, - }); -}; - -/** - * 2. INITIALIZATION - * - * This is where the tRPC API is initialized, connecting the context and transformer. We also parse - * ZodErrors so that you get typesafety on the frontend if your procedure fails due to validation - * errors on the backend. - */ - -const t = initTRPC.context().create({ +const t = initTRPC.context().create({ transformer: superjson, errorFormatter({ shape, error }) { return { @@ -79,48 +21,19 @@ const t = initTRPC.context().create({ }, }); -/** - * 3. ROUTER & PROCEDURE (THE IMPORTANT BIT) - * - * These are the pieces you use to build your tRPC API. You should import these a lot in the - * "/src/server/api/routers" directory. - */ - -/** - * This is how you create new routers and sub-routers in your tRPC API. - * - * @see https://trpc.io/docs/router - */ export const createTRPCRouter = t.router; -/** - * Public (unauthenticated) procedure - * - * This is the base piece you use to build new queries and mutations on your tRPC API. It does not - * guarantee that a user querying is authorized, but you can still access user session data if they - * are logged in. - */ export const publicProcedure = t.procedure; -/** Reusable middleware that enforces users are logged in before running the procedure. */ const enforceUserIsAuthed = t.middleware(({ ctx, next }) => { - if (!ctx.session?.user) { + if (!ctx.session?.userId) { throw new TRPCError({ code: 'UNAUTHORIZED' }); } return next({ ctx: { - // infers the `session` as non-nullable session: { ...ctx.session, user: ctx.session.user }, }, }); }); -/** - * Protected (authenticated) procedure - * - * If you want a query or mutation to ONLY be accessible to logged in users, use this. It verifies - * the session is valid and guarantees `ctx.session.user` is not null. - * - * @see https://trpc.io/docs/procedures - */ export const protectedProcedure = t.procedure.use(enforceUserIsAuthed); diff --git a/apps/web/src/server/auth.ts b/apps/web/src/server/auth.ts index 9f2e13bd..7ae3487b 100644 --- a/apps/web/src/server/auth.ts +++ b/apps/web/src/server/auth.ts @@ -1,124 +1,3 @@ -import { cache } from 'react'; -import { db } from '@/server/db'; -import { verifyPassword } from '@/server/services/hash.service'; -import type { NextApiRequest, NextApiResponse } from 'next'; -import { getServerSession } from 'next-auth'; -import type { DefaultSession, NextAuthOptions } from 'next-auth'; -import Credentials from 'next-auth/providers/credentials'; - -import { createError } from './exceptions'; - -/** - * Module augmentation for `next-auth` types. Allows us to add custom properties to the `session` - * object and keep type safety. - * - * @see https://next-auth.js.org/getting-started/typescript#module-augmentation - */ -declare module 'next-auth' { - interface Session extends DefaultSession { - user: DefaultSession['user'] & { - id: string; - }; - } - - // interface User { - // // ...other properties - // // role: UserRole; - // } -} - -/** - * Options for NextAuth.js used to configure adapters, providers, callbacks, etc. - * - * @see https://next-auth.js.org/configuration/options - */ -export const authOptions: NextAuthOptions = { - callbacks: { - session: ({ session, token }) => ({ - ...session, - user: { - ...session.user, - id: token.sub, - }, - }), - }, - session: { - strategy: 'jwt', - }, - providers: [ - Credentials({ - name: 'Credentials', - credentials: { - email: { label: 'Email', type: 'text', placeholder: 'jsmith' }, - password: { label: 'Password', type: 'password' }, - }, - async authorize(credentials) { - if (!credentials?.password || !credentials?.email) { - return null; - } - - const user = await db.user.findFirst({ - where: { email: credentials?.email }, - }); - - if (!user) { - return null; - } - - if (!(await verifyPassword(credentials.password, user.password))) { - return null; - } - - return { - ...user, - image: 'https://api.dicebear.com/7.x/adventurer/svg?seed=Abby', - }; - }, - }), - ], -}; - -export const getSession = cache( - async () => await getServerSession(authOptions) -); - -export async function validateSdkRequest( - req: NextApiRequest, - res: NextApiResponse -): Promise { - const clientId = req?.headers['mixan-client-id'] as string | undefined; - const clientSecret = req.headers['mixan-client-secret'] as string | undefined; - - if (!clientId) { - throw createError(401, 'Misisng client id'); - } - - const client = await db.client.findUnique({ - where: { - id: clientId, - }, - }); - - if (!client) { - throw createError(401, 'Invalid client id'); - } - - if (client.secret) { - if (!(await verifyPassword(clientSecret || '', client.secret))) { - throw createError(401, 'Invalid client secret'); - } - } else if (client.cors !== '*') { - const ok = client.cors.split(',').find((origin) => { - if (origin === req.headers.origin) { - return true; - } - }); - if (ok) { - res.setHeader('Access-Control-Allow-Origin', String(req.headers.origin)); - } else { - throw createError(401, 'Invalid cors settings'); - } - } - - return client.project_id; +export async function getSession() { + return true; } diff --git a/apps/web/src/server/services/clients.service.ts b/apps/web/src/server/services/clients.service.ts index 1a6111ef..fff7b08a 100644 --- a/apps/web/src/server/services/clients.service.ts +++ b/apps/web/src/server/services/clients.service.ts @@ -3,7 +3,7 @@ import { db } from '@mixan/db'; export function getClientsByOrganizationId(organizationId: string) { return db.client.findMany({ where: { - organization_id: organizationId, + organization_slug: organizationId, }, include: { project: true, diff --git a/apps/web/src/server/services/dashboard.service.ts b/apps/web/src/server/services/dashboard.service.ts index acf59b54..ec0f52ec 100644 --- a/apps/web/src/server/services/dashboard.service.ts +++ b/apps/web/src/server/services/dashboard.service.ts @@ -72,13 +72,13 @@ export async function createRecentDashboard({ user_id: userId, project_id: projectId, dashboard_id: dashboardId, - organization_id: organizationId, + organization_slug: organizationId, }, }); return db.recentDashboards.create({ data: { user_id: userId, - organization_id: organizationId, + organization_slug: organizationId, project_id: projectId, dashboard_id: dashboardId, }, diff --git a/apps/web/src/server/services/organization.service.ts b/apps/web/src/server/services/organization.service.ts index 45f52e8b..d03b8faa 100644 --- a/apps/web/src/server/services/organization.service.ts +++ b/apps/web/src/server/services/organization.service.ts @@ -1,37 +1,52 @@ +import { auth, clerkClient } from '@clerk/nextjs'; +import type { Organization } from '@clerk/nextjs/dist/types/server'; + import { db } from '../db'; export type IServiceOrganization = Awaited< ReturnType >[number]; -export function getOrganizations() { - return db.organization.findMany({ - where: { - // users: { - // some: { - // id: '1', - // }, - // } - }, - }); +function transformOrganization(org: Organization) { + return { + id: org.id, + name: org.name, + slug: org.slug, + }; } -export function getOrganizationById(id: string) { - return db.organization.findUniqueOrThrow({ - where: { - id, - }, - }); +export async function getOrganizations() { + const orgs = await clerkClient.organizations.getOrganizationList(); + return orgs.map(transformOrganization); } -export function getOrganizationByProjectId(projectId: string) { - return db.organization.findFirst({ +export async function getCurrentOrganization() { + const session = auth(); + if (!session?.orgSlug) { + return null; + } + + const organization = await clerkClient.organizations.getOrganization({ + slug: session.orgSlug, + }); + + return transformOrganization(organization); +} + +export function getOrganizationBySlug(slug: string) { + return clerkClient.organizations + .getOrganization({ slug }) + .then(transformOrganization); +} + +export async function getOrganizationByProjectId(projectId: string) { + const project = await db.project.findUniqueOrThrow({ where: { - projects: { - some: { - id: projectId, - }, - }, + id: projectId, }, }); + + return clerkClient.organizations.getOrganization({ + slug: project.organization_slug, + }); } diff --git a/apps/web/src/server/services/project.service.ts b/apps/web/src/server/services/project.service.ts index b7d7c004..923544ef 100644 --- a/apps/web/src/server/services/project.service.ts +++ b/apps/web/src/server/services/project.service.ts @@ -1,8 +1,7 @@ import { unstable_cache } from 'next/cache'; -import { chQuery } from '@mixan/db'; - import { db } from '../db'; +import { getCurrentOrganization } from './organization.service'; export type IServiceProject = Awaited>; @@ -14,44 +13,31 @@ export function getProjectById(id: string) { }); } -export function getProjectsByOrganizationId(organizationId: string) { - return db.project.findMany({ +export async function getCurrentProjects() { + const organization = await getCurrentOrganization(); + if (!organization?.slug) return []; + return await db.project.findMany({ where: { - organization_id: organizationId, + organization_slug: organization.slug, }, }); } -export async function getProjectWithMostEvents(organizationId: string) { +export function getProjectsByOrganizationSlug(slug: string) { + return db.project.findMany({ + where: { + organization_slug: slug, + }, + }); +} + +export async function getProjectWithMostEvents(slug: string) { return db.project.findFirst({ where: { - organization_id: organizationId, + organization_slug: slug, }, orderBy: { eventsCount: 'desc', }, }); } - -export function getFirstProjectByOrganizationId(organizationId: string) { - const tag = `getFirstProjectByOrganizationId_${organizationId}`; - return unstable_cache( - async (organizationId: string) => { - return db.project.findFirst({ - where: { - organization_id: organizationId, - }, - orderBy: { - events: { - _count: 'desc', - }, - }, - }); - }, - tag.split('_'), - { - tags: [tag], - revalidate: 3600 * 24, - } - )(organizationId); -} diff --git a/apps/web/src/server/services/user.service.ts b/apps/web/src/server/services/user.service.ts index 5f379f3c..a41815ce 100644 --- a/apps/web/src/server/services/user.service.ts +++ b/apps/web/src/server/services/user.service.ts @@ -1,20 +1,24 @@ -import { db } from '@/server/db'; +import { auth, clerkClient } from '@clerk/nextjs'; +import type { User } from '@clerk/nextjs/dist/types/server'; -export function getUserById(id: string) { - return db.user.findUniqueOrThrow({ - where: { - id, - }, - }); +export function transformUser(user: User) { + return { + name: `${user.firstName} ${user.lastName}`, + email: user.emailAddresses[0]?.emailAddress ?? '', + id: user.id, + lastName: user.lastName ?? '', + firstName: user.firstName ?? '', + }; } -export type IServiceInvite = Awaited< - ReturnType ->[number]; -export function getInvitesByOrganizationId(organizationId: string) { - return db.invite.findMany({ - where: { - organization_id: organizationId, - }, - }); +export async function getCurrentUser() { + const session = auth(); + if (!session.userId) { + return null; + } + return getUserById(session.userId); +} + +export async function getUserById(id: string) { + return clerkClient.users.getUser(id).then(transformUser); } diff --git a/packages/db/prisma/migrations/20240207084900_remove_users/migration.sql b/packages/db/prisma/migrations/20240207084900_remove_users/migration.sql new file mode 100644 index 00000000..02da6562 --- /dev/null +++ b/packages/db/prisma/migrations/20240207084900_remove_users/migration.sql @@ -0,0 +1,17 @@ +/* + Warnings: + + - You are about to drop the `users` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "recent_dashboards" DROP CONSTRAINT "recent_dashboards_user_id_fkey"; + +-- DropForeignKey +ALTER TABLE "users" DROP CONSTRAINT "users_organization_id_fkey"; + +-- AlterTable +ALTER TABLE "recent_dashboards" ALTER COLUMN "user_id" SET DATA TYPE TEXT; + +-- DropTable +DROP TABLE "users"; diff --git a/packages/db/prisma/migrations/20240207085251_remove_org/migration.sql b/packages/db/prisma/migrations/20240207085251_remove_org/migration.sql new file mode 100644 index 00000000..dafeeecb --- /dev/null +++ b/packages/db/prisma/migrations/20240207085251_remove_org/migration.sql @@ -0,0 +1,39 @@ +/* + Warnings: + + - You are about to drop the column `organization_id` on the `clients` table. All the data in the column will be lost. + - You are about to drop the column `organization_id` on the `projects` table. All the data in the column will be lost. + - You are about to drop the column `organization_id` on the `recent_dashboards` table. All the data in the column will be lost. + - You are about to drop the `invites` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `organizations` table. If the table is not empty, all the data it contains will be lost. + - Added the required column `organization_slug` to the `clients` table without a default value. This is not possible if the table is not empty. + - Added the required column `organization_slug` to the `projects` table without a default value. This is not possible if the table is not empty. + - Added the required column `organization_slug` to the `recent_dashboards` table without a default value. This is not possible if the table is not empty. + +*/ +-- DropForeignKey +ALTER TABLE "clients" DROP CONSTRAINT "clients_organization_id_fkey"; + +-- DropForeignKey +ALTER TABLE "invites" DROP CONSTRAINT "invites_organization_id_fkey"; + +-- DropForeignKey +ALTER TABLE "projects" DROP CONSTRAINT "projects_organization_id_fkey"; + +-- AlterTable +ALTER TABLE "clients" DROP COLUMN "organization_id", +ADD COLUMN "organization_slug" TEXT NOT NULL; + +-- AlterTable +ALTER TABLE "projects" DROP COLUMN "organization_id", +ADD COLUMN "organization_slug" TEXT NOT NULL; + +-- AlterTable +ALTER TABLE "recent_dashboards" DROP COLUMN "organization_id", +ADD COLUMN "organization_slug" TEXT NOT NULL; + +-- DropTable +DROP TABLE "invites"; + +-- DropTable +DROP TABLE "organizations"; diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma index fd2ae773..05375f90 100644 --- a/packages/db/prisma/schema.prisma +++ b/packages/db/prisma/schema.prisma @@ -10,29 +10,14 @@ datasource db { url = env("DATABASE_URL") } -model Organization { - id String @id @default(dbgenerated("gen_random_uuid()")) - name String - projects Project[] - users User[] - - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) @updatedAt - clients Client[] - Invite Invite[] - - @@map("organizations") -} - model Project { - id String @id @default(dbgenerated("gen_random_uuid()")) - name String - organization_id String - organization Organization @relation(fields: [organization_id], references: [id]) - events Event[] - eventsCount Int @default(0) - profiles Profile[] - clients Client[] + id String @id @default(dbgenerated("gen_random_uuid()")) + name String + organization_slug String + events Event[] + eventsCount Int @default(0) + profiles Profile[] + clients Client[] createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt @@ -43,21 +28,6 @@ model Project { @@map("projects") } -model User { - id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid - name String - email String - password String - organization_id String - organization Organization @relation(fields: [organization_id], references: [id]) - - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) @updatedAt - RecentDashboards RecentDashboards[] - - @@map("users") -} - model Event { id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid name String @@ -107,14 +77,13 @@ model EventFailed { } model Client { - id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid - name String - secret String? - project_id String - project Project @relation(fields: [project_id], references: [id]) - organization_id String - organization Organization @relation(fields: [organization_id], references: [id]) - cors String @default("*") + id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid + name String + secret String? + project_id String + project Project @relation(fields: [project_id], references: [id]) + organization_slug String + cors String @default("*") createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt @@ -123,15 +92,14 @@ model Client { } model RecentDashboards { - id String @id @default(dbgenerated("gen_random_uuid()")) - project_id String - project Project @relation(fields: [project_id], references: [id]) - organization_id String - dashboard_id String - dashboard Dashboard @relation(fields: [dashboard_id], references: [id]) - user_id String @db.Uuid - user User @relation(fields: [user_id], references: [id]) - createdAt DateTime @default(now()) + id String @id @default(dbgenerated("gen_random_uuid()")) + project_id String + project Project @relation(fields: [project_id], references: [id]) + organization_slug String + dashboard_id String + dashboard Dashboard @relation(fields: [dashboard_id], references: [id]) + user_id String + createdAt DateTime @default(now()) @@map("recent_dashboards") } @@ -198,20 +166,6 @@ model Report { @@map("reports") } -model Invite { - id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid - email String - organization_id String - organization Organization @relation(fields: [organization_id], references: [id]) - - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) @updatedAt - - accepted Boolean @default(false) - - @@map("invites") -} - model Waitlist { id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid email String @unique diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 27c58b60..a377d767 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -317,6 +317,9 @@ importers: apps/web: dependencies: + '@clerk/nextjs': + specifier: ^4.29.6 + version: 4.29.6(next@14.0.4)(react-dom@18.2.0)(react@18.2.0) '@clickhouse/client': specifier: ^0.2.9 version: 0.2.9 @@ -417,8 +420,8 @@ importers: specifier: ^2.4.0 version: 2.4.0(react-dom@18.2.0)(react@18.2.0) lucide-react: - specifier: ^0.286.0 - version: 0.286.0(react@18.2.0) + specifier: ^0.323.0 + version: 0.323.0(react@18.2.0) mathjs: specifier: ^12.3.0 version: 12.3.0 @@ -1239,6 +1242,93 @@ packages: '@bull-board/api': 5.13.0(@bull-board/ui@5.13.0) dev: false + /@clerk/backend@0.38.0(react@18.2.0): + resolution: {integrity: sha512-qSYg84WBElweSF24a74rp1HTOSFtCb6+CKsZydCkV32MOu2+mqnZSsQLBEO0P/dUegb92y+nJC1e77tQD2salg==} + engines: {node: '>=14'} + dependencies: + '@clerk/shared': 1.3.1(react@18.2.0) + '@clerk/types': 3.61.0 + '@peculiar/webcrypto': 1.4.1 + '@types/node': 16.18.6 + cookie: 0.5.0 + deepmerge: 4.2.2 + node-fetch-native: 1.0.1 + snakecase-keys: 5.4.4 + tslib: 2.4.1 + transitivePeerDependencies: + - react + dev: false + + /@clerk/clerk-react@4.30.4(react@18.2.0): + resolution: {integrity: sha512-XezWkEb9n9dsRGtyOAJY5CmgmgY6DTYm0P1hY5b2/l8xond5cAhjVoc+1aESHbM6Z3Yho9odYhStrkN1JeRRFg==} + engines: {node: '>=14'} + peerDependencies: + react: '>=16' + dependencies: + '@clerk/shared': 1.3.1(react@18.2.0) + '@clerk/types': 3.61.0 + react: 18.2.0 + tslib: 2.4.1 + dev: false + + /@clerk/clerk-sdk-node@4.13.8(react@18.2.0): + resolution: {integrity: sha512-FFsp01/kFcoHFY4g4Ah5e3M1R7c45lZAp9IrGWVmtjdNvG2W4aIra8tB07J0MV6hAFXk8dtiQzDH0wT91o6JMw==} + engines: {node: '>=14'} + dependencies: + '@clerk/backend': 0.38.0(react@18.2.0) + '@clerk/shared': 1.3.1(react@18.2.0) + '@clerk/types': 3.61.0 + '@types/cookies': 0.7.7 + '@types/express': 4.17.14 + '@types/node-fetch': 2.6.2 + camelcase-keys: 6.2.2 + snakecase-keys: 3.2.1 + tslib: 2.4.1 + transitivePeerDependencies: + - react + dev: false + + /@clerk/nextjs@4.29.6(next@14.0.4)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-JLMAuYqPQALQNjQ1+x36sQjEP5KhTz7GbpbtsAHKaVk33XadVHb13c+5zarLynLmqAaM2mNZb1LCPD5N4wVqYg==} + engines: {node: '>=14'} + peerDependencies: + next: '>=10' + react: ^17.0.2 || ^18.0.0-0 + react-dom: ^17.0.2 || ^18.0.0-0 + dependencies: + '@clerk/backend': 0.38.0(react@18.2.0) + '@clerk/clerk-react': 4.30.4(react@18.2.0) + '@clerk/clerk-sdk-node': 4.13.8(react@18.2.0) + '@clerk/shared': 1.3.1(react@18.2.0) + '@clerk/types': 3.61.0 + next: 14.0.4(react-dom@18.2.0)(react@18.2.0) + path-to-regexp: 6.2.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + tslib: 2.4.1 + dev: false + + /@clerk/shared@1.3.1(react@18.2.0): + resolution: {integrity: sha512-nzv4+uA90I/eQp55zfK9a1Po9VgCYlzlNhuZnKqyRsPyJ38l4gpIf3B3qSHHdN0+MTx9cWGFrik1CnpftdOBXQ==} + peerDependencies: + react: '>=16' + peerDependenciesMeta: + react: + optional: true + dependencies: + glob-to-regexp: 0.4.1 + js-cookie: 3.0.1 + react: 18.2.0 + swr: 2.2.0(react@18.2.0) + dev: false + + /@clerk/types@3.61.0: + resolution: {integrity: sha512-MOVBROPWEtKNX4zcPVTJK0ZhfENYQ6rFHeR0E8XIZ4s0fX5juziH1e+FTBHM0Fda9GvsEPyyBgsZoGwHQPy45w==} + engines: {node: '>=14'} + dependencies: + csstype: 3.1.1 + dev: false + /@clickhouse/client-common@0.2.9: resolution: {integrity: sha512-ecXcegMbT4HYNWtGcfyidW6lNVRqPogbFMY5kfjJmz4IXJ4WZbQMwj2IQgemwFwE7jyia2OEwPIVfw1sNfDHRA==} dev: false @@ -2072,6 +2162,32 @@ packages: resolution: {integrity: sha512-dhPeilub1NuIG0X5Kvhh9lH4iW3ZsHlnzwgwbOlgwQ2wG1IqFzsgHqmKPk3WzsdWAeaxKJxgM0+W433RmN45GA==} dev: false + /@peculiar/asn1-schema@2.3.8: + resolution: {integrity: sha512-ULB1XqHKx1WBU/tTFIA+uARuRoBVZ4pNdOA878RDrRbBfBGcSzi5HBkdScC6ZbHn8z7L8gmKCgPC1LHRrP46tA==} + dependencies: + asn1js: 3.0.5 + pvtsutils: 1.3.5 + tslib: 2.6.2 + dev: false + + /@peculiar/json-schema@1.1.12: + resolution: {integrity: sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==} + engines: {node: '>=8.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@peculiar/webcrypto@1.4.1: + resolution: {integrity: sha512-eK4C6WTNYxoI7JOabMoZICiyqRRtJB220bh0Mbj5RwRycleZf9BPyZoxsTvpP0FpmVS2aS13NKOuh5/tN3sIRw==} + engines: {node: '>=10.12.0'} + dependencies: + '@peculiar/asn1-schema': 2.3.8 + '@peculiar/json-schema': 1.1.12 + pvtsutils: 1.3.5 + tslib: 2.6.2 + webcrypto-core: 1.7.8 + dev: false + /@prisma/client@5.5.2(prisma@5.5.2): resolution: {integrity: sha512-54XkqR8M+fxbzYqe+bIXimYnkkcGqgOh0dn0yWtIk6CQT4IUCAvNFNcQZwk2KqaLU+/1PHTSWrcHtx4XjluR5w==} engines: {node: '>=16.13'} @@ -3230,13 +3346,20 @@ packages: dependencies: '@types/connect': 3.4.38 '@types/node': 18.18.8 - dev: true /@types/connect@3.4.38: resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} dependencies: '@types/node': 18.18.8 - dev: true + + /@types/cookies@0.7.7: + resolution: {integrity: sha512-h7BcvPUogWbKCzBR2lY4oqaZbO3jXZksexYJVFvkrFeLgbZjQkU4x8pRq6eg2MHXQhY0McQdqmmsxRWlVAHooA==} + dependencies: + '@types/connect': 3.4.38 + '@types/express': 4.17.21 + '@types/keygrip': 1.0.6 + '@types/node': 18.18.8 + dev: false /@types/d3-array@3.0.9: resolution: {integrity: sha512-mZowFN3p64ajCJJ4riVYlOjNlBJv3hctgAY01pjw3qTnJePD8s9DZmYDzhHKvzfCYvdjwylkU38+Vdt7Cu2FDA==} @@ -3296,7 +3419,15 @@ packages: '@types/qs': 6.9.11 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 - dev: true + + /@types/express@4.17.14: + resolution: {integrity: sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg==} + dependencies: + '@types/body-parser': 1.19.5 + '@types/express-serve-static-core': 4.17.41 + '@types/qs': 6.9.11 + '@types/serve-static': 1.15.5 + dev: false /@types/express@4.17.21: resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} @@ -3305,7 +3436,6 @@ packages: '@types/express-serve-static-core': 4.17.41 '@types/qs': 6.9.11 '@types/serve-static': 1.15.5 - dev: true /@types/hast@2.3.7: resolution: {integrity: sha512-EVLigw5zInURhzfXUM65eixfadfsHKomGKUakToXo84t8gGIJuTcD2xooM2See7GyQ7DRtYjhCHnSUQez8JaLw==} @@ -3322,7 +3452,6 @@ packages: /@types/http-errors@2.0.4: resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} - dev: true /@types/json-schema@7.0.14: resolution: {integrity: sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==} @@ -3331,6 +3460,10 @@ packages: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: false + /@types/keygrip@1.0.6: + resolution: {integrity: sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==} + dev: false + /@types/lodash.debounce@4.0.9: resolution: {integrity: sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==} dependencies: @@ -3343,24 +3476,31 @@ packages: /@types/mime@1.3.5: resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} - dev: true /@types/mime@3.0.4: resolution: {integrity: sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==} - dev: true + + /@types/node-fetch@2.6.2: + resolution: {integrity: sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==} + dependencies: + '@types/node': 18.18.8 + form-data: 3.0.1 + dev: false + + /@types/node@16.18.6: + resolution: {integrity: sha512-vmYJF0REqDyyU0gviezF/KHq/fYaUbFhkcNbQCuPGFQj6VTbXuHZoxs/Y7mutWe73C8AC6l9fFu8mSYiBAqkGA==} + dev: false /@types/node@18.18.8: resolution: {integrity: sha512-OLGBaaK5V3VRBS1bAkMVP2/W9B+H8meUfl866OrMNQqt7wDgdpWPp5o6gmIc9pB+lIQHSq4ZL8ypeH1vPxcPaQ==} dependencies: undici-types: 5.26.5 - dev: true /@types/prop-types@15.7.9: resolution: {integrity: sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g==} /@types/qs@6.9.11: resolution: {integrity: sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==} - dev: true /@types/ramda@0.29.7: resolution: {integrity: sha512-IUl6U95qwlQtVvZkSX4ODj08oJVtPyWMFRtPVNqhxc2rt+Bh7lCzTrGMYMZ7dmRKcAjtot3xrPnYGwsjdt8gzQ==} @@ -3370,7 +3510,6 @@ packages: /@types/range-parser@1.2.7: resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} - dev: true /@types/react-dom@18.2.14: resolution: {integrity: sha512-V835xgdSVmyQmI1KLV2BEIUgqEuinxp9O4G6g3FqO/SqLac049E53aysv0oEFD2kHfejeKU+ZqL2bcFWj9gLAQ==} @@ -3407,7 +3546,6 @@ packages: dependencies: '@types/mime': 1.3.5 '@types/node': 18.18.8 - dev: true /@types/serve-static@1.15.5: resolution: {integrity: sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==} @@ -3415,7 +3553,6 @@ packages: '@types/http-errors': 2.0.4 '@types/mime': 3.0.4 '@types/node': 18.18.8 - dev: true /@types/ua-parser-js@0.7.39: resolution: {integrity: sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg==} @@ -3769,6 +3906,15 @@ packages: is-shared-array-buffer: 1.0.2 dev: false + /asn1js@3.0.5: + resolution: {integrity: sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==} + engines: {node: '>=12.0.0'} + dependencies: + pvtsutils: 1.3.5 + pvutils: 1.1.3 + tslib: 2.6.2 + dev: false + /ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} dev: false @@ -3783,6 +3929,10 @@ packages: has-symbols: 1.0.3 dev: false + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: false + /atomic-sleep@1.0.0: resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} engines: {node: '>=8.0.0'} @@ -3967,6 +4117,20 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} + /camelcase-keys@6.2.2: + resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} + engines: {node: '>=8'} + dependencies: + camelcase: 5.3.1 + map-obj: 4.3.0 + quick-lru: 4.0.1 + dev: false + + /camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + dev: false + /caniuse-lite@1.0.30001559: resolution: {integrity: sha512-cPiMKZgqgkg5LY3/ntGeLFUpi6tzddBNS58A4tnTgQw1zON7u2sZMU7SzOeVH4tj20++9ggL+V6FDOFMTaFFYA==} dev: false @@ -4098,6 +4262,13 @@ packages: color-string: 1.9.1 dev: false + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: false + /comma-separated-tokens@1.0.8: resolution: {integrity: sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==} dev: false @@ -4177,6 +4348,10 @@ packages: engines: {node: '>=4'} hasBin: true + /csstype@3.1.1: + resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==} + dev: false + /csstype@3.1.2: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} @@ -4311,6 +4486,11 @@ packages: /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + /deepmerge@4.2.2: + resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==} + engines: {node: '>=0.10.0'} + dev: false + /define-data-property@1.1.1: resolution: {integrity: sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==} engines: {node: '>= 0.4'} @@ -4329,6 +4509,11 @@ packages: object-keys: 1.1.1 dev: false + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: false + /delegates@1.0.0: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} dev: false @@ -4393,6 +4578,13 @@ packages: '@babel/runtime': 7.23.2 dev: false + /dot-case@3.0.4: + resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + dependencies: + no-case: 3.0.4 + tslib: 2.6.2 + dev: false + /dotenv-cli@7.3.0: resolution: {integrity: sha512-314CA4TyK34YEJ6ntBf80eUY+t1XaFLyem1k9P0sX1gn30qThZ5qZr/ZwE318gEnzyYP9yj9HJk6SqwE0upkfw==} hasBin: true @@ -5158,6 +5350,15 @@ packages: is-callable: 1.2.7 dev: false + /form-data@3.0.1: + resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: false + /format@0.2.2: resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} engines: {node: '>=0.4.x'} @@ -5799,6 +6000,11 @@ packages: engines: {node: '>=10'} dev: true + /js-cookie@3.0.1: + resolution: {integrity: sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==} + engines: {node: '>=12'} + dev: false + /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} dev: false @@ -5951,6 +6157,12 @@ packages: resolution: {integrity: sha512-uvhvYPC8kGPjXT3MyKMrL3JitEAmDMp30lVkuq/590Mw9ok6pWcFCwXJveo0t5uqYw1UREQHofD+jVpdjBv8wg==} dev: false + /lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + dependencies: + tslib: 2.6.2 + dev: false + /lowlight@1.20.0: resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==} dependencies: @@ -5970,14 +6182,6 @@ packages: dependencies: yallist: 4.0.0 - /lucide-react@0.286.0(react@18.2.0): - resolution: {integrity: sha512-0+AOFa/uiXlXJJTqcKto1gqbU9XflYgYZbS9DN2ytSIhSBQaF5xfRKAq/k0okBInpgu5P6i7dhCcgbHV4OMkHQ==} - peerDependencies: - react: ^16.5.1 || ^17.0.0 || ^18.0.0 - dependencies: - react: 18.2.0 - dev: false - /lucide-react@0.323.0(react@18.2.0): resolution: {integrity: sha512-rTXZFILl2Y4d1SG9p1Mdcf17AcPvPvpc/egFIzUrp7IUy60MUQo3Oi1mu8LGYXUVwuRZYsSMt3csHRW5mAovJg==} peerDependencies: @@ -5998,6 +6202,11 @@ packages: semver: 6.3.1 dev: false + /map-obj@4.3.0: + resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} + engines: {node: '>=8'} + dev: false + /matchmediaquery@0.3.1: resolution: {integrity: sha512-Hlk20WQHRIm9EE9luN1kjRjYXAQToHOIAHPJn9buxBwuhfTHoKUcX+lXBbxc85DVQfXYbEQ4HcwQdd128E3qHQ==} dependencies: @@ -6282,6 +6491,13 @@ packages: - babel-plugin-macros dev: false + /no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + dependencies: + lower-case: 2.0.2 + tslib: 2.6.2 + dev: false + /node-abort-controller@3.1.1: resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} dev: false @@ -6290,6 +6506,10 @@ packages: resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} dev: false + /node-fetch-native@1.0.1: + resolution: {integrity: sha512-VzW+TAk2wE4X9maiKMlT+GsPU4OMmR1U9CrHSmd3DFLn2IcZ9VJ6M6BBugGfYUnPCLSYxXdZy17M0BEJyhUTwg==} + dev: false + /node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -6539,6 +6759,10 @@ packages: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} dev: false + /path-to-regexp@6.2.1: + resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==} + dev: false + /path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -6801,6 +7025,17 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + /pvtsutils@1.3.5: + resolution: {integrity: sha512-ARvb14YB9Nm2Xi6nBq1ZX6dAM0FsJnuk+31aUp4TrcZEdKUlSqOqsxJHUPJDNE3qiIp+iUPEIeR6Je/tgV7zsA==} + dependencies: + tslib: 2.6.2 + dev: false + + /pvutils@1.1.3: + resolution: {integrity: sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==} + engines: {node: '>=6.0.0'} + dev: false + /qs@6.11.0: resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} engines: {node: '>=0.6'} @@ -6815,6 +7050,11 @@ packages: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} dev: false + /quick-lru@4.0.1: + resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} + engines: {node: '>=8'} + dev: false + /ramda@0.29.1: resolution: {integrity: sha512-OfxIeWzd4xdUNxlWhgFazxsA/nl3mS4/jGZI5n00uWOoSSFRhC1b6gl6xvmzUamgmqELraWp0J/qqVlXYPDPyA==} dev: false @@ -7512,6 +7752,30 @@ packages: engines: {node: '>=8.0.0'} dev: false + /snake-case@3.0.4: + resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} + dependencies: + dot-case: 3.0.4 + tslib: 2.6.2 + dev: false + + /snakecase-keys@3.2.1: + resolution: {integrity: sha512-CjU5pyRfwOtaOITYv5C8DzpZ8XA/ieRsDpr93HI2r6e3YInC6moZpSQbmUtg8cTk58tq2x3jcG2gv+p1IZGmMA==} + engines: {node: '>=8'} + dependencies: + map-obj: 4.3.0 + to-snake-case: 1.0.0 + dev: false + + /snakecase-keys@5.4.4: + resolution: {integrity: sha512-YTywJG93yxwHLgrYLZjlC75moVEX04LZM4FHfihjHe1FCXm+QaLOFfSf535aXOAd0ArVQMWUAe8ZPm4VtWyXaA==} + engines: {node: '>=12'} + dependencies: + map-obj: 4.3.0 + snake-case: 3.0.4 + type-fest: 2.19.0 + dev: false + /sonic-boom@3.8.0: resolution: {integrity: sha512-ybz6OYOUjoQQCQ/i4LU8kaToD8ACtYP+Cj5qd2AO36bwbdewxWJ3ArmJ2cr6AvxlL2o0PqnCcPGUgkILbfkaCA==} dependencies: @@ -7680,6 +7944,15 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + /swr@2.2.0(react@18.2.0): + resolution: {integrity: sha512-AjqHOv2lAhkuUdIiBu9xbuettzAzWXmCEcLONNKJRba87WAefz8Ca9d6ds/SzrPc235n1IxWYdhJ2zF3MNUaoQ==} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + use-sync-external-store: 1.2.0(react@18.2.0) + dev: false + /tailwind-merge@1.14.0: resolution: {integrity: sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==} dev: false @@ -7772,12 +8045,28 @@ packages: engines: {node: '>=4'} dev: false + /to-no-case@1.0.2: + resolution: {integrity: sha512-Z3g735FxuZY8rodxV4gH7LxClE4H0hTIyHNIHdk+vpQxjLm0cwnKXq/OFVZ76SOQmto7txVcwSCwkU5kqp+FKg==} + dev: false + /to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} dependencies: is-number: 7.0.0 + /to-snake-case@1.0.0: + resolution: {integrity: sha512-joRpzBAk1Bhi2eGEYBjukEWHOe/IvclOkiJl3DtA91jV6NwQ3MwXA4FHYeqk8BNp/D8bmi9tcNbRu/SozP0jbQ==} + dependencies: + to-space-case: 1.0.0 + dev: false + + /to-space-case@1.0.0: + resolution: {integrity: sha512-rLdvwXZ39VOn1IxGL3V6ZstoTbwLRckQmn/U8ZDLuWwIXNpuZDhQ3AiRUlhTbOXFVE9C+dR51wM0CBDhk31VcA==} + dependencies: + to-no-case: 1.0.2 + dev: false + /toad-cache@3.7.0: resolution: {integrity: sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==} engines: {node: '>=12'} @@ -7827,6 +8116,10 @@ packages: strip-bom: 3.0.0 dev: false + /tslib@2.4.1: + resolution: {integrity: sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==} + dev: false + /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} dev: false @@ -7877,6 +8170,11 @@ packages: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} + /type-fest@2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} + dev: false + /type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} @@ -7954,7 +8252,6 @@ packages: /undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - dev: true /unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} @@ -8076,6 +8373,16 @@ packages: graceful-fs: 4.2.11 dev: false + /webcrypto-core@1.7.8: + resolution: {integrity: sha512-eBR98r9nQXTqXt/yDRtInszPMjTaSAMJAFDg2AHsgrnczawT1asx9YNBX6k5p+MekbPF4+s/UJJrr88zsTqkSg==} + dependencies: + '@peculiar/asn1-schema': 2.3.8 + '@peculiar/json-schema': 1.1.12 + asn1js: 3.0.5 + pvtsutils: 1.3.5 + tslib: 2.6.2 + dev: false + /webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} dev: false