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:
+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>
|
||||
@@ -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}/>
|
||||
)
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
}
|
||||
)
|
||||
);
|
||||
Reference in New Issue
Block a user