diff --git a/packages/admin/components/admin-sidebar.tsx b/packages/admin/components/admin-sidebar.tsx index b2c3bf8b3..c4b622025 100644 --- a/packages/admin/components/admin-sidebar.tsx +++ b/packages/admin/components/admin-sidebar.tsx @@ -71,7 +71,7 @@ export function AdminSidebar() { "flex items-center gap-3 rounded-md px-2 py-2 text-sm text-gray-700 hover:bg-gray-100", "focus-visible:bg-gray-100 focus-visible:outline-none", pathname === `${basePath}${item.href}` && - "bg-gray-100 font-medium", + "bg-gray-100 font-medium", item.isDisabled && "pointer-events-none opacity-50", )} > diff --git a/packages/admin/components/carrier-connections-table.tsx b/packages/admin/components/carrier-connections-table.tsx index e794b159d..b3d965210 100644 --- a/packages/admin/components/carrier-connections-table.tsx +++ b/packages/admin/components/carrier-connections-table.tsx @@ -102,8 +102,16 @@ export function CarrierConnectionsTable({ height={40} />
-
- {connection.display_name || connection.carrier_name} +
+ {connection.carrier_id} +
{connection.id} @@ -111,7 +119,7 @@ export function CarrierConnectionsTable({ variant="ghost" size="icon" className="h-3 w-3 p-0 hover:bg-transparent" - onClick={() => onCopy(connection.id, "Carrier ID has been copied to your clipboard")} + onClick={() => onCopy(connection.id, "Connection ID has been copied to your clipboard")} > diff --git a/packages/admin/modules/carrier-connections/index.tsx b/packages/admin/modules/carrier-connections/index.tsx index f953b2f81..e8c798dbe 100644 --- a/packages/admin/modules/carrier-connections/index.tsx +++ b/packages/admin/modules/carrier-connections/index.tsx @@ -4,9 +4,9 @@ import { CarrierConnectionsTable, Connection } from "@karrio/admin/components/ca import { GetRateSheets_rate_sheets_edges_node as RateSheet } from "@karrio/types/graphql/admin/types"; import { DeleteConfirmationDialog } from "@karrio/insiders/components/delete-confirmation-dialog"; import { CarrierConnectionDialog } from "@karrio/insiders/components/carrier-connection-dialog"; +import { Card, CardContent, CardHeader, CardTitle } from "@karrio/insiders/components/ui/card"; import { RateSheetDialog } from "@karrio/insiders/components/rate-sheet-dialog"; import { RateSheetsTable } from "@karrio/admin/components/rate-sheets-table"; -import { Card, CardContent } from "@karrio/insiders/components/ui/card"; import { Button } from "@karrio/insiders/components/ui/button"; import { useToast } from "@karrio/insiders/hooks/use-toast"; import { trpc } from "@karrio/trpc/client"; @@ -71,6 +71,22 @@ export default function CarrierConnections() { }); }; + const handleCopy = async (text: string, description: string) => { + try { + await navigator.clipboard.writeText(text); + toast({ + title: "Copied to clipboard", + description, + }); + } catch (error) { + toast({ + title: "Failed to copy to clipboard", + description: "Please copy the text manually", + variant: "destructive", + }); + } + }; + return (
@@ -81,9 +97,14 @@ export default function CarrierConnections() {
- + + System Carrier Connections +

+ Manage system-wide carrier connections that are available to all organizations. +

+
+ setIsEditOpen(true)} connections={connections?.edges?.map(edge => ({ ...edge.node, @@ -120,18 +141,22 @@ export default function CarrierConnections() { }, }); }} - onCopy={(text, description) => { - navigator.clipboard.writeText(text); - toast({ - title: "Copied to clipboard", - description, - }); - }} + onCopy={handleCopy} />
- + + + Rate Sheets +

+ Manage carrier rate sheets and service configurations. +

+
+ + + +
( - null, - ); + const [selectedRateSheet, setSelectedRateSheet] = useState(null); const { data: rateSheets, isLoading } = trpc.admin.rate_sheets.list.useQuery({ filter: {}, @@ -216,43 +239,48 @@ function RateSheets() { }); }; + const handleCopy = async (text: string, description: string) => { + try { + await navigator.clipboard.writeText(text); + toast({ + title: "Copied to clipboard", + description, + }); + } catch (error) { + toast({ + title: "Failed to copy to clipboard", + description: "Please copy the text manually", + variant: "destructive", + }); + } + }; + return (
- - -
-

Rate Sheets

- -
+
+ +
- { - const rateSheetWithMetadata = { - ...sheet, - metadata: sheet.metadata || {}, - }; - setSelectedRateSheet(rateSheetWithMetadata); - setIsEditOpen(true); - }} - onDelete={(sheet) => { - const rateSheetWithMetadata = { - ...sheet, - metadata: sheet.metadata || {}, - }; - setSelectedRateSheet(rateSheetWithMetadata); - setIsDeleteOpen(true); - }} - onCopy={(text, description) => { - navigator.clipboard.writeText(text); - toast({ - title: "Copied to clipboard", - description, - }); - }} - /> -
-
+ { + const rateSheetWithMetadata = { + ...sheet, + metadata: sheet.metadata || {}, + }; + setSelectedRateSheet(rateSheetWithMetadata); + setIsEditOpen(true); + }} + onDelete={(sheet) => { + const rateSheetWithMetadata = { + ...sheet, + metadata: sheet.metadata || {}, + }; + setSelectedRateSheet(rateSheetWithMetadata); + setIsDeleteOpen(true); + }} + onCopy={handleCopy} + /> (); + const [editSection, setEditSection] = useState(null); + + const { mutate: updateConfigs } = trpc.admin.configs.update.useMutation({ + onSuccess: () => { + toast({ title: "Settings saved successfully" }); + utils.admin.configs.list.invalidate(); + setEditSection(null); + }, + onError: (error) => { + toast({ + title: "Failed to save settings", + description: error.message, + variant: "destructive", + }); + }, + }); + + const handleUpdate = (data: Partial) => { + updateConfigs({ data }); + }; + + const currentConfig = configs ? { + ...defaultConfig, + ...Object.fromEntries( + Object.entries(configs).map(([key, value]) => [key, value === null ? defaultConfig[key as keyof ConfigData] : value]) + ) + } as ConfigData : defaultConfig; -export default function Page() { return ( - <> -
-

- Administration +
+
+

+ Platform Overview

- - - Staff - - +
+ {/* Platform Config */} + + + Platform Details +

+ Overview of your platform configuration and API endpoints. +

+
+ +
+
+
+ +

{metadata?.APP_NAME}

+
+
+ +

{metadata?.APP_WEBSITE}

+
+
+ +

{metadata?.HOST}

+
+
+ +

{metadata?.GRAPHQL}

+
+
+ +

{metadata?.OPENAPI}

+
+
+
+
+
+ + {/* Administration */} + + + Administration +

+ Configure user access and platform behavior settings. +

+
+ +
+
+
+
+ +

Allow user signup

+
+ +
+
+
+ +

User signup requires admin approval

+
+ +
+
+
+
+ +
+
+
+ + {/* Email Config */} + + + Email Configuration +

+ Configure SMTP settings for sending system emails and notifications. +

+
+ +
+
+
+
+ +

{currentConfig.EMAIL_HOST || 'Not configured'}

+
+
+ +

{currentConfig.EMAIL_PORT || 'Not configured'}

+
+
+ +

{currentConfig.EMAIL_HOST_USER || 'Not configured'}

+
+
+ +

{currentConfig.EMAIL_FROM_ADDRESS || 'Not configured'}

+
+
+
+
+
+ +
+
+
+ + {/* Data Retention */} + + + Data Retention +

+ Set retention periods for different types of data before automatic cleanup. +

+
+ +
+
+
+
+ +

{currentConfig.ORDER_DATA_RETENTION || 90} days

+
+
+ +

{currentConfig.SHIPMENT_DATA_RETENTION || 90} days

+
+
+ +

{currentConfig.API_LOGS_DATA_RETENTION || 30} days

+
+
+
+
+
+ +
+
+
+ + {/* API Keys */} + + + Address Validation & Autocomplete +

+ Configure third-party services for address validation and autocomplete functionality. +

+
+ +
+
+
+ +

+ {currentConfig.GOOGLE_CLOUD_API_KEY ? '••••••••' : 'Not configured'} +

+
+
+ +

+ {currentConfig.CANADAPOST_ADDRESS_COMPLETE_API_KEY ? '••••••••' : 'Not configured'} +

+
+
+
+
+ +
+
+
+
+ + setEditSection(null)} + configs={currentConfig} + onUpdate={handleUpdate} + /> +
+ ); +} + +function EditDialog({ + section, + onClose, + configs, + onUpdate +}: { + section: EditSection; + onClose: () => void; + configs: ConfigData; + onUpdate: (data: Partial) => void; +}) { + const [formData, setFormData] = useState(configs); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + const data: Partial = {}; + + switch (section) { + case 'administration': + data.ALLOW_SIGNUP = formData.ALLOW_SIGNUP; + data.ALLOW_ADMIN_APPROVED_SIGNUP = formData.ALLOW_ADMIN_APPROVED_SIGNUP; + break; + case 'email': + data.EMAIL_USE_TLS = formData.EMAIL_USE_TLS; + data.EMAIL_HOST_USER = formData.EMAIL_HOST_USER; + data.EMAIL_HOST_PASSWORD = formData.EMAIL_HOST_PASSWORD; + data.EMAIL_HOST = formData.EMAIL_HOST; + data.EMAIL_PORT = formData.EMAIL_PORT; + data.EMAIL_FROM_ADDRESS = formData.EMAIL_FROM_ADDRESS; + break; + case 'data_retention': + data.ORDER_DATA_RETENTION = formData.ORDER_DATA_RETENTION; + data.TRACKER_DATA_RETENTION = formData.TRACKER_DATA_RETENTION; + data.SHIPMENT_DATA_RETENTION = formData.SHIPMENT_DATA_RETENTION; + data.API_LOGS_DATA_RETENTION = formData.API_LOGS_DATA_RETENTION; + break; + case 'api_keys': + data.GOOGLE_CLOUD_API_KEY = formData.GOOGLE_CLOUD_API_KEY; + data.CANADAPOST_ADDRESS_COMPLETE_API_KEY = formData.CANADAPOST_ADDRESS_COMPLETE_API_KEY; + break; + } + + onUpdate(data); + }; + + const handleChange = (key: keyof ConfigData, value: any) => { + setFormData(prev => ({ ...prev, [key]: value })); + }; + + const renderContent = () => { + switch (section) { + case 'administration': + return (
-
-
MEMBER
-
ROLE
-
- LAST LOGIN +
+
+ +

Allow user signup

+
+ handleChange('ALLOW_SIGNUP', checked)} + /> +
+
+
+ +

User signup requires admin approval

+ handleChange('ALLOW_ADMIN_APPROVED_SIGNUP', checked)} + /> +
+
+ ); + + case 'email': + return ( +
+
+ + handleChange("EMAIL_HOST", e.target.value)} + /> +
+
+ + handleChange("EMAIL_PORT", Number(e.target.value))} + /> +
+
+ + handleChange("EMAIL_HOST_USER", e.target.value)} + /> +
+
+ + handleChange("EMAIL_HOST_PASSWORD", e.target.value)} + /> +
+
+ + handleChange("EMAIL_FROM_ADDRESS", e.target.value)} + />
-
-
admin@example.com
-
Super, Staff
-
Thu, Mar 14, 2024 11:45 PM
+
+ handleChange("EMAIL_USE_TLS", checked)} + /> +
- - - + ); + + case 'data_retention': + return ( +
+
+ + handleChange("ORDER_DATA_RETENTION", Number(e.target.value))} + /> +
+
+ + handleChange("SHIPMENT_DATA_RETENTION", Number(e.target.value))} + /> +
+
+ + handleChange("API_LOGS_DATA_RETENTION", Number(e.target.value))} + /> +
+
+ ); + + case 'api_keys': + return ( +
+
+ + handleChange("GOOGLE_CLOUD_API_KEY", e.target.value)} + /> +
+
+ + handleChange("CANADAPOST_ADDRESS_COMPLETE_API_KEY", e.target.value)} + /> +
+
+ ); + + default: + return null; + } + }; + + const titles = { + administration: 'Edit Administration Settings', + email: 'Edit Email Settings', + data_retention: 'Edit Data Retention Settings', + api_keys: 'Edit API Keys', + }; + + if (!section) return null; + + return ( + onClose()}> + + + {titles[section]} + + Update your platform settings. + + +
+ {renderContent()} + + + +
+
+
); } diff --git a/packages/trpc/server/context.ts b/packages/trpc/server/context.ts index bdcccde35..d8574422f 100644 --- a/packages/trpc/server/context.ts +++ b/packages/trpc/server/context.ts @@ -20,17 +20,6 @@ export const createContext = async () => { } as any, }) - console.log({ - basePath: url$`${(metadata?.HOST as string) || KARRIO_API}`, - headers: { - ...(session?.orgId ? { "x-org-id": session.orgId } : {}), - ...(session?.testMode ? { "x-test-mode": session.testMode } : {}), - ...(session?.accessToken - ? { Authorization: `Bearer ${session.accessToken}` } - : {}), - } as any, - }) - return { karrio, session, diff --git a/packages/types/graphql/admin/queries.ts b/packages/types/graphql/admin/queries.ts index b63f2c1b9..f5f85f94a 100644 --- a/packages/types/graphql/admin/queries.ts +++ b/packages/types/graphql/admin/queries.ts @@ -12,6 +12,7 @@ export const GET_SYSTEM_CONNECTIONS = gql` node { id carrier_name + carrier_id display_name test_mode active @@ -38,6 +39,7 @@ export const GET_SYSTEM_CONNECTION = gql` system_carrier_connection(id: $id) { id carrier_name + carrier_id display_name test_mode active @@ -60,6 +62,7 @@ export const CREATE_SYSTEM_CONNECTION = gql` connection { id carrier_name + carrier_id display_name test_mode active @@ -83,6 +86,7 @@ export const UPDATE_SYSTEM_CONNECTION = gql` connection { id carrier_name + carrier_id display_name test_mode active diff --git a/packages/types/graphql/admin/types.ts b/packages/types/graphql/admin/types.ts index e0989ccd8..4cbba1c71 100644 --- a/packages/types/graphql/admin/types.ts +++ b/packages/types/graphql/admin/types.ts @@ -10,6 +10,7 @@ export interface GetSystemConnections_system_carrier_connections_edges_node { id: string; carrier_name: string; + carrier_id: string; display_name: string; test_mode: boolean; active: boolean; @@ -56,6 +57,7 @@ export interface GetSystemConnectionsVariables { export interface GetSystemConnection_system_carrier_connection { id: string; carrier_name: string; + carrier_id: string; display_name: string; test_mode: boolean; active: boolean; @@ -90,6 +92,7 @@ export interface CreateSystemConnection_create_system_carrier_connection_errors export interface CreateSystemConnection_create_system_carrier_connection_connection { id: string; carrier_name: string; + carrier_id: string; display_name: string; test_mode: boolean; active: boolean; @@ -129,6 +132,7 @@ export interface UpdateSystemConnection_update_system_carrier_connection_errors export interface UpdateSystemConnection_update_system_carrier_connection_connection { id: string; carrier_name: string; + carrier_id: string; display_name: string; test_mode: boolean; active: boolean;