mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
Working on some bugs.
This commit is contained in:
+22
-1
@@ -1,4 +1,25 @@
|
|||||||
|
# Ignore Docker-related configs
|
||||||
|
.dockerignore
|
||||||
docker-compose.yml
|
docker-compose.yml
|
||||||
README.md
|
docker-compose*.yml
|
||||||
|
|
||||||
|
# Development/Editor config
|
||||||
.idea
|
.idea
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
# Node-related
|
||||||
|
node_modules
|
||||||
|
npm-debug.log
|
||||||
|
yarn-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
pnpm-debug.log
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
.next
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Docs and markdowns
|
||||||
|
README.md
|
||||||
|
*.md
|
||||||
|
|||||||
Binary file not shown.
@@ -32,13 +32,13 @@ export default function Layout({children}: { children: React.ReactNode }) {
|
|||||||
<div className="flex min-h-full flex-1 flex-col justify-center py-12 sm:px-6 lg:px-8 ">
|
<div className="flex min-h-full flex-1 flex-col justify-center py-12 sm:px-6 lg:px-8 ">
|
||||||
<div className="mx-auto w-full max-w-md">
|
<div className="mx-auto w-full max-w-md">
|
||||||
<div className="sm:mx-auto sm:w-full sm:max-w-md flex items-center justify-center space-x-2">
|
<div className="sm:mx-auto sm:w-full sm:max-w-md flex items-center justify-center space-x-2">
|
||||||
<Image
|
<img
|
||||||
className="p-12 text-black dark:text-white"
|
className="p-12 text-black dark:text-white"
|
||||||
src={imageTheme}
|
src={imageTheme}
|
||||||
loading="eager"
|
// loading="eager"
|
||||||
alt="Logo"
|
alt="Logo"
|
||||||
width={1024}
|
// width={1024}
|
||||||
height={1024}
|
// height={1024}
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
className="text-sm text-muted-foreground -ml-12 -mb-12">
|
className="text-sm text-muted-foreground -ml-12 -mb-12">
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import {useEffect, useRef, useState} from "react";
|
import {useEffect, useRef, useState} from "react";
|
||||||
import {useSearchParams} from "next/navigation";
|
import {useSearchParams} from "next/navigation";
|
||||||
|
|
||||||
import {LoginForm} from "@/components/wrappers/auth/login/loginForm/LoginForm";
|
import {LoginForm} from "@/components/wrappers/auth/login/login-form/login-form";
|
||||||
import {toast} from "sonner";
|
import {toast} from "sonner";
|
||||||
|
|
||||||
export default function SignInPage(props: {
|
export default function SignInPage(props: {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { PageParams } from "@/types/next";
|
import { PageParams } from "@/types/next";
|
||||||
import { Page, PageContent, PageHeader, PageTitle } from "@/features/layout/page";
|
import { Page, PageContent, PageHeader, PageTitle } from "@/features/layout/page";
|
||||||
import { AgentForm } from "@/components/wrappers/dashboard/agent/AgentForm/AgentForm";
|
import { AgentForm } from "@/components/wrappers/dashboard/agent/agent-form/agent-form";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { db } from "@/db";
|
import { db } from "@/db";
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,21 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import {GearIcon} from "@radix-ui/react-icons";
|
import {GearIcon} from "@radix-ui/react-icons";
|
||||||
import {PageParams} from "@/types/next";
|
import {PageParams} from "@/types/next";
|
||||||
import {Page, PageContent, PageDescription, PageTitle} from "@/features/layout/page";
|
import {Page, PageActions, PageContent, PageDescription, PageTitle} from "@/features/layout/page";
|
||||||
import {formatDateLastContact} from "@/utils/date-formatting";
|
import {formatDateLastContact} from "@/utils/date-formatting";
|
||||||
import {buttonVariants} from "@/components/ui/button";
|
import {buttonVariants} from "@/components/ui/button";
|
||||||
import {Card, CardContent, CardHeader} from "@/components/ui/card";
|
import {Card, CardContent, CardHeader} from "@/components/ui/card";
|
||||||
import {CardsWithPagination} from "@/components/wrappers/common/cards-with-pagination";
|
import {CardsWithPagination} from "@/components/wrappers/common/cards-with-pagination";
|
||||||
import {DatabaseCard} from "@/components/wrappers/dashboard/projects/ProjectCard/ProjectDatabaseCard";
|
import {DatabaseCard} from "@/components/wrappers/dashboard/projects/project-card/project-database-card";
|
||||||
import {AgentCardKey} from "@/components/wrappers/dashboard/agent/AgentCardKey/AgentCardKey";
|
import {AgentCardKey} from "@/components/wrappers/dashboard/agent/agent-card-key/agent-card-key";
|
||||||
import { db } from "@/db";
|
import { db } from "@/db";
|
||||||
import * as drizzleDb from "@/db";
|
import * as drizzleDb from "@/db";
|
||||||
import {and, eq} from "drizzle-orm";
|
import {and, eq} from "drizzle-orm";
|
||||||
import {notFound} from "next/navigation";
|
import {notFound} from "next/navigation";
|
||||||
|
import {
|
||||||
|
ButtonDeleteProject
|
||||||
|
} from "@/components/wrappers/dashboard/projects/button-delete-project/button-delete-project";
|
||||||
|
import {ButtonDeleteAgent} from "@/components/wrappers/dashboard/agent/button-delete-agent/button-delete-agent";
|
||||||
|
|
||||||
|
|
||||||
export default async function RoutePage(props: PageParams<{ agentId: string }>) {
|
export default async function RoutePage(props: PageParams<{ agentId: string }>) {
|
||||||
@@ -26,6 +30,7 @@ export default async function RoutePage(props: PageParams<{ agentId: string }>)
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (!agent) {
|
if (!agent) {
|
||||||
notFound()
|
notFound()
|
||||||
}
|
}
|
||||||
@@ -74,7 +79,9 @@ export default async function RoutePage(props: PageParams<{ agentId: string }>)
|
|||||||
<GearIcon className="w-7 h-7"/>
|
<GearIcon className="w-7 h-7"/>
|
||||||
</Link>
|
</Link>
|
||||||
</PageTitle>
|
</PageTitle>
|
||||||
|
<PageActions className="justify-between">
|
||||||
|
<ButtonDeleteAgent agentId={agentId} text={"Delete Agent"} />
|
||||||
|
</PageActions>
|
||||||
</div>
|
</div>
|
||||||
<PageDescription className="mt-5 sm:mt-0">{agent.description}</PageDescription>
|
<PageDescription className="mt-5 sm:mt-0">{agent.description}</PageDescription>
|
||||||
<PageContent className="flex flex-col w-full h-full">
|
<PageContent className="flex flex-col w-full h-full">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { PageParams } from "@/types/next";
|
import { PageParams } from "@/types/next";
|
||||||
import { Page, PageContent, PageHeader, PageTitle } from "@/features/layout/page";
|
import { Page, PageContent, PageHeader, PageTitle } from "@/features/layout/page";
|
||||||
import { AgentForm } from "@/components/wrappers/dashboard/agent/AgentForm/AgentForm";
|
import { AgentForm } from "@/components/wrappers/dashboard/agent/agent-form/agent-form";
|
||||||
|
|
||||||
export default async function RoutePage(props: PageParams<{}>) {
|
export default async function RoutePage(props: PageParams<{}>) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,15 +1,22 @@
|
|||||||
import { PageParams } from "@/types/next";
|
import { PageParams } from "@/types/next";
|
||||||
import { AgentCard } from "@/components/wrappers/dashboard/agent/AgentCard/AgentCard";
|
import { AgentCard } from "@/components/wrappers/dashboard/agent/agent-card/agent-card";
|
||||||
import { CardsWithPagination } from "@/components/wrappers/common/cards-with-pagination";
|
import { CardsWithPagination } from "@/components/wrappers/common/cards-with-pagination";
|
||||||
import { Button } from "@/components/ui/button";
|
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 { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { db } from "@/db";
|
import { db } from "@/db";
|
||||||
|
import * as drizzleDb from "@/db";
|
||||||
|
|
||||||
|
import {and, eq, not} from "drizzle-orm";
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
export default async function RoutePage(props: PageParams<{}>) {
|
export default async function RoutePage(props: PageParams<{}>) {
|
||||||
const agents = await db.query.agent.findMany();
|
|
||||||
|
const agents = await db.query.agent.findMany({
|
||||||
|
where: not(eq(drizzleDb.schemas.agent.isArchived, true))
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
if (!agents) {
|
if (!agents) {
|
||||||
notFound();
|
notFound();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {PageParams} from "@/types/next";
|
import {PageParams} from "@/types/next";
|
||||||
import {Page, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
|
import {Page, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
|
||||||
import {notFound} from "next/navigation";
|
import {notFound} from "next/navigation";
|
||||||
import {RestoreForm} from "@/components/wrappers/dashboard/database/RestoreForm";
|
import {RestoreForm} from "@/components/wrappers/dashboard/database/restore-form";
|
||||||
import {currentUser} from "@/lib/auth/current-user";
|
import {currentUser} from "@/lib/auth/current-user";
|
||||||
|
|
||||||
import {db} from "@/db";
|
import {db} from "@/db";
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
import { PageParams } from "@/types/next";
|
import { PageParams } from "@/types/next";
|
||||||
import { Page, PageContent, PageHeader, PageTitle } from "@/features/layout/page";
|
import { Page, PageContent, PageHeader, PageTitle } from "@/features/layout/page";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { DatabaseForm } from "@/components/wrappers/dashboard/database/DatabaseForm/DatabaseForm";
|
import { DatabaseForm } from "@/components/wrappers/dashboard/database/database-form/database-form";
|
||||||
|
|
||||||
import { db } from "@/db";
|
import { db } from "@/db";
|
||||||
import * as drizzleDb from "@/db";
|
import * as drizzleDb from "@/db";
|
||||||
|
|||||||
+4
-4
@@ -2,10 +2,10 @@ import {PageParams} from "@/types/next";
|
|||||||
import {notFound} from "next/navigation";
|
import {notFound} from "next/navigation";
|
||||||
import {Page, PageActions, PageContent, PageDescription, PageTitle} from "@/features/layout/page";
|
import {Page, PageActions, PageContent, PageDescription, PageTitle} from "@/features/layout/page";
|
||||||
import {BackupButton} from "@/components/wrappers/dashboard/backup/backup-button/backup-button";
|
import {BackupButton} from "@/components/wrappers/dashboard/backup/backup-button/backup-button";
|
||||||
import {DatabaseTabs} from "@/components/wrappers/dashboard/projects/Database/DatabaseTabs";
|
import {DatabaseTabs} from "@/components/wrappers/dashboard/projects/database/database-tabs";
|
||||||
import {DatabaseKpi} from "@/components/wrappers/dashboard/projects/Database/DatabaseKpi";
|
import {DatabaseKpi} from "@/components/wrappers/dashboard/projects/database/database-kpi";
|
||||||
import {EditButton} from "@/components/wrappers/dashboard/database/EditButton/EditButton";
|
import {EditButton} from "@/components/wrappers/dashboard/database/edit-button/edit-button";
|
||||||
import {CronButton} from "@/components/wrappers/dashboard/database/CronButton/CronButton";
|
import {CronButton} from "@/components/wrappers/dashboard/database/cron-button/cron-button";
|
||||||
|
|
||||||
import {db} from "@/db";
|
import {db} from "@/db";
|
||||||
import {eq, and} from "drizzle-orm";
|
import {eq, and} from "drizzle-orm";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {notFound} from "next/navigation";
|
import {notFound} from "next/navigation";
|
||||||
import {PageParams} from "@/types/next";
|
import {PageParams} from "@/types/next";
|
||||||
import {Page, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
|
import {Page, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
|
||||||
import {ProjectForm} from "@/components/wrappers/dashboard/projects/ProjectsForm/ProjectForm";
|
import {ProjectForm} from "@/components/wrappers/dashboard/projects/project-form/project-form";
|
||||||
import * as drizzleDb from "@/db";
|
import * as drizzleDb from "@/db";
|
||||||
|
|
||||||
import {db} from "@/db";
|
import {db} from "@/db";
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import { Page, PageActions, PageContent, PageDescription, PageTitle } from "@/fe
|
|||||||
import { buttonVariants } from "@/components/ui/button";
|
import { buttonVariants } from "@/components/ui/button";
|
||||||
import { GearIcon } from "@radix-ui/react-icons";
|
import { GearIcon } from "@radix-ui/react-icons";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { ButtonDeleteProject } from "@/components/wrappers/dashboard/projects/ButtonDeleteProject/ButtonDeleteProject";
|
import { ButtonDeleteProject } from "@/components/wrappers/dashboard/projects/button-delete-project/button-delete-project";
|
||||||
import { CardsWithPagination } from "@/components/wrappers/common/cards-with-pagination";
|
import { CardsWithPagination } from "@/components/wrappers/common/cards-with-pagination";
|
||||||
import { ProjectDatabaseCard } from "@/components/wrappers/dashboard/projects/ProjectCard/ProjectDatabaseCard";
|
import { ProjectDatabaseCard } from "@/components/wrappers/dashboard/projects/project-card/project-database-card";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
|
|
||||||
import { db } from "@/db";
|
import { db } from "@/db";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {PageParams} from "@/types/next";
|
import {PageParams} from "@/types/next";
|
||||||
import {Page, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
|
import {Page, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
|
||||||
import {ProjectForm} from "@/components/wrappers/dashboard/projects/ProjectsForm/ProjectForm";
|
import {ProjectForm} from "@/components/wrappers/dashboard/projects/project-form/project-form";
|
||||||
import {notFound} from "next/navigation";
|
import {notFound} from "next/navigation";
|
||||||
import {db} from "@/db";
|
import {db} from "@/db";
|
||||||
import {eq} from "drizzle-orm";
|
import {eq} from "drizzle-orm";
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {PageParams} from "@/types/next";
|
|||||||
import {CardsWithPagination} from "@/components/wrappers/common/cards-with-pagination";
|
import {CardsWithPagination} from "@/components/wrappers/common/cards-with-pagination";
|
||||||
import {Button} from "@/components/ui/button";
|
import {Button} from "@/components/ui/button";
|
||||||
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/project-card/project-card";
|
||||||
import {db} from "@/db";
|
import {db} from "@/db";
|
||||||
import {notFound} from "next/navigation";
|
import {notFound} from "next/navigation";
|
||||||
import {getOrganization} from "@/lib/auth/auth";
|
import {getOrganization} from "@/lib/auth/auth";
|
||||||
|
|||||||
@@ -1,15 +1,25 @@
|
|||||||
import {PageParams} from "@/types/next";
|
import {PageParams} from "@/types/next";
|
||||||
import {Page, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
|
import {Page, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
|
||||||
import {notFound} from "next/navigation";
|
import {notFound} from "next/navigation";
|
||||||
import {OrganizationForm} from "@/components/wrappers/dashboard/organization/OrganizationForm/OrganizationForm";
|
import {OrganizationForm} from "@/components/wrappers/dashboard/organization/organization-form/organization-form";
|
||||||
import {getOrganization} from "@/lib/auth/auth";
|
import {getOrganization} from "@/lib/auth/auth";
|
||||||
|
import {db} from "@/db";
|
||||||
|
import {isNull} from "drizzle-orm";
|
||||||
|
import {currentUser} from "@/lib/auth/current-user";
|
||||||
|
|
||||||
export default async function RoutePage(props: PageParams<{
|
export default async function RoutePage(props: PageParams<{
|
||||||
|
|
||||||
}>) {
|
}>) {
|
||||||
|
const user = await currentUser();
|
||||||
|
|
||||||
const organization = await getOrganization({});
|
const organization = await getOrganization({});
|
||||||
|
|
||||||
if (!organization || organization.slug == "default") {
|
const users = await db.query.user.findMany({
|
||||||
|
where: (fields) => isNull(fields.deletedAt)
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
if (!user || !users || !organization || organization.slug == "default") {
|
||||||
notFound();
|
notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,7 +31,7 @@ export default async function RoutePage(props: PageParams<{
|
|||||||
</PageTitle>
|
</PageTitle>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<PageContent>
|
<PageContent>
|
||||||
<OrganizationForm members={organization.members} defaultValues={organization}/>
|
<OrganizationForm currentUser={user} users={users} defaultValues={organization}/>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
</Page>
|
</Page>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import {getOrganization} from "@/lib/auth/auth";
|
|||||||
import {notFound} from "next/navigation";
|
import {notFound} from "next/navigation";
|
||||||
import {
|
import {
|
||||||
DeleteOrganizationButton
|
DeleteOrganizationButton
|
||||||
} from "@/components/wrappers/dashboard/organization/DeleteOrganization/DeleteOrganizationButton";
|
} from "@/components/wrappers/dashboard/organization/delete-organization/delete-organization-button";
|
||||||
import {EditButtonSettings} from "@/components/wrappers/dashboard/settings/EditButtonSettings/EditButtonSettings";
|
import {EditButtonSettings} from "@/components/wrappers/dashboard/settings/edit-button-settings/edit-button-settings";
|
||||||
import {
|
import {
|
||||||
SettingsOrganizationMembersTable
|
SettingsOrganizationMembersTable
|
||||||
} from "@/components/wrappers/dashboard/settings/settings-organization-members-table";
|
} from "@/components/wrappers/dashboard/settings/settings-organization-members-table";
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import { PageParams } from "@/types/next";
|
import {PageParams} from "@/types/next";
|
||||||
import { Page, PageContent, PageHeader, PageTitle } from "@/features/layout/page";
|
import {Page, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card";
|
||||||
import { EvolutionLineChart } from "@/components/wrappers/dashboard/statistics/charts/evolution-line-chart";
|
import {EvolutionLineChart} from "@/components/wrappers/dashboard/statistics/charts/evolution-line-chart";
|
||||||
import { PercentageLineChart } from "@/components/wrappers/dashboard/statistics/charts/percentage-line-chart";
|
import {PercentageLineChart} from "@/components/wrappers/dashboard/statistics/charts/percentage-line-chart";
|
||||||
import { notFound } from "next/navigation";
|
import {notFound} from "next/navigation";
|
||||||
import { db } from "@/db";
|
import {db} from "@/db";
|
||||||
import { asc, count, eq, inArray } from "drizzle-orm";
|
import {asc, count, eq, inArray} from "drizzle-orm";
|
||||||
import * as drizzleDb from "@/db";
|
import * as drizzleDb from "@/db";
|
||||||
import {getOrganization} from "@/lib/auth/auth";
|
import {getOrganization} from "@/lib/auth/auth";
|
||||||
|
import {DatabaseBackup, Folder, RefreshCcw} from "lucide-react";
|
||||||
|
|
||||||
export default async function RoutePage(props: PageParams<{ }>) {
|
export default async function RoutePage(props: PageParams<{}>) {
|
||||||
const organization = await getOrganization({});
|
const organization = await getOrganization({});
|
||||||
|
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
@@ -26,7 +27,13 @@ export default async function RoutePage(props: PageParams<{ }>) {
|
|||||||
where: eq(drizzleDb.schemas.project.organizationId, org.id),
|
where: eq(drizzleDb.schemas.project.organizationId, org.id),
|
||||||
});
|
});
|
||||||
|
|
||||||
const projectsCount = projects.length;
|
const projectIds = projects.map(project => project.id);
|
||||||
|
|
||||||
|
const databasesOfAllProjects = await db.query.database.findMany({
|
||||||
|
where: inArray(drizzleDb.schemas.database.projectId, projectIds),
|
||||||
|
})
|
||||||
|
const databaseIds = databasesOfAllProjects.map((database) => database.id);
|
||||||
|
|
||||||
|
|
||||||
const backupsEvolution = await db.query.backup.findMany({
|
const backupsEvolution = await db.query.backup.findMany({
|
||||||
columns: {
|
columns: {
|
||||||
@@ -34,9 +41,11 @@ export default async function RoutePage(props: PageParams<{ }>) {
|
|||||||
createdAt: true,
|
createdAt: true,
|
||||||
},
|
},
|
||||||
orderBy: [asc(drizzleDb.schemas.backup.id)],
|
orderBy: [asc(drizzleDb.schemas.backup.id)],
|
||||||
|
where: inArray(drizzleDb.schemas.backup.databaseId, databaseIds),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const backupsRate = await db
|
const backupsRate = await db
|
||||||
.select({
|
.select({
|
||||||
createdAt: drizzleDb.schemas.backup.createdAt,
|
createdAt: drizzleDb.schemas.backup.createdAt,
|
||||||
@@ -48,7 +57,20 @@ export default async function RoutePage(props: PageParams<{ }>) {
|
|||||||
.groupBy(drizzleDb.schemas.backup.createdAt, drizzleDb.schemas.backup.status)
|
.groupBy(drizzleDb.schemas.backup.createdAt, drizzleDb.schemas.backup.status)
|
||||||
.orderBy(drizzleDb.schemas.backup.createdAt);
|
.orderBy(drizzleDb.schemas.backup.createdAt);
|
||||||
|
|
||||||
console.log(backupsRate);
|
|
||||||
|
|
||||||
|
const restorationsCountResult = await db
|
||||||
|
.select({
|
||||||
|
count: count(),
|
||||||
|
})
|
||||||
|
.from(drizzleDb.schemas.restoration)
|
||||||
|
.where(inArray(drizzleDb.schemas.restoration.databaseId, databaseIds));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const restorationsCount = restorationsCountResult[0]?.count ?? 0;
|
||||||
|
const projectsCount = projects.length;
|
||||||
|
const backupsEvolutionCount = backupsEvolution.length;
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -58,23 +80,26 @@ export default async function RoutePage(props: PageParams<{ }>) {
|
|||||||
</PageHeader>
|
</PageHeader>
|
||||||
<PageContent className="flex flex-col gap-y-4">
|
<PageContent className="flex flex-col gap-y-4">
|
||||||
<div className="flex flex-col md:flex-row gap-4">
|
<div className="flex flex-col md:flex-row gap-4">
|
||||||
<Card className="w-full">
|
<Card className="w-full flex-1">
|
||||||
<CardHeader>
|
<CardHeader className="flex items-center gap-2">
|
||||||
|
<Folder className="w-5 h-5 text-muted-foreground" />
|
||||||
<CardTitle>Projects</CardTitle>
|
<CardTitle>Projects</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>{projectsCount}</CardContent>
|
<CardContent className="text-3xl font-bold">{projectsCount}</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<Card className="w-full">
|
<Card className="w-full flex-1">
|
||||||
<CardHeader>
|
<CardHeader className="flex items-center gap-2">
|
||||||
<CardTitle>KPI 2</CardTitle>
|
<DatabaseBackup className="w-5 h-5 text-muted-foreground" />
|
||||||
|
<CardTitle>Backups</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent></CardContent>
|
<CardContent className="text-3xl font-bold">{backupsEvolutionCount}</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<Card className="w-full">
|
<Card className="w-full flex-1">
|
||||||
<CardHeader>
|
<CardHeader className="flex items-center gap-2">
|
||||||
<CardTitle>KPI 3</CardTitle>
|
<RefreshCcw className="w-5 h-5 text-muted-foreground" />
|
||||||
|
<CardTitle>Restorations</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent></CardContent>
|
<CardContent className="text-3xl font-bold">{restorationsCount}</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col md:flex-row gap-4">
|
<div className="flex flex-col md:flex-row gap-4">
|
||||||
@@ -83,7 +108,7 @@ export default async function RoutePage(props: PageParams<{ }>) {
|
|||||||
<CardTitle>Evolution of the number of backups</CardTitle>
|
<CardTitle>Evolution of the number of backups</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<EvolutionLineChart data={backupsEvolution} />
|
<EvolutionLineChart data={backupsEvolution}/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<Card className="w-full">
|
<Card className="w-full">
|
||||||
@@ -91,7 +116,7 @@ export default async function RoutePage(props: PageParams<{ }>) {
|
|||||||
<CardTitle>Success rate of backups</CardTitle>
|
<CardTitle>Success rate of backups</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<PercentageLineChart data={backupsRate} />
|
<PercentageLineChart data={backupsRate}/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { LoadingSpinner } from "@/components/wrappers/common/loading/LoadingSpinner";
|
import { LoadingSpinner } from "@/components/wrappers/common/loading/loading-spinner";
|
||||||
|
|
||||||
export default function Loading() {
|
export default function Loading() {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import {PageParams} from "@/types/next";
|
import {PageParams} from "@/types/next";
|
||||||
import {Page, PageActions, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
|
import {Page, PageActions, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
|
||||||
import {notFound} from "next/navigation";
|
import {notFound} from "next/navigation";
|
||||||
import {UserForm} from "@/components/wrappers/dashboard/profile/UserForm/UserForm";
|
import {UserForm} from "@/components/wrappers/dashboard/profile/user-form/user-form";
|
||||||
import {Badge} from "@/components/ui/badge";
|
import {Badge} from "@/components/ui/badge";
|
||||||
import {ButtonDeleteAccount} from "@/components/wrappers/dashboard/profile/ButtonDeleteAccount/ButtonDeleteAccount";
|
import {ButtonDeleteAccount} from "@/components/wrappers/dashboard/profile/button-delete-account/button-delete-account";
|
||||||
import {AvatarWithUpload} from "@/components/wrappers/dashboard/profile/Avatar/AvatarWithUpload";
|
import {AvatarWithUpload} from "@/components/wrappers/dashboard/profile/avatar/avatar-with-upload";
|
||||||
import {currentUser} from "@/lib/auth/current-user";
|
import {currentUser} from "@/lib/auth/current-user";
|
||||||
import {getAccounts, getSessions} from "@/lib/auth/auth";
|
import {getAccounts, getSessions} from "@/lib/auth/auth";
|
||||||
//import { getAccounts, getSessions } from "@/lib/auth/auth";
|
//import { getAccounts, getSessions } from "@/lib/auth/auth";
|
||||||
|
|||||||
+35
-42
@@ -1,36 +1,29 @@
|
|||||||
name: portabase-dev
|
name: portabase-dev
|
||||||
|
|
||||||
services:
|
services:
|
||||||
app:
|
# app:
|
||||||
build:
|
# build:
|
||||||
context: .
|
# context: .
|
||||||
dockerfile: docker/dockerfile/Dockerfile
|
# dockerfile: docker/dockerfile/Dockerfile
|
||||||
target: dev
|
# target: dev
|
||||||
ports:
|
# ports:
|
||||||
- "8887:80"
|
# - "8887:8887"
|
||||||
environment:
|
# environment:
|
||||||
- TIME_ZONE="Europe/Paris"
|
# - TIME_ZONE="Europe/Paris"
|
||||||
- NODE_ENV=development
|
# - NODE_ENV=development
|
||||||
depends_on:
|
# depends_on:
|
||||||
db:
|
# db:
|
||||||
condition: service_healthy
|
# condition: service_healthy
|
||||||
volumes:
|
# volumes:
|
||||||
- .:/app
|
# - .:/app
|
||||||
- /app/node_modules
|
# - /app/node_modules
|
||||||
container_name: portabase-app
|
# container_name: portabase-app
|
||||||
|
#
|
||||||
develop:
|
|
||||||
watch:
|
|
||||||
- action: sync
|
|
||||||
path: .
|
|
||||||
target: /app
|
|
||||||
- action: rebuild
|
|
||||||
path: package.json
|
|
||||||
|
|
||||||
db:
|
db:
|
||||||
image: postgres:16-alpine
|
image: postgres:16-alpine
|
||||||
ports:
|
ports:
|
||||||
- "5433:5432"
|
- "5432:5432"
|
||||||
volumes:
|
volumes:
|
||||||
- postgres-data:/var/lib/postgresql/data
|
- postgres-data:/var/lib/postgresql/data
|
||||||
environment:
|
environment:
|
||||||
@@ -43,21 +36,21 @@ services:
|
|||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
|
||||||
db-pgadmin:
|
# db-pgadmin:
|
||||||
image: dpage/pgadmin4
|
# image: dpage/pgadmin4
|
||||||
environment:
|
# environment:
|
||||||
PGADMIN_DEFAULT_EMAIL: "devuser@devuser.devuser"
|
# PGADMIN_DEFAULT_EMAIL: "devuser@devuser.devuser"
|
||||||
PGADMIN_DEFAULT_PASSWORD: "changeme"
|
# PGADMIN_DEFAULT_PASSWORD: "changeme"
|
||||||
PGADMIN_CONFIG_SERVER_MODE: "False"
|
# PGADMIN_CONFIG_SERVER_MODE: "False"
|
||||||
POSTGRES_USER: "devuser"
|
# POSTGRES_USER: "devuser"
|
||||||
POSTGRES_PASSWORD: "changeme"
|
# POSTGRES_PASSWORD: "changeme"
|
||||||
volumes:
|
# volumes:
|
||||||
- pgadmin-data:/var/lib/pgadmin
|
# - pgadmin-data:/var/lib/pgadmin
|
||||||
ports:
|
# ports:
|
||||||
- "8080:80"
|
# - "8080:80"
|
||||||
restart: unless-stopped
|
# restart: unless-stopped
|
||||||
depends_on:
|
# depends_on:
|
||||||
- db
|
# - db
|
||||||
|
|
||||||
s3:
|
s3:
|
||||||
image: docker.io/bitnami/minio:latest
|
image: docker.io/bitnami/minio:latest
|
||||||
@@ -74,4 +67,4 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
postgres-data:
|
postgres-data:
|
||||||
minio_data:
|
minio_data:
|
||||||
pgadmin-data:
|
# pgadmin-data:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM node:23-alpine AS base
|
FROM node:22-alpine AS base
|
||||||
|
|
||||||
ENV YARN_VERSION=4.9.1
|
ENV YARN_VERSION=4.9.1
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
#npx drizzle-kit generate
|
||||||
|
#npx drizzle-kit migrate
|
||||||
|
#
|
||||||
|
#npm run dev
|
||||||
|
#
|
||||||
|
#exec "$@"
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
npm install -g npm@11.0.0
|
echo "▶ Running Drizzle codegen..."
|
||||||
npm -v
|
|
||||||
|
|
||||||
npx drizzle-kit generate
|
npx drizzle-kit generate
|
||||||
|
|
||||||
|
echo "▶ Applying migrations..."
|
||||||
npx drizzle-kit migrate
|
npx drizzle-kit migrate
|
||||||
|
|
||||||
npm run dev
|
echo "▶ Starting Next.js dev server..."
|
||||||
|
exec npm run dev
|
||||||
exec "$@"
|
|
||||||
|
|||||||
Vendored
-20
@@ -1,20 +0,0 @@
|
|||||||
// next-auth.d.ts
|
|
||||||
import NextAuth from "next-auth";
|
|
||||||
|
|
||||||
declare module "next-auth" {
|
|
||||||
interface Session {
|
|
||||||
user: {
|
|
||||||
id: string;
|
|
||||||
email: string;
|
|
||||||
name: string;
|
|
||||||
authMethod: string; // Add authMethod to the session user type
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface User {
|
|
||||||
id: string;
|
|
||||||
email: string;
|
|
||||||
name: string;
|
|
||||||
authMethod: string; // Add authMethod to the user type
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+3
-3
@@ -3,7 +3,7 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --turbopack -p 80",
|
"dev": "next dev --turbopack -p 8887",
|
||||||
"build": "next build --experimental-build-mode compile",
|
"build": "next build --experimental-build-mode compile",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
"@zenstackhq/runtime": "2.14.2",
|
"@zenstackhq/runtime": "2.14.2",
|
||||||
"argon2": "^0.43.0",
|
"argon2": "^0.43.0",
|
||||||
"bcrypt": "^6.0.0",
|
"bcrypt": "^6.0.0",
|
||||||
"better-auth": "^1.2.8",
|
"better-auth": "1.3.1",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.1.1",
|
"cmdk": "^1.1.1",
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
"input-otp": "^1.4.2",
|
"input-otp": "^1.4.2",
|
||||||
"lucide-react": "^0.510.0",
|
"lucide-react": "^0.510.0",
|
||||||
"minio": "^8.0.5",
|
"minio": "^8.0.5",
|
||||||
"next": "^15.4.2-canary.5",
|
"next": "15.4.2-canary.10",
|
||||||
"next-safe-action": "^7.10.8",
|
"next-safe-action": "^7.10.8",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"nodemailer": "^7.0.3",
|
"nodemailer": "^7.0.3",
|
||||||
|
|||||||
+3
-3
@@ -9,9 +9,9 @@ import { toast } from "sonner";
|
|||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { PasswordInput } from "@/components/wrappers/auth/PaswordInput/password-input";
|
import { PasswordInput } from "@/components/wrappers/auth/password-input/password-input";
|
||||||
import { LoginSchema, LoginType } from "@/components/wrappers/auth/login/loginForm/login-form.schema";
|
import { LoginSchema, LoginType } from "@/components/wrappers/auth/login/login-form/login-form.schema";
|
||||||
import { SocialAuthButton, SocialProviderType } from "@/components/wrappers/auth/login/buttonAuth/SocialAuthButton";
|
import { SocialAuthButton, SocialProviderType } from "@/components/wrappers/auth/login/button-auth/social-auth-button";
|
||||||
import { signIn } from "@/lib/auth/auth-client";
|
import { signIn } from "@/lib/auth/auth-client";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { Icon } from "@iconify/react";
|
import { Icon } from "@iconify/react";
|
||||||
@@ -12,7 +12,7 @@ import { Form } from "@/components/ui/form";
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
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/register-form/register-form.schema";
|
import { RegisterSchema, RegisterType } from "@/components/wrappers/auth/register/register-form/register-form.schema";
|
||||||
import { PasswordInput } from "@/components/wrappers/auth/PaswordInput/password-input";
|
import { PasswordInput } from "@/components/wrappers/auth/password-input/password-input";
|
||||||
import {signUp} from "@/lib/auth/auth-client";
|
import {signUp} from "@/lib/auth/auth-client";
|
||||||
|
|
||||||
export type registerFormProps = {
|
export type registerFormProps = {
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
"use server";
|
"use server";
|
||||||
import { userAction } from "@/safe-actions";
|
import { userAction } from "@/safe-actions";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { EmailFormSchema } from "@/components/wrappers/dashboard/admin/AdminEmailTab/EmailForm/email-form.schema";
|
import { EmailFormSchema } from "@/components/wrappers/dashboard/admin/admin-email-tab/email-form/email-form.schema";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { db } from "@/db";
|
import { db } from "@/db";
|
||||||
import * as drizzleDb from "@/db";
|
import * as drizzleDb from "@/db";
|
||||||
+3
-3
@@ -8,9 +8,9 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
|
|
||||||
import { EmailFormSchema, EmailFormType } from "@/components/wrappers/dashboard/admin/AdminEmailTab/EmailForm/email-form.schema";
|
import { EmailFormSchema, EmailFormType } from "@/components/wrappers/dashboard/admin/admin-email-tab/email-form/email-form.schema";
|
||||||
import { PasswordInput } from "@/components/wrappers/auth/PaswordInput/password-input";
|
import { PasswordInput } from "@/components/wrappers/auth/password-input/password-input";
|
||||||
import { updateEmailSettingsAction } from "@/components/wrappers/dashboard/admin/AdminEmailTab/EmailForm/email-form.action";
|
import { updateEmailSettingsAction } from "@/components/wrappers/dashboard/admin/admin-email-tab/email-form/email-form.action";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import { EmailForm } from "@/components/wrappers/dashboard/admin/AdminEmailTab/EmailForm/EmailForm";
|
import { EmailForm } from "@/components/wrappers/dashboard/admin/admin-email-tab/email-form/email-form";
|
||||||
import { Send } from "lucide-react";
|
import { Send } from "lucide-react";
|
||||||
import { ButtonWithLoading } from "@/components/wrappers/common/button/button-with-loading";
|
import { ButtonWithLoading } from "@/components/wrappers/common/button/button-with-loading";
|
||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
+2
-2
@@ -2,14 +2,14 @@ import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
|||||||
import { Info, ShieldCheck } from "lucide-react";
|
import { Info, ShieldCheck } from "lucide-react";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { StorageS3Form } from "@/components/wrappers/dashboard/admin/AdminStorageTab/StorageS3Form/StorageS3Form";
|
import { StorageS3Form } from "@/components/wrappers/dashboard/admin/admin-storage-tab/storage-s3/storage-s3-form";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { ButtonWithLoading } from "@/components/wrappers/common/button/button-with-loading";
|
import { ButtonWithLoading } from "@/components/wrappers/common/button/button-with-loading";
|
||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import { checkConnexionToS3 } from "@/features/upload/public/upload.action";
|
import { checkConnexionToS3 } from "@/features/upload/public/upload.action";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { updateStorageSettingsAction } from "@/components/wrappers/dashboard/admin/AdminStorageTab/StorageS3Form/s3-form.action";
|
import { updateStorageSettingsAction } from "@/components/wrappers/dashboard/admin/admin-storage-tab/storage-s3/s3-form.action";
|
||||||
import {Setting} from "@/db/schema/00_setting";
|
import {Setting} from "@/db/schema/00_setting";
|
||||||
|
|
||||||
export type SettingsStorageTabProps = {
|
export type SettingsStorageTabProps = {
|
||||||
+1
-1
@@ -3,7 +3,7 @@
|
|||||||
import { userAction } from "@/safe-actions";
|
import { userAction } from "@/safe-actions";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db } from "@/db";
|
import { db } from "@/db";
|
||||||
import { S3FormSchema, StorageSwitchSchema } from "@/components/wrappers/dashboard/admin/AdminStorageTab/StorageS3Form/s3-form.schema";
|
import { S3FormSchema, StorageSwitchSchema } from "@/components/wrappers/dashboard/admin/admin-storage-tab/storage-s3/s3-form.schema";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import * as drizzleDb from "@/db";
|
import * as drizzleDb from "@/db";
|
||||||
|
|
||||||
+2
-2
@@ -8,10 +8,10 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
|
|
||||||
import { S3FormSchema, S3FormType } from "@/components/wrappers/dashboard/admin/AdminStorageTab/StorageS3Form/s3-form.schema";
|
import { S3FormSchema, S3FormType } from "@/components/wrappers/dashboard/admin/admin-storage-tab/storage-s3/s3-form.schema";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { updateS3SettingsAction } from "@/components/wrappers/dashboard/admin/AdminStorageTab/StorageS3Form/s3-form.action";
|
import { updateS3SettingsAction } from "@/components/wrappers/dashboard/admin/admin-storage-tab/storage-s3/s3-form.action";
|
||||||
|
|
||||||
export type S3FormProps = {
|
export type S3FormProps = {
|
||||||
defaultValues?: S3FormType;
|
defaultValues?: S3FormType;
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { SettingsEmailTab } from "@/components/wrappers/dashboard/admin/AdminEmailTab/SettingsEmailTab";
|
import { SettingsEmailTab } from "@/components/wrappers/dashboard/admin/admin-email-tab/settings-email-tab";
|
||||||
import { SettingsStorageTab } from "@/components/wrappers/dashboard/admin/AdminStorageTab/SettingsStorageTab";
|
import { SettingsStorageTab } from "@/components/wrappers/dashboard/admin/admin-storage-tab/settings-storage-tab";
|
||||||
import { AdminUsersTable } from "@/components/wrappers/dashboard/admin/admin-user-table";
|
import { AdminUsersTable } from "@/components/wrappers/dashboard/admin/admin-user-table";
|
||||||
import { User } from "@/db/schema/01_user";
|
import { User } from "@/db/schema/01_user";
|
||||||
import { Setting } from "@/db/schema/00_setting";
|
import { Setting } from "@/db/schema/00_setting";
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import {ColumnDef} from "@tanstack/react-table";
|
import {ColumnDef} from "@tanstack/react-table";
|
||||||
import {Badge} from "@/components/ui/badge";
|
import {Badge} from "@/components/ui/badge";
|
||||||
import {updateUserAction} from "@/components/wrappers/dashboard/profile/UserForm/user-form.action";
|
import {updateUserAction} from "@/components/wrappers/dashboard/profile/user-form/user-form.action";
|
||||||
import {useMutation} from "@tanstack/react-query";
|
import {useMutation} from "@tanstack/react-query";
|
||||||
import {toast} from "sonner";
|
import {toast} from "sonner";
|
||||||
import {useRouter} from "next/navigation";
|
import {useRouter} from "next/navigation";
|
||||||
import {useState} from "react";
|
import {useState} from "react";
|
||||||
import {Trash2} from "lucide-react";
|
import {Trash2} from "lucide-react";
|
||||||
import {deleteUserAction} from "@/components/wrappers/dashboard/profile/ButtonDeleteAccount/delete-account.action";
|
import {deleteUserAction} from "@/components/wrappers/dashboard/profile/button-delete-account/delete-account.action";
|
||||||
import {ButtonWithLoading} from "@/components/wrappers/common/button/button-with-loading";
|
import {ButtonWithLoading} from "@/components/wrappers/common/button/button-with-loading";
|
||||||
import {User} from "@/db/schema/01_user";
|
import {User} from "@/db/schema/01_user";
|
||||||
import {useSession} from "@/lib/auth/auth-client";
|
import {useSession} from "@/lib/auth/auth-client";
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import {generateEdgeKey} from "@/utils/edge_key";
|
import {generateEdgeKey} from "@/utils/edge_key";
|
||||||
import {getServerUrl} from "@/utils/get-server-url";
|
import {getServerUrl} from "@/utils/get-server-url";
|
||||||
import {PasswordInput} from "@/components/wrappers/auth/PaswordInput/password-input";
|
import {PasswordInput} from "@/components/wrappers/auth/password-input/password-input";
|
||||||
import {useState} from "react";
|
import {useState} from "react";
|
||||||
import {CopyButton} from "@/components/wrappers/common/button/copy-button";
|
import {CopyButton} from "@/components/wrappers/common/button/copy-button";
|
||||||
import {Agent} from "@/db/schema/07_agent";
|
import {Agent} from "@/db/schema/07_agent";
|
||||||
+1
-1
@@ -16,7 +16,7 @@ export const AgentCard = (props: agentCardProps) => {
|
|||||||
return (
|
return (
|
||||||
<Link href={`/dashboard/agents/${agent.id}`} className="block transition-all duration-200 hover:scale-[1.01] hover:shadow-md">
|
<Link href={`/dashboard/agents/${agent.id}`} className="block transition-all duration-200 hover:scale-[1.01] hover:shadow-md">
|
||||||
<Card className="flex flex-row justify-between">
|
<Card className="flex flex-row justify-between">
|
||||||
<div>
|
<div className="flex-1 text-left">
|
||||||
<CardHeader className="text-2xl font-bold">{agent.name}</CardHeader>
|
<CardHeader className="text-2xl font-bold">{agent.name}</CardHeader>
|
||||||
<CardContent>Last contact: {formatDateLastContact(agent.lastContact)}</CardContent>
|
<CardContent>Last contact: {formatDateLastContact(agent.lastContact)}</CardContent>
|
||||||
</div>
|
</div>
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
"use server";
|
"use server";
|
||||||
import {ActionError, userAction} from "@/safe-actions";
|
import {ActionError, userAction} from "@/safe-actions";
|
||||||
import {AgentSchema} from "@/components/wrappers/dashboard/agent/AgentForm/agent-form.schema";
|
import {AgentSchema} from "@/components/wrappers/dashboard/agent/agent-form/agent-form.schema";
|
||||||
import {z} from "zod";
|
import {z} from "zod";
|
||||||
import {eq, and, ne, count} from "drizzle-orm";
|
import {eq, and, ne, count} from "drizzle-orm";
|
||||||
import {db} from "@/db";
|
import {db} from "@/db";
|
||||||
+2
-2
@@ -16,9 +16,9 @@ import {Button} from "@/components/ui/button";
|
|||||||
import {useRouter} from "next/navigation";
|
import {useRouter} from "next/navigation";
|
||||||
import {useMutation} from "@tanstack/react-query";
|
import {useMutation} from "@tanstack/react-query";
|
||||||
import {TooltipProvider} from "@/components/ui/tooltip";
|
import {TooltipProvider} from "@/components/ui/tooltip";
|
||||||
import {AgentSchema, AgentType} from "@/components/wrappers/dashboard/agent/AgentForm/agent-form.schema";
|
import {AgentSchema, AgentType} from "@/components/wrappers/dashboard/agent/agent-form/agent-form.schema";
|
||||||
import {toast} from "sonner";
|
import {toast} from "sonner";
|
||||||
import {createAgentAction, updateAgentAction} from "@/components/wrappers/dashboard/agent/AgentForm/agent-form.action";
|
import {createAgentAction, updateAgentAction} from "@/components/wrappers/dashboard/agent/agent-form/agent-form.action";
|
||||||
|
|
||||||
export type agentFormProps = {
|
export type agentFormProps = {
|
||||||
defaultValues?: AgentType;
|
defaultValues?: AgentType;
|
||||||
+1
-1
@@ -7,7 +7,7 @@ import { generateEdgeKey } from "@/utils/edge_key";
|
|||||||
import { PropsWithChildren } from "react";
|
import { PropsWithChildren } from "react";
|
||||||
import { CopyButton } from "@/components/wrappers/common/button/copy-button";
|
import { CopyButton } from "@/components/wrappers/common/button/copy-button";
|
||||||
import { getServerUrl } from "@/utils/get-server-url";
|
import { getServerUrl } from "@/utils/get-server-url";
|
||||||
import { CodeSnippet } from "@/components/wrappers/code-snippet/CodeSnippet";
|
import { CodeSnippet } from "@/components/wrappers/code-snippet/code-snippet";
|
||||||
import {Agent} from "@/db/schema/07_agent";
|
import {Agent} from "@/db/schema/07_agent";
|
||||||
|
|
||||||
export type agentRegistrationDialogProps = PropsWithChildren<{
|
export type agentRegistrationDialogProps = PropsWithChildren<{
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Trash2 } from "lucide-react";
|
||||||
|
import { ButtonWithConfirm } from "@/components/wrappers/common/button/button-with-confirm";
|
||||||
|
import { useMutation } from "@tanstack/react-query";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import {deleteAgentAction} from "@/components/wrappers/dashboard/agent/button-delete-agent/delete-agent.action";
|
||||||
|
|
||||||
|
export type ButtonDeleteAgentProps = {
|
||||||
|
text?: string;
|
||||||
|
agentId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ButtonDeleteAgent = (props: ButtonDeleteAgentProps) => {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationFn: () => deleteAgentAction(props.agentId),
|
||||||
|
onSuccess: async (result: any) => {
|
||||||
|
if (result.data?.success) {
|
||||||
|
toast.success(result.data.actionSuccess.message);
|
||||||
|
router.push("/dashboard/agents");
|
||||||
|
} else {
|
||||||
|
toast.error(result.data.actionError.message || "Unknown error occurred.");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ButtonWithConfirm
|
||||||
|
text={props.text ? props.text : ""}
|
||||||
|
onClick={() => {
|
||||||
|
mutation.mutate();
|
||||||
|
}}
|
||||||
|
variant={"destructive"}
|
||||||
|
isPending={mutation.isPending}
|
||||||
|
className="gap-2"
|
||||||
|
icon={<Trash2 />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
"use server";
|
||||||
|
|
||||||
|
import {userAction} from "@/safe-actions";
|
||||||
|
import {z} from "zod";
|
||||||
|
import {v4 as uuidv4} from "uuid";
|
||||||
|
import {ServerActionResult} from "@/types/action-type";
|
||||||
|
import {eq} from "drizzle-orm";
|
||||||
|
import {db} from "@/db";
|
||||||
|
import * as drizzleDb from "@/db";
|
||||||
|
import {Agent} from "@/db/schema/07_agent";
|
||||||
|
|
||||||
|
export const deleteAgentAction = userAction.schema(z.string()).action(async ({parsedInput}): Promise<ServerActionResult<Agent>> => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
// const deletedAgent: Agent[] = await db.delete(drizzleDb.schemas.agent).where(eq(drizzleDb.schemas.agent.id, parsedInput)).returning();
|
||||||
|
|
||||||
|
const uuid = uuidv4();
|
||||||
|
|
||||||
|
const updatedAgent = await db
|
||||||
|
.update(drizzleDb.schemas.agent)
|
||||||
|
.set({
|
||||||
|
isArchived: true,
|
||||||
|
slug: uuid,
|
||||||
|
})
|
||||||
|
.where(eq(drizzleDb.schemas.agent.id, parsedInput))
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
|
||||||
|
if (!updatedAgent[0]) {
|
||||||
|
throw new Error("Agent not found or update failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
value: updatedAgent[0],
|
||||||
|
actionSuccess: {
|
||||||
|
message: "Agent has been successfully deleted.",
|
||||||
|
messageParams: {projectId: parsedInput},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
actionError: {
|
||||||
|
message: "Failed to delete agent.",
|
||||||
|
status: 500,
|
||||||
|
cause: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
messageParams: {projectId: parsedInput},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
+1
-1
@@ -1,8 +1,8 @@
|
|||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
import { SidebarMenuButton } from "@/components/ui/sidebar";
|
import { SidebarMenuButton } from "@/components/ui/sidebar";
|
||||||
import { ChevronUp } from "lucide-react";
|
import { ChevronUp } from "lucide-react";
|
||||||
import { LoggedInDropdown } from "@/components/wrappers/dashboard/loggedInDropdown/LoggedInDropdown";
|
|
||||||
import { currentUser } from "@/lib/auth/current-user";
|
import { currentUser } from "@/lib/auth/current-user";
|
||||||
|
import {LoggedInDropdown} from "@/components/wrappers/dashboard/common/logged-in/logged-in-dropdown";
|
||||||
|
|
||||||
export const LoggedInButton = async () => {
|
export const LoggedInButton = async () => {
|
||||||
const user = await currentUser();
|
const user = await currentUser();
|
||||||
@@ -8,8 +8,8 @@ import {
|
|||||||
import {SidebarLogo} from "@/components/wrappers/dashboard/common/sidebar/logo-sidebar";
|
import {SidebarLogo} from "@/components/wrappers/dashboard/common/sidebar/logo-sidebar";
|
||||||
import {SidebarMenuCustomMain} from "@/components/wrappers/dashboard/common/sidebar/menu-sidebar-main";
|
import {SidebarMenuCustomMain} from "@/components/wrappers/dashboard/common/sidebar/menu-sidebar-main";
|
||||||
import {SideBarFooterCredit} from "@/components/wrappers/dashboard/common/sidebar/side-bar-footer-credit";
|
import {SideBarFooterCredit} from "@/components/wrappers/dashboard/common/sidebar/side-bar-footer-credit";
|
||||||
import {LoggedInButton} from "@/components/wrappers/dashboard/loggedInButton/LoggedInButton";
|
|
||||||
import {OrganizationCombobox} from "@/components/wrappers/dashboard/organization/organization-combobox";
|
import {OrganizationCombobox} from "@/components/wrappers/dashboard/organization/organization-combobox";
|
||||||
|
import {LoggedInButton} from "@/components/wrappers/dashboard/common/logged-in/logged-in-button";
|
||||||
|
|
||||||
export function AppSidebar() {
|
export function AppSidebar() {
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export const SidebarMenuCustomMain = () => {
|
|||||||
const member = authClient.useActiveMember();
|
const member = authClient.useActiveMember();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (isPending) return null;
|
if (isPending) return null;
|
||||||
|
|
||||||
if (error || !session) {
|
if (error || !session) {
|
||||||
|
|||||||
+2
-2
@@ -2,7 +2,7 @@
|
|||||||
import { Clock9 } from "lucide-react";
|
import { Clock9 } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
||||||
import { CronInput } from "@/components/wrappers/dashboard/database/CronButton/CronInput";
|
import { CronInput } from "@/components/wrappers/dashboard/database/cron-button/cron-input";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
@@ -10,7 +10,7 @@ import { useState } from "react";
|
|||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { updateDatabaseBackupPolicyAction } from "@/components/wrappers/dashboard/database/CronButton/cron.action";
|
import { updateDatabaseBackupPolicyAction } from "@/components/wrappers/dashboard/database/cron-button/cron.action";
|
||||||
import {Database} from "@/db/schema/06_database";
|
import {Database} from "@/db/schema/06_database";
|
||||||
|
|
||||||
export type CronButtonProps = {
|
export type CronButtonProps = {
|
||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
import { AdvancedCronSelect } from "./AdvancedCronSelect";
|
import { AdvancedCronSelect } from "./advanced-cron-select";
|
||||||
import { updateDatabaseBackupPolicyAction } from "@/components/wrappers/dashboard/database/CronButton/cron.action";
|
import { updateDatabaseBackupPolicyAction } from "@/components/wrappers/dashboard/database/cron-button/cron.action";
|
||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
+2
-2
@@ -8,8 +8,8 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
import { DatabaseSchema, DatabaseType } from "@/components/wrappers/dashboard/database/DatabaseForm/form-database.schema";
|
import { DatabaseSchema, DatabaseType } from "@/components/wrappers/dashboard/database/database-form/form-database.schema";
|
||||||
import { updateDatabaseAction } from "@/components/wrappers/dashboard/database/DatabaseForm/form-database.action";
|
import { updateDatabaseAction } from "@/components/wrappers/dashboard/database/database-form/form-database.action";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
export type DatabaseFormProps = {
|
export type DatabaseFormProps = {
|
||||||
+1
-1
@@ -4,7 +4,7 @@ import { z } from "zod";
|
|||||||
import { userAction } from "@/safe-actions";
|
import { userAction } from "@/safe-actions";
|
||||||
import { db } from "@/db";
|
import { db } from "@/db";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { DatabaseSchema } from "@/components/wrappers/dashboard/database/DatabaseForm/form-database.schema";
|
import { DatabaseSchema } from "@/components/wrappers/dashboard/database/database-form/form-database.schema";
|
||||||
import * as drizzleDb from "@/db";
|
import * as drizzleDb from "@/db";
|
||||||
|
|
||||||
export const updateDatabaseAction = userAction
|
export const updateDatabaseAction = userAction
|
||||||
@@ -4,6 +4,8 @@ import {ComboBox} from "@/components/wrappers/common/combobox";
|
|||||||
import {useSidebar} from "@/components/ui/sidebar";
|
import {useSidebar} from "@/components/ui/sidebar";
|
||||||
import {useRouter} from "next/navigation";
|
import {useRouter} from "next/navigation";
|
||||||
import {authClient} from "@/lib/auth/auth-client";
|
import {authClient} from "@/lib/auth/auth-client";
|
||||||
|
import {auth} from "@/lib/auth/auth";
|
||||||
|
|
||||||
|
|
||||||
export function OrganizationCombobox() {
|
export function OrganizationCombobox() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -24,11 +26,15 @@ export function OrganizationCombobox() {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const onValueChange = async (slug: string) => {
|
const onValueChange = async (slug: string) => {
|
||||||
|
|
||||||
await authClient.organization.setActive({
|
await authClient.organization.setActive({
|
||||||
organizationSlug: slug,
|
organizationSlug: slug,
|
||||||
});
|
});
|
||||||
router.refresh();
|
router.refresh();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ export const OrganizationFormSchema = z.object({
|
|||||||
.regex(/^[a-zA-Z0-9_-]*$/, 'Slug can only contain letters, numbers, underscores, and hyphens')
|
.regex(/^[a-zA-Z0-9_-]*$/, 'Slug can only contain letters, numbers, underscores, and hyphens')
|
||||||
.min(5, 'Slug must be at least 5 characters long')
|
.min(5, 'Slug must be at least 5 characters long')
|
||||||
.max(20, 'Slug must be at most 20 characters long'),
|
.max(20, 'Slug must be at most 20 characters long'),
|
||||||
members: z.array(z.string()),
|
users: z.array(z.string()),
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
+64
-35
@@ -1,19 +1,38 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
import {Card, CardContent, CardHeader} from "@/components/ui/card";
|
||||||
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, useZodForm } from "@/components/ui/form";
|
import {
|
||||||
import { Input } from "@/components/ui/input";
|
FormControl,
|
||||||
import { Form } from "@/components/ui/form";
|
FormDescription,
|
||||||
import { Button } from "@/components/ui/button";
|
FormField,
|
||||||
import { useMutation } from "@tanstack/react-query";
|
FormItem,
|
||||||
import { useRouter } from "next/navigation";
|
FormLabel,
|
||||||
import { MultiSelect } from "@/components/wrappers/common/multiselect/multi-select";
|
FormMessage,
|
||||||
import { OrganizationFormSchema, OrganizationFormType } from "@/components/wrappers/dashboard/organization/OrganizationForm/organization-form.schema";
|
useZodForm
|
||||||
|
} from "@/components/ui/form";
|
||||||
|
import {Input} from "@/components/ui/input";
|
||||||
|
import {Form} from "@/components/ui/form";
|
||||||
|
import {Button} from "@/components/ui/button";
|
||||||
|
import {useMutation} from "@tanstack/react-query";
|
||||||
|
import {useRouter} from "next/navigation";
|
||||||
|
import {MultiSelect} from "@/components/wrappers/common/multiselect/multi-select";
|
||||||
|
import {
|
||||||
|
OrganizationFormSchema,
|
||||||
|
OrganizationFormType
|
||||||
|
} from "@/components/wrappers/dashboard/organization/organization-form/organization-form.schema";
|
||||||
import {MemberWithUser, OrganizationWithMembers} from "@/db/schema/02_organization";
|
import {MemberWithUser, OrganizationWithMembers} from "@/db/schema/02_organization";
|
||||||
|
import {
|
||||||
|
deleteOrganizationAction,
|
||||||
|
updateOrganizationAction
|
||||||
|
} from "@/components/wrappers/dashboard/organization/organization.action";
|
||||||
|
import {toast} from "sonner";
|
||||||
|
import {User as BetterAuthUser} from "better-auth";
|
||||||
|
import {User} from "@/db/schema/01_user";
|
||||||
|
|
||||||
export type organizationFormProps = {
|
export type organizationFormProps = {
|
||||||
defaultValues?: OrganizationWithMembers;
|
defaultValues?: OrganizationWithMembers;
|
||||||
members: MemberWithUser[];
|
users: User[];
|
||||||
|
currentUser: BetterAuthUser;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const OrganizationForm = (props: organizationFormProps) => {
|
export const OrganizationForm = (props: organizationFormProps) => {
|
||||||
@@ -21,17 +40,20 @@ export const OrganizationForm = (props: organizationFormProps) => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const isCreate = !Boolean(props.defaultValues);
|
const isCreate = !Boolean(props.defaultValues);
|
||||||
|
|
||||||
const formatUsersList = (members: MemberWithUser[]) => {
|
const formatUsersList = (users: User[]) => {
|
||||||
|
|
||||||
return members.map((member) => ({
|
return users
|
||||||
value: member.user.id,
|
.filter((user) => user.id !== props.currentUser.id)
|
||||||
label: `${member.user.name} | ${member.user.email}`,
|
.map((user) => ({
|
||||||
|
value: user.id,
|
||||||
|
label: `${user.name} | ${user.email}`,
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatDefaultUsers = (members: MemberWithUser[]): string[] => {
|
const formatDefaultUsers = (members: MemberWithUser[]): string[] => {
|
||||||
console.log("ici",members);
|
return members
|
||||||
return members.map((member) => member.userId);
|
.filter((member) => member.userId !== props.currentUser.id)
|
||||||
|
.map((member) => member.userId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const formattedDefaultValues = {
|
const formattedDefaultValues = {
|
||||||
@@ -46,21 +68,28 @@ export const OrganizationForm = (props: organizationFormProps) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationFn: async (values: OrganizationFormType) => {
|
mutationFn: (values: OrganizationFormType) => updateOrganizationAction({
|
||||||
console.log(values);
|
data: values,
|
||||||
// const organization = await updateOrganizationAction({ data: values, organizationId: props.defaultValues.id });
|
organizationId: props.defaultValues?.id ?? ""
|
||||||
// console.log(organization);
|
}),
|
||||||
// if (organization.data.success) {
|
onSuccess: async (result) => {
|
||||||
// // toast.success(organization.data.actionSuccess.message);
|
if (result?.data?.success) {
|
||||||
// // router.push(`/dashboard/${organization.data.value.slug}/settings`);
|
toast.success(result.data.actionSuccess?.message || "Organization updated successfully.");
|
||||||
// // router.refresh();
|
router.push("/dashboard/settings");
|
||||||
// } else {
|
} else {
|
||||||
// // toast.success(organization.data.actionError.message);
|
// @ts-ignore
|
||||||
// }
|
const errorMsg = result?.data?.actionError?.message || result?.data?.actionError?.messageParams?.message || "Failed to update the organization.";
|
||||||
|
toast.error(errorMsg);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
onError: (error: any) => {
|
||||||
|
console.error("Mutation network error:", error);
|
||||||
|
toast.error(error?.message || "A network error occurred.");
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(formatUsersList(props.members))
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
@@ -77,13 +106,13 @@ export const OrganizationForm = (props: organizationFormProps) => {
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="name"
|
name="name"
|
||||||
defaultValue=""
|
defaultValue=""
|
||||||
render={({ field }) => (
|
render={({field}) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Name</FormLabel>
|
<FormLabel>Name</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="Organization 1" {...field} />
|
<Input placeholder="Organization 1" {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -91,7 +120,7 @@ export const OrganizationForm = (props: organizationFormProps) => {
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="slug"
|
name="slug"
|
||||||
defaultValue=""
|
defaultValue=""
|
||||||
render={({ field }) => (
|
render={({field}) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Slug</FormLabel>
|
<FormLabel>Slug</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
@@ -104,19 +133,19 @@ export const OrganizationForm = (props: organizationFormProps) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="users"
|
name="users"
|
||||||
render={({ field }) => (
|
render={({field}) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Users</FormLabel>
|
<FormLabel>Users</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
options={formatUsersList(props.members)}
|
options={formatUsersList(props.users)}
|
||||||
onValueChange={field.onChange}
|
onValueChange={field.onChange}
|
||||||
defaultValue={field.value ?? []}
|
defaultValue={field.value ?? []}
|
||||||
placeholder="Select users"
|
placeholder="Select users"
|
||||||
@@ -126,7 +155,7 @@ export const OrganizationForm = (props: organizationFormProps) => {
|
|||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>Select users you want to add to this organization</FormDescription>
|
<FormDescription>Select users you want to add to this organization</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -4,14 +4,14 @@ import { userAction } from "@/safe-actions";
|
|||||||
import { OrganizationSchema } from "@/components/wrappers/dashboard/organization/organization.schema";
|
import { OrganizationSchema } from "@/components/wrappers/dashboard/organization/organization.schema";
|
||||||
import { ServerActionResult } from "@/types/action-type";
|
import { ServerActionResult } from "@/types/action-type";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { OrganizationFormSchema } from "@/components/wrappers/dashboard/organization/organization-form/organization-form.schema";
|
||||||
import { OrganizationFormSchema } from "@/components/wrappers/dashboard/organization/OrganizationForm/organization-form.schema";
|
|
||||||
import { db } from "@/db";
|
import { db } from "@/db";
|
||||||
import { eq } from "drizzle-orm";
|
import {and, eq, inArray} from "drizzle-orm";
|
||||||
import {checkSlugOrganization, createOrganization, deleteOrganization} from "@/lib/auth/auth";
|
import {auth, checkSlugOrganization, createOrganization, deleteOrganization} from "@/lib/auth/auth";
|
||||||
import {slugify} from "@/utils/slugify";
|
import {slugify} from "@/utils/slugify";
|
||||||
import {Organization} from "@/db/schema/02_organization";
|
import {Organization} from "@/db/schema/02_organization";
|
||||||
import * as drizzleDb from "@/db";
|
import * as drizzleDb from "@/db";
|
||||||
|
import {headers} from "next/headers";
|
||||||
|
|
||||||
export const createOrganizationAction = userAction.schema(OrganizationSchema).action(async ({ parsedInput }): Promise<ServerActionResult<Organization>> => {
|
export const createOrganizationAction = userAction.schema(OrganizationSchema).action(async ({ parsedInput }): Promise<ServerActionResult<Organization>> => {
|
||||||
try {
|
try {
|
||||||
@@ -72,53 +72,117 @@ export const updateOrganizationAction = userAction
|
|||||||
organizationId: z.string(),
|
organizationId: z.string(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.action(async ({ parsedInput }): Promise<ServerActionResult<Organization>> => {
|
.action(async ({ parsedInput, ctx }): Promise<ServerActionResult<Organization>> => {
|
||||||
try {
|
try {
|
||||||
const newUserList = parsedInput.data.users;
|
const newUserList = parsedInput.data.users;
|
||||||
|
console.log(parsedInput);
|
||||||
|
const organization = await db.query.organization.findFirst({
|
||||||
|
where: eq(drizzleDb.schemas.organization.id, parsedInput.organizationId),
|
||||||
|
with: {
|
||||||
|
members: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const organization = await db.select().from(drizzleDb.schemas.organization).where(eq(drizzleDb.schemas.organization.id, parsedInput.organizationId)).execute();
|
|
||||||
|
|
||||||
if (organization.length === 0) {
|
if (!organization) {
|
||||||
throw new Error("Organization not found.");
|
return {
|
||||||
|
success: false,
|
||||||
|
actionError: {
|
||||||
|
message: "Organization not found.",
|
||||||
|
status: 404,
|
||||||
|
cause: "not_found",
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingItemIds = organization[0].users.map((user) => user.userId);
|
const existingItemIds = organization.members
|
||||||
|
.filter((member) => member.userId !== ctx.user.id)
|
||||||
|
.map((member) => member.userId);
|
||||||
const usersToAdd = newUserList.filter((id) => !existingItemIds.includes(id));
|
const usersToAdd = newUserList.filter((id) => !existingItemIds.includes(id));
|
||||||
const usersToRemove = existingItemIds.filter((id) => !newUserList.includes(id));
|
const usersToRemove = existingItemIds.filter((id) => !newUserList.includes(id));
|
||||||
|
|
||||||
if (usersToAdd.length > 0) {
|
if (usersToAdd.length > 0) {
|
||||||
await db
|
for (const userToAdd of usersToAdd) {
|
||||||
.insert(drizzleDb.schemas.organization)
|
await auth.api.addMember({
|
||||||
.values(
|
body: {
|
||||||
usersToAdd.map((userId) => ({
|
userId: userToAdd,
|
||||||
userId,
|
|
||||||
organizationId: organization[0].id,
|
|
||||||
role: "member",
|
role: "member",
|
||||||
}))
|
organizationId: organization.id,
|
||||||
)
|
},
|
||||||
.execute();
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// await db
|
||||||
|
// .insert(drizzleDb.schemas.member)
|
||||||
|
// .values(
|
||||||
|
// usersToAdd.map((userId) => ({
|
||||||
|
// userId,
|
||||||
|
// organizationId: organization.id,
|
||||||
|
// role: "member",
|
||||||
|
// }))
|
||||||
|
// )
|
||||||
|
// .execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (usersToRemove.length > 0) {
|
if (usersToRemove.length > 0) {
|
||||||
await db.delete().from(userOrganization).where(inArray(userOrganization.userId, usersToRemove)).execute();
|
await db.delete(drizzleDb.schemas.member).where(inArray(drizzleDb.schemas.member.userId, usersToRemove)).execute();
|
||||||
|
// TODO : Do not delete, go permission error with better auth
|
||||||
|
// for (const userToRemove of usersToRemove) {
|
||||||
|
//
|
||||||
|
// const memberToRemove = await db.query.member.findFirst({
|
||||||
|
// where: and(eq(drizzleDb.schemas.member.userId, userToRemove), eq(drizzleDb.schemas.member.organizationId, organization.id)),
|
||||||
|
// with: {
|
||||||
|
// user: true
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// console.log(memberToRemove)
|
||||||
|
//
|
||||||
|
// if (memberToRemove) {
|
||||||
|
// console.log("ici")
|
||||||
|
// await auth.api.removeMember({
|
||||||
|
// body: {
|
||||||
|
// memberIdOrEmail: memberToRemove.user.email,
|
||||||
|
// organizationId: organization.id,
|
||||||
|
// },
|
||||||
|
// headers: await headers()
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// const updatedOrganization = await auth.api.updateOrganization({
|
||||||
|
// body: {
|
||||||
|
// data: {
|
||||||
|
// name: parsedInput.data.name,
|
||||||
|
// slug: parsedInput.data.slug,
|
||||||
|
// },
|
||||||
|
// organizationId: organization.id,
|
||||||
|
// },
|
||||||
|
// headers: await headers(),
|
||||||
|
// });
|
||||||
|
|
||||||
|
|
||||||
const updatedOrganization = await db
|
const updatedOrganization = await db
|
||||||
.update(organization)
|
.update(drizzleDb.schemas.organization)
|
||||||
.set({
|
.set({
|
||||||
name: parsedInput.data.name,
|
name: parsedInput.data.name,
|
||||||
slug: parsedInput.data.slug,
|
slug: parsedInput.data.slug,
|
||||||
})
|
})
|
||||||
.where(eq(organization.id, parsedInput.organizationId))
|
.where(eq(drizzleDb.schemas.organization.id, parsedInput.organizationId))
|
||||||
.returning()
|
.returning()
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
value: updatedOrganization[0],
|
value: updatedOrganization as unknown as Organization,
|
||||||
actionSuccess: {
|
actionSuccess: {
|
||||||
message: "Organization has been successfully updated.",
|
message: "Organization has been successfully updated.",
|
||||||
messageParams: { organizationId: updatedOrganization[0].id },
|
messageParams: { organizationId: organization.id},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -128,7 +192,7 @@ export const updateOrganizationAction = userAction
|
|||||||
actionError: {
|
actionError: {
|
||||||
message: "Failed to update organization.",
|
message: "Failed to update organization.",
|
||||||
status: 500,
|
status: 500,
|
||||||
cause: error.message ?? "Unknown error",
|
cause: "server_error",
|
||||||
messageParams: { message: "Error updating the organization" },
|
messageParams: { message: "Error updating the organization" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ import { UploadIcon } from "lucide-react";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { uploadImageAction } from "@/features/upload/public/upload.action";
|
import { uploadImageAction } from "@/features/upload/public/upload.action";
|
||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import { updateImageUserAction } from "@/components/wrappers/dashboard/profile/Avatar/avatar.action";
|
import { updateImageUserAction } from "@/components/wrappers/dashboard/profile/avatar/avatar.action";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { User } from "@/db/schema/01_user";
|
import { User } from "@/db/schema/01_user";
|
||||||
|
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
"use server";
|
"use server";
|
||||||
import { userAction } from "@/safe-actions";
|
import { userAction } from "@/safe-actions";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { UserSchema } from "@/components/wrappers/dashboard/profile/UserForm/user-form.schema";
|
import { UserSchema } from "@/components/wrappers/dashboard/profile/user-form/user-form.schema";
|
||||||
import { db } from "@/db";
|
import { db } from "@/db";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import * as drizzleDb from "@/db";
|
import * as drizzleDb from "@/db";
|
||||||
+2
-2
@@ -8,9 +8,9 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
import { UserSchema, UserType } from "@/components/wrappers/dashboard/profile/UserForm/user-form.schema";
|
import { UserSchema, UserType } from "@/components/wrappers/dashboard/profile/user-form/user-form.schema";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { updateUserAction } from "@/components/wrappers/dashboard/profile/UserForm/user-form.action";
|
import { updateUserAction } from "@/components/wrappers/dashboard/profile/user-form/user-form.action";
|
||||||
|
|
||||||
export type userFormProps = {
|
export type userFormProps = {
|
||||||
defaultValues?: UserType;
|
defaultValues?: UserType;
|
||||||
+1
-1
@@ -3,7 +3,7 @@
|
|||||||
import { Trash2 } from "lucide-react";
|
import { Trash2 } from "lucide-react";
|
||||||
import { ButtonWithConfirm } from "@/components/wrappers/common/button/button-with-confirm";
|
import { ButtonWithConfirm } from "@/components/wrappers/common/button/button-with-confirm";
|
||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import { deleteProjectAction } from "@/components/wrappers/dashboard/projects/ButtonDeleteProject/delete-project.action";
|
import { deleteProjectAction } from "@/components/wrappers/dashboard/projects/button-delete-project/delete-project.action";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { userAction } from "@/safe-actions";
|
import { userAction } from "@/safe-actions";
|
||||||
import { ProjectSchema } from "@/components/wrappers/dashboard/projects/ProjectsForm/ProjectForm.schema";
|
import { ProjectSchema } from "@/components/wrappers/dashboard/projects/project-form/project-form.schema";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { ServerActionResult } from "@/types/action-type";
|
import { ServerActionResult } from "@/types/action-type";
|
||||||
import { db } from "@/db";
|
import { db } from "@/db";
|
||||||
+2
-2
@@ -6,8 +6,8 @@ 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 { 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/project-form/project-form.schema";
|
||||||
import { createProjectAction, updateProjectAction } from "@/components/wrappers/dashboard/projects/ProjectsForm/project-form.action";
|
import { createProjectAction, updateProjectAction } from "@/components/wrappers/dashboard/projects/project-form/project-form.action";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { MultiSelect } from "@/components/wrappers/common/multiselect/multi-select";
|
import { MultiSelect } from "@/components/wrappers/common/multiselect/multi-select";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@@ -5,7 +5,7 @@ import {MemberWithUser} from "@/db/schema/02_organization";
|
|||||||
import {useState} from "react";
|
import {useState} from "react";
|
||||||
import {useSession} from "@/lib/auth/auth-client";
|
import {useSession} from "@/lib/auth/auth-client";
|
||||||
import {useMutation} from "@tanstack/react-query";
|
import {useMutation} from "@tanstack/react-query";
|
||||||
import {updateUserAction} from "@/components/wrappers/dashboard/profile/UserForm/user-form.action";
|
import {updateUserAction} from "@/components/wrappers/dashboard/profile/user-form/user-form.action";
|
||||||
import {toast} from "sonner";
|
import {toast} from "sonner";
|
||||||
import {Badge} from "@/components/ui/badge";
|
import {Badge} from "@/components/ui/badge";
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
"use client";
|
|
||||||
import { useSidebar } from "@/components/ui/sidebar";
|
|
||||||
import { env } from "@/env.mjs";
|
|
||||||
|
|
||||||
export type SideBarFooterCreditProps = {};
|
|
||||||
|
|
||||||
export const SideBarFooterCredit = (props: SideBarFooterCreditProps) => {
|
|
||||||
const { state } = useSidebar();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{state === "expanded" && (
|
|
||||||
<div className="text-center">
|
|
||||||
<h1 className="text-[10px]">Portabase Community Edition v{env.NEXT_PUBLIC_PROJECT_VERSION}</h1>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
"use client"
|
|
||||||
import {useSidebar} from "@/components/ui/sidebar";
|
|
||||||
|
|
||||||
export type SideBarLogoProps = {}
|
|
||||||
|
|
||||||
export const SideBarLogo = (props: SideBarLogoProps) => {
|
|
||||||
const { state, isMobile } = useSidebar();
|
|
||||||
return (
|
|
||||||
<div className="flex justify-center items-center ">
|
|
||||||
<h1 className="font-bold text-xl">
|
|
||||||
{state === 'collapsed' && !isMobile ? "P" : "Portabase"}
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { JSX, useEffect, useState } from "react";
|
|
||||||
import Link from "next/link";
|
|
||||||
import {
|
|
||||||
SidebarGroup,
|
|
||||||
SidebarGroupContent,
|
|
||||||
SidebarGroupLabel,
|
|
||||||
SidebarMenu as SM,
|
|
||||||
SidebarMenuAction,
|
|
||||||
SidebarMenuButton,
|
|
||||||
SidebarMenuItem,
|
|
||||||
} from "@/components/ui/sidebar";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import { buttonVariants } from "@/components/ui/button";
|
|
||||||
import { ChevronDown, MoreHorizontal } from "lucide-react";
|
|
||||||
import { usePathname } from "next/navigation";
|
|
||||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
|
||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
|
||||||
|
|
||||||
export type SidebarMenuProps = {
|
|
||||||
baseUrl: string;
|
|
||||||
items: SidebarItem[];
|
|
||||||
};
|
|
||||||
|
|
||||||
type SidebarItemContent = {
|
|
||||||
title: string;
|
|
||||||
url: string;
|
|
||||||
icon: JSX.Element;
|
|
||||||
dropdown?: {
|
|
||||||
title: string;
|
|
||||||
url: string;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
|
|
||||||
type SidebarListItem = {
|
|
||||||
type: "list";
|
|
||||||
content: SidebarItemContent;
|
|
||||||
};
|
|
||||||
|
|
||||||
type SidebarCollapseItem = {
|
|
||||||
type: "collapse";
|
|
||||||
title: string;
|
|
||||||
content: SidebarItemContent[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SidebarItem = SidebarListItem | SidebarCollapseItem;
|
|
||||||
|
|
||||||
export const SidebarMenu = (props: SidebarMenuProps) => {
|
|
||||||
const pathname = usePathname();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const currentUrl = pathname;
|
|
||||||
|
|
||||||
const currentItem = props.items.find((item) => {
|
|
||||||
if (item.type === "list") {
|
|
||||||
return `${props.baseUrl}/${item.content.url}` === currentUrl;
|
|
||||||
} else if (item.type === "collapse") {
|
|
||||||
return item.content.some((content) => `${props.baseUrl}/${content.url}` === currentUrl);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (currentItem) {
|
|
||||||
if (currentItem.type === "list") {
|
|
||||||
setActiveItem(currentItem.content.title);
|
|
||||||
} else if (currentItem.type === "collapse") {
|
|
||||||
setActiveItem(currentItem.title!);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setActiveItem("");
|
|
||||||
}
|
|
||||||
}, [pathname]);
|
|
||||||
|
|
||||||
const [activeItem, setActiveItem] = useState("");
|
|
||||||
const handleItemClick = (title: string) => {
|
|
||||||
setActiveItem(title);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SM>
|
|
||||||
{props.items.map((item, index) =>
|
|
||||||
item.type === "collapse" ? (
|
|
||||||
<Collapsible defaultOpen key={index} className={`group/collapsible`}>
|
|
||||||
<SidebarGroup>
|
|
||||||
<SidebarGroupLabel asChild>
|
|
||||||
<CollapsibleTrigger>
|
|
||||||
{item.title ?? "Please define a title"}
|
|
||||||
<ChevronDown className="ml-auto transition-transform group-data-[state=open]/collapsible:rotate-180" />
|
|
||||||
</CollapsibleTrigger>
|
|
||||||
</SidebarGroupLabel>
|
|
||||||
<CollapsibleContent>
|
|
||||||
<SidebarGroupContent>
|
|
||||||
{item.content.map((content) => (
|
|
||||||
<SidebarMenuItem key={content.title}>
|
|
||||||
<SidebarMenuButton asChild>
|
|
||||||
<Link
|
|
||||||
className={cn(
|
|
||||||
buttonVariants({
|
|
||||||
size: "lg",
|
|
||||||
variant: activeItem === content.title ? "secondary" : "ghost",
|
|
||||||
}),
|
|
||||||
"justify-start p-0"
|
|
||||||
)}
|
|
||||||
href={`${props.baseUrl}/${content.url}`}
|
|
||||||
onClick={() => handleItemClick(content.title)}
|
|
||||||
>
|
|
||||||
{content.icon}
|
|
||||||
<span>{content.title}</span>
|
|
||||||
</Link>
|
|
||||||
</SidebarMenuButton>
|
|
||||||
|
|
||||||
{content.dropdown && (
|
|
||||||
<SidebarMenuAction>
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<SidebarMenuAction>
|
|
||||||
<MoreHorizontal />
|
|
||||||
</SidebarMenuAction>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent side="right" align="start">
|
|
||||||
{content.dropdown.map((dropdown) => (
|
|
||||||
<Link href={`${props.baseUrl}/${dropdown.url}`} className="justify-start p-0">
|
|
||||||
<DropdownMenuItem>
|
|
||||||
<span>{dropdown.title ?? "Please define a title"}</span>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</SidebarMenuAction>
|
|
||||||
)}
|
|
||||||
</SidebarMenuItem>
|
|
||||||
))}
|
|
||||||
</SidebarGroupContent>
|
|
||||||
</CollapsibleContent>
|
|
||||||
</SidebarGroup>
|
|
||||||
</Collapsible>
|
|
||||||
) : (
|
|
||||||
<SidebarMenuItem key={index}>
|
|
||||||
<SidebarMenuButton asChild>
|
|
||||||
<Link
|
|
||||||
className={cn(
|
|
||||||
buttonVariants({
|
|
||||||
size: "lg",
|
|
||||||
variant: activeItem === item.content.title ? "secondary" : "ghost",
|
|
||||||
}),
|
|
||||||
"justify-start p-0"
|
|
||||||
)}
|
|
||||||
href={`${props.baseUrl}/${item.content.url}`}
|
|
||||||
onClick={() => handleItemClick(item.content.title)}
|
|
||||||
>
|
|
||||||
{item.content.icon}
|
|
||||||
<span>{item.content.title}</span>
|
|
||||||
</Link>
|
|
||||||
</SidebarMenuButton>
|
|
||||||
</SidebarMenuItem>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</SM>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
import {
|
|
||||||
Sidebar,
|
|
||||||
SidebarContent,
|
|
||||||
SidebarFooter,
|
|
||||||
SidebarHeader,
|
|
||||||
SidebarMenu as SM,
|
|
||||||
SidebarMenuItem,
|
|
||||||
} from "@/components/ui/sidebar";
|
|
||||||
import {OrganizationCombobox} from "@/components/wrappers/dashboard/organization/organization-combobox";
|
|
||||||
import {SideBarLogo} from "@/components/wrappers/dashboard/sideBar/SideBarLogo/SideBarLogo";
|
|
||||||
import {SideBarFooterCredit} from "@/components/wrappers/dashboard/sideBar/SideBarFooterCredit/SideBarFooterCredit";
|
|
||||||
import {LoggedInButton} from "@/components/wrappers/dashboard/loggedInButton/LoggedInButton";
|
|
||||||
import {SidebarContentA} from "./sidebar-content";
|
|
||||||
import {getActiveMember, getOrganization} from "@/lib/auth/auth";
|
|
||||||
import {notFound} from "next/navigation";
|
|
||||||
|
|
||||||
export async function AppSidebar() {
|
|
||||||
const member = await getActiveMember();
|
|
||||||
|
|
||||||
console.log("member", member);
|
|
||||||
|
|
||||||
if (!member) {
|
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
// const organization = await getOrganization({organizationId: member.organizationId});
|
|
||||||
|
|
||||||
// console.log("organization", organization);
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Sidebar collapsible="icon">
|
|
||||||
<SidebarHeader>
|
|
||||||
<SideBarLogo/>
|
|
||||||
<SM>
|
|
||||||
<SidebarMenuItem>
|
|
||||||
<OrganizationCombobox/>
|
|
||||||
</SidebarMenuItem>
|
|
||||||
</SM>
|
|
||||||
</SidebarHeader>
|
|
||||||
<SidebarContent>
|
|
||||||
<SidebarContentA/>
|
|
||||||
</SidebarContent>
|
|
||||||
<SidebarFooter>
|
|
||||||
<SM>
|
|
||||||
<SidebarMenuItem>
|
|
||||||
<LoggedInButton/>
|
|
||||||
</SidebarMenuItem>
|
|
||||||
</SM>
|
|
||||||
<SideBarFooterCredit/>
|
|
||||||
</SidebarFooter>
|
|
||||||
</Sidebar>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
"use client";
|
|
||||||
import { SidebarGroup, SidebarGroupLabel, SidebarGroupContent, SidebarContent } from "@/components/ui/sidebar";
|
|
||||||
import { authClient, useSession } from "@/lib/auth/auth-client";
|
|
||||||
import { Layers, ChartArea, Settings, ShieldHalf } from "lucide-react";
|
|
||||||
import { SidebarItem, SidebarMenu } from "./SideBarMenu/SideBarMenu";
|
|
||||||
|
|
||||||
export const SidebarContentA = () => {
|
|
||||||
const { data: activeOrganization } = authClient.useActiveOrganization();
|
|
||||||
const { data: organizations } = authClient.useListOrganizations();
|
|
||||||
const { data: session } = useSession();
|
|
||||||
const member = authClient.useActiveMember(); // Moved here — always called
|
|
||||||
|
|
||||||
|
|
||||||
const appItems: SidebarItem[] = [
|
|
||||||
{
|
|
||||||
type: "list",
|
|
||||||
content: {
|
|
||||||
title: "Projects",
|
|
||||||
url: "projects",
|
|
||||||
icon: <Layers />,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "list",
|
|
||||||
content: {
|
|
||||||
title: "Statistics",
|
|
||||||
url: "statistics",
|
|
||||||
icon: <ChartArea />,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
if (activeOrganization && member?.data?.role === "admin" || member?.data?.role === "owner") {
|
|
||||||
appItems.push({
|
|
||||||
type: "list",
|
|
||||||
content: {
|
|
||||||
title: "Settings",
|
|
||||||
url: "settings",
|
|
||||||
icon: <Settings />,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const adminItems: SidebarItem[] = [
|
|
||||||
{
|
|
||||||
type: "list",
|
|
||||||
content: {
|
|
||||||
title: "Agents",
|
|
||||||
url: "agents",
|
|
||||||
icon: <ShieldHalf />,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "list",
|
|
||||||
content: {
|
|
||||||
title: "Administration panel",
|
|
||||||
url: "admin",
|
|
||||||
icon: <Settings />,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
activeOrganization && (
|
|
||||||
<>
|
|
||||||
<SidebarGroup>
|
|
||||||
<SidebarGroupLabel>Application</SidebarGroupLabel>
|
|
||||||
<SidebarGroupContent>
|
|
||||||
<SidebarMenu items={appItems} baseUrl={`/dashboard`} />
|
|
||||||
</SidebarGroupContent>
|
|
||||||
</SidebarGroup>
|
|
||||||
{(session && (session.user.role === "admin" || session.user.role === "superadmin")) && (
|
|
||||||
<SidebarGroup>
|
|
||||||
<SidebarGroupLabel>Administration</SidebarGroupLabel>
|
|
||||||
<SidebarGroupContent>
|
|
||||||
<SidebarMenu items={adminItems} baseUrl={`/dashboard`} />
|
|
||||||
</SidebarGroupContent>
|
|
||||||
</SidebarGroup>
|
|
||||||
)}
|
|
||||||
|
|
||||||
</>
|
|
||||||
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
+2
-2
@@ -154,6 +154,7 @@ CREATE TABLE "agents" (
|
|||||||
"slug" text NOT NULL,
|
"slug" text NOT NULL,
|
||||||
"name" text NOT NULL,
|
"name" text NOT NULL,
|
||||||
"description" text NOT NULL,
|
"description" text NOT NULL,
|
||||||
|
"is_archived" boolean DEFAULT false,
|
||||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
"updated_at" timestamp,
|
"updated_at" timestamp,
|
||||||
"last_contact" timestamp,
|
"last_contact" timestamp,
|
||||||
@@ -171,5 +172,4 @@ ALTER TABLE "backups" ADD CONSTRAINT "backups_database_id_databases_id_fk" FOREI
|
|||||||
ALTER TABLE "databases" ADD CONSTRAINT "databases_agent_id_agents_id_fk" FOREIGN KEY ("agent_id") REFERENCES "public"."agents"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
ALTER TABLE "databases" ADD CONSTRAINT "databases_agent_id_agents_id_fk" FOREIGN KEY ("agent_id") REFERENCES "public"."agents"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
ALTER TABLE "databases" ADD CONSTRAINT "databases_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
ALTER TABLE "databases" ADD CONSTRAINT "databases_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||||
ALTER TABLE "restorations" ADD CONSTRAINT "restorations_backup_id_backups_id_fk" FOREIGN KEY ("backup_id") REFERENCES "public"."backups"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
ALTER TABLE "restorations" ADD CONSTRAINT "restorations_backup_id_backups_id_fk" FOREIGN KEY ("backup_id") REFERENCES "public"."backups"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
ALTER TABLE "restorations" ADD CONSTRAINT "restorations_database_id_databases_id_fk" FOREIGN KEY ("database_id") REFERENCES "public"."databases"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
ALTER TABLE "restorations" ADD CONSTRAINT "restorations_database_id_databases_id_fk" FOREIGN KEY ("database_id") REFERENCES "public"."databases"("id") ON DELETE cascade ON UPDATE no action;
|
||||||
CREATE UNIQUE INDEX "database_id_status_unique" ON "backups" USING btree ("database_id","status");
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
DROP INDEX "database_id_status_unique";
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"id": "3d165821-b11d-4be7-b3fb-83c4485e44a8",
|
"id": "5d68ff75-f31b-4f6a-a16f-f820fc2234aa",
|
||||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"dialect": "postgresql",
|
"dialect": "postgresql",
|
||||||
@@ -814,29 +814,7 @@
|
|||||||
"notNull": true
|
"notNull": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": {
|
"indexes": {},
|
||||||
"database_id_status_unique": {
|
|
||||||
"name": "database_id_status_unique",
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"expression": "database_id",
|
|
||||||
"isExpression": false,
|
|
||||||
"asc": true,
|
|
||||||
"nulls": "last"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"expression": "status",
|
|
||||||
"isExpression": false,
|
|
||||||
"asc": true,
|
|
||||||
"nulls": "last"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isUnique": true,
|
|
||||||
"concurrently": false,
|
|
||||||
"method": "btree",
|
|
||||||
"with": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"foreignKeys": {
|
"foreignKeys": {
|
||||||
"backups_database_id_databases_id_fk": {
|
"backups_database_id_databases_id_fk": {
|
||||||
"name": "backups_database_id_databases_id_fk",
|
"name": "backups_database_id_databases_id_fk",
|
||||||
@@ -1090,6 +1068,13 @@
|
|||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
|
"is_archived": {
|
||||||
|
"name": "is_archived",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
"created_at": {
|
"created_at": {
|
||||||
"name": "created_at",
|
"name": "created_at",
|
||||||
"type": "timestamp",
|
"type": "timestamp",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,15 +5,8 @@
|
|||||||
{
|
{
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1752684754669,
|
"when": 1753637475061,
|
||||||
"tag": "0000_awesome_nomad",
|
"tag": "0000_misty_liz_osborn",
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 1,
|
|
||||||
"version": "7",
|
|
||||||
"when": 1752689557905,
|
|
||||||
"tag": "0001_wealthy_leo",
|
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
|
import {boolean, pgTable, text, timestamp, uuid} from "drizzle-orm/pg-core";
|
||||||
import { createSelectSchema } from "drizzle-zod";
|
import { createSelectSchema } from "drizzle-zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {database} from "@/db/schema/06_database";
|
import {database} from "@/db/schema/06_database";
|
||||||
@@ -9,6 +9,7 @@ export const agent = pgTable("agents", {
|
|||||||
slug: text("slug").notNull().unique(),
|
slug: text("slug").notNull().unique(),
|
||||||
name: text("name").notNull().notNull(),
|
name: text("name").notNull().notNull(),
|
||||||
description: text("description").notNull(),
|
description: text("description").notNull(),
|
||||||
|
isArchived: boolean("is_archived").default(false),
|
||||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||||
updatedAt: timestamp("updated_at"),
|
updatedAt: timestamp("updated_at"),
|
||||||
lastContact: timestamp("last_contact"),
|
lastContact: timestamp("last_contact"),
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ import {createRestorationAction, deleteBackupAction} from "@/features/dashboard/
|
|||||||
import {toast} from "sonner";
|
import {toast} from "sonner";
|
||||||
import {useRouter} from "next/navigation";
|
import {useRouter} from "next/navigation";
|
||||||
import {StatusBadge} from "@/components/wrappers/common/status-badge";
|
import {StatusBadge} from "@/components/wrappers/common/status-badge";
|
||||||
import {TooltipCustom} from "@/components/wrappers/common/tooltipCustom/TooltipCustom";
|
|
||||||
import {Backup} from "@/db/schema/06_database";
|
import {Backup} from "@/db/schema/06_database";
|
||||||
import {formatFrenchDate} from "@/utils/date-formatting";
|
import {formatFrenchDate} from "@/utils/date-formatting";
|
||||||
|
import {TooltipCustom} from "@/components/wrappers/common/tooltip-custom";
|
||||||
|
|
||||||
|
|
||||||
export function backupColumns(isAlreadyRestore: boolean): ColumnDef<Backup>[] {
|
export function backupColumns(isAlreadyRestore: boolean): ColumnDef<Backup>[] {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user