diff --git a/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/components/client/auth-options-form.client.tsx b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/components/client/auth-options-form.client.tsx index 231de6ddb84..7cd2fd5df03 100644 --- a/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/components/client/auth-options-form.client.tsx +++ b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/components/client/auth-options-form.client.tsx @@ -1,9 +1,22 @@ "use client"; import { ConfirmationDialog } from "@/components/ui/ConfirmationDialog"; +import { Button } from "@/components/ui/button"; +import { Card } from "@/components/ui/card"; import { Checkbox, CheckboxWithLabel } from "@/components/ui/checkbox"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { FormDescription } from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; import { Skeleton } from "@/components/ui/skeleton"; import { cn } from "@/lib/utils"; import { useState } from "react"; +import { useFieldArray, useForm } from "react-hook-form"; import { toast } from "sonner"; import invariant from "tiny-invariant"; import { type Ecosystem, authOptions } from "../../../../types"; @@ -25,65 +38,212 @@ export function AuthOptionsForm({ ecosystem }: { ecosystem: Ecosystem }) { } = useUpdateEcosystem({ onError: (error) => { const message = - error instanceof Error ? error.message : "Failed to create ecosystem"; + error instanceof Error ? error.message : "Failed to update ecosystem"; toast.error(message); }, }); return ( -
- {authOptions.map((option) => ( - - { - if (ecosystem.authOptions.includes(option)) { - setMessageToConfirm({ - title: `Are you sure you want to remove ${option.slice(0, 1).toUpperCase() + option.slice(1)} as an authentication option for this ecosystem?`, - description: - "Users will no longer be able to log into your ecosystem using this option. Any users that previously used this option will be unable to log in.", - authOptions: ecosystem.authOptions.filter( - (o) => o !== option, - ), - }); - } else { - setMessageToConfirm({ - title: `Are you sure you want to add ${option.slice(0, 1).toUpperCase() + option.slice(1)} as an authentication option for this ecosystem?`, - description: - "Users will be able to log into your ecosystem using this option. If you later remove this option users that used it will no longer be able to log in.", - authOptions: [...ecosystem.authOptions, option], - }); - } - }} - /> - {option.slice(0, 1).toUpperCase() + option.slice(1)} - - ))} - { - if (!open) { - setMessageToConfirm(undefined); - } - }} - title={messageToConfirm?.title} - description={messageToConfirm?.description} - onSubmit={() => { - invariant(messageToConfirm, "Must have message for modal to be open"); - updateEcosystem({ - ecosystem, - authOptions: messageToConfirm.authOptions, - }); - }} - /> +
+
+ {authOptions.map((option) => ( + + { + if (ecosystem.authOptions?.includes(option)) { + setMessageToConfirm({ + title: `Are you sure you want to remove ${option.slice(0, 1).toUpperCase() + option.slice(1)} as an authentication option for this ecosystem?`, + description: + "Users will no longer be able to log into your ecosystem using this option. Any users that previously used this option will be unable to log in.", + authOptions: ecosystem.authOptions?.filter( + (o) => o !== option, + ), + }); + } else { + setMessageToConfirm({ + title: `Are you sure you want to add ${option.slice(0, 1).toUpperCase() + option.slice(1)} as an authentication option for this ecosystem?`, + description: + "Users will be able to log into your ecosystem using this option. If you later remove this option users that used it will no longer be able to log in.", + authOptions: [...ecosystem.authOptions, option], + }); + } + }} + /> + {option.slice(0, 1).toUpperCase() + option.slice(1)} + + ))} + { + if (!open) { + setMessageToConfirm(undefined); + } + }} + title={messageToConfirm?.title} + description={messageToConfirm?.description} + onSubmit={() => { + invariant( + messageToConfirm, + "Must have message for modal to be open", + ); + updateEcosystem({ + ...ecosystem, + authOptions: messageToConfirm.authOptions, + }); + }} + /> +
+ +
+ ); +} + +function CustomAuthOptionsForm({ ecosystem }: { ecosystem: Ecosystem }) { + const form = useForm({ + defaultValues: { + customAuthEndpoint: ecosystem.customAuthOptions?.authEndpoint?.url, + customHeaders: ecosystem.customAuthOptions?.authEndpoint?.headers, + }, + }); + const { fields, remove, append } = useFieldArray({ + control: form.control, + name: "customHeaders", + }); + const { mutateAsync: updateEcosystem, isPending } = useUpdateEcosystem({ + onError: (error) => { + const message = + error instanceof Error ? error.message : "Failed to update ecosystem"; + toast.error(message); + }, + onSuccess: () => { + toast.success("Custom Auth Options updated"); + }, + }); + return ( +
+

+ Custom Auth Options +

+ +
+
+ ( + + Authentication Endpoint + + Enter the URL for your own authentication endpoint.{" "} + + Learn more. + + + + + + + + )} + /> + ( + + Headers + + Optional: Add headers for your authentication endpoint + + +
+ {fields.map((item, index) => ( +
+ + + +
+ ))} + +
+
+ +
+ )} + /> + +
+ +
+ +
+
); } diff --git a/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/components/client/integration-permissions-toggle.client.tsx b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/components/client/integration-permissions-toggle.client.tsx index 330e9428d5f..eaaec5c6a21 100644 --- a/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/components/client/integration-permissions-toggle.client.tsx +++ b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/components/client/integration-permissions-toggle.client.tsx @@ -89,7 +89,7 @@ export function IntegrationPermissionsToggle({ onSubmit={() => { invariant(messageToConfirm, "Must have message for modal to be open"); updateEcosystem({ - ecosystem, + ...ecosystem, permission: messageToConfirm.permission, }); }} diff --git a/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/hooks/use-update-ecosystem.ts b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/hooks/use-update-ecosystem.ts index 9525f1aa7b1..9af2c713066 100644 --- a/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/hooks/use-update-ecosystem.ts +++ b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/hooks/use-update-ecosystem.ts @@ -4,19 +4,10 @@ import { useMutation, useQueryClient, } from "@tanstack/react-query"; -import type { AuthOption, Ecosystem } from "../../../types"; - -type UpdateEcosystemParams = { - ecosystem: Ecosystem; - permission?: "PARTNER_WHITELIST" | "ANYONE"; - authOptions?: AuthOption[]; -}; +import type { Ecosystem } from "../../../types"; export function useUpdateEcosystem( - options?: Omit< - UseMutationOptions, - "mutationFn" - >, + options?: Omit, "mutationFn">, ) { const { onSuccess, ...queryOptions } = options || {}; const { isLoggedIn, user } = useLoggedInUser(); @@ -24,25 +15,19 @@ export function useUpdateEcosystem( return useMutation({ // Returns true if the update was successful - mutationFn: async (params: UpdateEcosystemParams): Promise => { + mutationFn: async (params: Ecosystem): Promise => { if (!isLoggedIn || !user?.jwt) { throw new Error("Please login to update this ecosystem"); } - const res = await fetch( - `${params.ecosystem.url}/${params.ecosystem.id}`, - { - method: "PATCH", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${user.jwt}`, - }, - body: JSON.stringify({ - permission: params.permission, - authOptions: params.authOptions, - }), + const res = await fetch(`${params.url}/${params.id}`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${user.jwt}`, }, - ); + body: JSON.stringify(params), + }); if (!res.ok) { const body = await res.json(); diff --git a/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/types.ts b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/types.ts index b9ea79d386f..b8607d1c66d 100644 --- a/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/types.ts +++ b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/types.ts @@ -13,7 +13,6 @@ export const authOptions = [ "coinbase", "line", ] as const; -export type AuthOption = (typeof authOptions)[number]; export type Ecosystem = { name: string; @@ -22,6 +21,21 @@ export type Ecosystem = { slug: string; permission: "PARTNER_WHITELIST" | "ANYONE"; authOptions: (typeof authOptions)[number][]; + customAuthOptions?: { + authEndpoint?: { + url: string; + headers?: { key: string; value: string }[]; + }; + jwt?: { + jwksUri: string; + aud: string; + }; + }; + smartAccountOptions?: { + chainIds: number[]; + sponsorGas: boolean; + accountFactoryAddress: string; + }; url: string; status: "active" | "requested" | "paymentFailed"; createdAt: string;