diff --git a/apps/client-ts/src/app/(Dashboard)/api-keys/page.tsx b/apps/client-ts/src/app/(Dashboard)/api-keys/page.tsx index 3b6613cdd..def0960d4 100644 --- a/apps/client-ts/src/app/(Dashboard)/api-keys/page.tsx +++ b/apps/client-ts/src/app/(Dashboard)/api-keys/page.tsx @@ -47,7 +47,6 @@ interface TSApiKeys { id_api_key: string; name : string; token : string; - created : string; } export default function Page() { @@ -66,7 +65,6 @@ export default function Page() { id_api_key: key.id_api_key, name: key.name || "", token: key.api_key_hash, - created: new Date().toISOString() })) setTSApiKeys(temp_tsApiKeys) },[apiKeys]) diff --git a/apps/client-ts/src/app/(Dashboard)/configuration/page.tsx b/apps/client-ts/src/app/(Dashboard)/configuration/page.tsx index 005a1bef4..33066589c 100644 --- a/apps/client-ts/src/app/(Dashboard)/configuration/page.tsx +++ b/apps/client-ts/src/app/(Dashboard)/configuration/page.tsx @@ -30,6 +30,9 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/comp import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { HelpCircle } from "lucide-react"; import { Button } from "@/components/ui/button"; +import { LoadingSpinner } from "@/components/ui/loading-spinner"; +import { CatalogWidget } from "@/components/Configuration/Catalog/CatalogWidget"; +import { CopySnippet } from "@/components/Configuration/Catalog/CopySnippet"; export default function Page() { @@ -97,6 +100,9 @@ export default function Page() { Webhooks + + Manage Catalog Widget + Manage Connectors @@ -121,8 +127,10 @@ export default function Page() {
-
-

What are linked accounts ?

+
+

What are linked accounts ?

+

The linked-user object represents your end-user entity inside our system.

+

It is a mirror of the end-user that exist in your backend. It helps Panora have the same source of truth about your user’s information.

@@ -131,8 +139,8 @@ export default function Page() { - - You connected {linkedUsers ? linkedUsers.length : } linked accounts. + + You connected {linkedUsers ? linkedUsers.length : } linked accounts. @@ -162,8 +170,18 @@ export default function Page() {
-
-

What are field mappings ?

+
+

What are field mappings ?

+

+ By default, our unified models are predefined as you can see in the API reference.
+

+

Now with field mappings, you have the option to map your custom fields (that may exist in your end-customer's tools) to our unified model !

+

+ It is done in 2 steps. First you must define your custom field so it is recognized by Panora. Lastly, you must map this field to your remote field that exist in a 3rd party. +

+

+
That way, Panora can retrieve the newly created custom field directly within the unified model. +

@@ -172,8 +190,8 @@ export default function Page() { - - You built {mappings ? mappings.length : } fields mappings. + + You built {mappings ? mappings.length : } fields mappings. Learn more about custom field mappings in our docs ! @@ -191,8 +209,8 @@ export default function Page() { Your Webhooks - - You enabled {webhooks ? webhooks.length : } webhooks. + + You enabled {webhooks ? webhooks.length : } webhooks. Read more about webhooks from our documentation @@ -207,6 +225,24 @@ export default function Page() { + + +
+ + + + Customize Your Embedded Widget + + Select connectors you would like to have in the UI widget catalog. By default, they are all displayed. + + + + + + + +
+
diff --git a/apps/client-ts/src/components/ApiKeys/columns.tsx b/apps/client-ts/src/components/ApiKeys/columns.tsx index cb399e906..00183c51d 100644 --- a/apps/client-ts/src/components/ApiKeys/columns.tsx +++ b/apps/client-ts/src/components/ApiKeys/columns.tsx @@ -82,16 +82,6 @@ export function useColumns() { ); }, }, - { - accessorKey: "created", - header: ({ column }) => ( - - ), - cell: ({ row }) =>
{row.getValue("created")}
, - filterFn: (row, id, value) => { - return value.includes(row.getValue(id)) - }, - }, { id: "actions", cell: ({ row }) => , diff --git a/apps/client-ts/src/components/ApiKeys/schema.ts b/apps/client-ts/src/components/ApiKeys/schema.ts index b7fcd2442..dfb2acf32 100644 --- a/apps/client-ts/src/components/ApiKeys/schema.ts +++ b/apps/client-ts/src/components/ApiKeys/schema.ts @@ -4,7 +4,6 @@ export const apiKeySchema = z.object({ id_api_key: z.string(), name: z.string(), token: z.string(), - created: z.string(), }) export type ApiKey = z.infer \ No newline at end of file diff --git a/apps/client-ts/src/components/Configuration/Catalog/CatalogWidget.tsx b/apps/client-ts/src/components/Configuration/Catalog/CatalogWidget.tsx new file mode 100644 index 000000000..6d17b6062 --- /dev/null +++ b/apps/client-ts/src/components/Configuration/Catalog/CatalogWidget.tsx @@ -0,0 +1,130 @@ +import { ComponentProps, useState } from "react" +import { cn } from "@/lib/utils" +import { Badge } from "@/components/ui/badge" +import { ScrollArea } from "@/components/ui/scroll-area" +import { AuthStrategy, categoriesVerticals, Provider, providersArray } from "@panora/shared" +import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" +import { Button } from "@/components/ui/button" +import { ListFilter } from "lucide-react" +import { Card } from "@/components/ui/card" +import { Switch } from "@/components/ui/switch" +import { Label } from "@/components/ui/label" + +export const verticals = categoriesVerticals as string[]; + +export function CatalogWidget() { + const [vertical, setVertical] = useState("All") + const filteredConnectors = vertical === "All" + ? providersArray() + : providersArray(vertical); + + const handleCheckboxChange = (vertical: string) => { + setVertical(vertical); + }; + return ( + <> +
+ +
+ + + + + + Filter by + + handleCheckboxChange("All")} + > + All + + + {verticals.map((v) => ( + handleCheckboxChange(v)} + > + {v} + + ))} + + +
+ + + +
+ + +
+ {filteredConnectors.map((item) => ( + +
+
+
+ +
{`${item.name.substring(0, 1).toUpperCase()}${item.name.substring(1)}`}
+
+
+ {item.description!.substring(0, 300)} +
+
+ {item.vertical && + + {item.vertical} + + } + {item.authStrategy && + + {item.authStrategy} + + } +
+
+
+ disableWebhook(row.original.id_webhook_endpoint, !row.getValue('active')) } + /> +
+
+
+ + ))} +
+
+ + ) +} + +function getBadgeVariantFromLabel( + label?: string +): ComponentProps["variant"] { + if (label === AuthStrategy.oauth2) { + return "secondary" + } + return "default" +} diff --git a/apps/client-ts/src/components/Configuration/Catalog/CopySnippet.tsx b/apps/client-ts/src/components/Configuration/Catalog/CopySnippet.tsx new file mode 100644 index 000000000..084b9d49a --- /dev/null +++ b/apps/client-ts/src/components/Configuration/Catalog/CopySnippet.tsx @@ -0,0 +1,199 @@ +'use client' + +import { Button } from "@/components/ui/button"; +import { Card } from "@/components/ui/card"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + } from "@/components/ui/dialog" +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; +import { Copy } from "lucide-react"; +import { useState } from "react"; + +export const CopySnippet = () => { + const [open,setOpen] = useState(false); + const [copiedLeft, setCopiedLeft] = useState(false); + const [copiedRight, setCopiedRight] = useState(false); + + const handleClick = () => { + setOpen(true); + } + + const handleCopyLeft = () => { + navigator.clipboard.writeText( + ` + projectId={'c9a1b1f8-466d-442d-a95e-11cdd00baf49'} + returnUrl={'https://acme.inc'} + linkedUserId={'b860d6c1-28f9-485c-86cd-fb09e60f10a2'} + ` + ); + setCopiedLeft(true); + setTimeout(() => { + setCopiedLeft(false); + }, 2000); + }; + + const handleCopyRight = () => { + navigator.clipboard.writeText( + ` + name={'hubspot'} + vertical={'crm'} + projectId={'c9a1b1f8-466d-442d-a95e-11cdd00baf49'} + returnUrl={'https://acme.inc'} + linkedUserId={'b860d6c1-28f9-485c-86cd-fb09e60f10a2'} + ` + ); + setCopiedRight(true); + setTimeout(() => { + setCopiedRight(false); + }, 2000); + }; + + return ( + <> + + + + + Import UI catalog components + + You can either import the whole catalog or import a specific connector! More info in our docs. + + + +
+
+
Import the whole catalog
+
+ +
+ + + + + + +

Copy

+
+
+
+
+ + + + {``} + + + + projectId{`={'c9a1b1f8-466d-442d-a95e-11cdd00baf49'}`} + + + returnUrl{`={'https://acme.inc'}`} + + + linkedUserId{`={'b860d6c1-28f9-485c-86cd-fb09e60f10a2'}`} + + + + {``} + + + +
+
+
+
+
Import Specific Connector
+
+ +
+ + + + + + +

Copy

+
+
+
+
+ + + + {``} + + + + name{`={'hubspot'}`} + + + vertical{`={'crm'}`} + + + projectId{`={'c9a1b1f8-466d-442d-a95e-11cdd00baf49'}`} + + + returnUrl{`={'https://acme.inc'}`} + + + linkedUserId{`={'b860d6c1-28f9-485c-86cd-fb09e60f10a2'}`} + + + + {``} + + + +
+
+
+
+
+
+
+ + + + ) +} \ No newline at end of file diff --git a/apps/client-ts/src/components/Configuration/Connector/CustomConnectorPage.tsx b/apps/client-ts/src/components/Configuration/Connector/CustomConnectorPage.tsx index 74f81e840..5f3628174 100644 --- a/apps/client-ts/src/components/Configuration/Connector/CustomConnectorPage.tsx +++ b/apps/client-ts/src/components/Configuration/Connector/CustomConnectorPage.tsx @@ -12,7 +12,7 @@ export default function CustomConnectorPage() { setSearchQuery(query) } - const filteredConnectors = vertical === "" + const filteredConnectors = vertical === "All" ? providersArray() : providersArray(vertical); diff --git a/apps/client-ts/src/components/Configuration/Connector/VerticalSelector.tsx b/apps/client-ts/src/components/Configuration/Connector/VerticalSelector.tsx index 9745d639a..b709fbd59 100644 --- a/apps/client-ts/src/components/Configuration/Connector/VerticalSelector.tsx +++ b/apps/client-ts/src/components/Configuration/Connector/VerticalSelector.tsx @@ -10,7 +10,7 @@ import { CommandGroup, CommandInput, CommandItem, -} from "@/components/ui/command" +} from "@/components/ui/command" import { Popover, PopoverContent, @@ -18,6 +18,7 @@ import { } from "@/components/ui/popover" import useVerticalStore from "@/state/verticalStore" import { categoriesVerticals } from '@panora/shared'; +import { Separator } from "@/components/ui/separator" export const verticals = categoriesVerticals as string[]; @@ -45,6 +46,26 @@ export function VerticalSelector({ onSelectVertical }: { onSelectVertical: (vert No verticals found. + { + setVertical("All") + setSelectedVertical("All") + setOpen(false) + onSelectVertical("All") + }} + > + All + + + {verticals.map((vertical) => ( void; fieldToMa - {isLoading ? "Loading..." : error ? "Error fetching properties" : sourceCustomFieldsData.map(field => ( + {isLoading ?

Loading...

: error ?

Error fetching properties

: sourceCustomFieldsData.map(field => ( {field.name} ))}
diff --git a/apps/client-ts/src/components/Configuration/LinkedUsers/AddLinkedAccount.tsx b/apps/client-ts/src/components/Configuration/LinkedUsers/AddLinkedAccount.tsx index e2772bb28..a5c388f82 100644 --- a/apps/client-ts/src/components/Configuration/LinkedUsers/AddLinkedAccount.tsx +++ b/apps/client-ts/src/components/Configuration/LinkedUsers/AddLinkedAccount.tsx @@ -94,8 +94,6 @@ const AddLinkedAccount = () => { }) form.reset() } - - return ( diff --git a/apps/client-ts/src/components/Configuration/Webhooks/columns.tsx b/apps/client-ts/src/components/Configuration/Webhooks/columns.tsx index 8ea70fe30..3f12bae8a 100644 --- a/apps/client-ts/src/components/Configuration/Webhooks/columns.tsx +++ b/apps/client-ts/src/components/Configuration/Webhooks/columns.tsx @@ -58,7 +58,7 @@ export function useColumns(webhooks: Webhook[] | undefined, setWebhooks: React.D {row.getValue("url")} , @@ -120,7 +120,7 @@ export function useColumns(webhooks: Webhook[] | undefined, setWebhooks: React.D {row.getValue("endpoint_description")} , diff --git a/apps/client-ts/src/components/Connection/columns.tsx b/apps/client-ts/src/components/Connection/columns.tsx index ffc3be2ac..b331c636d 100644 --- a/apps/client-ts/src/components/Connection/columns.tsx +++ b/apps/client-ts/src/components/Connection/columns.tsx @@ -8,43 +8,7 @@ import React from "react" import { ClipboardIcon } from '@radix-ui/react-icons' import { toast } from "sonner" import { getLogoURL } from "@panora/shared" - - -function truncateMiddle(str: string, maxLength: number) { - if (str.length <= maxLength) { - return str; - } - - const start = str.substring(0, maxLength / 2); - const end = str.substring(str.length - maxLength / 2); - return `${start}...${end}`; -} - -function insertDots(originalString: string): string { - if(!originalString) return ""; - // if (originalString.length <= 50) { - // return originalString; - // } - return originalString.substring(0, 7) + '...'; -} - -function formatISODate(ISOString: string): string { - const date = new Date(ISOString); - const options: Intl.DateTimeFormatOptions = { - weekday: 'long', // "Monday" - year: 'numeric', // "2024" - month: 'long', // "April" - day: 'numeric', // "27" - hour: '2-digit', // "02" - minute: '2-digit', // "58" - second: '2-digit', // "59" - timeZoneName: 'short' // "GMT" - }; - - // Create a formatter (using US English locale as an example) - const formatter = new Intl.DateTimeFormat('en-US', options); - return formatter.format(date); -} +import { formatISODate, truncateMiddle } from "@/lib/utils" const connectionTokenComponent = ({row}:{row:any}) => { const handleCopy = async () => { diff --git a/apps/client-ts/src/components/Events/columns.tsx b/apps/client-ts/src/components/Events/columns.tsx index 1cf6f8072..2c494ad5e 100644 --- a/apps/client-ts/src/components/Events/columns.tsx +++ b/apps/client-ts/src/components/Events/columns.tsx @@ -5,6 +5,7 @@ import { Badge } from "@/components/ui/badge" import { DataTableColumnHeader } from "../shared/data-table-column-header" import { Event } from "./schema" import { getLogoURL } from "@panora/shared" +import { formatISODate } from "@/lib/utils" export const columns: ColumnDef[] = [ { @@ -13,13 +14,9 @@ export const columns: ColumnDef[] = [ ), cell: ({ row }) =>{ - //const label = labels2.find((label) => label.value === row.original.method) - return (
- {row.getValue("method") ? {row.getValue("method")} - : _null_ - } + {row.getValue("method")}
) }, @@ -32,13 +29,9 @@ export const columns: ColumnDef[] = [ ), cell: ({ row }) => { - //const label = labels.find((label) => label.value === row.original.url) - return (
- {row.getValue("url") ? {row.getValue("url")} - : _null_ - } + {row.getValue("url")}
) }, @@ -49,19 +42,9 @@ export const columns: ColumnDef[] = [ ), cell: ({ row }) => { - /*const status = statuses.find( - (status) => status.value === row.getValue("status") - ) - - if (!status) { - return null - }*/ - return (
- {row.getValue("status") ? {row.getValue("status")} - : _null_ - } + {row.getValue("status")}
) }, @@ -75,19 +58,9 @@ export const columns: ColumnDef[] = [ ), cell: ({ row }) => { - /*const direction = priorities.find( - (direction) => direction.value === row.getValue("direction") - ) - - if (!direction) { - return null - }*/ - return (
- {row.getValue("direction") ? {row.getValue("direction")} - : _null_ - } + {row.getValue("direction")}
) }, @@ -101,25 +74,15 @@ export const columns: ColumnDef[] = [ ), cell: ({ row }) => { - /*const status = statuses.find( - (status) => status.value === row.getValue("integration") - ) - - if (!status) { - return null - }*/ const provider = (row.getValue("integration") as string).toLowerCase(); return (
- {row.getValue("integration") ? {provider} - : _null_ - }
) }, @@ -133,13 +96,9 @@ export const columns: ColumnDef[] = [ ), cell: ({ row }) => { - //const label = labels.find((label) => label.value === row.original.date) - return (
- {row.getValue("date") ? {row.getValue("date")} - : _null_ - } + {formatISODate(row.getValue("date"))}
) }, diff --git a/apps/client-ts/src/components/ui/loading-spinner.tsx b/apps/client-ts/src/components/ui/loading-spinner.tsx new file mode 100644 index 000000000..db4a2b4ac --- /dev/null +++ b/apps/client-ts/src/components/ui/loading-spinner.tsx @@ -0,0 +1,19 @@ +import { cn } from "@/lib/utils" + +export const LoadingSpinner = ({className} : {className?: string}) => { + return ( + + + ) +} \ No newline at end of file diff --git a/apps/client-ts/src/lib/utils.ts b/apps/client-ts/src/lib/utils.ts index fd581b930..596760aa2 100644 --- a/apps/client-ts/src/lib/utils.ts +++ b/apps/client-ts/src/lib/utils.ts @@ -8,3 +8,40 @@ export function cn(...inputs: ClassValue[]) { export function toDomain(email: string): string { return email.split("@")[1]; } + +export function formatISODate(ISOString: string): string { + const date = new Date(ISOString); + const options: Intl.DateTimeFormatOptions = { + weekday: 'long', // "Monday" + year: 'numeric', // "2024" + month: 'long', // "April" + day: 'numeric', // "27" + hour: '2-digit', // "02" + minute: '2-digit', // "58" + second: '2-digit', // "59" + timeZoneName: 'short' // "GMT" + }; + + // Create a formatter (using US English locale as an example) + const formatter = new Intl.DateTimeFormat('en-US', options); + return formatter.format(date); +} + + +export function truncateMiddle(str: string, maxLength: number) { + if (str.length <= maxLength) { + return str; + } + + const start = str.substring(0, maxLength / 2); + const end = str.substring(str.length - maxLength / 2); + return `${start}...${end}`; +} + +export function insertDots(originalString: string): string { + if(!originalString) return ""; + // if (originalString.length <= 50) { + // return originalString; + // } + return originalString.substring(0, 7) + '...'; +} \ No newline at end of file diff --git a/apps/client-ts/src/state/verticalStore.ts b/apps/client-ts/src/state/verticalStore.ts index 399e5d3ad..9c56d874a 100644 --- a/apps/client-ts/src/state/verticalStore.ts +++ b/apps/client-ts/src/state/verticalStore.ts @@ -6,7 +6,7 @@ interface VerticalState { } const useVerticalStore = create()((set) => ({ - vertical: "", + vertical: "All", setVertical: (name) => set({ vertical: name }), }));