Merge remote-tracking branch 'origin/main'

This commit is contained in:
charles-gauthereau
2024-12-12 15:33:18 +01:00
28 changed files with 362 additions and 92 deletions
+2
View File
@@ -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>
+2
View File
@@ -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>
+16 -11
View File
@@ -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,16 +23,20 @@ export default async function Layout({children}: { children: React.ReactNode })
if (!user) redirect('/login')
return (
<SidebarProvider>
<div className="flex flex-col lg:flex-row w-full">
<AppSidebar/>
<SidebarInset>
<Header/>
<main className="h-full">
{children}
</main>
</SidebarInset>
</div>
</SidebarProvider>
// <HydrationZustand>
<GlobalStoreProvider>
<SidebarProvider>
<div className="flex flex-col lg:flex-row w-full">
<AppSidebar/>
<SidebarInset>
<Header/>
<main className="h-full">
{children}
</main>
</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',
},
+12 -1
View File
@@ -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: {}
}
})
+11
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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;
+20 -7
View File
@@ -54,19 +54,20 @@ model VerificationToken {
}
model User {
id String @id @default(cuid())
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime? @map("email_verified")
email String? @unique
emailVerified DateTime? @map("email_verified")
image String?
role Role
password String?
authMethod String? @map("auth_method")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime? @updatedAt @map("updated_at")
deleted Boolean? @default(false)
authMethod String? @map("auth_method")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime? @updatedAt @map("updated_at")
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
View File
@@ -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>
@@ -125,24 +137,24 @@ export const ProjectForm = (props: projectFormProps) => {
control={form.control}
name="databases"
render={({field}) => (
<FormItem>
<FormLabel>Databases</FormLabel>
<FormControl>
<FormItem>
<FormLabel>Databases</FormLabel>
<FormControl>
<MultiSelect
options={formatDatabasesList(props.databases)}
onValueChange={field.onChange}
defaultValue={field.value ?? []}
placeholder="Select databases"
variant="inverted"
animation={2}
// maxCount={100}
/>
</FormControl>
<FormDescription>Select databases you want to add to this project</FormDescription>
<FormMessage/>
</FormItem>
)
<MultiSelect
options={formatDatabasesList(props.databases)}
onValueChange={field.onChange}
defaultValue={field.value ?? []}
placeholder="Select databases"
variant="inverted"
animation={2}
// maxCount={100}
/>
</FormControl>
<FormDescription>Select databases you want to add to this project</FormDescription>
<FormMessage/>
</FormItem>
)
}
/>
<Button>
@@ -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}/>
)
}
+6 -3
View File
@@ -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)
+2 -2
View File
@@ -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
+31
View File
@@ -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>
)
}
+30
View File
@@ -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",
}
)
);
+5
View File
@@ -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==