mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
Merge remote-tracking branch 'origin/main'
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
migrate:
|
||||
docker compose run --rm app sh -c "npx prisma migrate dev"
|
||||
@@ -13,6 +13,7 @@ import {AgentModalKey} from "@/components/wrappers/Agent/AgentModalKey/AgentModa
|
||||
import {KeyRound} from "lucide-react";
|
||||
import {BackupButton} from "@/components/wrappers/BackupButton/BackupButton";
|
||||
import {backups, restaurations} from "@/utils/mock-data";
|
||||
import {formatDateLastContact} from "@/utils/date-formatting";
|
||||
|
||||
|
||||
export default async function RoutePage(props: PageParams<{ agentId: string }>) {
|
||||
@@ -84,7 +85,7 @@ export default async function RoutePage(props: PageParams<{ agentId: string }>)
|
||||
Last contact
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{agent.lastContact?.toDateString() ?? "Never connected."}
|
||||
{formatDateLastContact(agent.lastContact)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -6,9 +6,11 @@ import {Button} from "@/components/ui/button";
|
||||
import Link from 'next/link'
|
||||
import {Page, PageActions, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
|
||||
|
||||
|
||||
export default async function RoutePage(props: PageParams<{}>) {
|
||||
|
||||
const agents = await prisma.agent.findMany({})
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<PageHeader>
|
||||
|
||||
@@ -6,6 +6,7 @@ import {AppSidebar} from "@/components/wrappers/Dashboard/SideBar/app-sidebar";
|
||||
import {currentUser} from "@/auth/current-user";
|
||||
import {Header} from "@/features/layout/Header";
|
||||
import {prisma} from "@/prisma";
|
||||
import {GlobalStoreProvider} from "@/state-management/provider";
|
||||
|
||||
|
||||
export default async function Layout({children}: { children: React.ReactNode }) {
|
||||
@@ -22,6 +23,8 @@ export default async function Layout({children}: { children: React.ReactNode })
|
||||
if (!user) redirect('/login')
|
||||
|
||||
return (
|
||||
// <HydrationZustand>
|
||||
<GlobalStoreProvider>
|
||||
<SidebarProvider>
|
||||
<div className="flex flex-col lg:flex-row w-full">
|
||||
<AppSidebar/>
|
||||
@@ -33,5 +36,7 @@ export default async function Layout({children}: { children: React.ReactNode })
|
||||
</SidebarInset>
|
||||
</div>
|
||||
</SidebarProvider>
|
||||
</GlobalStoreProvider>
|
||||
// </HydrationZustand>
|
||||
)
|
||||
}
|
||||
@@ -32,10 +32,13 @@ export default async function RoutePage(props: PageParams<{
|
||||
const availableDatabases = await prisma.database.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ projectId: null },
|
||||
{ projectId: project.id },
|
||||
{projectId: null},
|
||||
{projectId: project.id},
|
||||
],
|
||||
},
|
||||
include: {
|
||||
agent: {}
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
@@ -50,7 +53,8 @@ export default async function RoutePage(props: PageParams<{
|
||||
</PageTitle>
|
||||
</PageHeader>
|
||||
<PageContent>
|
||||
<ProjectForm organization={organization} databases={availableDatabases} defaultValues={project} projectId={project.id}/>
|
||||
<ProjectForm organization={organization} databases={availableDatabases} defaultValues={project}
|
||||
projectId={project.id}/>
|
||||
</PageContent>
|
||||
</Page>
|
||||
)
|
||||
|
||||
@@ -10,6 +10,9 @@ export default async function RoutePage(props: PageParams<{}>) {
|
||||
where: {
|
||||
projectId: null
|
||||
},
|
||||
include: {
|
||||
agent: {}
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
|
||||
@@ -5,17 +5,28 @@ import {Button} from "@/components/ui/button";
|
||||
import Link from 'next/link'
|
||||
import {Page, PageActions, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
|
||||
import {ProjectCard} from "@/components/wrappers/Dashboard/Projects/ProjectCard/ProjectCard";
|
||||
import {requiredCurrentUser} from "@/auth/current-user";
|
||||
|
||||
export default async function RoutePage(props: PageParams<{}>) {
|
||||
|
||||
// const {searchParams} = props
|
||||
// const organizationId = searchParams?.organizationId || "default";
|
||||
|
||||
const user = await requiredCurrentUser()
|
||||
|
||||
const projects = await prisma.project.findMany({
|
||||
where: {
|
||||
organization: {
|
||||
slug: "default",
|
||||
// id: organizationId,
|
||||
users: {
|
||||
some: {
|
||||
userId: user.id
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
include:{
|
||||
include: {
|
||||
databases: {}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// import {NextResponse} from "next/server";
|
||||
// import {useStore} from "@/state-management/store";
|
||||
//
|
||||
// export function middleware(req) {
|
||||
// // const organizationId = req.cookies.get("organizationId") || "default"; // Retrieve from cookies
|
||||
// const url = req.nextUrl.clone();
|
||||
// const {organizationId, moveToAnotherOrganization} = useStore((state) => state);
|
||||
// console.log("middlewaressss", organizationId);
|
||||
// // url.searchParams.set("organizationId", organizationId); // Append to query string
|
||||
// return NextResponse.rewrite(url);
|
||||
// }
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:20-alpine AS base
|
||||
FROM node:20.1.0-alpine AS base
|
||||
|
||||
ENV YARN_VERSION=4.2.2
|
||||
|
||||
|
||||
+2
-2
@@ -8,7 +8,6 @@
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"email": "email dev"
|
||||
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth/prisma-adapter": "^2.4.1",
|
||||
@@ -76,7 +75,8 @@
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"uuid": "^11.0.3",
|
||||
"vaul": "^1.1.1",
|
||||
"zod": "^3.23.8"
|
||||
"zod": "^3.23.8",
|
||||
"zustand": "^5.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20",
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "_OrganizationToUser" (
|
||||
"A" TEXT NOT NULL,
|
||||
"B" TEXT NOT NULL
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "_OrganizationToUser_AB_unique" ON "_OrganizationToUser"("A", "B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_OrganizationToUser_B_index" ON "_OrganizationToUser"("B");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_OrganizationToUser" ADD CONSTRAINT "_OrganizationToUser_A_fkey" FOREIGN KEY ("A") REFERENCES "organizations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_OrganizationToUser" ADD CONSTRAINT "_OrganizationToUser_B_fkey" FOREIGN KEY ("B") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the `_OrganizationToUser` table. If the table is not empty, all the data it contains will be lost.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "_OrganizationToUser" DROP CONSTRAINT "_OrganizationToUser_A_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "_OrganizationToUser" DROP CONSTRAINT "_OrganizationToUser_B_fkey";
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "_OrganizationToUser";
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "UserOrganization" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"organizationId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "UserOrganization_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "UserOrganization_userId_organizationId_key" ON "UserOrganization"("userId", "organizationId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "UserOrganization" ADD CONSTRAINT "UserOrganization_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "UserOrganization" ADD CONSTRAINT "UserOrganization_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "organizations"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the `UserOrganization` table. If the table is not empty, all the data it contains will be lost.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "UserOrganization" DROP CONSTRAINT "UserOrganization_organizationId_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "UserOrganization" DROP CONSTRAINT "UserOrganization_userId_fkey";
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "UserOrganization";
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "users_organisations" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"organizationId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "users_organisations_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "users_organisations_userId_organizationId_key" ON "users_organisations"("userId", "organizationId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "users_organisations" ADD CONSTRAINT "users_organisations_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "users_organisations" ADD CONSTRAINT "users_organisations_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "organizations"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -67,6 +67,7 @@ model User {
|
||||
deleted Boolean? @default(false)
|
||||
accounts Account[]
|
||||
sessions Session[]
|
||||
organizations UserOrganization[]
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
@@ -84,10 +85,22 @@ model Organization {
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
projects Project[]
|
||||
users UserOrganization[]
|
||||
|
||||
@@map("organizations")
|
||||
}
|
||||
|
||||
model UserOrganization {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
organizationId String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
organization Organization @relation(fields: [organizationId], references: [id])
|
||||
|
||||
@@unique([userId, organizationId])
|
||||
@@map("users_organisations")
|
||||
}
|
||||
|
||||
model Project {
|
||||
id String @id @default(cuid())
|
||||
slug String @unique
|
||||
|
||||
+20
-9
@@ -6,7 +6,6 @@ import {env} from "@/env.mjs";
|
||||
import GoogleProvider from "next-auth/providers/google";
|
||||
|
||||
|
||||
|
||||
export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({
|
||||
adapter: PrismaAdapter(prisma),
|
||||
theme: {
|
||||
@@ -79,20 +78,20 @@ export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({
|
||||
// // session.user.authMethod = user.authMethod;
|
||||
// return session
|
||||
// },
|
||||
async jwt({ token, trigger, session, user }) {
|
||||
async jwt({token, trigger, session, user}) {
|
||||
if (trigger === "update" && session) {
|
||||
return { ...token, ...session?.user };
|
||||
return {...token, ...session?.user};
|
||||
}
|
||||
|
||||
return { ...token, ...user };
|
||||
return {...token, ...user};
|
||||
},
|
||||
async session({ session, token, user }) {
|
||||
async session({session, token, user}) {
|
||||
session.user = token;
|
||||
return session;
|
||||
},
|
||||
async signIn({ account, user, profile }) {
|
||||
async signIn({account, user, profile}) {
|
||||
const existingUser = await prisma.user.findFirst({
|
||||
where: { email: user.email },
|
||||
where: {email: user.email},
|
||||
});
|
||||
|
||||
if (!existingUser) {
|
||||
@@ -106,7 +105,7 @@ export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({
|
||||
})
|
||||
const role = users.length > 0 ? "pending" : "admin"
|
||||
|
||||
await prisma.user.create({
|
||||
const newUser = await prisma.user.create({
|
||||
data: {
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
@@ -115,6 +114,18 @@ export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({
|
||||
authMethod: account.provider,
|
||||
},
|
||||
});
|
||||
|
||||
const defaultOrganization = await prisma.organization.findUnique({
|
||||
where: {slug: "default"},
|
||||
});
|
||||
|
||||
await prisma.userOrganization.create({
|
||||
data: {
|
||||
userId: newUser.id,
|
||||
organizationId: defaultOrganization.id
|
||||
},
|
||||
});
|
||||
|
||||
return role !== "pending";
|
||||
|
||||
}
|
||||
@@ -125,7 +136,7 @@ export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({
|
||||
|
||||
// Update auth method if user exists
|
||||
await prisma.user.update({
|
||||
where: { id: existingUser.id },
|
||||
where: {id: existingUser.id},
|
||||
data: {
|
||||
authMethod: account.provider,
|
||||
},
|
||||
|
||||
@@ -5,6 +5,7 @@ import Link from "next/link";
|
||||
import {ValueIcon} from "@radix-ui/react-icons";
|
||||
import {Circle} from "lucide-react";
|
||||
import {ConnectionCircle} from "@/components/wrappers/connection-circle";
|
||||
import {formatDateLastContact} from "@/utils/date-formatting";
|
||||
|
||||
export type agentCardProps = {
|
||||
data: any
|
||||
@@ -20,7 +21,7 @@ export const AgentCard = (props: agentCardProps) => {
|
||||
<div className="">
|
||||
<CardHeader>{agent.name}</CardHeader>
|
||||
<CardContent>
|
||||
Last contact : {agent.lastContact?.toDateString() ?? "Never connected."}
|
||||
Last contact : {formatDateLastContact(agent.lastContact)}
|
||||
</CardContent>
|
||||
</div>
|
||||
<div className="mt-3 mr-3">
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import {useRouter} from "next/navigation";
|
||||
import {Info} from "lucide-react";
|
||||
import {useMutation} from "@tanstack/react-query";
|
||||
import {toast} from "sonner";
|
||||
|
||||
import {Card, CardContent, CardHeader} from "@/components/ui/card";
|
||||
import {
|
||||
FormControl, FormField, FormItem, FormLabel, FormMessage, useZodForm
|
||||
} from "@/components/ui/form";
|
||||
import {FormControl, FormField, FormItem, FormLabel, FormMessage, useZodForm} from "@/components/ui/form";
|
||||
import {Input} from "@/components/ui/input";
|
||||
import {Form} from "@/components/ui/form"
|
||||
import {Button} from "@/components/ui/button";
|
||||
import {toast} from "sonner";
|
||||
import {useRouter} from "next/navigation";
|
||||
import {useMutation} from "@tanstack/react-query";
|
||||
import {TooltipProvider, TooltipTrigger, Tooltip, TooltipContent} from "@/components/ui/tooltip";
|
||||
import {RegisterSchema, RegisterType} from "@/components/wrappers/Auth/Register/RegisterForm/register-form.schema";
|
||||
import {registerUserAction} from "@/components/wrappers/Auth/Register/RegisterForm/register-form.action";
|
||||
import {Info} from "lucide-react";
|
||||
import {PasswordInput} from "@/components/wrappers/Auth/PaswordInput/password-input";
|
||||
|
||||
export type registerFormProps = {
|
||||
@@ -104,7 +103,8 @@ export const RegisterForm = (props: registerFormProps) => {
|
||||
<Info className="ml-3" size="15"/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p> Min. 8 characters, 1 uppercase (A-Z), 1 lowercase (a-z), 1 number (0-9), 1 special character (!, @, etc.)</p>
|
||||
<p> Min. 8 characters, 1 uppercase (A-Z), 1 lowercase (a-z), 1
|
||||
number (0-9), 1 special character (!, @, etc.)</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
@@ -8,18 +8,18 @@ import {hashPassword} from "@/utils/password";
|
||||
export const registerUserAction = action
|
||||
.schema(RegisterSchema)
|
||||
.action(async ({parsedInput, ctx}) => {
|
||||
const user = await prisma.user.findUnique({ where: { email: parsedInput.email } });
|
||||
const user = await prisma.user.findUnique({where: {email: parsedInput.email}});
|
||||
console.log(user);
|
||||
if (!user && parsedInput.password === parsedInput.confirmPassword) {
|
||||
|
||||
const users = await prisma.user.findMany({
|
||||
where:{
|
||||
deleted: { not: true },
|
||||
where: {
|
||||
deleted: {not: true},
|
||||
}
|
||||
})
|
||||
const role = users.length > 0 ? "pending" : "admin"
|
||||
|
||||
const new_user = await prisma.user.create({
|
||||
const newUser = await prisma.user.create({
|
||||
data: {
|
||||
name: parsedInput.name,
|
||||
email: parsedInput.email,
|
||||
@@ -27,8 +27,20 @@ export const registerUserAction = action
|
||||
role: role
|
||||
},
|
||||
});
|
||||
|
||||
const defaultOrganization = await prisma.organization.findUnique({
|
||||
where: {slug: "default"},
|
||||
});
|
||||
|
||||
await prisma.userOrganization.create({
|
||||
data: {
|
||||
userId: newUser.id,
|
||||
organizationId: defaultOrganization.id
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
data: new_user,
|
||||
data: newUser,
|
||||
}
|
||||
}
|
||||
throw new Error('An error occured while creating user');
|
||||
|
||||
@@ -15,13 +15,14 @@ import {Form} from "@/components/ui/form"
|
||||
import {Button} from "@/components/ui/button";
|
||||
import {useMutation} from "@tanstack/react-query";
|
||||
import {ProjectSchema, ProjectType} from "@/components/wrappers/Dashboard/Projects/ProjectsForm/ProjectForm.schema";
|
||||
import {createProjectAction, updateProjectAction} from "@/components/wrappers/Dashboard/Projects/ProjectsForm/project-form.action";
|
||||
import {
|
||||
createProjectAction,
|
||||
updateProjectAction
|
||||
} from "@/components/wrappers/Dashboard/Projects/ProjectsForm/project-form.action";
|
||||
import {useRouter} from "next/navigation";
|
||||
import {Database, Organization, Projects} from "@prisma/client"
|
||||
import {MultiSelect} from "@/components/wrappers/MultiSelect/MultiSelect";
|
||||
import {ZodString} from "zod";
|
||||
import {toast} from "sonner";
|
||||
import {ServerActionResult} from "@/types/action-type";
|
||||
|
||||
|
||||
export type projectFormProps = {
|
||||
@@ -41,7 +42,7 @@ export const ProjectForm = (props: projectFormProps) => {
|
||||
const formatDatabasesList = (databases: Database[]) => {
|
||||
return databases.map(database => ({
|
||||
value: database.id,
|
||||
label: `${database.name} | ${database.generatedId}`,
|
||||
label: `${database.name} (${database.generatedId}) | ${database.agent.name}`,
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -64,14 +65,21 @@ export const ProjectForm = (props: projectFormProps) => {
|
||||
const mutation = useMutation({
|
||||
mutationFn: async (values: ProjectType) => {
|
||||
console.log(values)
|
||||
const project: Projects = isCreate ? await createProjectAction({data: values, organizationId: props.organization.id}) : await updateProjectAction({data: values, organizationId: props.organization.id, projectId: props.projectId});
|
||||
const project: Projects = isCreate ? await createProjectAction({
|
||||
data: values,
|
||||
organizationId: props.organization.id
|
||||
}) : await updateProjectAction({
|
||||
data: values,
|
||||
organizationId: props.organization.id,
|
||||
projectId: props.projectId
|
||||
});
|
||||
console.log(project)
|
||||
|
||||
if (project.data.success) {
|
||||
toast.success(project.data.actionSuccess.message);
|
||||
router.push(`/dashboard/projects/${project.data.value.id}`);
|
||||
router.refresh()
|
||||
}else{
|
||||
} else {
|
||||
toast.success(project.data.actionError.message);
|
||||
}
|
||||
|
||||
@@ -115,7 +123,11 @@ export const ProjectForm = (props: projectFormProps) => {
|
||||
<FormLabel>Slug</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="project-1" {...field} />
|
||||
placeholder="project-1" {...field}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value.replaceAll(" ", "-").toLowerCase()
|
||||
field.onChange(value)
|
||||
}}/>
|
||||
</FormControl>
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
|
||||
@@ -130,7 +130,7 @@ export const updateProjectAction = userAction
|
||||
success: true,
|
||||
value: updatedProject,
|
||||
actionSuccess: {
|
||||
message: "ProjectsForm has been successfully updated.",
|
||||
message: "Project has been successfully updated.",
|
||||
messageParams: {projectName: parsedInput.data.name},
|
||||
},
|
||||
};
|
||||
@@ -138,7 +138,7 @@ export const updateProjectAction = userAction
|
||||
return {
|
||||
success: false,
|
||||
actionError: {
|
||||
message: "Failed to update ProjectsForm.",
|
||||
message: "Failed to update project.",
|
||||
status: 500, // Optional: Use a meaningful status code
|
||||
cause: error instanceof Error ? error.message : "Unknown error",
|
||||
messageParams: {projectName: parsedInput.data.name},
|
||||
|
||||
@@ -10,10 +10,21 @@ import {LoggedInButton} from "@/components/wrappers/Dashboard/LoggedInButton/Log
|
||||
import {SidebarMenuCustom} from "@/components/wrappers/Dashboard/SideBar/SideBarMenu/SideBarMenu";
|
||||
import {OrganizationComboBox} from "@/components/wrappers/Organization/OrganizationCombobox";
|
||||
import {prisma} from "@/prisma";
|
||||
import {requiredCurrentUser} from "@/auth/current-user";
|
||||
|
||||
export async function AppSidebar() {
|
||||
|
||||
const organizations = await prisma.organization.findMany({})
|
||||
const user = await requiredCurrentUser()
|
||||
|
||||
const organizations = await prisma.organization.findMany({
|
||||
where: {
|
||||
users: {
|
||||
some: {
|
||||
userId: user.id
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
const defaultOrganization = await prisma.organization.findUnique({
|
||||
where: {
|
||||
slug: "default"
|
||||
|
||||
@@ -1,21 +1,32 @@
|
||||
"use client"
|
||||
|
||||
import {useSession} from "next-auth/react";
|
||||
import {useEffect} from "react";
|
||||
|
||||
import {ComboBox} from "@/components/wrappers/combobox";
|
||||
import {Organization} from "@prisma/client";
|
||||
import {useStore} from "@/state-management/store";
|
||||
|
||||
export type organizationComboBoxProps = {
|
||||
organizations: Organization[]
|
||||
defaultOrganization: Organization
|
||||
|
||||
}
|
||||
|
||||
|
||||
export function OrganizationComboBox(props: organizationComboBoxProps) {
|
||||
|
||||
const {organizationId, moveToAnotherOrganization} = useStore((state) => state);
|
||||
|
||||
const {organizations, defaultOrganization} = props
|
||||
|
||||
useEffect(() => {
|
||||
if (organizationId == "") {
|
||||
moveToAnotherOrganization(defaultOrganization.id)
|
||||
} else {
|
||||
const organization = organizations.find(organization => organization.id === organizationId)
|
||||
if (!organization) moveToAnotherOrganization(defaultOrganization.id)
|
||||
}
|
||||
}, [organizationId])
|
||||
|
||||
const values = organizations.map(organization => {
|
||||
return ({
|
||||
value: organization.id,
|
||||
@@ -23,19 +34,13 @@ export function OrganizationComboBox(props: organizationComboBoxProps) {
|
||||
})
|
||||
})
|
||||
|
||||
const {data: session, update} = useSession();
|
||||
|
||||
const updateSession = async (organizationId: string) => {
|
||||
const organization = organizations.find(organization => organization.id === organizationId)
|
||||
await update({...session, organization: organization})
|
||||
console.log("session updtated", organization)
|
||||
}
|
||||
const onValueChange = async (id: string) => moveToAnotherOrganization(id)
|
||||
|
||||
return (
|
||||
<ComboBox
|
||||
sideBar={true}
|
||||
values={values}
|
||||
defaultValue={defaultOrganization.id}
|
||||
onValueChange={updateSession}/>
|
||||
defaultValue={organizationId}
|
||||
onValueChange={onValueChange}/>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import {useState} from "react";
|
||||
import {useEffect, useState} from "react";
|
||||
|
||||
import {Check, ChevronDown} from "lucide-react"
|
||||
|
||||
@@ -33,10 +33,13 @@ export type comboBoxProps = {
|
||||
|
||||
export function ComboBox(props: comboBoxProps) {
|
||||
|
||||
|
||||
const {values: choices, defaultValue: defaultChoice = "", onValueChange, searchField = false} = props;
|
||||
|
||||
const [value, setValue] = useState(defaultChoice)
|
||||
const [value, setValue] = useState<string>()
|
||||
|
||||
useEffect(() => {
|
||||
setValue(defaultChoice)
|
||||
}, [defaultChoice])
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ export const createRestaurationAction = userAction
|
||||
success: true,
|
||||
value: restauration,
|
||||
actionSuccess: {
|
||||
message: "Restauration has been successfully created.",
|
||||
message: "Restoration has been successfully created.",
|
||||
messageParams: { restaurationId: restauration.id },
|
||||
},
|
||||
};
|
||||
@@ -38,7 +38,7 @@ export const createRestaurationAction = userAction
|
||||
message: "Failed to create backup.",
|
||||
status: 500,
|
||||
cause: error instanceof Error ? error.message : "Unknown error",
|
||||
messageParams: { message: "Error creating the restauration" },
|
||||
messageParams: { message: "Error creating the restoration" },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
"use client"
|
||||
|
||||
import {useEffect, useState} from "react"
|
||||
|
||||
const HydrationZustand = ({children}) => {
|
||||
const [isHydrated, setIsHydrated] = useState(false)
|
||||
|
||||
// Wait till Next.js rehydration completes
|
||||
useEffect(() => {
|
||||
setIsHydrated(true)
|
||||
}, [])
|
||||
|
||||
return <>{isHydrated ? <div>{children}</div> : null}</>
|
||||
}
|
||||
|
||||
export default HydrationZustand
|
||||
@@ -0,0 +1,31 @@
|
||||
'use client'
|
||||
|
||||
import {type ReactNode, createContext, useRef} from 'react'
|
||||
|
||||
import {useStore} from '@/state-management/store'
|
||||
|
||||
export type GlobalStoreApi = ReturnType<typeof useStore>
|
||||
|
||||
export const GlobalStoreContext = createContext<GlobalStoreApi | undefined>(
|
||||
undefined,
|
||||
)
|
||||
|
||||
export interface CounterStoreProviderProps {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export const GlobalStoreProvider = ({
|
||||
children,
|
||||
}: CounterStoreProviderProps) => {
|
||||
const storeRef = useRef<GlobalStoreApi>()
|
||||
if (!storeRef.current) {
|
||||
storeRef.current = useStore()
|
||||
}
|
||||
|
||||
return (
|
||||
<GlobalStoreContext.Provider value={storeRef.current}>
|
||||
{children}
|
||||
</GlobalStoreContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import {persist} from "zustand/middleware";
|
||||
|
||||
import {create} from "zustand";
|
||||
|
||||
export type GlobalState = {
|
||||
organizationId: string
|
||||
}
|
||||
|
||||
export type GlobalActions = {
|
||||
moveToAnotherOrganization: (id: string) => void
|
||||
}
|
||||
|
||||
export type GlobalStore = GlobalState & GlobalActions
|
||||
|
||||
export const defaultInitState: GlobalState = {
|
||||
organizationId: "",
|
||||
}
|
||||
|
||||
export const useStore = create<GlobalStore>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
...defaultInitState,
|
||||
moveToAnotherOrganization: (id: string) => set((state) => ({organizationId: id})),
|
||||
}),
|
||||
{
|
||||
name: "global-storage",
|
||||
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -5830,3 +5830,8 @@ zod@^3.23.8:
|
||||
version "3.23.8"
|
||||
resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d"
|
||||
integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==
|
||||
|
||||
zustand@^5.0.2:
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/zustand/-/zustand-5.0.2.tgz#f7595ada55a565f1fd6464f002a91e701ee0cfca"
|
||||
integrity sha512-8qNdnJVJlHlrKXi50LDqqUNmUbuBjoKLrYQBnoChIbVph7vni+sY+YpvdjXG9YLd/Bxr6scMcR+rm5H3aSqPaw==
|
||||
|
||||
Reference in New Issue
Block a user