diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 2d0de59a2..269b283dd 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -2355,6 +2355,9 @@ export const AppConnections = { sslRejectUnauthorized: "Whether or not to reject unauthorized SSL certificates (true/false). Set to false only in test environments with self-signed certificates.", sslCertificate: "The SSL certificate (PEM format) to use for secure connection." + }, + LARAVEL_FORGE: { + apiToken: "The API token used to authenticate with Laravel Forge." } } }; diff --git a/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts b/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts index e5549f9fe..c799ef0f0 100644 --- a/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts +++ b/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts @@ -77,6 +77,10 @@ import { HumanitecConnectionListItemSchema, SanitizedHumanitecConnectionSchema } from "@app/services/app-connection/humanitec"; +import { + LaravelForgeConnectionListItemSchema, + SanitizedLaravelForgeConnectionSchema +} from "@app/services/app-connection/laravel-forge"; import { LdapConnectionListItemSchema, SanitizedLdapConnectionSchema } from "@app/services/app-connection/ldap"; import { MsSqlConnectionListItemSchema, SanitizedMsSqlConnectionSchema } from "@app/services/app-connection/mssql"; import { MySqlConnectionListItemSchema, SanitizedMySqlConnectionSchema } from "@app/services/app-connection/mysql"; @@ -158,7 +162,8 @@ const SanitizedAppConnectionSchema = z.union([ ...SanitizedNetlifyConnectionSchema.options, ...SanitizedOktaConnectionSchema.options, ...SanitizedAzureADCSConnectionSchema.options, - ...SanitizedRedisConnectionSchema.options + ...SanitizedRedisConnectionSchema.options, + ...SanitizedLaravelForgeConnectionSchema.options ]); const AppConnectionOptionsSchema = z.discriminatedUnion("app", [ @@ -200,7 +205,8 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [ NetlifyConnectionListItemSchema, OktaConnectionListItemSchema, AzureADCSConnectionListItemSchema, - RedisConnectionListItemSchema + RedisConnectionListItemSchema, + LaravelForgeConnectionListItemSchema ]); export const registerAppConnectionRouter = async (server: FastifyZodProvider) => { diff --git a/backend/src/server/routes/v1/app-connection-routers/index.ts b/backend/src/server/routes/v1/app-connection-routers/index.ts index 11d9ce5e6..2e3da4420 100644 --- a/backend/src/server/routes/v1/app-connection-routers/index.ts +++ b/backend/src/server/routes/v1/app-connection-routers/index.ts @@ -24,6 +24,7 @@ import { registerGitLabConnectionRouter } from "./gitlab-connection-router"; import { registerHCVaultConnectionRouter } from "./hc-vault-connection-router"; import { registerHerokuConnectionRouter } from "./heroku-connection-router"; import { registerHumanitecConnectionRouter } from "./humanitec-connection-router"; +import { registerLaravelForgeConnectionRouter } from "./laravel-forge-connection-router"; import { registerLdapConnectionRouter } from "./ldap-connection-router"; import { registerMsSqlConnectionRouter } from "./mssql-connection-router"; import { registerMySqlConnectionRouter } from "./mysql-connection-router"; @@ -71,6 +72,7 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record { + registerAppConnectionEndpoints({ + app: AppConnection.LaravelForge, + server, + sanitizedResponseSchema: SanitizedLaravelForgeConnectionSchema, + createSchema: CreateLaravelForgeConnectionSchema, + updateSchema: UpdateLaravelForgeConnectionSchema + }); +}; diff --git a/backend/src/services/app-connection/app-connection-enums.ts b/backend/src/services/app-connection/app-connection-enums.ts index 996cd872a..54b70c7d3 100644 --- a/backend/src/services/app-connection/app-connection-enums.ts +++ b/backend/src/services/app-connection/app-connection-enums.ts @@ -37,7 +37,8 @@ export enum AppConnection { DigitalOcean = "digital-ocean", Netlify = "netlify", Okta = "okta", - Redis = "redis" + Redis = "redis", + LaravelForge = "laravel-forge" } export enum AWSRegion { diff --git a/backend/src/services/app-connection/app-connection-fns.ts b/backend/src/services/app-connection/app-connection-fns.ts index 73abef78d..464f718ce 100644 --- a/backend/src/services/app-connection/app-connection-fns.ts +++ b/backend/src/services/app-connection/app-connection-fns.ts @@ -103,6 +103,11 @@ import { HumanitecConnectionMethod, validateHumanitecConnectionCredentials } from "./humanitec"; +import { + getLaravelForgeConnectionListItem, + LaravelForgeConnectionMethod, + validateLaravelForgeConnectionCredentials +} from "./laravel-forge"; import { getLdapConnectionListItem, LdapConnectionMethod, validateLdapConnectionCredentials } from "./ldap"; import { getMsSqlConnectionListItem, MsSqlConnectionMethod } from "./mssql"; import { MySqlConnectionMethod } from "./mysql/mysql-connection-enums"; @@ -187,6 +192,7 @@ export const listAppConnectionOptions = (projectType?: ProjectType) => { getOnePassConnectionListItem(), getHerokuConnectionListItem(), getRenderConnectionListItem(), + getLaravelForgeConnectionListItem(), getFlyioConnectionListItem(), getGitLabConnectionListItem(), getCloudflareConnectionListItem(), @@ -316,6 +322,7 @@ export const validateAppConnectionCredentials = async ( [AppConnection.OnePass]: validateOnePassConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.Heroku]: validateHerokuConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.Render]: validateRenderConnectionCredentials as TAppConnectionCredentialsValidator, + [AppConnection.LaravelForge]: validateLaravelForgeConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.Flyio]: validateFlyioConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.GitLab]: validateGitLabConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.Cloudflare]: validateCloudflareConnectionCredentials as TAppConnectionCredentialsValidator, @@ -368,6 +375,7 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) => case ZabbixConnectionMethod.ApiToken: case DigitalOceanConnectionMethod.ApiToken: case OktaConnectionMethod.ApiToken: + case LaravelForgeConnectionMethod.ApiToken: return "API Token"; case PostgresConnectionMethod.UsernameAndPassword: case MsSqlConnectionMethod.UsernameAndPassword: @@ -463,7 +471,8 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record< [AppConnection.DigitalOcean]: platformManagedCredentialsNotSupported, [AppConnection.Netlify]: platformManagedCredentialsNotSupported, [AppConnection.Okta]: platformManagedCredentialsNotSupported, - [AppConnection.Redis]: platformManagedCredentialsNotSupported + [AppConnection.Redis]: platformManagedCredentialsNotSupported, + [AppConnection.LaravelForge]: platformManagedCredentialsNotSupported }; export const enterpriseAppCheck = async ( diff --git a/backend/src/services/app-connection/app-connection-maps.ts b/backend/src/services/app-connection/app-connection-maps.ts index e3235d2f7..c01d9d1b4 100644 --- a/backend/src/services/app-connection/app-connection-maps.ts +++ b/backend/src/services/app-connection/app-connection-maps.ts @@ -28,6 +28,7 @@ export const APP_CONNECTION_NAME_MAP: Record = { [AppConnection.OnePass]: "1Password", [AppConnection.Heroku]: "Heroku", [AppConnection.Render]: "Render", + [AppConnection.LaravelForge]: "Laravel Forge", [AppConnection.Flyio]: "Fly.io", [AppConnection.GitLab]: "GitLab", [AppConnection.Cloudflare]: "Cloudflare", @@ -70,6 +71,7 @@ export const APP_CONNECTION_PLAN_MAP: Record { + return { + name: "Laravel Forge" as const, + app: AppConnection.LaravelForge as const, + methods: Object.values(LaravelForgeConnectionMethod) as [LaravelForgeConnectionMethod.ApiToken] + }; +}; + +export const validateLaravelForgeConnectionCredentials = async (config: TLaravelForgeConnectionConfig) => { + const { credentials: inputCredentials } = config; + + try { + // Using the /api/me endpoint to validate the API token + await request.get(`${IntegrationUrls.LARAVELFORGE_API_URL}/api/me`, { + headers: { + Authorization: `Bearer ${inputCredentials.apiToken}`, + Accept: "application/json", + "Content-Type": "application/json" + } + }); + } catch (error) { + if (error instanceof AxiosError) { + throw new BadRequestError({ + message: `Failed to validate credentials: ${error.message || "Unknown error"}` + }); + } + throw new BadRequestError({ + message: "Unable to validate connection: verify credentials" + }); + } + + return inputCredentials; +}; diff --git a/backend/src/services/app-connection/laravel-forge/laravel-forge-connection-schemas.ts b/backend/src/services/app-connection/laravel-forge/laravel-forge-connection-schemas.ts new file mode 100644 index 000000000..1647b38a1 --- /dev/null +++ b/backend/src/services/app-connection/laravel-forge/laravel-forge-connection-schemas.ts @@ -0,0 +1,58 @@ +import z from "zod"; + +import { AppConnections } from "@app/lib/api-docs"; +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; +import { + BaseAppConnectionSchema, + GenericCreateAppConnectionFieldsSchema, + GenericUpdateAppConnectionFieldsSchema +} from "@app/services/app-connection/app-connection-schemas"; + +import { LaravelForgeConnectionMethod } from "./laravel-forge-connection-enums"; + +export const LaravelForgeConnectionApiTokenCredentialsSchema = z.object({ + apiToken: z.string().trim().min(1, "API token required").describe(AppConnections.CREDENTIALS.LARAVEL_FORGE.apiToken) +}); + +const BaseLaravelForgeConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.LaravelForge) }); + +export const LaravelForgeConnectionSchema = BaseLaravelForgeConnectionSchema.extend({ + method: z.literal(LaravelForgeConnectionMethod.ApiToken), + credentials: LaravelForgeConnectionApiTokenCredentialsSchema +}); + +export const SanitizedLaravelForgeConnectionSchema = z.discriminatedUnion("method", [ + BaseLaravelForgeConnectionSchema.extend({ + method: z.literal(LaravelForgeConnectionMethod.ApiToken), + credentials: LaravelForgeConnectionApiTokenCredentialsSchema.pick({}) + }) +]); + +export const ValidateLaravelForgeConnectionCredentialsSchema = z.discriminatedUnion("method", [ + z.object({ + method: z + .literal(LaravelForgeConnectionMethod.ApiToken) + .describe(AppConnections.CREATE(AppConnection.LaravelForge).method), + credentials: LaravelForgeConnectionApiTokenCredentialsSchema.describe( + AppConnections.CREATE(AppConnection.LaravelForge).credentials + ) + }) +]); + +export const CreateLaravelForgeConnectionSchema = ValidateLaravelForgeConnectionCredentialsSchema.and( + GenericCreateAppConnectionFieldsSchema(AppConnection.LaravelForge) +); + +export const UpdateLaravelForgeConnectionSchema = z + .object({ + credentials: LaravelForgeConnectionApiTokenCredentialsSchema.optional().describe( + AppConnections.UPDATE(AppConnection.LaravelForge).credentials + ) + }) + .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.LaravelForge)); + +export const LaravelForgeConnectionListItemSchema = z.object({ + name: z.literal("Laravel Forge"), + app: z.literal(AppConnection.LaravelForge), + methods: z.nativeEnum(LaravelForgeConnectionMethod).array() +}); diff --git a/backend/src/services/app-connection/laravel-forge/laravel-forge-connection-service.ts b/backend/src/services/app-connection/laravel-forge/laravel-forge-connection-service.ts new file mode 100644 index 000000000..92a7e97e1 --- /dev/null +++ b/backend/src/services/app-connection/laravel-forge/laravel-forge-connection-service.ts @@ -0,0 +1,14 @@ +import { OrgServiceActor } from "@app/lib/types"; + +import { AppConnection } from "../app-connection-enums"; +import { TLaravelForgeConnection } from "./laravel-forge-connection-types"; + +type TGetAppConnectionFunc = ( + app: AppConnection, + connectionId: string, + actor: OrgServiceActor +) => Promise; + +export const laravelForgeConnectionService = (getAppConnection: TGetAppConnectionFunc) => { + console.log("laravelForgeConnectionService", getAppConnection); +}; diff --git a/backend/src/services/app-connection/laravel-forge/laravel-forge-connection-types.ts b/backend/src/services/app-connection/laravel-forge/laravel-forge-connection-types.ts new file mode 100644 index 000000000..9ae3ce6e1 --- /dev/null +++ b/backend/src/services/app-connection/laravel-forge/laravel-forge-connection-types.ts @@ -0,0 +1,25 @@ +import z from "zod"; + +import { DiscriminativePick } from "@app/lib/types"; + +import { AppConnection } from "../app-connection-enums"; +import { + CreateLaravelForgeConnectionSchema, + LaravelForgeConnectionSchema, + ValidateLaravelForgeConnectionCredentialsSchema +} from "./laravel-forge-connection-schemas"; + +export type TLaravelForgeConnection = z.infer; + +export type TLaravelForgeConnectionInput = z.infer & { + app: AppConnection.LaravelForge; +}; + +export type TValidateLaravelForgeConnectionCredentialsSchema = typeof ValidateLaravelForgeConnectionCredentialsSchema; + +export type TLaravelForgeConnectionConfig = DiscriminativePick< + TLaravelForgeConnectionInput, + "method" | "app" | "credentials" +> & { + orgId: string; +}; diff --git a/backend/src/services/secret-sync/secret-sync-enums.ts b/backend/src/services/secret-sync/secret-sync-enums.ts index fe0dc9f56..235b3db3a 100644 --- a/backend/src/services/secret-sync/secret-sync-enums.ts +++ b/backend/src/services/secret-sync/secret-sync-enums.ts @@ -28,7 +28,8 @@ export enum SecretSync { Checkly = "checkly", DigitalOceanAppPlatform = "digital-ocean-app-platform", Netlify = "netlify", - Bitbucket = "bitbucket" + Bitbucket = "bitbucket", + LaravelForge = "laravel-forge" } export enum SecretSyncInitialSyncBehavior { diff --git a/backend/src/services/secret-sync/secret-sync-maps.ts b/backend/src/services/secret-sync/secret-sync-maps.ts index 1fbc66cca..0ec8aede0 100644 --- a/backend/src/services/secret-sync/secret-sync-maps.ts +++ b/backend/src/services/secret-sync/secret-sync-maps.ts @@ -32,7 +32,8 @@ export const SECRET_SYNC_NAME_MAP: Record = { [SecretSync.Checkly]: "Checkly", [SecretSync.DigitalOceanAppPlatform]: "Digital Ocean App Platform", [SecretSync.Netlify]: "Netlify", - [SecretSync.Bitbucket]: "Bitbucket" + [SecretSync.Bitbucket]: "Bitbucket", + [SecretSync.LaravelForge]: "Laravel Forge" }; export const SECRET_SYNC_CONNECTION_MAP: Record = { @@ -65,7 +66,8 @@ export const SECRET_SYNC_CONNECTION_MAP: Record = { [SecretSync.Checkly]: AppConnection.Checkly, [SecretSync.DigitalOceanAppPlatform]: AppConnection.DigitalOcean, [SecretSync.Netlify]: AppConnection.Netlify, - [SecretSync.Bitbucket]: AppConnection.Bitbucket + [SecretSync.Bitbucket]: AppConnection.Bitbucket, + [SecretSync.LaravelForge]: AppConnection.LaravelForge }; export const SECRET_SYNC_PLAN_MAP: Record = { @@ -98,7 +100,8 @@ export const SECRET_SYNC_PLAN_MAP: Record = { [SecretSync.Checkly]: SecretSyncPlanType.Regular, [SecretSync.DigitalOceanAppPlatform]: SecretSyncPlanType.Regular, [SecretSync.Netlify]: SecretSyncPlanType.Regular, - [SecretSync.Bitbucket]: SecretSyncPlanType.Regular + [SecretSync.Bitbucket]: SecretSyncPlanType.Regular, + [SecretSync.LaravelForge]: SecretSyncPlanType.Regular }; export const SECRET_SYNC_SKIP_FIELDS_MAP: Record = { @@ -140,7 +143,8 @@ export const SECRET_SYNC_SKIP_FIELDS_MAP: Record = { [SecretSync.Checkly]: ["groupName", "accountName"], [SecretSync.DigitalOceanAppPlatform]: ["appName"], [SecretSync.Netlify]: ["accountName", "siteName"], - [SecretSync.Bitbucket]: [] + [SecretSync.Bitbucket]: [], + [SecretSync.LaravelForge]: [] }; const defaultDuplicateCheck: DestinationDuplicateCheckFn = () => true; @@ -199,5 +203,6 @@ export const DESTINATION_DUPLICATE_CHECK_MAP: Record { @@ -151,6 +153,7 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"]) case ZabbixConnectionMethod.ApiToken: case DigitalOceanConnectionMethod.ApiToken: case OktaConnectionMethod.ApiToken: + case LaravelForgeConnectionMethod.ApiToken: return { name: "API Token", icon: faKey }; case PostgresConnectionMethod.UsernameAndPassword: case MsSqlConnectionMethod.UsernameAndPassword: diff --git a/frontend/src/hooks/api/appConnections/enums.ts b/frontend/src/hooks/api/appConnections/enums.ts index e897cf0f0..66fed8a5d 100644 --- a/frontend/src/hooks/api/appConnections/enums.ts +++ b/frontend/src/hooks/api/appConnections/enums.ts @@ -37,5 +37,6 @@ export enum AppConnection { DigitalOcean = "digital-ocean", Netlify = "netlify", Okta = "okta", - Redis = "redis" + Redis = "redis", + LaravelForge = "laravel-forge" } diff --git a/frontend/src/hooks/api/appConnections/types/app-options.ts b/frontend/src/hooks/api/appConnections/types/app-options.ts index fdaae2c74..797e7a7c6 100644 --- a/frontend/src/hooks/api/appConnections/types/app-options.ts +++ b/frontend/src/hooks/api/appConnections/types/app-options.ts @@ -164,6 +164,10 @@ export type TOktaConnectionOption = TAppConnectionOptionBase & { app: AppConnection.Okta; }; +export type TLaravelForgeConnectionOption = TAppConnectionOptionBase & { + app: AppConnection.LaravelForge; +}; + export type TAzureAdCsConnectionOption = TAppConnectionOptionBase & { app: AppConnection.AzureADCS; }; @@ -210,7 +214,8 @@ export type TAppConnectionOption = | TDigitalOceanConnectionOption | TNetlifyConnectionOption | TOktaConnectionOption - | TAzureAdCsConnectionOption; + | TAzureAdCsConnectionOption + | TLaravelForgeConnectionOption; export type TAppConnectionOptionMap = { [AppConnection.AWS]: TAwsConnectionOption; @@ -252,4 +257,5 @@ export type TAppConnectionOptionMap = { [AppConnection.Okta]: TOktaConnectionOption; [AppConnection.AzureADCS]: TAzureAdCsConnectionOption; [AppConnection.Redis]: TRedisConnectionOption; + [AppConnection.LaravelForge]: TLaravelForgeConnectionOption; }; diff --git a/frontend/src/hooks/api/appConnections/types/index.ts b/frontend/src/hooks/api/appConnections/types/index.ts index 9f8df7cfa..fd840d5de 100644 --- a/frontend/src/hooks/api/appConnections/types/index.ts +++ b/frontend/src/hooks/api/appConnections/types/index.ts @@ -22,6 +22,7 @@ import { TGitLabConnection } from "./gitlab-connection"; import { THCVaultConnection } from "./hc-vault-connection"; import { THerokuConnection } from "./heroku-connection"; import { THumanitecConnection } from "./humanitec-connection"; +import { TLaravelForgeConnection } from "./laravel-forge-connection"; import { TLdapConnection } from "./ldap-connection"; import { TMsSqlConnection } from "./mssql-connection"; import { TMySqlConnection } from "./mysql-connection"; @@ -61,6 +62,7 @@ export * from "./gitlab-connection"; export * from "./hc-vault-connection"; export * from "./heroku-connection"; export * from "./humanitec-connection"; +export * from "./laravel-forge-connection"; export * from "./ldap-connection"; export * from "./mssql-connection"; export * from "./mysql-connection"; @@ -105,6 +107,7 @@ export type TAppConnection = | TOCIConnection | TOnePassConnection | THerokuConnection + | TLaravelForgeConnection | TRenderConnection | TFlyioConnection | TGitLabConnection diff --git a/frontend/src/hooks/api/appConnections/types/laravel-forge-connection.ts b/frontend/src/hooks/api/appConnections/types/laravel-forge-connection.ts new file mode 100644 index 000000000..ac48d929d --- /dev/null +++ b/frontend/src/hooks/api/appConnections/types/laravel-forge-connection.ts @@ -0,0 +1,13 @@ +import { AppConnection } from "@app/hooks/api/appConnections/enums"; +import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-connection"; + +export enum LaravelForgeConnectionMethod { + ApiToken = "api-token" +} + +export type TLaravelForgeConnection = TRootAppConnection & { app: AppConnection.LaravelForge } & { + method: LaravelForgeConnectionMethod.ApiToken; + credentials: { + apiToken: string; + }; +}; diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx index 42b00f44a..a44c4746b 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx @@ -31,6 +31,7 @@ import { GitLabConnectionForm } from "./GitLabConnectionForm"; import { HCVaultConnectionForm } from "./HCVaultConnectionForm"; import { HerokuConnectionForm } from "./HerokuAppConnectionForm"; import { HumanitecConnectionForm } from "./HumanitecConnectionForm"; +import { LaravelForgeConnectionForm } from "./LaravelForgeConnectionForm"; import { LdapConnectionForm } from "./LdapConnectionForm"; import { MsSqlConnectionForm } from "./MsSqlConnectionForm"; import { MySqlConnectionForm } from "./MySqlConnectionForm"; @@ -146,6 +147,8 @@ const CreateForm = ({ app, onComplete, projectId }: CreateFormProps) => { return ; case AppConnection.Render: return ; + case AppConnection.LaravelForge: + return ; case AppConnection.Flyio: return ; case AppConnection.GitLab: @@ -297,6 +300,8 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => { ); case AppConnection.Render: return ; + case AppConnection.LaravelForge: + return ; case AppConnection.Flyio: return ; case AppConnection.GitLab: diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/LaravelForgeConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/LaravelForgeConnectionForm.tsx new file mode 100644 index 000000000..f115942eb --- /dev/null +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/LaravelForgeConnectionForm.tsx @@ -0,0 +1,136 @@ +import { Controller, FormProvider, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; + +import { + Button, + FormControl, + ModalClose, + SecretInput, + Select, + SelectItem +} from "@app/components/v2"; +import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections"; +import { + LaravelForgeConnectionMethod, + TLaravelForgeConnection +} from "@app/hooks/api/appConnections"; +import { AppConnection } from "@app/hooks/api/appConnections/enums"; + +import { + genericAppConnectionFieldsSchema, + GenericAppConnectionsFields +} from "./GenericAppConnectionFields"; + +type Props = { + appConnection?: TLaravelForgeConnection; + onSubmit: (formData: FormData) => Promise; +}; + +const rootSchema = genericAppConnectionFieldsSchema.extend({ + app: z.literal(AppConnection.LaravelForge) +}); + +const formSchema = z.discriminatedUnion("method", [ + rootSchema.extend({ + method: z.literal(LaravelForgeConnectionMethod.ApiToken), + credentials: z.object({ + apiToken: z.string().trim().min(1, "API Token required") + }) + }) +]); + +type FormData = z.infer; + +export const LaravelForgeConnectionForm = ({ appConnection, onSubmit }: Props) => { + const isUpdate = Boolean(appConnection); + + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: appConnection ?? { + app: AppConnection.LaravelForge, + method: LaravelForgeConnectionMethod.ApiToken + } + }); + + const { + handleSubmit, + control, + formState: { isSubmitting, isDirty } + } = form; + + return ( + +
+ {!isUpdate && } + + ( + + + + )} + /> + ( + + onChange(e.target.value)} + /> + + )} + /> +
+ + + + +
+ +
+ ); +};