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 def0960d4..dc0480787 100644 --- a/apps/client-ts/src/app/(Dashboard)/api-keys/page.tsx +++ b/apps/client-ts/src/app/(Dashboard)/api-keys/page.tsx @@ -1,6 +1,5 @@ 'use client' -import { PlusCircledIcon } from "@radix-ui/react-icons"; import { Button } from "@/components/ui/button" import { DataTable } from "@/components/shared/data-table"; import { @@ -26,7 +25,6 @@ import useProjectStore from "@/state/projectStore"; import useCreateApiKey from "@/hooks/create/useCreateApiKey"; import useProfileStore from "@/state/profileStore"; import { Suspense, useEffect, useState } from "react"; -import { cn } from "@/lib/utils"; import { usePostHog } from 'posthog-js/react' import config from "@/lib/config"; import * as z from "zod" @@ -36,13 +34,15 @@ import { DataTableLoading } from "@/components/shared/data-table-loading"; import {CustomHeading} from "@/components/shared/custom-heading"; import { useColumns } from "@/components/ApiKeys/columns"; import { PlusCircle } from "lucide-react"; +import { toast } from "sonner"; +import { Badge } from "@/components/ui/badge"; +import { useQueryClient } from "@tanstack/react-query"; const formSchema = z.object({ apiKeyIdentifier: z.string().min(2, { message: "apiKeyIdentifier must be at least 2 characters.", }) }) - interface TSApiKeys { id_api_key: string; name : string; @@ -53,11 +53,12 @@ export default function Page() { const [open,setOpen] = useState(false) const [tsApiKeys,setTSApiKeys] = useState([]) + const queryClient = useQueryClient(); + const {idProject} = useProjectStore(); const {profile} = useProfileStore(); - + const { createApiKeyPromise } = useCreateApiKey(); const { data: apiKeys, isLoading, error } = useApiKeys(); - const { mutate } = useCreateApiKey(); const columns = useColumns(); useEffect(() => { @@ -94,11 +95,33 @@ export default function Page() { const onSubmit = (values: z.infer) => { - mutate({ - userId: profile!.id_user, - projectId: idProject, - keyName: values.apiKeyIdentifier + toast.promise( + createApiKeyPromise({ + userId: profile!.id_user, + projectId: idProject, + keyName: values.apiKeyIdentifier + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['api-keys'], (oldQueryData = []) => { + return [...oldQueryData, data]; + }); + return ( +
+ +
+ Api key + {`${data.name}`} + has been created +
+
+ ) + ; + }, + error: 'Error', }); + posthog?.capture('api_key_created', { id_project: idProject, mode: config.DISTRIBUTION diff --git a/apps/client-ts/src/app/(Dashboard)/b2c/profile/page.tsx b/apps/client-ts/src/app/(Dashboard)/b2c/profile/page.tsx index e902452a0..6fddc0616 100644 --- a/apps/client-ts/src/app/(Dashboard)/b2c/profile/page.tsx +++ b/apps/client-ts/src/app/(Dashboard)/b2c/profile/page.tsx @@ -16,6 +16,7 @@ import useProfileStore from "@/state/profileStore"; import useProjectStore from "@/state/projectStore" import { useQueryClient } from '@tanstack/react-query'; import { useState } from "react"; +import { toast } from "sonner"; const Profile = () => { @@ -30,6 +31,12 @@ const Profile = () => { try { await navigator.clipboard.writeText(email) setCopied(true); + toast.success("Email copied", { + action: { + label: "Close", + onClick: () => console.log("Close"), + }, + }) setTimeout(() => setCopied(false), 2000); // Reset copied state after 2 seconds } catch (err) { console.error('Failed to copy: ', err); diff --git a/apps/client-ts/src/app/(Dashboard)/configuration/page.tsx b/apps/client-ts/src/app/(Dashboard)/configuration/page.tsx index 33066589c..cdcb55516 100644 --- a/apps/client-ts/src/app/(Dashboard)/configuration/page.tsx +++ b/apps/client-ts/src/app/(Dashboard)/configuration/page.tsx @@ -18,7 +18,6 @@ import FieldMappingsTable from "@/components/Configuration/FieldMappings/FieldMa import AddLinkedAccount from "@/components/Configuration/LinkedUsers/AddLinkedAccount"; import useLinkedUsers from "@/hooks/get/useLinkedUsers"; import useFieldMappings from "@/hooks/get/useFieldMappings"; -import { Skeleton } from "@/components/ui/skeleton"; import { useState } from "react"; import AddWebhook from "@/components/Configuration/Webhooks/AddWebhook"; import { WebhooksPage } from "@/components/Configuration/Webhooks/WebhooksPage"; @@ -33,6 +32,7 @@ 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"; +import {Button as Button2} from "@/components/ui/button2" export default function Page() { @@ -191,8 +191,10 @@ 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 ! + @@ -210,8 +212,10 @@ export default function Page() { Your Webhooks - You enabled {webhooks ? webhooks.length : } webhooks. - Read more about webhooks from our documentation + You enabled {webhooks ? webhooks.length : } webhooks. + + Read more about webhooks from our documentation + diff --git a/apps/client-ts/src/components/ApiKeys/columns.tsx b/apps/client-ts/src/components/ApiKeys/columns.tsx index 00183c51d..067bd7943 100644 --- a/apps/client-ts/src/components/ApiKeys/columns.tsx +++ b/apps/client-ts/src/components/ApiKeys/columns.tsx @@ -9,6 +9,8 @@ import { PasswordInput } from "../ui/password-input" import { useState } from "react" import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip" import { Button } from "../ui/button" +import { toast } from "sonner" +import { Card } from "antd" export function useColumns() { const [copiedState, setCopiedState] = useState<{ [key: string]: boolean }>({}); @@ -19,6 +21,12 @@ export function useColumns() { ...prevState, [token]: true, })); + toast.success("Api key copied", { + action: { + label: "Close", + onClick: () => console.log("Close"), + }, + }) setTimeout(() => { setCopiedState((prevState) => ({ ...prevState, diff --git a/apps/client-ts/src/components/Auth/CustomLoginComponent/CreateUserForm.tsx b/apps/client-ts/src/components/Auth/CustomLoginComponent/CreateUserForm.tsx index c8705d92a..2b96b4427 100644 --- a/apps/client-ts/src/components/Auth/CustomLoginComponent/CreateUserForm.tsx +++ b/apps/client-ts/src/components/Auth/CustomLoginComponent/CreateUserForm.tsx @@ -10,7 +10,6 @@ import { } from "@/components/ui/card" import { Input } from "@/components/ui/input" import { Button } from "@/components/ui/button" -import { Label } from "@/components/ui/label" import * as z from "zod" import { Form, @@ -24,6 +23,9 @@ import { zodResolver } from "@hookform/resolvers/zod" import { useForm } from "react-hook-form" import { PasswordInput } from '@/components/ui/password-input' import useCreateUser from '@/hooks/create/useCreateUser' +import { toast } from 'sonner' +import { Badge } from '@/components/ui/badge' +import { useQueryClient } from '@tanstack/react-query' const formSchema = z.object({ first_name: z.string().min(2,{ @@ -42,8 +44,8 @@ const formSchema = z.object({ }) const CreateUserForm = () => { - - const {mutate : createUserMutate} = useCreateUser(); + const {createUserPromise} = useCreateUser(); + const queryClient = useQueryClient(); const form = useForm>({ resolver: zodResolver(formSchema), @@ -56,109 +58,122 @@ const CreateUserForm = () => { }) const onSubmit = (values: z.infer) => { - // console.log(values) - createUserMutate({ - first_name:values.first_name, - last_name:values.last_name, - email:values.email, - strategy:'b2c', - password_hash:values.password - }, - { - onSuccess:() => { - form.reset(); - } - }); - - - } - + toast.promise( + createUserPromise({ + first_name:values.first_name, + last_name:values.last_name, + email:values.email, + strategy:'b2c', + password_hash:values.password + }), + { + loading: 'Loading...', + success: (data: any) => { + form.reset(); + queryClient.setQueryData(['users'], (oldQueryData = []) => { + return [...oldQueryData, data]; + }); + return ( +
+ +
+ User + {`${data.email}`} + has been created +
+
+ ) + ; + }, + error: 'Error', + }); + }; - return ( - <> -
- - - - Sign Up - - Create your account. - - - -
-
+ return ( + <> + + + + + Sign Up + + Create your account. + + + +
+
+
+ ( + + First Name + + + + + + )} + /> +
+
+ ( + + Last Name + + + + + + )} + /> +
+
- ( - - First Name - - - - - - )} - /> + ( + + Email + + + + + + )} + />
- ( - - Last Name - - - - - - )} - /> + ( + + Password + + + + + + )} + />
-
- ( - - Email - - - - - - )} - /> -
-
- ( - - Password - - - - - - )} - /> -
-
- - - - - - - - - ) + + + + + + + + + ) } export default CreateUserForm \ No newline at end of file diff --git a/apps/client-ts/src/components/Auth/CustomLoginComponent/LoginUserForm.tsx b/apps/client-ts/src/components/Auth/CustomLoginComponent/LoginUserForm.tsx index 076257337..9093f6f10 100644 --- a/apps/client-ts/src/components/Auth/CustomLoginComponent/LoginUserForm.tsx +++ b/apps/client-ts/src/components/Auth/CustomLoginComponent/LoginUserForm.tsx @@ -24,6 +24,11 @@ import { zodResolver } from "@hookform/resolvers/zod" import { useForm } from "react-hook-form" import useCreateLogin from '@/hooks/create/useCreateLogin' import { useRouter } from "next/navigation"; +import { Badge } from '@/components/ui/badge' +import { toast } from 'sonner' +import useProfileStore from '@/state/profileStore'; +import Cookies from 'js-cookie'; +import { useQueryClient } from '@tanstack/react-query' const formSchema = z.object({ email: z.string().email({ @@ -37,8 +42,9 @@ const formSchema = z.object({ const LoginUserForm = () => { const router = useRouter() - - const {mutate : loginMutate} = useCreateLogin() + const queryClient = useQueryClient(); + const {loginPromise} = useCreateLogin() + const {setProfile}= useProfileStore(); const form = useForm>({ resolver: zodResolver(formSchema), @@ -48,18 +54,36 @@ const LoginUserForm = () => { }, }) -const onSubmit = (values: z.infer) => { - loginMutate({ - email:values.email, - password_hash:values.password - }, - { - onSuccess: () => router.replace("/connections") - }) - -} - - + const onSubmit = (values: z.infer) => { + toast.promise( + loginPromise({ + email:values.email, + password_hash:values.password + }), + { + loading: 'Loading...', + success: (data: any) => { + setProfile(data.user); + Cookies.set('access_token',data.access_token,{expires:1}); + router.replace("/connections"); + queryClient.setQueryData(['users'], (oldQueryData = []) => { + return [...oldQueryData, data]; + }); + return ( +
+ +
+ User + {`${data.email}`} + has been logged in +
+
+ ) + ; + }, + error: 'Error', + }); + }; return ( <> @@ -107,7 +131,7 @@ const onSubmit = (values: z.infer) => { - + diff --git a/apps/client-ts/src/components/Configuration/Catalog/CatalogWidget.tsx b/apps/client-ts/src/components/Configuration/Catalog/CatalogWidget.tsx index 54d9fe564..edad14f74 100644 --- a/apps/client-ts/src/components/Configuration/Catalog/CatalogWidget.tsx +++ b/apps/client-ts/src/components/Configuration/Catalog/CatalogWidget.tsx @@ -11,6 +11,7 @@ import { Switch } from "@/components/ui/switch" import useProjectStore from "@/state/projectStore" import useUpdateProjectConnectors from "@/hooks/update/useUpdateProjectConnectors" import useProjectConnectors from "@/hooks/get/useProjectConnectors" +import { toast } from "sonner" export const verticals = categoriesVerticals as string[]; @@ -23,10 +24,10 @@ export function CatalogWidget() { : providersArray(vertical); const {idProject} = useProjectStore(); - + const {data} = useProjectConnectors(idProject); - const {mutate} = useUpdateProjectConnectors(); + const {updateProjectConnectorPromise} = useUpdateProjectConnectors(); useEffect(() => { if (data) { @@ -52,10 +53,25 @@ export function CatalogWidget() { ...prev, [providerKey]: newStatus })); - - mutate({ + toast.promise( + updateProjectConnectorPromise({ column: providerKey, status: newStatus + }), + { + loading: 'Loading...', + success: (data: any) => { + return ( +
+ +
+ Changes have been saved +
+
+ ) + ; + }, + error: 'Error', }); }; diff --git a/apps/client-ts/src/components/Configuration/Catalog/CopySnippet.tsx b/apps/client-ts/src/components/Configuration/Catalog/CopySnippet.tsx index 084b9d49a..879cf906f 100644 --- a/apps/client-ts/src/components/Configuration/Catalog/CopySnippet.tsx +++ b/apps/client-ts/src/components/Configuration/Catalog/CopySnippet.tsx @@ -12,6 +12,7 @@ import { import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { Copy } from "lucide-react"; import { useState } from "react"; +import { toast } from "sonner"; export const CopySnippet = () => { const [open,setOpen] = useState(false); @@ -30,6 +31,12 @@ export const CopySnippet = () => { linkedUserId={'b860d6c1-28f9-485c-86cd-fb09e60f10a2'} ` ); + toast.success("Code snippet copied", { + action: { + label: "Close", + onClick: () => console.log("Close"), + }, + }) setCopiedLeft(true); setTimeout(() => { setCopiedLeft(false); @@ -46,6 +53,12 @@ export const CopySnippet = () => { linkedUserId={'b860d6c1-28f9-485c-86cd-fb09e60f10a2'} ` ); + toast.success("Code snippet copied", { + action: { + label: "Close", + onClick: () => console.log("Close"), + }, + }) setCopiedRight(true); setTimeout(() => { setCopiedRight(false); diff --git a/apps/client-ts/src/components/Configuration/Connector/ConnectorDisplay.tsx b/apps/client-ts/src/components/Configuration/Connector/ConnectorDisplay.tsx index 440497963..3f39d905c 100644 --- a/apps/client-ts/src/components/Configuration/Connector/ConnectorDisplay.tsx +++ b/apps/client-ts/src/components/Configuration/Connector/ConnectorDisplay.tsx @@ -18,6 +18,8 @@ import { DataTableFacetedFilter } from "@/components/shared/data-table-faceted-f import useCreateConnectionStrategy from "@/hooks/create/useCreateConnectionStrategy" import useUpdateConnectionStrategy from "@/hooks/update/useUpdateConnectionStrategy" import useConnectionStrategyAuthCredentials from "@/hooks/get/useConnectionStrategyAuthCredentials" +import { useQueryClient } from "@tanstack/react-query" +import { toast } from "sonner" interface ItemDisplayProps { item?: Provider @@ -49,10 +51,11 @@ export function ConnectorDisplay({ item }: ItemDisplayProps) { const [switchEnabled, setSwitchEnabled] = useState(false); const { idProject } = useProjectStore() const { data: connectionStrategies, isLoading: isConnectionStrategiesLoading, error: isConnectionStategiesError } = useConnectionStrategies() - const { mutate: createCS } = useCreateConnectionStrategy(); - const { mutate: updateCS } = useUpdateConnectionStrategy() + const { createCsPromise } = useCreateConnectionStrategy(); + const { updateCsPromise } = useUpdateConnectionStrategy() const { mutateAsync: fetchCredentials, data: fetchedData } = useConnectionStrategyAuthCredentials(); - + const queryClient = useQueryClient(); + const posthog = usePostHog() const mappingConnectionStrategies = connectionStrategies?.filter((cs) => extractVertical(cs.type).toLowerCase() == item?.vertical && extractProvider(cs.type).toLowerCase() == item?.name) @@ -72,6 +75,12 @@ export function ConnectorDisplay({ item }: ItemDisplayProps) { try { await navigator.clipboard.writeText(`${config.API_URL}/connections/oauth/callback`) setCopied(true); + toast.success("Redirect uri copied", { + action: { + label: "Close", + onClick: () => console.log("Close"), + }, + }) setTimeout(() => setCopied(false), 2000); // Reset copied state after 2 seconds } catch (err) { console.error('Failed to copy: ', err); @@ -97,22 +106,60 @@ export function ConnectorDisplay({ item }: ItemDisplayProps) { } if (performUpdate) { const dataToUpdate = mappingConnectionStrategies[0]; - updateCS({ - id_cs: dataToUpdate.id_connection_strategy, - updateToggle: false, - status: dataToUpdate.status, - attributes: ["client_id", "client_secret", "scope"], - values: [client_id, client_secret, scope] - }) + toast.promise( + updateCsPromise({ + id_cs: dataToUpdate.id_connection_strategy, + updateToggle: false, + status: dataToUpdate.status, + attributes: ["client_id", "client_secret", "scope"], + values: [client_id, client_secret, scope] + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['connection-strategies'], (oldQueryData = []) => { + return oldQueryData.map((CS) => CS.id_connection_strategy === data.id_connection_strategy ? data : CS) + }); + return ( +
+ +
+ Changes have been saved +
+
+ ) + ; + }, + error: 'Error', + }); posthog?.capture("Connection_strategy_OAuth2_updated", { id_project: idProject, mode: config.DISTRIBUTION }); } else { - createCS({ - type: providerToType(item?.name, item?.vertical!, AuthStrategy.oauth2), - attributes: ["client_id", "client_secret", "scope"], - values: [client_id, client_secret, scope] + toast.promise( + createCsPromise({ + type: providerToType(item?.name, item?.vertical!, AuthStrategy.oauth2), + attributes: ["client_id", "client_secret", "scope"], + values: [client_id, client_secret, scope] + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['connections-strategies'], (oldQueryData = []) => { + return [...oldQueryData, data]; + }); + return ( +
+ +
+ Changes have been saved +
+
+ ) + ; + }, + error: 'Error', }); posthog?.capture("Connection_strategy_OAuth2_created", { id_project: idProject, @@ -130,22 +177,60 @@ export function ConnectorDisplay({ item }: ItemDisplayProps) { } if (performUpdate) { const dataToUpdate = mappingConnectionStrategies[0]; - updateCS({ - id_cs: dataToUpdate.id_connection_strategy, - updateToggle: false, - status: dataToUpdate.status, - attributes: ["api_key"], - values: [api_key] - }) + toast.promise( + updateCsPromise({ + id_cs: dataToUpdate.id_connection_strategy, + updateToggle: false, + status: dataToUpdate.status, + attributes: ["api_key"], + values: [api_key] + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['connection-strategies'], (oldQueryData = []) => { + return oldQueryData.map((CS) => CS.id_connection_strategy === data.id_connection_strategy ? data : CS) + }); + return ( +
+ +
+ Changes have been saved +
+
+ ) + ; + }, + error: 'Error', + }); posthog?.capture("Connection_strategy_API_KEY_updated", { id_project: idProject, mode: config.DISTRIBUTION }); } else { - createCS({ - type: providerToType(item?.name, item?.vertical!, AuthStrategy.api_key), - attributes: ["api_key"], - values: [api_key] + toast.promise( + createCsPromise({ + type: providerToType(item?.name, item?.vertical!, AuthStrategy.api_key), + attributes: ["api_key"], + values: [api_key] + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['connections-strategies'], (oldQueryData = []) => { + return [...oldQueryData, data]; + }); + return ( +
+ +
+ Changes have been saved +
+
+ ) + ; + }, + error: 'Error', }); posthog?.capture("Connection_strategy_API_KEY_created", { id_project: idProject, @@ -168,22 +253,61 @@ export function ConnectorDisplay({ item }: ItemDisplayProps) { } if (performUpdate) { const dataToUpdate = mappingConnectionStrategies[0]; - updateCS({ - id_cs: dataToUpdate.id_connection_strategy, - updateToggle: false, - status: dataToUpdate.status, - attributes: ["username", "secret"], - values: [username, secret] - }) + toast.promise( + updateCsPromise({ + id_cs: dataToUpdate.id_connection_strategy, + updateToggle: false, + status: dataToUpdate.status, + attributes: ["username", "secret"], + values: [username, secret] + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['connection-strategies'], (oldQueryData = []) => { + return oldQueryData.map((CS) => CS.id_connection_strategy === data.id_connection_strategy ? data : CS) + }); + return ( +
+ +
+ Changes have been saved +
+
+ ) + ; + }, + error: 'Error', + }); posthog?.capture("Connection_strategy_BASIC_AUTH_updated", { id_project: idProject, mode: config.DISTRIBUTION }); + } else { - createCS({ - type: providerToType(item?.name, item?.vertical!, AuthStrategy.basic), - attributes: ["username", "secret"], - values: [username, secret] + toast.promise( + createCsPromise({ + type: providerToType(item?.name, item?.vertical!, AuthStrategy.basic), + attributes: ["username", "secret"], + values: [username, secret] + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['connections-strategies'], (oldQueryData = []) => { + return [...oldQueryData, data]; + }); + return ( +
+ +
+ Changes have been saved +
+
+ ) + ; + }, + error: 'Error', }); posthog?.capture("Connection_strategy_BASIC_AUTH_created", { id_project: idProject, @@ -228,10 +352,31 @@ export function ConnectorDisplay({ item }: ItemDisplayProps) { const handleSwitchChange = (enabled: boolean) => { if (mappingConnectionStrategies && mappingConnectionStrategies.length > 0) { const dataToUpdate = mappingConnectionStrategies[0]; - updateCS({ - id_cs: dataToUpdate.id_connection_strategy, - updateToggle: true + toast.promise( + updateCsPromise({ + id_cs: dataToUpdate.id_connection_strategy, + updateToggle: true + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['connection-strategies'], (oldQueryData = []) => { + return oldQueryData.map((CS) => CS.id_connection_strategy === data.id_connection_strategy ? data : CS) + }); + return ( +
+ +
+ Changes have been saved +
+
+ ) + ; + }, + error: 'Error', }); + + setSwitchEnabled(enabled); } }; diff --git a/apps/client-ts/src/components/Configuration/FieldMappings/FieldMappingModal.tsx b/apps/client-ts/src/components/Configuration/FieldMappings/FieldMappingModal.tsx index 169dc008e..62d2a0c8c 100644 --- a/apps/client-ts/src/components/Configuration/FieldMappings/FieldMappingModal.tsx +++ b/apps/client-ts/src/components/Configuration/FieldMappings/FieldMappingModal.tsx @@ -48,6 +48,9 @@ import { usePostHog } from 'posthog-js/react' import config from "@/lib/config" import { CRM_PROVIDERS } from "@panora/shared" import useDefineField from "@/hooks/create/useDefineField" +import { toast } from "sonner" +import { Badge } from "@/components/ui/badge" +import { useQueryClient } from "@tanstack/react-query" const defineFormSchema = z.object({ @@ -106,10 +109,11 @@ export function FModal({ onClose }: {onClose: () => void}) { const [ linkedUserId, sourceProvider ] = mapForm.watch(['linkedUserId', 'sourceProvider']); const {idProject} = useProjectStore(); + const queryClient = useQueryClient(); const { data: mappings } = useFieldMappings(); - const { mutate: mutateDefineField } = useDefineField(); - const { mutate: mutateMapField } = useMapField(); + const { defineMappingPromise } = useDefineField(); + const { mapMappingPromise } = useMapField(); const { data: linkedUsers } = useLinkedUsers(); // TODO: HANDLE VERTICAL AND PROVIDERS FOR CUSTOM MAPPINGS const { data: sourceCustomFields, error, isLoading } = useProviderProperties(linkedUserId,sourceProvider, "crm"); @@ -125,12 +129,32 @@ export function FModal({ onClose }: {onClose: () => void}) { function onDefineSubmit(values: z.infer) { - console.log(values) - mutateDefineField({ - object_type_owner: values.standardModel, - name: values.fieldName, - description: values.fieldDescription, - data_type: values.fieldType, + toast.promise( + defineMappingPromise({ + object_type_owner: values.standardModel, + name: values.fieldName, + description: values.fieldDescription, + data_type: values.fieldType, + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['mappings'], (oldQueryData = []) => { + return [...oldQueryData, data]; + }); + return ( +
+ +
+ Custom field + {`${ values.fieldName }`} + has been defined +
+
+ ) + ; + }, + error: 'Error', }); posthog?.capture("field_defined", { id_project: idProject, @@ -140,12 +164,32 @@ export function FModal({ onClose }: {onClose: () => void}) { } function onMapSubmit(values: z.infer) { - console.log(values) - mutateMapField({ - attributeId: values.attributeId.trim(), - source_custom_field_id: values.sourceCustomFieldId, - source_provider: values.sourceProvider, - linked_user_id: values.linkedUserId, + toast.promise( + mapMappingPromise({ + attributeId: values.attributeId.trim(), + source_custom_field_id: values.sourceCustomFieldId, + source_provider: values.sourceProvider, + linked_user_id: values.linkedUserId, + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['mappings'], (oldQueryData = []) => { + return [...oldQueryData, data]; + }); + return ( +
+ +
+ Custom field + {`${data.name}`} + has been mapped +
+
+ ) + ; + }, + error: 'Error', }); posthog?.capture("field_mapped", { id_project: idProject, diff --git a/apps/client-ts/src/components/Configuration/FieldMappings/defineForm.tsx b/apps/client-ts/src/components/Configuration/FieldMappings/defineForm.tsx index 48f32935e..89e0c2d66 100644 --- a/apps/client-ts/src/components/Configuration/FieldMappings/defineForm.tsx +++ b/apps/client-ts/src/components/Configuration/FieldMappings/defineForm.tsx @@ -3,7 +3,6 @@ import { Button } from "@/components/ui/button" import { - Card, CardContent, CardDescription, CardFooter, @@ -11,16 +10,9 @@ import { CardTitle, } from "@/components/ui/card" import { Input } from "@/components/ui/input" -import { - Tabs, - TabsContent, - TabsList, - TabsTrigger, -} from "@/components/ui/tabs" import { Form, FormControl, - FormDescription, FormField, FormItem, FormLabel, @@ -34,20 +26,18 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select" -import useMapField from "@/hooks/create/useMapField" -import { useEffect, useState } from "react" import useFieldMappings from "@/hooks/get/useFieldMappings" -import useProviderProperties from "@/hooks/get/useProviderProperties" import { standardObjects } from "@panora/shared"; import useProjectStore from "@/state/projectStore" -import useLinkedUsers from "@/hooks/get/useLinkedUsers" import { zodResolver } from "@hookform/resolvers/zod" import { useForm } from "react-hook-form" import * as z from "zod" import { usePostHog } from 'posthog-js/react' import config from "@/lib/config" -import { CRM_PROVIDERS } from "@panora/shared" import useDefineField from "@/hooks/create/useDefineField" +import { toast } from "sonner" +import { Badge } from "@/components/ui/badge" +import { useQueryClient } from "@tanstack/react-query" const defineFormSchema = z.object({ @@ -64,7 +54,7 @@ const defineFormSchema = z.object({ message: "fieldType must be at least 2 characters.", }), }) - + export function DefineForm({ onClose }: {onClose: () => void}) { @@ -81,19 +71,39 @@ export function DefineForm({ onClose }: {onClose: () => void}) { const {idProject} = useProjectStore(); const { data: mappings } = useFieldMappings(); - const { mutate: mutateDefineField } = useDefineField(); + const { defineMappingPromise } = useDefineField(); - - const posthog = usePostHog() + const queryClient = useQueryClient(); + const posthog = usePostHog() function onDefineSubmit(values: z.infer) { - console.log(values) - mutateDefineField({ - object_type_owner: values.standardModel, - name: values.fieldName, - description: values.fieldDescription, - data_type: values.fieldType, + toast.promise( + defineMappingPromise({ + object_type_owner: values.standardModel, + name: values.fieldName, + description: values.fieldDescription, + data_type: values.fieldType, + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['mappings'], (oldQueryData = []) => { + return [...oldQueryData, data]; + }); + return ( +
+ +
+ Custom field + {`${ values.fieldName }`} + has been defined +
+
+ ) + ; + }, + error: 'Error', }); posthog?.capture("field_defined", { id_project: idProject, diff --git a/apps/client-ts/src/components/Configuration/FieldMappings/mapForm.tsx b/apps/client-ts/src/components/Configuration/FieldMappings/mapForm.tsx index 70e884773..436ca01af 100644 --- a/apps/client-ts/src/components/Configuration/FieldMappings/mapForm.tsx +++ b/apps/client-ts/src/components/Configuration/FieldMappings/mapForm.tsx @@ -37,8 +37,12 @@ import { useForm } from "react-hook-form" import * as z from "zod" import { usePostHog } from 'posthog-js/react' import config from "@/lib/config" -import { CRM_PROVIDERS, providersArray } from "@panora/shared" +import { providersArray } from "@panora/shared" import { LoadingSpinner } from "@/components/ui/loading-spinner" +import { toast } from "sonner" +import { Badge } from "@/components/ui/badge" +import { useQueryClient } from "@tanstack/react-query" + const mapFormSchema = z.object({ attributeId: z.string().min(2, { @@ -71,9 +75,10 @@ export function MapForm({ onClose, fieldToMap }: {onClose: () => void; fieldToMa const [connectorVertical, setConnectorVertical] = useState(""); const {idProject} = useProjectStore(); + const queryClient = useQueryClient(); const { data: mappings } = useFieldMappings(); - const { mutate: mutateMapField } = useMapField(); + const { mapMappingPromise } = useMapField(); const { data: linkedUsers } = useLinkedUsers(); const { data: sourceCustomFields, error, isLoading } = useProviderProperties(linkedUserId, sourceProvider, connectorVertical); const connectors = providersArray(); @@ -91,11 +96,32 @@ export function MapForm({ onClose, fieldToMap }: {onClose: () => void; fieldToMa } function onMapSubmit(values: z.infer) { - mutateMapField({ - attributeId: values.attributeId.trim(), - source_custom_field_id: values.sourceCustomFieldId, - source_provider: values.sourceProvider, - linked_user_id: values.linkedUserId, + toast.promise( + mapMappingPromise({ + attributeId: values.attributeId.trim(), + source_custom_field_id: values.sourceCustomFieldId, + source_provider: values.sourceProvider, + linked_user_id: values.linkedUserId, + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['mappings'], (oldQueryData = []) => { + return [...oldQueryData, data]; + }); + return ( +
+ +
+ Custom field + {`${data.name}`} + has been mapped +
+
+ ) + ; + }, + error: 'Error', }); posthog?.capture("field_mapped", { id_project: idProject, diff --git a/apps/client-ts/src/components/Configuration/LinkedUsers/AddLinkedAccount.tsx b/apps/client-ts/src/components/Configuration/LinkedUsers/AddLinkedAccount.tsx index 9d80e4574..8780b6156 100644 --- a/apps/client-ts/src/components/Configuration/LinkedUsers/AddLinkedAccount.tsx +++ b/apps/client-ts/src/components/Configuration/LinkedUsers/AddLinkedAccount.tsx @@ -42,6 +42,10 @@ import { usePostHog } from 'posthog-js/react' import config from "@/lib/config" import { FileUploader } from "@/components/ui/file-uploader" import * as XLSX from 'xlsx'; +import { toast } from "sonner" +import { Badge } from "@/components/ui/badge" +import { useQueryClient } from "@tanstack/react-query" +import useCreateBatchLinkedUser from "@/hooks/create/useCreateBatchLinkedUser" interface LinkedUserModalObj { open: boolean; @@ -63,7 +67,10 @@ const AddLinkedAccount = () => { const [importing, setImporting] = useState(false); const [successImporting, setSuccessImporting] = useState(false); - const { mutate } = useCreateLinkedUser(); + const { createLinkedUserPromise } = useCreateLinkedUser(); + const { createBatchLinkedUserPromise } = useCreateBatchLinkedUser(); + + const queryClient = useQueryClient(); const handleOpenChange = (open: boolean) => { setShowNewLinkedUserDialog(prevState => ({ ...prevState, open })); @@ -82,11 +89,31 @@ const AddLinkedAccount = () => { }) function onSubmit(values: z.infer) { - console.log(values) - mutate({ - linked_user_origin_id: values.linkedUserIdentifier, - alias: "", //TODO - id_project: idProject + toast.promise( + createLinkedUserPromise({ + linked_user_origin_id: values.linkedUserIdentifier, + alias: "", //TODO + id_project: idProject + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['linked-users'], (oldQueryData = []) => { + return [...oldQueryData, data]; + }); + return ( +
+ +
+ Linked account + {`${data.linked_user_origin_id}`} + has been created +
+
+ ) + ; + }, + error: 'Error', }); setShowNewLinkedUserDialog({open: false}) posthog?.capture("linked_account_created", { @@ -108,18 +135,43 @@ const AddLinkedAccount = () => { const json = XLSX.utils.sheet_to_json(worksheet); if (json.length > 0) { setImporting(true); + const ids: string[] = []; // Initialize an empty array to hold the IDs json.forEach((row: any) => { - const linked_user_origin_id = row[Object.keys(row)[0]]; // Assumes first column is the IDs - if (linked_user_origin_id) { - mutate({ - linked_user_origin_id: linked_user_origin_id.toString(), - alias: '', - id_project: idProject + const linked_user_origin_id = row[Object.keys(row)[0]]; + if(linked_user_origin_id){ + ids.push(linked_user_origin_id); + } + }); + toast.promise( + createBatchLinkedUserPromise({ + linked_user_origin_ids: ids, + alias: "", //TODO + id_project: idProject + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['linked-users'], (oldQueryData = []) => { + return [...oldQueryData, data]; }); - } + setSuccessImporting(true); + setImporting(false); + return ( +
+ +
+ Linked accounts have been imported +
+
+ ) + ; + }, + error: 'Error', }); - setImporting(false); - setSuccessImporting(true); + posthog?.capture("batch_linked_account_created", { + id_project: idProject, + mode: config.DISTRIBUTION + }) } }; reader.readAsBinaryString(file); diff --git a/apps/client-ts/src/components/Configuration/Webhooks/AddWebhook.tsx b/apps/client-ts/src/components/Configuration/Webhooks/AddWebhook.tsx index 4b4cbc9ff..87b750a53 100644 --- a/apps/client-ts/src/components/Configuration/Webhooks/AddWebhook.tsx +++ b/apps/client-ts/src/components/Configuration/Webhooks/AddWebhook.tsx @@ -26,7 +26,6 @@ import { FormLabel, FormMessage, } from "@/components/ui/form" -import { cn } from "@/lib/utils" import { zodResolver } from "@hookform/resolvers/zod" import { useForm } from "react-hook-form" import * as z from "zod" @@ -34,6 +33,9 @@ import { usePostHog } from 'posthog-js/react' import config from "@/lib/config" import { DataTableFacetedFilterWebhook } from "../../shared/data-table-webhook-scopes" import useCreateWebhook from "@/hooks/create/useCreateWebhook" +import { toast } from "sonner" +import { Badge } from "@/components/ui/badge" +import { useQueryClient } from "@tanstack/react-query" const formSchema = z.object({ @@ -52,8 +54,9 @@ const AddWebhook = () => { const posthog = usePostHog() const {idProject} = useProjectStore(); + const queryClient = useQueryClient(); - const { mutate } = useCreateWebhook(); + const { createWebhookPromise } = useCreateWebhook(); const form = useForm>({ resolver: zodResolver(formSchema), @@ -73,11 +76,32 @@ const AddWebhook = () => { function onSubmit(values: z.infer) { const selectedScopes = values.scopes ? values.scopes.split(' ') : []; console.log({ ...values, scopes: selectedScopes }); - mutate({ - url: values.url, - description: values.description, - id_project: idProject, - scope: selectedScopes, + toast.promise( + createWebhookPromise({ + url: values.url, + description: values.description, + id_project: idProject, + scope: selectedScopes, + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['webhooks'], (oldQueryData = []) => { + return [...oldQueryData, data]; + }); + return ( +
+ +
+ Webhook + {`${data.url}`} + has been created +
+
+ ) + ; + }, + error: 'Error', }); handleOpenChange(false); posthog?.capture("webhook_created", { diff --git a/apps/client-ts/src/components/Configuration/Webhooks/columns.tsx b/apps/client-ts/src/components/Configuration/Webhooks/columns.tsx index 3f12bae8a..7be05a6b2 100644 --- a/apps/client-ts/src/components/Configuration/Webhooks/columns.tsx +++ b/apps/client-ts/src/components/Configuration/Webhooks/columns.tsx @@ -12,25 +12,39 @@ import { DataTableRowActions } from "@/components/shared/data-table-row-actions" import { Switch } from "@/components/ui/switch" import useUpdateWebhookStatus from "@/hooks/update/useUpdateWebhookStatus" import { Webhook } from "./WebhooksPage" - +import { useQueryClient } from "@tanstack/react-query" +import { toast } from "sonner" export function useColumns(webhooks: Webhook[] | undefined, setWebhooks: React.Dispatch>) { const [copiedState, setCopiedState] = useState<{ [key: string]: boolean }>({}); - const { mutate } = useUpdateWebhookStatus(); + const { updateWebhookPromise } = useUpdateWebhookStatus(); const disableWebhook = (webhook_id: string, status: boolean) => { - mutate({ - id: webhook_id, - active: status, - }, { - onSuccess: () => { - const index = webhooks!.findIndex(webhook => webhook.id_webhook_endpoint === webhook_id); - if (index !== -1) { - const updatedWebhooks = [...webhooks!]; - updatedWebhooks[index].active = status; - setWebhooks(updatedWebhooks); - } - } + toast.promise( + updateWebhookPromise({ + id: webhook_id, + active: status, + }), + { + loading: 'Loading...', + success: (data: any) => { + const index = webhooks!.findIndex(webhook => webhook.id_webhook_endpoint === webhook_id); + if (index !== -1) { + const updatedWebhooks = [...webhooks!]; + updatedWebhooks[index].active = status; + setWebhooks(updatedWebhooks); + } + return ( +
+ +
+ Changes have been saved +
+
+ ) + ; + }, + error: 'Error', }); } @@ -40,6 +54,12 @@ export function useColumns(webhooks: Webhook[] | undefined, setWebhooks: React.D ...prevState, [token]: true, })); + toast.success("Webhook copied", { + action: { + label: "Close", + onClick: () => console.log("Close"), + }, + }) setTimeout(() => { setCopiedState((prevState) => ({ ...prevState, diff --git a/apps/client-ts/src/components/Connection/AddConnectionButton.tsx b/apps/client-ts/src/components/Connection/AddConnectionButton.tsx index a9f2c3fec..c226f451b 100644 --- a/apps/client-ts/src/components/Connection/AddConnectionButton.tsx +++ b/apps/client-ts/src/components/Connection/AddConnectionButton.tsx @@ -43,6 +43,9 @@ import { useForm } from "react-hook-form" import * as z from "zod" import { usePostHog } from 'posthog-js/react' import config from "@/lib/config" +import { toast } from "sonner" +import { useQueryClient } from "@tanstack/react-query" +import useMagicLinkStore from "@/state/magicLinkStore" const formSchema = z.object({ linkedUserIdentifier: z.string().min(2, { @@ -72,10 +75,11 @@ const AddConnectionButton = ({ const posthog = usePostHog() - const { mutate, isError, error } = useCreateMagicLink(); - + const { createMagicLinkPromise } = useCreateMagicLink(); + const {setUniqueLink} = useMagicLinkStore(); const {nameOrg} = useOrganisationStore(); const {idProject} = useProjectStore(); + const queryClient = useQueryClient(); const form = useForm>({ resolver: zodResolver(formSchema), @@ -86,18 +90,33 @@ const AddConnectionButton = ({ }) function onSubmit(values: z.infer) { - console.log(values) - mutate({ - linked_user_origin_id: values.linkedUserIdentifier, - email: values.linkedUserMail, - alias: nameOrg, - id_project: idProject + toast.promise( + createMagicLinkPromise({ + linked_user_origin_id: values.linkedUserIdentifier, + email: values.linkedUserMail, + alias: nameOrg, + id_project: idProject + }), + { + loading: 'Loading...', + success: (data: any) => { + setUniqueLink(data.id_invite_link); + queryClient.setQueryData(['magic-links'], (oldQueryData = []) => { + return [...oldQueryData, data]; + }); + return ( +
+ +
+ Magic Link has been created +
+
+ ) + ; + }, + error: 'Error', }); - if(isError) { - console.log(error); - } - posthog?.capture("magic_link_created", { id_project: idProject, mode: config.DISTRIBUTION diff --git a/apps/client-ts/src/components/Connection/CopyLinkInput.tsx b/apps/client-ts/src/components/Connection/CopyLinkInput.tsx index 3bb7f773b..538861246 100644 --- a/apps/client-ts/src/components/Connection/CopyLinkInput.tsx +++ b/apps/client-ts/src/components/Connection/CopyLinkInput.tsx @@ -6,6 +6,7 @@ import useMagicLinkStore from '@/state/magicLinkStore'; import config from '@/lib/config'; import { useState } from 'react'; import { LoadingSpinner } from './LoadingSpinner'; +import { toast } from 'sonner'; const CopyLinkInput = () => { const [copied, setCopied] = useState(false); @@ -15,6 +16,12 @@ const CopyLinkInput = () => { const handleCopy = async () => { try { await navigator.clipboard.writeText(`${config.MAGIC_LINK_DOMAIN}/?uniqueLink=${uniqueLink}`); + toast.success("Magic link copied", { + action: { + label: "Close", + onClick: () => console.log("Close"), + }, + }) setCopied(true); setTimeout(() => setCopied(false), 2000); // Reset copied state after 2 seconds } catch (err) { diff --git a/apps/client-ts/src/components/Connection/columns.tsx b/apps/client-ts/src/components/Connection/columns.tsx index b331c636d..f8d951e4d 100644 --- a/apps/client-ts/src/components/Connection/columns.tsx +++ b/apps/client-ts/src/components/Connection/columns.tsx @@ -14,7 +14,12 @@ const connectionTokenComponent = ({row}:{row:any}) => { const handleCopy = async () => { try { await navigator.clipboard.writeText(row.getValue("connectionToken")); - toast("Connection Token copied to clipboard!!") + toast.success("Connection token copied", { + action: { + label: "Close", + onClick: () => console.log("Close"), + }, + }) } catch (err) { console.error('Failed to copy: ', err); } @@ -24,7 +29,6 @@ const connectionTokenComponent = ({row}:{row:any}) => {
{truncateMiddle(row.getValue("connectionToken"),6)} -
) diff --git a/apps/client-ts/src/components/shared/data-table-row-actions.tsx b/apps/client-ts/src/components/shared/data-table-row-actions.tsx index 07f035a17..d81354487 100644 --- a/apps/client-ts/src/components/shared/data-table-row-actions.tsx +++ b/apps/client-ts/src/components/shared/data-table-row-actions.tsx @@ -13,6 +13,9 @@ import { } from "@/components/ui/dropdown-menu" import useDeleteApiKey from "@/hooks/delete/useDeleteApiKey" import useDeleteWebhook from "@/hooks/delete/useDeleteWebhook" +import { toast } from "sonner" +import { Badge } from "@/components/ui/badge" +import { useQueryClient } from "@tanstack/react-query" interface DataTableRowActionsProps { row: Row; @@ -24,20 +27,63 @@ export function DataTableRowActions({ object }: DataTableRowActionsProps) { - const {mutate: removeApiKey} = useDeleteApiKey(); - const {mutate: removeWebhook} = useDeleteWebhook(); + const {deleteApiKeyPromise} = useDeleteApiKey(); + const {deleteWebhookPromise} = useDeleteWebhook(); + const queryClient = useQueryClient(); const handleDeletion = () => { switch(object) { case 'webhook': - removeWebhook({ - id_webhook: (row.original as any).id_webhook_endpoint - }) + toast.promise( + deleteWebhookPromise({ + id_webhook: (row.original as any).id_webhook_endpoint + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['webhooks'], (oldQueryData = []) => { + return oldQueryData.filter((wh) => wh.id_webhook_endpoint !== data.id_webhook_endpoint); + }); + return ( +
+ +
+ Webhook + {`${data.url}`} + has been deleted +
+
+ ) + ; + }, + error: 'Error', + }); break; case 'api-key': - removeApiKey({ - id_api_key: (row.original as any).id_api_key - }) + toast.promise( + deleteApiKeyPromise({ + id_api_key: (row.original as any).id_api_key + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['api-keys'], (oldQueryData = []) => { + return oldQueryData.filter((api_key) => api_key.id_api_key !== data.id_api_key); + }); + return ( +
+ +
+ Api key + {`${data.name}`} + has been deleted +
+
+ ) + ; + }, + error: 'Error', + }); break; default: break; diff --git a/apps/client-ts/src/components/shared/team-switcher.tsx b/apps/client-ts/src/components/shared/team-switcher.tsx index 01533a572..e1b3826e1 100644 --- a/apps/client-ts/src/components/shared/team-switcher.tsx +++ b/apps/client-ts/src/components/shared/team-switcher.tsx @@ -57,7 +57,9 @@ import { Skeleton } from "@/components/ui/skeleton"; import useProfileStore from "@/state/profileStore" import { projects as Project } from 'api'; import useRefreshAccessTokenMutation from "@/hooks/create/useRefreshAccessToken" - +import { toast } from "sonner" +import { useQueryClient } from "@tanstack/react-query" +import { Badge } from "../ui/badge" const projectFormSchema = z.object({ projectName: z.string().min(2, { @@ -92,7 +94,9 @@ export default function TeamSwitcher({ className ,projects}: TeamSwitcherProps) setShowNewDialog(prevState => ({ ...prevState, open })); }; - const { mutate: mutateProject } = useProjectMutation(); + const queryClient = useQueryClient(); + + const { createProjectsPromise } = useProjectMutation(); const projectForm = useForm>({ resolver: zodResolver(projectFormSchema), @@ -102,10 +106,30 @@ export default function TeamSwitcher({ className ,projects}: TeamSwitcherProps) }) function onProjectSubmit(values: z.infer) { - console.log(values) - mutateProject({ - name: values.projectName, - id_user: profile!.id_user, + toast.promise( + createProjectsPromise({ + name: values.projectName, + id_user: profile!.id_user, + }), + { + loading: 'Loading...', + success: (data: any) => { + queryClient.setQueryData(['projects'], (oldQueryData = []) => { + return [...oldQueryData, data]; + }); + return ( +
+ +
+ Project + {`${data.name}`} + has been created +
+
+ ) + ; + }, + error: 'Error', }); setShowNewDialog({open: false}) projectForm.reset(); diff --git a/apps/client-ts/src/components/ui/button2.tsx b/apps/client-ts/src/components/ui/button2.tsx new file mode 100644 index 000000000..b1f45e2fc --- /dev/null +++ b/apps/client-ts/src/components/ui/button2.tsx @@ -0,0 +1,108 @@ +import * as React from "react"; +import { Slot, Slottable } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import { cn } from "@/lib/utils"; + +const buttonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + expandIcon: + "group relative text-primary-foreground bg-primary hover:bg-primary/90", + ringHover: + "bg-primary text-primary-foreground transition-all duration-300 hover:bg-primary/90 hover:ring-2 hover:ring-primary/90 hover:ring-offset-2", + shine: + "text-primary-foreground animate-shine bg-gradient-to-r from-primary via-primary/75 to-primary bg-[length:400%_100%] ", + gooeyRight: + "text-primary-foreground relative bg-primary z-0 overflow-hidden transition-all duration-500 before:absolute before:inset-0 before:-z-10 before:translate-x-[150%] before:translate-y-[150%] before:scale-[2.5] before:rounded-[100%] before:bg-gradient-to-r from-zinc-400 before:transition-transform before:duration-1000 hover:before:translate-x-[0%] hover:before:translate-y-[0%] ", + gooeyLeft: + "text-primary-foreground relative bg-primary z-0 overflow-hidden transition-all duration-500 after:absolute after:inset-0 after:-z-10 after:translate-x-[-150%] after:translate-y-[150%] after:scale-[2.5] after:rounded-[100%] after:bg-gradient-to-l from-zinc-400 after:transition-transform after:duration-1000 hover:after:translate-x-[0%] hover:after:translate-y-[0%] ", + linkHover1: + "relative after:absolute after:bg-primary after:bottom-2 after:h-[1px] after:w-2/3 after:origin-bottom-left after:scale-x-100 hover:after:origin-bottom-right hover:after:scale-x-0 after:transition-transform after:ease-in-out after:duration-300", + linkHover2: + "relative after:absolute after:bg-primary after:bottom-2 after:h-[1px] after:w-2/3 after:origin-bottom-right after:scale-x-0 hover:after:origin-bottom-left hover:after:scale-x-100 after:transition-transform after:ease-in-out after:duration-300", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +); + +interface IconProps { + Icon: React.ElementType; + iconPlacement: "left" | "right"; +} + +interface IconRefProps { + Icon?: never; + iconPlacement?: undefined; +} + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +export type ButtonIconProps = IconProps | IconRefProps; + +const Button = React.forwardRef< + HTMLButtonElement, + ButtonProps & ButtonIconProps +>( + ( + { + className, + variant, + size, + asChild = false, + Icon, + iconPlacement, + ...props + }, + ref + ) => { + const Comp = asChild ? Slot : "button"; + return ( + + {Icon && iconPlacement === "left" && ( +
+ +
+ )} + {props.children} + {Icon && iconPlacement === "right" && ( +
+ +
+ )} +
+ ); + } +); +Button.displayName = "Button"; + +export { Button, buttonVariants }; + \ No newline at end of file diff --git a/apps/client-ts/src/hooks/create/useCreateApiKey.tsx b/apps/client-ts/src/hooks/create/useCreateApiKey.tsx index 2415f69b7..469926b6e 100644 --- a/apps/client-ts/src/hooks/create/useCreateApiKey.tsx +++ b/apps/client-ts/src/hooks/create/useCreateApiKey.tsx @@ -1,15 +1,15 @@ import config from '@/lib/config'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { toast } from "sonner" +import { useMutation } from '@tanstack/react-query'; import Cookies from 'js-cookie'; -interface IApiKeyDto { +export interface IApiKeyDto { projectId: string; userId: string; keyName?: string; } + +// Adjusted useCreateApiKey hook to include a promise-returning function const useCreateApiKey = () => { - const queryClient = useQueryClient(); const addApiKey = async (data: IApiKeyDto) => { const response = await fetch(`${config.API_URL}/auth/generate-apikey`, { method: 'POST', @@ -26,41 +26,26 @@ const useCreateApiKey = () => { return response.json(); }; - return useMutation({ - mutationFn: addApiKey, - onMutate: () => { - /*toast("Api key is being generated !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - }, - onError: (error) => { - /*toast("Api key generation failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - }, - onSuccess: (data) => { - queryClient.setQueryData(['api-keys'], (oldQueryData = []) => { - return [...oldQueryData, data]; - }); - toast("Api key generated !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSettled: () => { - }, - }); + + // Expose a promise-returning function alongside mutate + const createApiKeyPromise = (data: IApiKeyDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await addApiKey(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + + return { + mutate: useMutation({ + mutationFn: addApiKey, + }), + createApiKeyPromise, + }; }; export default useCreateApiKey; diff --git a/apps/client-ts/src/hooks/create/useCreateBatchLinkedUser.tsx b/apps/client-ts/src/hooks/create/useCreateBatchLinkedUser.tsx new file mode 100644 index 000000000..55a812e68 --- /dev/null +++ b/apps/client-ts/src/hooks/create/useCreateBatchLinkedUser.tsx @@ -0,0 +1,44 @@ +import config from '@/lib/config'; +import { useMutation } from '@tanstack/react-query'; + +interface ILinkedUserDto { + linked_user_origin_ids: string[]; + alias: string; + id_project: string; +} +const useCreateBatchLinkedUser = () => { + const add = async (linkedUserData: ILinkedUserDto) => { + const response = await fetch(`${config.API_URL}/linked-users/batch`, { + method: 'POST', + body: JSON.stringify(linkedUserData), + headers: { + 'Content-Type': 'application/json', + }, + }); + + if (!response.ok) { + throw new Error('Failed to batch add linked user'); + } + + return response.json(); + }; + const createBatchLinkedUserPromise = (data: ILinkedUserDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await add(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + return { + mutationFn: useMutation({ + mutationFn: add, + }), + createBatchLinkedUserPromise + }; +}; + +export default useCreateBatchLinkedUser; diff --git a/apps/client-ts/src/hooks/create/useCreateConnectionStrategy.tsx b/apps/client-ts/src/hooks/create/useCreateConnectionStrategy.tsx index 1f7d0325e..72f7ab198 100644 --- a/apps/client-ts/src/hooks/create/useCreateConnectionStrategy.tsx +++ b/apps/client-ts/src/hooks/create/useCreateConnectionStrategy.tsx @@ -1,6 +1,5 @@ import config from '@/lib/config'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { toast } from "sonner" +import { useMutation } from '@tanstack/react-query'; import Cookies from 'js-cookie'; interface IConnectionStrategyDto { @@ -9,17 +8,7 @@ interface IConnectionStrategyDto { values: string[], } -interface IFetchConnectionStrategyDto { - id_cs: string, - type: string, - attributes: string[], - values: string[], -} - - -const useCreateConnectionStrategy = () => { - const queryClient = useQueryClient(); - +const useCreateConnectionStrategy = () => { const add = async (connectionStrategyData: IConnectionStrategyDto) => { const response = await fetch(`${config.API_URL}/connections-strategies/create`, { method: 'POST', @@ -30,49 +19,32 @@ const useCreateConnectionStrategy = () => { }, }); - // console.log(response.status) - if (!response.ok) { throw new Error('Failed to add Connection Strategy'); } return response.json(); }; - return useMutation({ - mutationFn: add, - onMutate: () => { - /*toast("Connection Strategy is being created !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - }, - onError: (error) => { - /*toast("The creation of Connection Strategy has failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - }, - onSuccess: (data) => { - queryClient.setQueryData(['connection-strategies'], (oldQueryData = []) => { - return [...oldQueryData, data]; - }); - toast("Changes saved !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSettled: () => { - }, - }); + + // Expose a promise-returning function alongside mutate + const createCsPromise = (data: IConnectionStrategyDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await add(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + + return { + mutationFn: useMutation({ + mutationFn: add, + }), + createCsPromise + }; }; export default useCreateConnectionStrategy; diff --git a/apps/client-ts/src/hooks/create/useCreateLinkedUser.tsx b/apps/client-ts/src/hooks/create/useCreateLinkedUser.tsx index 13ae2acb0..4305ad914 100644 --- a/apps/client-ts/src/hooks/create/useCreateLinkedUser.tsx +++ b/apps/client-ts/src/hooks/create/useCreateLinkedUser.tsx @@ -1,14 +1,11 @@ import config from '@/lib/config'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { toast } from "sonner" +import { useMutation } from '@tanstack/react-query'; interface ILinkedUserDto { linked_user_origin_id: string; alias: string; id_project: string; } -const useCreateLinkedUser = () => { - const queryClient = useQueryClient(); - +const useCreateLinkedUser = () => { const add = async (linkedUserData: ILinkedUserDto) => { const response = await fetch(`${config.API_URL}/linked-users`, { method: 'POST', @@ -24,41 +21,23 @@ const useCreateLinkedUser = () => { return response.json(); }; - return useMutation({ - mutationFn: add, - onMutate: () => { - /*toast("Linked user is being created !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - }, - onError: (error) => { - /*toast("The creation of linked user has failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - }, - onSuccess: (data) => { - queryClient.setQueryData(['linked-users'], (oldQueryData = []) => { - return [...oldQueryData, data]; - }); - toast("New linked user created !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSettled: () => { - }, - }); + const createLinkedUserPromise = (data: ILinkedUserDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await add(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + return { + mutationFn: useMutation({ + mutationFn: add, + }), + createLinkedUserPromise + }; }; export default useCreateLinkedUser; diff --git a/apps/client-ts/src/hooks/create/useCreateLogin.tsx b/apps/client-ts/src/hooks/create/useCreateLogin.tsx index 5446eaca9..c3d37a488 100644 --- a/apps/client-ts/src/hooks/create/useCreateLogin.tsx +++ b/apps/client-ts/src/hooks/create/useCreateLogin.tsx @@ -1,8 +1,5 @@ import config from '@/lib/config'; import { useMutation } from '@tanstack/react-query'; -import { toast } from "sonner" -import useProfileStore from '@/state/profileStore'; -import Cookies from 'js-cookie'; type IUserDto = { id_user: string; @@ -23,9 +20,6 @@ interface ILoginOutputDto { } const useCreateLogin = () => { - - const {setProfile} = useProfileStore() - const add = async (userData: ILoginInputDto) => { // Fetch the token const response = await fetch(`${config.API_URL}/auth/login`, { @@ -42,40 +36,23 @@ const useCreateLogin = () => { return response.json(); }; - return useMutation({ - mutationFn: add, - onMutate: () => { - // toast("Logging the user !", { - // description: "", - // action: { - // label: "Close", - // onClick: () => console.log("Close"), - // }, - // }) - }, - onError: (error) => { - /*toast.error("User generation failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - }, - onSuccess: (data : ILoginOutputDto) => { - setProfile(data.user); - Cookies.set('access_token',data.access_token,{expires:1}); - toast.success("Logged In !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSettled: () => { - }, - }); + const loginPromise = (data: ILoginInputDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await add(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + return { + mutationFn: useMutation({ + mutationFn: add, + }), + loginPromise + } }; export default useCreateLogin; diff --git a/apps/client-ts/src/hooks/create/useCreateMagicLink.tsx b/apps/client-ts/src/hooks/create/useCreateMagicLink.tsx index 4537253f7..c3115fd81 100644 --- a/apps/client-ts/src/hooks/create/useCreateMagicLink.tsx +++ b/apps/client-ts/src/hooks/create/useCreateMagicLink.tsx @@ -1,7 +1,5 @@ -import useMagicLinkStore from '@/state/magicLinkStore'; import config from '@/lib/config'; import { useMutation } from '@tanstack/react-query'; -import { toast } from "sonner" import Cookies from 'js-cookie'; interface ILinkDto { @@ -28,41 +26,23 @@ const useCreateMagicLink = () => { return response.json(); }; - const {setUniqueLink} = useMagicLinkStore(); - - return useMutation({ - mutationFn: add, - onMutate: () => { - /*toast("Magic link is being generated !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - }, - onError: (error) => { - /*toast("Magic link generation failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - }, - onSuccess: (data) => { - setUniqueLink(data.id_invite_link) - toast("Magic link generated!", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSettled: () => { - }, - }); + const createMagicLinkPromise = (data: ILinkDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await add(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + return { + mutationFn: useMutation({ + mutationFn: add, + }), + createMagicLinkPromise + }; }; export default useCreateMagicLink; diff --git a/apps/client-ts/src/hooks/create/useCreateProfile.tsx b/apps/client-ts/src/hooks/create/useCreateProfile.tsx index 2f575e351..cc1f19bc7 100644 --- a/apps/client-ts/src/hooks/create/useCreateProfile.tsx +++ b/apps/client-ts/src/hooks/create/useCreateProfile.tsx @@ -1,6 +1,5 @@ import config from '@/lib/config'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { toast } from "sonner" +import { useMutation } from '@tanstack/react-query'; interface IProfileDto { first_name: string; @@ -11,9 +10,7 @@ interface IProfileDto { id_organization?: string } -const useCreateProfile = () => { - const queryClient = useQueryClient(); - +const useCreateProfile = () => { const add = async (data: IProfileDto) => { const response = await fetch(`${config.API_URL}/auth/users/create`, { method: 'POST', @@ -29,45 +26,32 @@ const useCreateProfile = () => { return response.json(); }; - return useMutation({ - mutationFn: add, - onMutate: () => { - /*toast("Profile is being created !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - }, - onError: (error) => { - /*toast("Profile creation has failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - }, - onSuccess: (data) => { - queryClient.invalidateQueries({ + const createProfilePromise = (data: IProfileDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await add(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + return { + mutationFn: useMutation({ + mutationFn: add, + }), + createProfilePromise + /* + queryClient.invalidateQueries({ queryKey: ['profiles'], refetchType: 'active', }) queryClient.setQueryData(['profiles'], (oldQueryData = []) => { return [...oldQueryData, data]; }); - toast("Profile created !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSettled: () => { - }, - }); + */ + }; }; export default useCreateProfile; diff --git a/apps/client-ts/src/hooks/create/useCreateProject.tsx b/apps/client-ts/src/hooks/create/useCreateProject.tsx index 930181e4d..98e7e0468 100644 --- a/apps/client-ts/src/hooks/create/useCreateProject.tsx +++ b/apps/client-ts/src/hooks/create/useCreateProject.tsx @@ -1,6 +1,5 @@ import config from '@/lib/config'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { toast } from "sonner" +import { useMutation } from '@tanstack/react-query'; import Cookies from 'js-cookie'; interface IProDto { @@ -8,9 +7,7 @@ interface IProDto { id_user: string; } -const useCreateProject = () => { - const queryClient = useQueryClient(); - +const useCreateProject = () => { const add = async (data: IProDto) => { const response = await fetch(`${config.API_URL}/projects`, { method: 'POST', @@ -27,41 +24,23 @@ const useCreateProject = () => { return response.json(); }; - return useMutation({ - mutationFn: add, - onMutate: () => { - /*toast("Project is being created !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - }, - onError: (error) => { - /*toast("Project creation has failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - }, - onSuccess: (data) => { - queryClient.setQueryData(['projects'], (oldQueryData = []) => { - return [...oldQueryData, data]; - }); - toast("Project created !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSettled: () => { - }, - }); + const createProjectsPromise = (data: IProDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await add(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + return { + mutationFn: useMutation({ + mutationFn: add, + }), + createProjectsPromise + } }; export default useCreateProject; diff --git a/apps/client-ts/src/hooks/create/useCreateUser.tsx b/apps/client-ts/src/hooks/create/useCreateUser.tsx index e518856e5..a4ac462b2 100644 --- a/apps/client-ts/src/hooks/create/useCreateUser.tsx +++ b/apps/client-ts/src/hooks/create/useCreateUser.tsx @@ -1,6 +1,5 @@ import config from '@/lib/config'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { toast } from "sonner" +import { useMutation } from '@tanstack/react-query'; interface IUserDto { first_name: string, @@ -11,7 +10,6 @@ interface IUserDto { id_organisation?: string, } const useCreateUser = () => { - const add = async (userData: IUserDto) => { // Fetch the token const response = await fetch(`${config.API_URL}/auth/register`, { @@ -28,39 +26,24 @@ const useCreateUser = () => { return response.json(); }; - return useMutation({ - mutationFn: add, - onMutate: () => { - /*toast("User is being created !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - }, - onError: (error) => { - /*toast("User generation failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - }, - onSuccess: (data) => { - - toast("User created !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSettled: () => { - }, - }); + const createUserPromise = (data: IUserDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await add(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + return { + mutationFn: useMutation({ + mutationFn: add, + }), + createUserPromise + } + }; export default useCreateUser; diff --git a/apps/client-ts/src/hooks/create/useCreateWebhook.tsx b/apps/client-ts/src/hooks/create/useCreateWebhook.tsx index a0e7461d3..8bb644674 100644 --- a/apps/client-ts/src/hooks/create/useCreateWebhook.tsx +++ b/apps/client-ts/src/hooks/create/useCreateWebhook.tsx @@ -1,6 +1,5 @@ import config from '@/lib/config'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { toast } from "sonner" +import { useMutation } from '@tanstack/react-query'; import Cookies from 'js-cookie'; interface IWebhookDto { @@ -10,7 +9,6 @@ interface IWebhookDto { scope: string[]; } const useCreateWebhook = () => { - const queryClient = useQueryClient(); const add = async (data: IWebhookDto) => { const response = await fetch(`${config.API_URL}/webhook`, { method: 'POST', @@ -27,41 +25,25 @@ const useCreateWebhook = () => { return response.json(); }; - return useMutation({ - mutationFn: add, - onMutate: () => { - /*toast("Webhook endpoint has been created !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - }, - onError: (error) => { - /*toast("Webhook endpoint creation has failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - }, - onSuccess: (data) => { - queryClient.setQueryData(['webhooks'], (oldQueryData = []) => { - return [...oldQueryData, data]; - }); - toast("Webhook created ! ", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSettled: () => { - }, - }); + + const createWebhookPromise = (data: IWebhookDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await add(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + + return { + mutationFn: useMutation({ + mutationFn: add, + }), + createWebhookPromise + } }; export default useCreateWebhook; diff --git a/apps/client-ts/src/hooks/create/useDefineField.tsx b/apps/client-ts/src/hooks/create/useDefineField.tsx index 886fa5481..985a39c57 100644 --- a/apps/client-ts/src/hooks/create/useDefineField.tsx +++ b/apps/client-ts/src/hooks/create/useDefineField.tsx @@ -1,6 +1,5 @@ import config from '@/lib/config'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { toast } from "sonner" +import { useMutation } from '@tanstack/react-query'; import Cookies from 'js-cookie'; interface IDefineTargetFieldDto{ @@ -11,8 +10,6 @@ interface IDefineTargetFieldDto{ } const useDefineField = () => { - const queryClient = useQueryClient() - const define = async (data: IDefineTargetFieldDto) => { const response = await fetch(`${config.API_URL}/field-mappings/define`, { method: 'POST', @@ -29,41 +26,24 @@ const useDefineField = () => { return response.json(); }; - return useMutation({ - mutationFn: define, - onMutate: () => { - /*toast("Field mapping is being defined !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - }, - onError: (error) => { - /*toast("Field mapping definition failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - }, - onSuccess: (data) => { - queryClient.setQueryData(['mappings'], (oldQueryData = []) => { - return [...oldQueryData, data]; - }); - toast("Custom field defined !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSettled: () => { - }, - }); + + const defineMappingPromise = (data: IDefineTargetFieldDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await define(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + return { + mutationFn: useMutation({ + mutationFn: define, + }), + defineMappingPromise + } }; export default useDefineField; diff --git a/apps/client-ts/src/hooks/create/useMapField.tsx b/apps/client-ts/src/hooks/create/useMapField.tsx index 5d89d86b9..019d5a069 100644 --- a/apps/client-ts/src/hooks/create/useMapField.tsx +++ b/apps/client-ts/src/hooks/create/useMapField.tsx @@ -1,6 +1,5 @@ import config from '@/lib/config'; import { useMutation } from '@tanstack/react-query'; -import { toast } from "sonner" import Cookies from 'js-cookie'; interface IMapTargetFieldDto { @@ -27,39 +26,23 @@ const useMapField = () => { return response.json(); }; - return useMutation({ - mutationFn: map, - onMutate: () => { - /*toast("Field is being mapped !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - }, - onError: (error) => { - /*toast("Field mapping failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - - }, - onSuccess: () => { - toast("Custom field mapped !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSettled: () => { - }, - }); + const mapMappingPromise = (data: IMapTargetFieldDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await map(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + return { + mutationFn: useMutation({ + mutationFn: map, + }), + mapMappingPromise + } }; export default useMapField; diff --git a/apps/client-ts/src/hooks/create/useRefreshAccessToken.tsx b/apps/client-ts/src/hooks/create/useRefreshAccessToken.tsx index 044b369c0..bedea3d80 100644 --- a/apps/client-ts/src/hooks/create/useRefreshAccessToken.tsx +++ b/apps/client-ts/src/hooks/create/useRefreshAccessToken.tsx @@ -1,6 +1,5 @@ import config from '@/lib/config'; import { useMutation } from '@tanstack/react-query'; -import { toast } from "sonner" import Cookies from 'js-cookie'; interface IRefreshOutputDto { @@ -28,23 +27,10 @@ const useRefreshAccessToken = () => { }; return useMutation({ mutationFn: refreshAccessToken, - onMutate: () => { - }, - onError: (error) => { - /*toast.error("Refreshing token generation failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - }, onSuccess: (data : IRefreshOutputDto) => { Cookies.remove('access_token'); Cookies.set('access_token', data.access_token, {expires:1}); }, - onSettled: () => { - }, }); }; diff --git a/apps/client-ts/src/hooks/delete/useDeleteApiKey.tsx b/apps/client-ts/src/hooks/delete/useDeleteApiKey.tsx index 190d777ba..94c8a2459 100644 --- a/apps/client-ts/src/hooks/delete/useDeleteApiKey.tsx +++ b/apps/client-ts/src/hooks/delete/useDeleteApiKey.tsx @@ -1,7 +1,5 @@ import config from '@/lib/config'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { toast } from "sonner" -import { api_keys as ApiKey } from 'api'; +import { useMutation } from '@tanstack/react-query'; import Cookies from 'js-cookie'; interface IApiKeyDto { @@ -9,8 +7,6 @@ interface IApiKeyDto { } const useDeleteApiKey = () => { - const queryClient = useQueryClient(); - const remove = async (apiKeyData: IApiKeyDto) => { const response = await fetch(`${config.API_URL}/auth/api-keys/${apiKeyData.id_api_key}`, { method: 'DELETE', @@ -25,41 +21,24 @@ const useDeleteApiKey = () => { } return response.json(); }; - return useMutation({ - mutationFn: remove, - onMutate: () => { - /*toast("api key is being deleted !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - }, - onError: (error) => { - /*toast("The deleting of api key has failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - }, - onSuccess: (data: ApiKey) => { - queryClient.setQueryData(['api-keys'], (oldQueryData = []) => { - return oldQueryData.filter((api_key) => api_key.id_api_key !== data.id_api_key); - }); - toast("Api Key deleted !", { - description: "The api key has been removed from your list.", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }); - }, - onSettled: () => { - }, - }); + + const deleteApiKeyPromise = (data: IApiKeyDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await remove(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + return { + mutate: useMutation({ + mutationFn: remove, + }), + deleteApiKeyPromise, + }; }; export default useDeleteApiKey; diff --git a/apps/client-ts/src/hooks/delete/useDeleteConnectionStrategy.tsx b/apps/client-ts/src/hooks/delete/useDeleteConnectionStrategy.tsx index 06dd33307..64304e31a 100644 --- a/apps/client-ts/src/hooks/delete/useDeleteConnectionStrategy.tsx +++ b/apps/client-ts/src/hooks/delete/useDeleteConnectionStrategy.tsx @@ -1,16 +1,11 @@ import config from '@/lib/config'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { toast } from "sonner" -import { connection_strategies as ConnectionStrategies } from 'api'; +import { useMutation } from '@tanstack/react-query'; import Cookies from 'js-cookie'; - interface IDeleteConnectionStrategyDto { id_cs: string } -const useDeleteConnectionStrategy = () => { - const queryClient = useQueryClient(); - +const useDeleteConnectionStrategy = () => { const remove = async (connectionStrategyData: IDeleteConnectionStrategyDto) => { const response = await fetch(`${config.API_URL}/connections-strategies/delete`, { method: 'POST', @@ -27,42 +22,31 @@ const useDeleteConnectionStrategy = () => { return response.json(); }; - return useMutation({ - mutationFn: remove, - onMutate: () => { - /*toast("Connection Strategy is being deleted !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - }, - onError: (error) => { - /*toast("The deleting of Connection Strategy has failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - }, - onSuccess: (data : ConnectionStrategies) => { - console.log("After Delete Data Received : ",JSON.stringify(data)) - queryClient.setQueryData(['connection-strategies'], (oldQueryData = []) => { + + const deleteCsPromise = (data: IDeleteConnectionStrategyDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await remove(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + /* + queryClient.setQueryData(['connection-strategies'], (oldQueryData = []) => { return oldQueryData.filter((CS) => CS.id_connection_strategy !== data.id_connection_strategy) }); - toast("Connection Strategy has been deleted !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSettled: () => { - }, - }); + */ + + return { + mutate: useMutation({ + mutationFn: remove, + }), + deleteCsPromise, + }; + }; export default useDeleteConnectionStrategy; diff --git a/apps/client-ts/src/hooks/delete/useDeleteWebhook.tsx b/apps/client-ts/src/hooks/delete/useDeleteWebhook.tsx index bcaab2248..984d07d2d 100644 --- a/apps/client-ts/src/hooks/delete/useDeleteWebhook.tsx +++ b/apps/client-ts/src/hooks/delete/useDeleteWebhook.tsx @@ -1,7 +1,5 @@ import config from '@/lib/config'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { toast } from "sonner" -import { webhook_endpoints as Webhook } from 'api'; +import { useMutation } from '@tanstack/react-query'; import Cookies from 'js-cookie'; interface IWebhookDto { @@ -9,8 +7,6 @@ interface IWebhookDto { } const useDeleteWebhook = () => { - const queryClient = useQueryClient(); - const remove = async (webhookData: IWebhookDto) => { const response = await fetch(`${config.API_URL}/webhook/${webhookData.id_webhook}`, { method: 'DELETE', @@ -25,41 +21,25 @@ const useDeleteWebhook = () => { } return response.json(); }; - return useMutation({ - mutationFn: remove, - onMutate: () => { - /*toast("api key is being deleted !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - }, - onError: (error) => { - /*toast("The deleting of api key has failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - }, - onSuccess: (data: Webhook) => { - queryClient.setQueryData(['webhooks'], (oldQueryData = []) => { - return oldQueryData.filter((wh) => wh.id_webhook_endpoint !== data.id_webhook_endpoint); - }); - toast("Webhook deleted !", { - description: "The webhook has been removed from your list.", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }); - }, - onSettled: () => { - }, - }); + + const deleteWebhookPromise = (data: IWebhookDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await remove(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + + return { + mutate: useMutation({ + mutationFn: remove, + }), + deleteWebhookPromise, + }; }; export default useDeleteWebhook; diff --git a/apps/client-ts/src/hooks/get/useUser.tsx b/apps/client-ts/src/hooks/get/useUser.tsx index c80c48080..21ecf9f91 100644 --- a/apps/client-ts/src/hooks/get/useUser.tsx +++ b/apps/client-ts/src/hooks/get/useUser.tsx @@ -35,15 +35,6 @@ const useUser = () => { }; return useMutation({ mutationFn: getUser, - onMutate: () => { - // toast("Fetching the user !", { - // description: "", - // action: { - // label: "Close", - // onClick: () => console.log("Close"), - // }, - // }) - }, onError: (error) => { Cookies.remove('access_token') /*toast.error("Fetch User failed !", { @@ -56,13 +47,6 @@ const useUser = () => { }, onSuccess: (data : IUserDto) => { setProfile(data); - /*toast.success("User has been fetched !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ }, onSettled: () => { }, diff --git a/apps/client-ts/src/hooks/update/useUpdateConnectionStrategy.tsx b/apps/client-ts/src/hooks/update/useUpdateConnectionStrategy.tsx index 6894fba2b..cd32638ba 100644 --- a/apps/client-ts/src/hooks/update/useUpdateConnectionStrategy.tsx +++ b/apps/client-ts/src/hooks/update/useUpdateConnectionStrategy.tsx @@ -1,9 +1,6 @@ import config from '@/lib/config'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { toast } from "sonner" -import { connection_strategies as ConnectionStrategies } from 'api'; +import { useMutation } from '@tanstack/react-query'; import Cookies from 'js-cookie'; - interface IUpdateConnectionStrategyDto { id_cs?: string updateToggle: boolean, @@ -15,9 +12,7 @@ interface IUpdateConnectionStrategyDto { } -const useUpdateConnectionStrategy = () => { - const queryClient = useQueryClient(); - +const useUpdateConnectionStrategy = () => { const update = async (connectionStrategyData: IUpdateConnectionStrategyDto) => { if(connectionStrategyData.updateToggle) { console.log(connectionStrategyData.id_cs) @@ -57,41 +52,25 @@ const useUpdateConnectionStrategy = () => { } }; - return useMutation({ - mutationFn: update, - onMutate: () => { - /*toast("Connection Strategy is being updated !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - }, - onError: (error) => { - /*toast("The updating of Connection Strategy has failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - }, - onSuccess: (data : ConnectionStrategies) => { - queryClient.setQueryData(['connection-strategies'], (oldQueryData = []) => { - return oldQueryData.map((CS) => CS.id_connection_strategy === data.id_connection_strategy ? data : CS) - }); - toast("Changes saved !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSettled: () => { - }, - }); + + const updateCsPromise = (data: IUpdateConnectionStrategyDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await update(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + return { + mutate: useMutation({ + mutationFn: update, + }), + updateCsPromise, + }; + }; export default useUpdateConnectionStrategy; diff --git a/apps/client-ts/src/hooks/update/useUpdateProjectConnectors.tsx b/apps/client-ts/src/hooks/update/useUpdateProjectConnectors.tsx index 56f748ab3..219884810 100644 --- a/apps/client-ts/src/hooks/update/useUpdateProjectConnectors.tsx +++ b/apps/client-ts/src/hooks/update/useUpdateProjectConnectors.tsx @@ -1,6 +1,5 @@ import config from '@/lib/config'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { toast } from "sonner" +import { useMutation } from '@tanstack/react-query'; import Cookies from 'js-cookie'; interface IUpdateProjectConnectorsDto { @@ -8,9 +7,7 @@ interface IUpdateProjectConnectorsDto { status: boolean; } -const useUpdateProjectConnectors = () => { - const queryClient = useQueryClient(); - +const useUpdateProjectConnectors = () => { const update = async (data: IUpdateProjectConnectorsDto) => { const response = await fetch(`${config.API_URL}/project-connectors`, { method: 'POST', @@ -28,38 +25,25 @@ const useUpdateProjectConnectors = () => { return response.json(); }; - return useMutation({ - mutationFn: update, - onMutate: () => { - /*toast("Catalog option is being updated !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - }, - onError: (error) => { - /*toast("The updating of Connection Strategy has failed !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - })*/ - }, - onSuccess: () => { - toast("Changes saved !", { - description: "", - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - onSettled: () => { - }, - }); + + const updateProjectConnectorPromise = (data: IUpdateProjectConnectorsDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await update(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + + return { + mutate: useMutation({ + mutationFn: update, + }), + updateProjectConnectorPromise, + }; }; export default useUpdateProjectConnectors; diff --git a/apps/client-ts/src/hooks/update/useUpdateWebhookStatus.tsx b/apps/client-ts/src/hooks/update/useUpdateWebhookStatus.tsx index 2cee35534..de57151db 100644 --- a/apps/client-ts/src/hooks/update/useUpdateWebhookStatus.tsx +++ b/apps/client-ts/src/hooks/update/useUpdateWebhookStatus.tsx @@ -1,6 +1,5 @@ import config from '@/lib/config'; import { useMutation } from '@tanstack/react-query'; -import { toast } from "sonner" import Cookies from 'js-cookie'; interface IWebhookUpdateDto { @@ -24,18 +23,25 @@ const useUpdateWebhookStatus = () => { return response.json(); }; - return useMutation({ - mutationFn: update, - onError: (error) => { - toast("Changes saved !", { - description: error as any, - action: { - label: "Close", - onClick: () => console.log("Close"), - }, - }) - }, - }); + + const updateWebhookPromise = (data: IWebhookUpdateDto) => { + return new Promise(async (resolve, reject) => { + try { + const result = await update(data); + resolve(result); + + } catch (error) { + reject(error); + } + }); + }; + + return { + mutate: useMutation({ + mutationFn: update, + }), + updateWebhookPromise, + }; }; export default useUpdateWebhookStatus; diff --git a/apps/client-ts/tailwind.config.ts b/apps/client-ts/tailwind.config.ts index 84287e82f..79e700a58 100644 --- a/apps/client-ts/tailwind.config.ts +++ b/apps/client-ts/tailwind.config.ts @@ -74,6 +74,17 @@ const config = { }, }, }, + extend: { + keyframes: { + "shine": { + from: { backgroundPosition: '200% 0' }, + to: { backgroundPosition: '-200% 0' }, + }, + }, + animation: { + "shine": "shine 8s ease-in-out infinite", + }, + }, plugins: [require("tailwindcss-animate")], } satisfies Config diff --git a/apps/magic-link/src/hooks/queries/useProjectConnectors.tsx b/apps/magic-link/src/hooks/queries/useProjectConnectors.tsx index 1949f8162..db60fdd08 100644 --- a/apps/magic-link/src/hooks/queries/useProjectConnectors.tsx +++ b/apps/magic-link/src/hooks/queries/useProjectConnectors.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react/no-explicit-any */ import { useQuery } from '@tanstack/react-query'; import config from '@/helpers/config'; diff --git a/apps/magic-link/src/lib/ProviderModal.tsx b/apps/magic-link/src/lib/ProviderModal.tsx index e44e743ef..a3a229104 100644 --- a/apps/magic-link/src/lib/ProviderModal.tsx +++ b/apps/magic-link/src/lib/ProviderModal.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react/no-non-null-asserted-optional-chain */ import { useEffect, useState } from 'react'; import useOAuth from '@/hooks/useOAuth'; import { findProviderByName, providersArray, categoryFromSlug, Provider } from '@panora/shared/src'; diff --git a/packages/api/src/@core/linked-users/dto/create-linked-user.dto.ts b/packages/api/src/@core/linked-users/dto/create-linked-user.dto.ts index 8c8c4db51..a2251914e 100644 --- a/packages/api/src/@core/linked-users/dto/create-linked-user.dto.ts +++ b/packages/api/src/@core/linked-users/dto/create-linked-user.dto.ts @@ -8,3 +8,12 @@ export class CreateLinkedUserDto { @ApiProperty() id_project: string; } + +export class CreateBatchLinkedUserDto { + @ApiProperty() + linked_user_origin_ids: string[]; + @ApiProperty() + alias: string; + @ApiProperty() + id_project: string; +} diff --git a/packages/api/src/@core/linked-users/linked-users.controller.ts b/packages/api/src/@core/linked-users/linked-users.controller.ts index 05068a15e..7c65c6b22 100644 --- a/packages/api/src/@core/linked-users/linked-users.controller.ts +++ b/packages/api/src/@core/linked-users/linked-users.controller.ts @@ -9,7 +9,10 @@ import { } from '@nestjs/common'; import { LinkedUsersService } from './linked-users.service'; import { LoggerService } from '../logger/logger.service'; -import { CreateLinkedUserDto } from './dto/create-linked-user.dto'; +import { + CreateBatchLinkedUserDto, + CreateLinkedUserDto, +} from './dto/create-linked-user.dto'; import { ApiBody, ApiOperation, @@ -38,6 +41,18 @@ export class LinkedUsersController { return this.linkedUsersService.addLinkedUser(linkedUserCreateDto); } + @ApiOperation({ + operationId: 'addBatchLinkedUsers', + summary: 'Add Batch Linked Users', + }) + @ApiBody({ type: CreateBatchLinkedUserDto }) + @ApiResponse({ status: 201 }) + //@UseGuards(JwtAuthGuard) + @Post('batch') + addBatchLinkedUsers(@Body() data: CreateBatchLinkedUserDto) { + return this.linkedUsersService.addBatchLinkedUsers(data); + } + @ApiOperation({ operationId: 'getLinkedUsers', summary: 'Retrieve Linked Users', diff --git a/packages/api/src/@core/linked-users/linked-users.service.ts b/packages/api/src/@core/linked-users/linked-users.service.ts index 66589fc89..86db512da 100644 --- a/packages/api/src/@core/linked-users/linked-users.service.ts +++ b/packages/api/src/@core/linked-users/linked-users.service.ts @@ -1,5 +1,8 @@ import { Injectable } from '@nestjs/common'; -import { CreateLinkedUserDto } from './dto/create-linked-user.dto'; +import { + CreateBatchLinkedUserDto, + CreateLinkedUserDto, +} from './dto/create-linked-user.dto'; import { PrismaService } from '../prisma/prisma.service'; import { LoggerService } from '../logger/logger.service'; import { v4 as uuidv4 } from 'uuid'; @@ -58,4 +61,25 @@ export class LinkedUsersService { handleServiceError(error, this.logger); } } + async addBatchLinkedUsers(data: CreateBatchLinkedUserDto) { + try { + const { linked_user_origin_ids, alias, id_project } = data; + + const linkedUsersData = linked_user_origin_ids.map((id) => ({ + id_linked_user: uuidv4(), // Ensure each user gets a unique ID + linked_user_origin_id: id, + alias, + id_project, + })); + + const res = await this.prisma.linked_users.createMany({ + data: linkedUsersData, + skipDuplicates: true, // Optional: skip entries if they already exist + }); + + return res; + } catch (error) { + handleServiceError(error, this.logger); + } + } } diff --git a/packages/api/swagger/swagger-spec.json b/packages/api/swagger/swagger-spec.json index dfa1f44df..5e1456392 100644 --- a/packages/api/swagger/swagger-spec.json +++ b/packages/api/swagger/swagger-spec.json @@ -515,6 +515,31 @@ ] } }, + "/linked-users/batch": { + "post": { + "operationId": "addBatchLinkedUsers", + "summary": "Add Batch Linked Users", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateBatchLinkedUserDto" + } + } + } + }, + "responses": { + "201": { + "description": "" + } + }, + "tags": [ + "linked-users" + ] + } + }, "/linked-users/single": { "get": { "operationId": "getLinkedUser", @@ -4795,6 +4820,28 @@ "id_project" ] }, + "CreateBatchLinkedUserDto": { + "type": "object", + "properties": { + "linked_user_origin_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "alias": { + "type": "string" + }, + "id_project": { + "type": "string" + } + }, + "required": [ + "linked_user_origin_ids", + "alias", + "id_project" + ] + }, "CreateOrganizationDto": { "type": "object", "properties": {