diff --git a/server/auth/sessions/olm.ts b/server/auth/sessions/olm.ts index 8d24c16f..263489ca 100644 --- a/server/auth/sessions/olm.ts +++ b/server/auth/sessions/olm.ts @@ -2,7 +2,7 @@ import { encodeHexLowerCase, } from "@oslojs/encoding"; import { sha256 } from "@oslojs/crypto/sha2"; -import { Olm, olms, olmSessions, OlmSession } from "@server/db/schema"; +import { Olm, olms, olmSessions, OlmSession } from "@server/db/schemas"; import db from "@server/db"; import { eq } from "drizzle-orm"; diff --git a/server/lib/ip.ts b/server/lib/ip.ts index 16a926f1..8d8e6d75 100644 --- a/server/lib/ip.ts +++ b/server/lib/ip.ts @@ -1,5 +1,5 @@ import db from "@server/db"; -import { clients, orgs, sites } from "@server/db/schema"; +import { clients, orgs, sites } from "@server/db/schemas"; import { and, eq, isNotNull } from "drizzle-orm"; import config from "@server/lib/config"; diff --git a/server/middlewares/verifyClientAccess.ts b/server/middlewares/verifyClientAccess.ts index b024d416..e5c566d1 100644 --- a/server/middlewares/verifyClientAccess.ts +++ b/server/middlewares/verifyClientAccess.ts @@ -1,6 +1,6 @@ import { Request, Response, NextFunction } from "express"; import { db } from "@server/db"; -import { userOrgs, clients, roleClients, userClients } from "@server/db/schema"; +import { userOrgs, clients, roleClients, userClients } from "@server/db/schemas"; import { and, eq } from "drizzle-orm"; import createHttpError from "http-errors"; import HttpCode from "@server/types/HttpCode"; diff --git a/server/routers/client/createClient.ts b/server/routers/client/createClient.ts index dc005700..75d0bdc5 100644 --- a/server/routers/client/createClient.ts +++ b/server/routers/client/createClient.ts @@ -12,7 +12,7 @@ import { exitNodes, orgs, sites -} from "@server/db/schema"; +} from "@server/db/schemas"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; diff --git a/server/routers/client/deleteClient.ts b/server/routers/client/deleteClient.ts index f99f6fee..a46d4f46 100644 --- a/server/routers/client/deleteClient.ts +++ b/server/routers/client/deleteClient.ts @@ -1,7 +1,7 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; import { db } from "@server/db"; -import { clients, clientSites } from "@server/db/schema"; +import { clients, clientSites } from "@server/db/schemas"; import { eq } from "drizzle-orm"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; diff --git a/server/routers/client/getClient.ts b/server/routers/client/getClient.ts index 470d712e..ae1178ff 100644 --- a/server/routers/client/getClient.ts +++ b/server/routers/client/getClient.ts @@ -1,7 +1,7 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; import { db } from "@server/db"; -import { clients } from "@server/db/schema"; +import { clients } from "@server/db/schemas"; import { eq, and } from "drizzle-orm"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; diff --git a/server/routers/client/listClients.ts b/server/routers/client/listClients.ts index f9cf5295..9a0ae23c 100644 --- a/server/routers/client/listClients.ts +++ b/server/routers/client/listClients.ts @@ -6,7 +6,7 @@ import { sites, userClients, clientSites -} from "@server/db/schema"; +} from "@server/db/schemas"; import logger from "@server/logger"; import HttpCode from "@server/types/HttpCode"; import response from "@server/lib/response"; @@ -53,6 +53,7 @@ function queryClients(orgId: string, accessibleClientIds: number[]) { }) .from(clients) .leftJoin(orgs, eq(clients.orgId, orgs.orgId)) + .leftJoin(orgs, eq(clients.orgId, orgs.orgId)) .where( and( inArray(clients.clientId, accessibleClientIds), @@ -63,7 +64,7 @@ function queryClients(orgId: string, accessibleClientIds: number[]) { async function getSiteAssociations(clientIds: number[]) { if (clientIds.length === 0) return []; - + return db .select({ clientId: clientSites.clientId, @@ -205,4 +206,4 @@ export async function listClients( createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") ); } -} \ No newline at end of file +} diff --git a/server/routers/client/updateClient.ts b/server/routers/client/updateClient.ts index 9bdd4295..097b4a4d 100644 --- a/server/routers/client/updateClient.ts +++ b/server/routers/client/updateClient.ts @@ -4,7 +4,7 @@ import { db } from "@server/db"; import { clients, clientSites -} from "@server/db/schema"; +} from "@server/db/schemas"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; diff --git a/server/routers/gerbil/getAllRelays.ts b/server/routers/gerbil/getAllRelays.ts index c975efec..ffffe424 100644 --- a/server/routers/gerbil/getAllRelays.ts +++ b/server/routers/gerbil/getAllRelays.ts @@ -1,6 +1,6 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; -import { clients, exitNodes, newts, olms, Site, sites, clientSites } from "@server/db/schema"; +import { clients, exitNodes, newts, olms, Site, sites, clientSites } from "@server/db/schemas"; import { db } from "@server/db"; import { eq } from "drizzle-orm"; import HttpCode from "@server/types/HttpCode"; diff --git a/server/routers/gerbil/updateHolePunch.ts b/server/routers/gerbil/updateHolePunch.ts index 910b2900..62a1b837 100644 --- a/server/routers/gerbil/updateHolePunch.ts +++ b/server/routers/gerbil/updateHolePunch.ts @@ -1,6 +1,6 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; -import { clients, newts, olms, Site, sites, clientSites } from "@server/db/schema"; +import { clients, newts, olms, Site, sites, clientSites } from "@server/db/schemas"; import { db } from "@server/db"; import { eq } from "drizzle-orm"; import HttpCode from "@server/types/HttpCode"; diff --git a/server/routers/newt/handleGetConfigMessage.ts b/server/routers/newt/handleGetConfigMessage.ts index 07a598f7..579500cf 100644 --- a/server/routers/newt/handleGetConfigMessage.ts +++ b/server/routers/newt/handleGetConfigMessage.ts @@ -3,7 +3,7 @@ import { MessageHandler } from "../ws"; import logger from "@server/logger"; import { fromError } from "zod-validation-error"; import db from "@server/db"; -import { clients, clientSites, Newt, sites } from "@server/db/schema"; +import { clients, clientSites, Newt, sites } from "@server/db/schemas"; import { eq } from "drizzle-orm"; import { updatePeer } from "../olm/peers"; diff --git a/server/routers/newt/handleReceiveBandwidthMessage.ts b/server/routers/newt/handleReceiveBandwidthMessage.ts index 41bfd39a..92de1350 100644 --- a/server/routers/newt/handleReceiveBandwidthMessage.ts +++ b/server/routers/newt/handleReceiveBandwidthMessage.ts @@ -1,6 +1,6 @@ import db from "@server/db"; import { MessageHandler } from "../ws"; -import { clients, Newt } from "@server/db/schema"; +import { clients, Newt } from "@server/db/schemas"; import { eq } from "drizzle-orm"; import logger from "@server/logger"; diff --git a/server/routers/newt/peers.ts b/server/routers/newt/peers.ts index f5e6c518..18afd1e7 100644 --- a/server/routers/newt/peers.ts +++ b/server/routers/newt/peers.ts @@ -1,28 +1,38 @@ -import db from '@server/db'; -import { newts, sites } from '@server/db/schema'; -import { eq } from 'drizzle-orm'; -import { sendToClient } from '../ws'; -import logger from '@server/logger'; +import db from "@server/db"; +import { newts, sites } from "@server/db/schemas"; +import { eq } from "drizzle-orm"; +import { sendToClient } from "../ws"; +import logger from "@server/logger"; -export async function addPeer(siteId: number, peer: { - publicKey: string; - allowedIps: string[]; - endpoint: string; -}) { - - const [site] = await db.select().from(sites).where(eq(sites.siteId, siteId)).limit(1); +export async function addPeer( + siteId: number, + peer: { + publicKey: string; + allowedIps: string[]; + endpoint: string; + } +) { + const [site] = await db + .select() + .from(sites) + .where(eq(sites.siteId, siteId)) + .limit(1); if (!site) { throw new Error(`Exit node with ID ${siteId} not found`); } // get the newt on the site - const [newt] = await db.select().from(newts).where(eq(newts.siteId, siteId)).limit(1); + const [newt] = await db + .select() + .from(newts) + .where(eq(newts.siteId, siteId)) + .limit(1); if (!newt) { throw new Error(`Site found for site ${siteId}`); } sendToClient(newt.newtId, { - type: 'newt/wg/peer/add', + type: "newt/wg/peer/add", data: peer }); @@ -30,19 +40,27 @@ export async function addPeer(siteId: number, peer: { } export async function deletePeer(siteId: number, publicKey: string) { - const [site] = await db.select().from(sites).where(eq(sites.siteId, siteId)).limit(1); + const [site] = await db + .select() + .from(sites) + .where(eq(sites.siteId, siteId)) + .limit(1); if (!site) { throw new Error(`Site with ID ${siteId} not found`); } // get the newt on the site - const [newt] = await db.select().from(newts).where(eq(newts.siteId, siteId)).limit(1); + const [newt] = await db + .select() + .from(newts) + .where(eq(newts.siteId, siteId)) + .limit(1); if (!newt) { throw new Error(`Newt not found for site ${siteId}`); } sendToClient(newt.newtId, { - type: 'newt/wg/peer/remove', + type: "newt/wg/peer/remove", data: { publicKey } @@ -51,23 +69,35 @@ export async function deletePeer(siteId: number, publicKey: string) { logger.info(`Deleted peer ${publicKey} from newt ${newt.newtId}`); } -export async function updatePeer(siteId: number, publicKey: string, peer: { - allowedIps?: string[]; - endpoint?: string; -}) { - const [site] = await db.select().from(sites).where(eq(sites.siteId, siteId)).limit(1); +export async function updatePeer( + siteId: number, + publicKey: string, + peer: { + allowedIps?: string[]; + endpoint?: string; + } +) { + const [site] = await db + .select() + .from(sites) + .where(eq(sites.siteId, siteId)) + .limit(1); if (!site) { throw new Error(`Site with ID ${siteId} not found`); } // get the newt on the site - const [newt] = await db.select().from(newts).where(eq(newts.siteId, siteId)).limit(1); + const [newt] = await db + .select() + .from(newts) + .where(eq(newts.siteId, siteId)) + .limit(1); if (!newt) { throw new Error(`Newt not found for site ${siteId}`); } sendToClient(newt.newtId, { - type: 'newt/wg/peer/update', + type: "newt/wg/peer/update", data: { publicKey, ...peer @@ -75,4 +105,4 @@ export async function updatePeer(siteId: number, publicKey: string, peer: { }); logger.info(`Updated peer ${publicKey} on newt ${newt.newtId}`); -} \ No newline at end of file +} diff --git a/server/routers/olm/createOlm.ts b/server/routers/olm/createOlm.ts index d43c4cc6..25f4bb31 100644 --- a/server/routers/olm/createOlm.ts +++ b/server/routers/olm/createOlm.ts @@ -3,7 +3,7 @@ import db from "@server/db"; import { hash } from "@node-rs/argon2"; import HttpCode from "@server/types/HttpCode"; import { z } from "zod"; -import { newts } from "@server/db/schema"; +import { newts } from "@server/db/schemas"; import createHttpError from "http-errors"; import response from "@server/lib/response"; import { SqliteError } from "better-sqlite3"; diff --git a/server/routers/olm/getOlmToken.ts b/server/routers/olm/getOlmToken.ts index 00ab9358..97274189 100644 --- a/server/routers/olm/getOlmToken.ts +++ b/server/routers/olm/getOlmToken.ts @@ -1,6 +1,6 @@ import { generateSessionToken } from "@server/auth/sessions/app"; import db from "@server/db"; -import { olms } from "@server/db/schema"; +import { olms } from "@server/db/schemas"; import HttpCode from "@server/types/HttpCode"; import response from "@server/lib/response"; import { eq } from "drizzle-orm"; diff --git a/server/routers/olm/handleOlmPingMessage.ts b/server/routers/olm/handleOlmPingMessage.ts index c958c38d..3040e04b 100644 --- a/server/routers/olm/handleOlmPingMessage.ts +++ b/server/routers/olm/handleOlmPingMessage.ts @@ -1,6 +1,6 @@ import db from "@server/db"; import { MessageHandler } from "../ws"; -import { clients, Olm } from "@server/db/schema"; +import { clients, Olm } from "@server/db/schemas"; import { eq, lt, isNull } from "drizzle-orm"; import logger from "@server/logger"; diff --git a/server/routers/olm/handleOlmRegisterMessage.ts b/server/routers/olm/handleOlmRegisterMessage.ts index a398d5e4..af525fc4 100644 --- a/server/routers/olm/handleOlmRegisterMessage.ts +++ b/server/routers/olm/handleOlmRegisterMessage.ts @@ -7,7 +7,7 @@ import { Olm, olms, sites -} from "@server/db/schema"; +} from "@server/db/schemas"; import { eq, inArray } from "drizzle-orm"; import { addPeer, deletePeer } from "../newt/peers"; import logger from "@server/logger"; diff --git a/server/routers/olm/handleOlmRelayMessage.ts b/server/routers/olm/handleOlmRelayMessage.ts index a6bee3b8..85c987ee 100644 --- a/server/routers/olm/handleOlmRelayMessage.ts +++ b/server/routers/olm/handleOlmRelayMessage.ts @@ -1,6 +1,6 @@ import db from "@server/db"; import { MessageHandler } from "../ws"; -import { clients, clientSites, Olm } from "@server/db/schema"; +import { clients, clientSites, Olm } from "@server/db/schemas"; import { eq } from "drizzle-orm"; import { updatePeer } from "../newt/peers"; import logger from "@server/logger"; diff --git a/server/routers/olm/peers.ts b/server/routers/olm/peers.ts index 969c71cf..7af1e355 100644 --- a/server/routers/olm/peers.ts +++ b/server/routers/olm/peers.ts @@ -1,23 +1,30 @@ -import db from '@server/db'; -import { clients, olms, newts } from '@server/db/schema'; -import { eq } from 'drizzle-orm'; -import { sendToClient } from '../ws'; -import logger from '@server/logger'; +import db from "@server/db"; +import { clients, olms, newts } from "@server/db/schemas"; +import { eq } from "drizzle-orm"; +import { sendToClient } from "../ws"; +import logger from "@server/logger"; -export async function addPeer(clientId: number, peer: { - siteId: number, - publicKey: string; - endpoint: string; - serverIP: string | null; - serverPort: number | null; -}) { - const [olm] = await db.select().from(olms).where(eq(olms.clientId, clientId)).limit(1); +export async function addPeer( + clientId: number, + peer: { + siteId: number; + publicKey: string; + endpoint: string; + serverIP: string | null; + serverPort: number | null; + } +) { + const [olm] = await db + .select() + .from(olms) + .where(eq(olms.clientId, clientId)) + .limit(1); if (!olm) { throw new Error(`Olm with ID ${clientId} not found`); } sendToClient(olm.olmId, { - type: 'olm/wg/peer/add', + type: "olm/wg/peer/add", data: { siteId: peer.siteId, publicKey: peer.publicKey, @@ -31,13 +38,17 @@ export async function addPeer(clientId: number, peer: { } export async function deletePeer(clientId: number, publicKey: string) { - const [olm] = await db.select().from(olms).where(eq(olms.clientId, clientId)).limit(1); + const [olm] = await db + .select() + .from(olms) + .where(eq(olms.clientId, clientId)) + .limit(1); if (!olm) { throw new Error(`Olm with ID ${clientId} not found`); } sendToClient(olm.olmId, { - type: 'olm/wg/peer/remove', + type: "olm/wg/peer/remove", data: { publicKey } @@ -46,20 +57,27 @@ export async function deletePeer(clientId: number, publicKey: string) { logger.info(`Deleted peer ${publicKey} from olm ${olm.olmId}`); } -export async function updatePeer(clientId: number, peer: { - siteId: number, - publicKey: string; - endpoint: string; - serverIP: string | null; - serverPort: number | null; -}) { - const [olm] = await db.select().from(olms).where(eq(olms.clientId, clientId)).limit(1); +export async function updatePeer( + clientId: number, + peer: { + siteId: number; + publicKey: string; + endpoint: string; + serverIP: string | null; + serverPort: number | null; + } +) { + const [olm] = await db + .select() + .from(olms) + .where(eq(olms.clientId, clientId)) + .limit(1); if (!olm) { throw new Error(`Olm with ID ${clientId} not found`); } sendToClient(olm.olmId, { - type: 'olm/wg/peer/update', + type: "olm/wg/peer/update", data: { siteId: peer.siteId, publicKey: peer.publicKey, @@ -70,4 +88,4 @@ export async function updatePeer(clientId: number, peer: { }); logger.info(`Added peer ${peer.publicKey} to olm ${olm.olmId}`); -} \ No newline at end of file +} diff --git a/src/app/[orgId]/settings/clients/ClientsDataTable.tsx b/src/app/[orgId]/settings/clients/ClientsDataTable.tsx index 07bee372..4f4c3b36 100644 --- a/src/app/[orgId]/settings/clients/ClientsDataTable.tsx +++ b/src/app/[orgId]/settings/clients/ClientsDataTable.tsx @@ -2,30 +2,8 @@ import { ColumnDef, - flexRender, - getCoreRowModel, - useReactTable, - getPaginationRowModel, - SortingState, - getSortedRowModel, - ColumnFiltersState, - getFilteredRowModel } from "@tanstack/react-table"; - -import { - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TableHeader, - TableRow -} from "@/components/ui/table"; -import { Button } from "@app/components/ui/button"; -import { useState } from "react"; -import { Input } from "@app/components/ui/input"; -import { DataTablePagination } from "@app/components/DataTablePagination"; -import { Plus, Search } from "lucide-react"; +import { DataTable } from "@app/components/ui/data-table"; interface DataTableProps { columns: ColumnDef[]; @@ -34,120 +12,19 @@ interface DataTableProps { } export function ClientsDataTable({ - addClient, columns, - data + data, + addClient }: DataTableProps) { - const [sorting, setSorting] = useState([]); - const [columnFilters, setColumnFilters] = useState([]); - - const table = useReactTable({ - data, - columns, - getCoreRowModel: getCoreRowModel(), - getPaginationRowModel: getPaginationRowModel(), - onSortingChange: setSorting, - getSortedRowModel: getSortedRowModel(), - onColumnFiltersChange: setColumnFilters, - getFilteredRowModel: getFilteredRowModel(), - initialState: { - pagination: { - pageSize: 20, - pageIndex: 0 - } - }, - state: { - sorting, - columnFilters - } - }); - return ( -
-
-
- - table - .getColumn("name") - ?.setFilterValue(event.target.value) - } - className="w-full pl-8" - /> - -
- -
- - - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => { - return ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef - .header, - header.getContext() - )} - - ); - })} - - ))} - - - {table.getRowModel().rows?.length ? ( - table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - - ))} - - )) - ) : ( - - - No clients. Create one to get started. - - - )} - -
-
-
- -
-
+ ); } diff --git a/src/app/[orgId]/settings/clients/CreateClientsForm.tsx b/src/app/[orgId]/settings/clients/CreateClientsForm.tsx index 0e8a31f0..1c459ddb 100644 --- a/src/app/[orgId]/settings/clients/CreateClientsForm.tsx +++ b/src/app/[orgId]/settings/clients/CreateClientsForm.tsx @@ -47,6 +47,7 @@ import { import { ScrollArea } from "@app/components/ui/scroll-area"; import { Badge } from "@app/components/ui/badge"; import { X } from "lucide-react"; +import { Tag, TagInput } from "@app/components/tags/tag-input"; const createClientFormSchema = z.object({ name: z @@ -57,9 +58,16 @@ const createClientFormSchema = z.object({ .max(30, { message: "Name must not be longer than 30 characters." }), - siteIds: z.array(z.number()).min(1, { - message: "Select at least one site." - }), + siteIds: z + .array( + z.object({ + id: z.string(), + text: z.string() + }) + ) + .refine((val) => val.length > 0, { + message: "At least one site is required." + }), subnet: z.string().min(1, { message: "Subnet is required." }) @@ -89,7 +97,7 @@ export default function CreateClientForm({ const api = createApiClient(useEnvContext()); const { env } = useEnvContext(); - const [sites, setSites] = useState([]); + const [sites, setSites] = useState([]); const [isLoading, setIsLoading] = useState(false); const [isChecked, setIsChecked] = useState(false); const [clientDefaults, setClientDefaults] = @@ -98,6 +106,9 @@ export default function CreateClientForm({ const [selectedSites, setSelectedSites] = useState< Array<{ id: number; name: string }> >([]); + const [activeSitesTagIndex, setActiveSitesTagIndex] = useState< + number | null + >(null); const handleCheckboxChange = (checked: boolean) => { setIsChecked(checked); @@ -111,14 +122,6 @@ export default function CreateClientForm({ defaultValues }); - useEffect(() => { - // Update form value when selectedSites changes - form.setValue( - "siteIds", - selectedSites.map((site) => site.id) - ); - }, [selectedSites, form]); - useEffect(() => { if (!open) return; @@ -137,7 +140,12 @@ export default function CreateClientForm({ const sites = res.data.data.sites.filter( (s) => s.type === "newt" && s.subnet ); - setSites(sites); + setSites( + sites.map((site) => ({ + id: site.siteId.toString(), + text: site.name + })) + ); }; const fetchDefaults = async () => { @@ -155,7 +163,7 @@ export default function CreateClientForm({ setClientDefaults(data); const olmConfig = `olm --id ${data?.olmId} --secret ${data?.olmSecret} --endpoint ${env.app.dashboardUrl}`; setOlmCommand(olmConfig); - + // Set the subnet value from client defaults if (data?.subnet) { form.setValue("subnet", data.subnet); @@ -167,19 +175,6 @@ export default function CreateClientForm({ fetchDefaults(); }, [open]); - const addSite = (siteId: number, siteName: string) => { - if (!selectedSites.some((site) => site.id === siteId)) { - setSelectedSites([ - ...selectedSites, - { id: siteId, name: siteName } - ]); - } - }; - - const removeSite = (siteId: number) => { - setSelectedSites(selectedSites.filter((site) => site.id !== siteId)); - }; - async function onSubmit(data: CreateClientFormValues) { setLoading?.(true); setIsLoading(true); @@ -197,7 +192,7 @@ export default function CreateClientForm({ const payload = { name: data.name, - siteIds: data.siteIds, + siteIds: data.siteIds.map((site) => parseInt(site.id)), olmId: clientDefaults.olmId, secret: clientDefaults.olmSecret, subnet: data.subnet, @@ -274,7 +269,8 @@ export default function CreateClientForm({ /> - The address that this client will use for connectivity. + The address that this client will use for + connectivity. @@ -284,97 +280,28 @@ export default function CreateClientForm({ ( + render={(field) => ( Sites - - - - - - - - - - - - No sites found. - - - - {sites.map((site) => ( - { - addSite( - site.siteId, - site.name - ); - }} - > - - s.id === - site.siteId - ) - ? "opacity-100" - : "opacity-0" - )} - /> - {site.name} - - ))} - - - - - - - - {selectedSites.length > 0 && ( -
- {selectedSites.map((site) => ( - - {site.name} - - - ))} -
- )} - + { + form.setValue( + "siteIds", + newTags as [Tag, ...Tag[]] + ); + }} + enableAutocomplete={true} + autocompleteOptions={sites} + allowDuplicates={false} + restrictTagsToAutocompleteOptions={true} + sortTags={true} + /> The client will have connectivity to the selected sites. The sites must be configured @@ -419,4 +346,4 @@ export default function CreateClientForm({ ); -} \ No newline at end of file +} diff --git a/src/app/[orgId]/settings/clients/[clientId]/general/page.tsx b/src/app/[orgId]/settings/clients/[clientId]/general/page.tsx index 001e92c4..ae0bd4a8 100644 --- a/src/app/[orgId]/settings/clients/[clientId]/general/page.tsx +++ b/src/app/[orgId]/settings/clients/[clientId]/general/page.tsx @@ -40,11 +40,8 @@ type GeneralFormValues = z.infer; export default function GeneralPage() { const { client, updateClient } = useClientContext(); - const api = createApiClient(useEnvContext()); - const [loading, setLoading] = useState(false); - const router = useRouter(); const form = useForm({ @@ -58,31 +55,31 @@ export default function GeneralPage() { async function onSubmit(data: GeneralFormValues) { setLoading(true); - await api - .post(`/client/${client?.clientId}`, { + try { + await api.post(`/client/${client?.clientId}`, { name: data.name - }) - .catch((e) => { - toast({ - variant: "destructive", - title: "Failed to update client", - description: formatAxiosError( - e, - "An error occurred while updating the client." - ) - }); }); - updateClient({ name: data.name }); + updateClient({ name: data.name }); - toast({ - title: "Client updated", - description: "The client has been updated." - }); + toast({ + title: "Client updated", + description: "The client has been updated." + }); - setLoading(false); - - router.refresh(); + router.refresh(); + } catch (e) { + toast({ + variant: "destructive", + title: "Failed to update client", + description: formatAxiosError( + e, + "An error occurred while updating the client." + ) + }); + } finally { + setLoading(false); + } } return ( diff --git a/src/app/[orgId]/settings/clients/[clientId]/layout.tsx b/src/app/[orgId]/settings/clients/[clientId]/layout.tsx index 20caf093..41f0bbf7 100644 --- a/src/app/[orgId]/settings/clients/[clientId]/layout.tsx +++ b/src/app/[orgId]/settings/clients/[clientId]/layout.tsx @@ -2,21 +2,14 @@ import { internal } from "@app/lib/api"; import { AxiosResponse } from "axios"; import { authCookieHeader } from "@app/lib/api/cookies"; import { SidebarSettings } from "@app/components/SidebarSettings"; -import Link from "next/link"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; -import { - Breadcrumb, - BreadcrumbItem, - BreadcrumbList, - BreadcrumbPage, - BreadcrumbSeparator -} from "@app/components/ui/breadcrumb"; import { GetClientResponse } from "@server/routers/client"; import ClientInfoCard from "./ClientInfoCard"; import ClientProvider from "@app/providers/ClientProvider"; import { redirect } from "next/navigation"; +import { HorizontalTabs } from "@app/components/HorizontalTabs"; -interface SettingsLayoutProps { +type SettingsLayoutProps = { children: React.ReactNode; params: Promise<{ clientId: number; orgId: string }>; } @@ -38,39 +31,27 @@ export default async function SettingsLayout(props: SettingsLayoutProps) { redirect(`/${params.orgId}/settings/clients`); } - const sidebarNavItems = [ + const navItems = [ { title: "General", - href: "/{orgId}/settings/clients/{clientId}/general" + href: `/{orgId}/settings/clients/{clientId}/general` } ]; return ( <> -
- - - - Clients - - - - {client.name} - - - -
- - +
- {children} - + + {children} + +
); diff --git a/src/app/navigation.tsx b/src/app/navigation.tsx index dcda0471..0935a75b 100644 --- a/src/app/navigation.tsx +++ b/src/app/navigation.tsx @@ -6,7 +6,8 @@ import { Link as LinkIcon, Waypoints, Combine, - Fingerprint + Fingerprint, + Workflow } from "lucide-react"; export const rootNavItems: SidebarNavItem[] = [ @@ -28,6 +29,11 @@ export const orgNavItems: SidebarNavItem[] = [ href: "/{orgId}/settings/resources", icon: }, + { + title: "Clients", + href: "/{orgId}/settings/clients", + icon: + }, { title: "Access Control", href: "/{orgId}/settings/access", diff --git a/src/components/HorizontalTabs.tsx b/src/components/HorizontalTabs.tsx index 8517fd35..251e3bad 100644 --- a/src/components/HorizontalTabs.tsx +++ b/src/components/HorizontalTabs.tsx @@ -29,7 +29,8 @@ export function HorizontalTabs({ .replace("{orgId}", params.orgId as string) .replace("{resourceId}", params.resourceId as string) .replace("{niceId}", params.niceId as string) - .replace("{userId}", params.userId as string); + .replace("{userId}", params.userId as string) + .replace("{clientId}", params.clientId as string); } return (