"use client"; import { useEffect, useState } from "react"; import { Button } from "@kandev/ui/button"; import { Input } from "@kandev/ui/input"; import { Label } from "@kandev/ui/label"; import { Spinner } from "@kandev/ui/spinner"; import { useToast } from "@/components/toast-provider"; import type { useGitHubAppRegistrations } from "@/hooks/domains/github/use-github-app-registrations"; import type { GitHubAppOwnerType, PrepareGitHubAppImportResponse } from "@/lib/types/github"; import { appRegistrationError, defaultVisibility, githubAppSettingsURL, normalizePublicBaseUrl, validateImportSecrets, validateRegistrationBasics, type AppSetupErrors, } from "./github-app-onboarding-model"; import { GitHubAppImportGuide } from "./github-app-import-guide"; import { GitHubAppImportIdentityFields, GitHubAppImportSecretFields, type GitHubAppImportValues, } from "./github-app-import-fields"; import { GitHubAppVisibilityField } from "./github-app-visibility-field"; import { useTranslation } from "react-i18next"; type RegistrationHook = ReturnType; export function GitHubAppImportForm({ workspaceId, registrations, onImported, }: { workspaceId: string; registrations: RegistrationHook; onImported: (registrationId: string) => void; }) { const { t } = useTranslation(); const [preparation, setPreparation] = useState(null); const [values, setValues] = useState(initialValues); const [errors, setErrors] = useState({}); const { toast } = useToast(); useEffect(() => { setPreparation(null); setValues(initialValues); setErrors({}); }, [workspaceId]); const update = (name: keyof typeof values, value: string) => setValues((current) => ({ ...current, [name]: value })); async function prepare(event: React.FormEvent) { event.preventDefault(); const nextErrors: AppSetupErrors = {}; const publicBaseUrl = normalizePublicBaseUrl(values.publicBaseUrl, nextErrors); setErrors(nextErrors); if (!publicBaseUrl) return; try { setPreparation(await registrations.prepareImport({ public_base_url: publicBaseUrl })); } catch (error) { showError(error, t("github:importSetupCouldNotStart"), toast); } } async function submit(event: React.FormEvent) { event.preventDefault(); if (!preparation) return; const ownerType = values.ownerType as GitHubAppOwnerType; const basics = validateRegistrationBasics({ ...values, ownerType }); const secrets = validateImportSecrets(values); const nextErrors = { ...basics.errors, ...secrets.errors }; setErrors(nextErrors); if (!basics.publicBaseUrl || Object.keys(nextErrors).length) return; try { const imported = await registrations.importRegistration({ registration_id: preparation.registration_id, display_name: basics.displayName, github_host: "github.com", app_id: secrets.appId, client_id: values.clientId.trim(), client_secret: values.clientSecret, private_key: values.privateKey.trim(), webhook_secret: values.webhookSecret, slug: values.slug.trim(), owner_login: basics.ownerLogin, owner_type: ownerType, visibility: values.visibility, public_base_url: basics.publicBaseUrl, }); setValues(clearImportSecrets); toast({ description: t("github:githubAppImportedItIsReady"), variant: "success", }); onImported(imported.id); } catch (error) { showError(error, t("github:githubAppCouldNotBeImported"), toast); } } if (!preparation) { return ( update("publicBaseUrl", value)} onSubmit={prepare} /> ); } const settingsUrl = importSettingsUrl(values); const startOver = () => { setPreparation(null); setErrors({}); setValues(clearImportSecrets); }; return ( setValues((current) => ({ ...current, visibility }))} onStartOver={startOver} onSubmit={submit} /> ); } function PrepareImportForm(props: { publicBaseUrl: string; error?: string; mutating: boolean; onPublicBaseUrl: (value: string) => void; onSubmit: (event: React.FormEvent) => void; }) { const { t } = useTranslation(); return (

{t("github:addAnExistingGithubApp")}

{t("github:kandevFirstReservesExactCallbackAnd")}

props.onPublicBaseUrl(event.target.value)} />
); } type PreparedImportProps = { preparation: PrepareGitHubAppImportResponse; settingsUrl?: string; values: GitHubAppImportValues; errors: AppSetupErrors; mutating: boolean; update: (name: keyof GitHubAppImportValues, value: string) => void; onVisibility: (value: "private" | "public") => void; onStartOver: () => void; onSubmit: (event: React.FormEvent) => void; }; function PreparedImportForm(props: PreparedImportProps) { const { t } = useTranslation(); return (
); } const initialValues: GitHubAppImportValues = { displayName: "", ownerLogin: "", ownerType: "Organization" as GitHubAppOwnerType, publicBaseUrl: "", visibility: defaultVisibility, appId: "", clientId: "", clientSecret: "", privateKey: "", webhookSecret: "", slug: "", }; function clearImportSecrets(values: GitHubAppImportValues): GitHubAppImportValues { return { ...values, clientSecret: "", privateKey: "", webhookSecret: "" }; } function importSettingsUrl(values: GitHubAppImportValues) { if (!values.ownerLogin.trim() || !values.slug.trim()) return undefined; return githubAppSettingsURL(values.ownerType, values.ownerLogin, values.slug); } function showError(error: unknown, fallback: string, toast: ReturnType["toast"]) { const detail = appRegistrationError(error); toast({ description: detail?.error ?? fallback, variant: "error" }); } function Field({ label, error, children, }: { label: string; error?: string; children: React.ReactNode; }) { return ( ); }