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 {KeyRound} from "lucide-react";
|
||||||
import {BackupButton} from "@/components/wrappers/BackupButton/BackupButton";
|
import {BackupButton} from "@/components/wrappers/BackupButton/BackupButton";
|
||||||
import {backups, restaurations} from "@/utils/mock-data";
|
import {backups, restaurations} from "@/utils/mock-data";
|
||||||
|
import {formatDateLastContact} from "@/utils/date-formatting";
|
||||||
|
|
||||||
|
|
||||||
export default async function RoutePage(props: PageParams<{ agentId: string }>) {
|
export default async function RoutePage(props: PageParams<{ agentId: string }>) {
|
||||||
@@ -84,7 +85,7 @@ export default async function RoutePage(props: PageParams<{ agentId: string }>)
|
|||||||
Last contact
|
Last contact
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{agent.lastContact?.toDateString() ?? "Never connected."}
|
{formatDateLastContact(agent.lastContact)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ import {Button} from "@/components/ui/button";
|
|||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import {Page, PageActions, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
|
import {Page, PageActions, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
|
||||||
|
|
||||||
|
|
||||||
export default async function RoutePage(props: PageParams<{}>) {
|
export default async function RoutePage(props: PageParams<{}>) {
|
||||||
|
|
||||||
const agents = await prisma.agent.findMany({})
|
const agents = await prisma.agent.findMany({})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<PageHeader>
|
<PageHeader>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {AppSidebar} from "@/components/wrappers/Dashboard/SideBar/app-sidebar";
|
|||||||
import {currentUser} from "@/auth/current-user";
|
import {currentUser} from "@/auth/current-user";
|
||||||
import {Header} from "@/features/layout/Header";
|
import {Header} from "@/features/layout/Header";
|
||||||
import {prisma} from "@/prisma";
|
import {prisma} from "@/prisma";
|
||||||
|
import {GlobalStoreProvider} from "@/state-management/provider";
|
||||||
|
|
||||||
|
|
||||||
export default async function Layout({children}: { children: React.ReactNode }) {
|
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')
|
if (!user) redirect('/login')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarProvider>
|
// <HydrationZustand>
|
||||||
<div className="flex flex-col lg:flex-row w-full">
|
<GlobalStoreProvider>
|
||||||
<AppSidebar/>
|
<SidebarProvider>
|
||||||
<SidebarInset>
|
<div className="flex flex-col lg:flex-row w-full">
|
||||||
<Header/>
|
<AppSidebar/>
|
||||||
<main className="h-full">
|
<SidebarInset>
|
||||||
{children}
|
<Header/>
|
||||||
</main>
|
<main className="h-full">
|
||||||
</SidebarInset>
|
{children}
|
||||||
</div>
|
</main>
|
||||||
</SidebarProvider>
|
</SidebarInset>
|
||||||
|
</div>
|
||||||
|
</SidebarProvider>
|
||||||
|
</GlobalStoreProvider>
|
||||||
|
// </HydrationZustand>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -32,10 +32,13 @@ export default async function RoutePage(props: PageParams<{
|
|||||||
const availableDatabases = await prisma.database.findMany({
|
const availableDatabases = await prisma.database.findMany({
|
||||||
where: {
|
where: {
|
||||||
OR: [
|
OR: [
|
||||||
{ projectId: null },
|
{projectId: null},
|
||||||
{ projectId: project.id },
|
{projectId: project.id},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
include: {
|
||||||
|
agent: {}
|
||||||
|
},
|
||||||
orderBy: {
|
orderBy: {
|
||||||
createdAt: 'desc',
|
createdAt: 'desc',
|
||||||
},
|
},
|
||||||
@@ -50,7 +53,8 @@ export default async function RoutePage(props: PageParams<{
|
|||||||
</PageTitle>
|
</PageTitle>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<PageContent>
|
<PageContent>
|
||||||
<ProjectForm organization={organization} databases={availableDatabases} defaultValues={project} projectId={project.id}/>
|
<ProjectForm organization={organization} databases={availableDatabases} defaultValues={project}
|
||||||
|
projectId={project.id}/>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
</Page>
|
</Page>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ export default async function RoutePage(props: PageParams<{}>) {
|
|||||||
where: {
|
where: {
|
||||||
projectId: null
|
projectId: null
|
||||||
},
|
},
|
||||||
|
include: {
|
||||||
|
agent: {}
|
||||||
|
},
|
||||||
orderBy: {
|
orderBy: {
|
||||||
createdAt: 'desc',
|
createdAt: 'desc',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,17 +5,28 @@ import {Button} from "@/components/ui/button";
|
|||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import {Page, PageActions, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
|
import {Page, PageActions, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
|
||||||
import {ProjectCard} from "@/components/wrappers/Dashboard/Projects/ProjectCard/ProjectCard";
|
import {ProjectCard} from "@/components/wrappers/Dashboard/Projects/ProjectCard/ProjectCard";
|
||||||
|
import {requiredCurrentUser} from "@/auth/current-user";
|
||||||
|
|
||||||
export default async function RoutePage(props: PageParams<{}>) {
|
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({
|
const projects = await prisma.project.findMany({
|
||||||
where: {
|
where: {
|
||||||
organization: {
|
organization: {
|
||||||
slug: "default",
|
slug: "default",
|
||||||
|
// id: organizationId,
|
||||||
|
users: {
|
||||||
|
some: {
|
||||||
|
userId: user.id
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
include:{
|
include: {
|
||||||
databases: {}
|
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
|
ENV YARN_VERSION=4.2.2
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -8,7 +8,6 @@
|
|||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"email": "email dev"
|
"email": "email dev"
|
||||||
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@auth/prisma-adapter": "^2.4.1",
|
"@auth/prisma-adapter": "^2.4.1",
|
||||||
@@ -76,7 +75,8 @@
|
|||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"uuid": "^11.0.3",
|
"uuid": "^11.0.3",
|
||||||
"vaul": "^1.1.1",
|
"vaul": "^1.1.1",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8",
|
||||||
|
"zustand": "^5.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20",
|
"@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
@@ -54,19 +54,20 @@ model VerificationToken {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String?
|
name String?
|
||||||
email String? @unique
|
email String? @unique
|
||||||
emailVerified DateTime? @map("email_verified")
|
emailVerified DateTime? @map("email_verified")
|
||||||
image String?
|
image String?
|
||||||
role Role
|
role Role
|
||||||
password String?
|
password String?
|
||||||
authMethod String? @map("auth_method")
|
authMethod String? @map("auth_method")
|
||||||
createdAt DateTime @default(now()) @map("created_at")
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
updatedAt DateTime? @updatedAt @map("updated_at")
|
updatedAt DateTime? @updatedAt @map("updated_at")
|
||||||
deleted Boolean? @default(false)
|
deleted Boolean? @default(false)
|
||||||
accounts Account[]
|
accounts Account[]
|
||||||
sessions Session[]
|
sessions Session[]
|
||||||
|
organizations UserOrganization[]
|
||||||
|
|
||||||
@@map("users")
|
@@map("users")
|
||||||
}
|
}
|
||||||
@@ -84,10 +85,22 @@ model Organization {
|
|||||||
createdAt DateTime @default(now()) @map("created_at")
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
|
|
||||||
projects Project[]
|
projects Project[]
|
||||||
|
users UserOrganization[]
|
||||||
|
|
||||||
@@map("organizations")
|
@@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 {
|
model Project {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
slug String @unique
|
slug String @unique
|
||||||
|
|||||||
+20
-9
@@ -6,7 +6,6 @@ import {env} from "@/env.mjs";
|
|||||||
import GoogleProvider from "next-auth/providers/google";
|
import GoogleProvider from "next-auth/providers/google";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({
|
export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({
|
||||||
adapter: PrismaAdapter(prisma),
|
adapter: PrismaAdapter(prisma),
|
||||||
theme: {
|
theme: {
|
||||||
@@ -79,20 +78,20 @@ export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({
|
|||||||
// // session.user.authMethod = user.authMethod;
|
// // session.user.authMethod = user.authMethod;
|
||||||
// return session
|
// return session
|
||||||
// },
|
// },
|
||||||
async jwt({ token, trigger, session, user }) {
|
async jwt({token, trigger, session, user}) {
|
||||||
if (trigger === "update" && session) {
|
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;
|
session.user = token;
|
||||||
return session;
|
return session;
|
||||||
},
|
},
|
||||||
async signIn({ account, user, profile }) {
|
async signIn({account, user, profile}) {
|
||||||
const existingUser = await prisma.user.findFirst({
|
const existingUser = await prisma.user.findFirst({
|
||||||
where: { email: user.email },
|
where: {email: user.email},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!existingUser) {
|
if (!existingUser) {
|
||||||
@@ -106,7 +105,7 @@ export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({
|
|||||||
})
|
})
|
||||||
const role = users.length > 0 ? "pending" : "admin"
|
const role = users.length > 0 ? "pending" : "admin"
|
||||||
|
|
||||||
await prisma.user.create({
|
const newUser = await prisma.user.create({
|
||||||
data: {
|
data: {
|
||||||
email: user.email,
|
email: user.email,
|
||||||
name: user.name,
|
name: user.name,
|
||||||
@@ -115,6 +114,18 @@ export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({
|
|||||||
authMethod: account.provider,
|
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";
|
return role !== "pending";
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -125,7 +136,7 @@ export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({
|
|||||||
|
|
||||||
// Update auth method if user exists
|
// Update auth method if user exists
|
||||||
await prisma.user.update({
|
await prisma.user.update({
|
||||||
where: { id: existingUser.id },
|
where: {id: existingUser.id},
|
||||||
data: {
|
data: {
|
||||||
authMethod: account.provider,
|
authMethod: account.provider,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import Link from "next/link";
|
|||||||
import {ValueIcon} from "@radix-ui/react-icons";
|
import {ValueIcon} from "@radix-ui/react-icons";
|
||||||
import {Circle} from "lucide-react";
|
import {Circle} from "lucide-react";
|
||||||
import {ConnectionCircle} from "@/components/wrappers/connection-circle";
|
import {ConnectionCircle} from "@/components/wrappers/connection-circle";
|
||||||
|
import {formatDateLastContact} from "@/utils/date-formatting";
|
||||||
|
|
||||||
export type agentCardProps = {
|
export type agentCardProps = {
|
||||||
data: any
|
data: any
|
||||||
@@ -20,7 +21,7 @@ export const AgentCard = (props: agentCardProps) => {
|
|||||||
<div className="">
|
<div className="">
|
||||||
<CardHeader>{agent.name}</CardHeader>
|
<CardHeader>{agent.name}</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
Last contact : {agent.lastContact?.toDateString() ?? "Never connected."}
|
Last contact : {formatDateLastContact(agent.lastContact)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 mr-3">
|
<div className="mt-3 mr-3">
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
"use client";
|
"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 {Card, CardContent, CardHeader} from "@/components/ui/card";
|
||||||
import {
|
import {FormControl, FormField, FormItem, FormLabel, FormMessage, useZodForm} from "@/components/ui/form";
|
||||||
FormControl, FormField, FormItem, FormLabel, FormMessage, useZodForm
|
|
||||||
} from "@/components/ui/form";
|
|
||||||
import {Input} from "@/components/ui/input";
|
import {Input} from "@/components/ui/input";
|
||||||
import {Form} from "@/components/ui/form"
|
import {Form} from "@/components/ui/form"
|
||||||
import {Button} from "@/components/ui/button";
|
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 {TooltipProvider, TooltipTrigger, Tooltip, TooltipContent} from "@/components/ui/tooltip";
|
||||||
import {RegisterSchema, RegisterType} from "@/components/wrappers/Auth/Register/RegisterForm/register-form.schema";
|
import {RegisterSchema, RegisterType} from "@/components/wrappers/Auth/Register/RegisterForm/register-form.schema";
|
||||||
import {registerUserAction} from "@/components/wrappers/Auth/Register/RegisterForm/register-form.action";
|
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";
|
import {PasswordInput} from "@/components/wrappers/Auth/PaswordInput/password-input";
|
||||||
|
|
||||||
export type registerFormProps = {
|
export type registerFormProps = {
|
||||||
@@ -104,7 +103,8 @@ export const RegisterForm = (props: registerFormProps) => {
|
|||||||
<Info className="ml-3" size="15"/>
|
<Info className="ml-3" size="15"/>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<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>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
|||||||
@@ -8,18 +8,18 @@ import {hashPassword} from "@/utils/password";
|
|||||||
export const registerUserAction = action
|
export const registerUserAction = action
|
||||||
.schema(RegisterSchema)
|
.schema(RegisterSchema)
|
||||||
.action(async ({parsedInput, ctx}) => {
|
.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);
|
console.log(user);
|
||||||
if (!user && parsedInput.password === parsedInput.confirmPassword) {
|
if (!user && parsedInput.password === parsedInput.confirmPassword) {
|
||||||
|
|
||||||
const users = await prisma.user.findMany({
|
const users = await prisma.user.findMany({
|
||||||
where:{
|
where: {
|
||||||
deleted: { not: true },
|
deleted: {not: true},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const role = users.length > 0 ? "pending" : "admin"
|
const role = users.length > 0 ? "pending" : "admin"
|
||||||
|
|
||||||
const new_user = await prisma.user.create({
|
const newUser = await prisma.user.create({
|
||||||
data: {
|
data: {
|
||||||
name: parsedInput.name,
|
name: parsedInput.name,
|
||||||
email: parsedInput.email,
|
email: parsedInput.email,
|
||||||
@@ -27,8 +27,20 @@ export const registerUserAction = action
|
|||||||
role: role
|
role: role
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const defaultOrganization = await prisma.organization.findUnique({
|
||||||
|
where: {slug: "default"},
|
||||||
|
});
|
||||||
|
|
||||||
|
await prisma.userOrganization.create({
|
||||||
|
data: {
|
||||||
|
userId: newUser.id,
|
||||||
|
organizationId: defaultOrganization.id
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: new_user,
|
data: newUser,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new Error('An error occured while creating user');
|
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 {Button} from "@/components/ui/button";
|
||||||
import {useMutation} from "@tanstack/react-query";
|
import {useMutation} from "@tanstack/react-query";
|
||||||
import {ProjectSchema, ProjectType} from "@/components/wrappers/Dashboard/Projects/ProjectsForm/ProjectForm.schema";
|
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 {useRouter} from "next/navigation";
|
||||||
import {Database, Organization, Projects} from "@prisma/client"
|
import {Database, Organization, Projects} from "@prisma/client"
|
||||||
import {MultiSelect} from "@/components/wrappers/MultiSelect/MultiSelect";
|
import {MultiSelect} from "@/components/wrappers/MultiSelect/MultiSelect";
|
||||||
import {ZodString} from "zod";
|
|
||||||
import {toast} from "sonner";
|
import {toast} from "sonner";
|
||||||
import {ServerActionResult} from "@/types/action-type";
|
|
||||||
|
|
||||||
|
|
||||||
export type projectFormProps = {
|
export type projectFormProps = {
|
||||||
@@ -41,7 +42,7 @@ export const ProjectForm = (props: projectFormProps) => {
|
|||||||
const formatDatabasesList = (databases: Database[]) => {
|
const formatDatabasesList = (databases: Database[]) => {
|
||||||
return databases.map(database => ({
|
return databases.map(database => ({
|
||||||
value: database.id,
|
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({
|
const mutation = useMutation({
|
||||||
mutationFn: async (values: ProjectType) => {
|
mutationFn: async (values: ProjectType) => {
|
||||||
console.log(values)
|
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)
|
console.log(project)
|
||||||
|
|
||||||
if (project.data.success) {
|
if (project.data.success) {
|
||||||
toast.success(project.data.actionSuccess.message);
|
toast.success(project.data.actionSuccess.message);
|
||||||
router.push(`/dashboard/projects/${project.data.value.id}`);
|
router.push(`/dashboard/projects/${project.data.value.id}`);
|
||||||
router.refresh()
|
router.refresh()
|
||||||
}else{
|
} else {
|
||||||
toast.success(project.data.actionError.message);
|
toast.success(project.data.actionError.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +123,11 @@ export const ProjectForm = (props: projectFormProps) => {
|
|||||||
<FormLabel>Slug</FormLabel>
|
<FormLabel>Slug</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
placeholder="project-1" {...field} />
|
placeholder="project-1" {...field}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value.replaceAll(" ", "-").toLowerCase()
|
||||||
|
field.onChange(value)
|
||||||
|
}}/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage/>
|
<FormMessage/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -125,24 +137,24 @@ export const ProjectForm = (props: projectFormProps) => {
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="databases"
|
name="databases"
|
||||||
render={({field}) => (
|
render={({field}) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Databases</FormLabel>
|
<FormLabel>Databases</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
|
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
options={formatDatabasesList(props.databases)}
|
options={formatDatabasesList(props.databases)}
|
||||||
onValueChange={field.onChange}
|
onValueChange={field.onChange}
|
||||||
defaultValue={field.value ?? []}
|
defaultValue={field.value ?? []}
|
||||||
placeholder="Select databases"
|
placeholder="Select databases"
|
||||||
variant="inverted"
|
variant="inverted"
|
||||||
animation={2}
|
animation={2}
|
||||||
// maxCount={100}
|
// maxCount={100}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>Select databases you want to add to this project</FormDescription>
|
<FormDescription>Select databases you want to add to this project</FormDescription>
|
||||||
<FormMessage/>
|
<FormMessage/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Button>
|
<Button>
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ export const updateProjectAction = userAction
|
|||||||
success: true,
|
success: true,
|
||||||
value: updatedProject,
|
value: updatedProject,
|
||||||
actionSuccess: {
|
actionSuccess: {
|
||||||
message: "ProjectsForm has been successfully updated.",
|
message: "Project has been successfully updated.",
|
||||||
messageParams: {projectName: parsedInput.data.name},
|
messageParams: {projectName: parsedInput.data.name},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -138,7 +138,7 @@ export const updateProjectAction = userAction
|
|||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
actionError: {
|
actionError: {
|
||||||
message: "Failed to update ProjectsForm.",
|
message: "Failed to update project.",
|
||||||
status: 500, // Optional: Use a meaningful status code
|
status: 500, // Optional: Use a meaningful status code
|
||||||
cause: error instanceof Error ? error.message : "Unknown error",
|
cause: error instanceof Error ? error.message : "Unknown error",
|
||||||
messageParams: {projectName: parsedInput.data.name},
|
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 {SidebarMenuCustom} from "@/components/wrappers/Dashboard/SideBar/SideBarMenu/SideBarMenu";
|
||||||
import {OrganizationComboBox} from "@/components/wrappers/Organization/OrganizationCombobox";
|
import {OrganizationComboBox} from "@/components/wrappers/Organization/OrganizationCombobox";
|
||||||
import {prisma} from "@/prisma";
|
import {prisma} from "@/prisma";
|
||||||
|
import {requiredCurrentUser} from "@/auth/current-user";
|
||||||
|
|
||||||
export async function AppSidebar() {
|
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({
|
const defaultOrganization = await prisma.organization.findUnique({
|
||||||
where: {
|
where: {
|
||||||
slug: "default"
|
slug: "default"
|
||||||
|
|||||||
@@ -1,21 +1,32 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import {useSession} from "next-auth/react";
|
import {useEffect} from "react";
|
||||||
|
|
||||||
import {ComboBox} from "@/components/wrappers/combobox";
|
import {ComboBox} from "@/components/wrappers/combobox";
|
||||||
import {Organization} from "@prisma/client";
|
import {Organization} from "@prisma/client";
|
||||||
|
import {useStore} from "@/state-management/store";
|
||||||
|
|
||||||
export type organizationComboBoxProps = {
|
export type organizationComboBoxProps = {
|
||||||
organizations: Organization[]
|
organizations: Organization[]
|
||||||
defaultOrganization: Organization
|
defaultOrganization: Organization
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function OrganizationComboBox(props: organizationComboBoxProps) {
|
export function OrganizationComboBox(props: organizationComboBoxProps) {
|
||||||
|
|
||||||
|
const {organizationId, moveToAnotherOrganization} = useStore((state) => state);
|
||||||
|
|
||||||
const {organizations, defaultOrganization} = props
|
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 => {
|
const values = organizations.map(organization => {
|
||||||
return ({
|
return ({
|
||||||
value: organization.id,
|
value: organization.id,
|
||||||
@@ -23,19 +34,13 @@ export function OrganizationComboBox(props: organizationComboBoxProps) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const {data: session, update} = useSession();
|
const onValueChange = async (id: string) => moveToAnotherOrganization(id)
|
||||||
|
|
||||||
const updateSession = async (organizationId: string) => {
|
|
||||||
const organization = organizations.find(organization => organization.id === organizationId)
|
|
||||||
await update({...session, organization: organization})
|
|
||||||
console.log("session updtated", organization)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ComboBox
|
<ComboBox
|
||||||
sideBar={true}
|
sideBar={true}
|
||||||
values={values}
|
values={values}
|
||||||
defaultValue={defaultOrganization.id}
|
defaultValue={organizationId}
|
||||||
onValueChange={updateSession}/>
|
onValueChange={onValueChange}/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import {useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
|
|
||||||
import {Check, ChevronDown} from "lucide-react"
|
import {Check, ChevronDown} from "lucide-react"
|
||||||
|
|
||||||
@@ -33,10 +33,13 @@ export type comboBoxProps = {
|
|||||||
|
|
||||||
export function ComboBox(props: comboBoxProps) {
|
export function ComboBox(props: comboBoxProps) {
|
||||||
|
|
||||||
|
|
||||||
const {values: choices, defaultValue: defaultChoice = "", onValueChange, searchField = false} = props;
|
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)
|
const [open, setOpen] = useState(false)
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export const createRestaurationAction = userAction
|
|||||||
success: true,
|
success: true,
|
||||||
value: restauration,
|
value: restauration,
|
||||||
actionSuccess: {
|
actionSuccess: {
|
||||||
message: "Restauration has been successfully created.",
|
message: "Restoration has been successfully created.",
|
||||||
messageParams: { restaurationId: restauration.id },
|
messageParams: { restaurationId: restauration.id },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -38,7 +38,7 @@ export const createRestaurationAction = userAction
|
|||||||
message: "Failed to create backup.",
|
message: "Failed to create backup.",
|
||||||
status: 500,
|
status: 500,
|
||||||
cause: error instanceof Error ? error.message : "Unknown error",
|
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"
|
version "3.23.8"
|
||||||
resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d"
|
resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d"
|
||||||
integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==
|
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