save and update branding

This commit is contained in:
Fred KISSIE
2025-11-13 02:18:52 +01:00
parent d218a4bbc3
commit 02cd2cfb17
6 changed files with 166 additions and 39 deletions

View File

@@ -1737,6 +1737,11 @@
"authPageDomain": "Auth Page Domain", "authPageDomain": "Auth Page Domain",
"authPageBranding": "Branding", "authPageBranding": "Branding",
"authPageBrandingDescription": "Configure the branding for the auth page for your organization", "authPageBrandingDescription": "Configure the branding for the auth page for your organization",
"authPageBrandingUpdated": "Auth page Branding updated successfully",
"authPageBrandingRemoved": "Auth page Branding removed successfully",
"authPageBrandingRemoveTitle": "Remove Auth Page Branding",
"authPageBrandingQuestionRemove": "Are you sure you want to remove the branding for Auth Pages ?",
"authPageBrandingDeleteConfirm": "Confirm Delete Branding",
"brandingLogoURL": "Logo URL", "brandingLogoURL": "Logo URL",
"brandingLogoWidth": "Width (px)", "brandingLogoWidth": "Width (px)",
"brandingLogoHeight": "Height (px)", "brandingLogoHeight": "Height (px)",

View File

@@ -102,7 +102,7 @@ export async function deleteLoginPageBranding(
success: true, success: true,
error: false, error: false,
message: "Login page branding deleted successfully", message: "Login page branding deleted successfully",
status: HttpCode.CREATED status: HttpCode.OK
}); });
} catch (error) { } catch (error) {
logger.error(error); logger.error(error);

View File

@@ -92,7 +92,7 @@ export async function getLoginPageBranding(
success: true, success: true,
error: false, error: false,
message: "Login page branding retrieved successfully", message: "Login page branding retrieved successfully",
status: HttpCode.CREATED status: HttpCode.OK
}); });
} catch (error) { } catch (error) {
logger.error(error); logger.error(error);

View File

@@ -143,7 +143,7 @@ export async function upsertLoginPageBranding(
message: existingLoginPageBranding message: existingLoginPageBranding
? "Login page branding updated successfully" ? "Login page branding updated successfully"
: "Login page branding created successfully", : "Login page branding created successfully",
status: HttpCode.CREATED status: existingLoginPageBranding ? HttpCode.OK : HttpCode.CREATED
}); });
} catch (error) { } catch (error) {
logger.error(error); logger.error(error);

View File

@@ -1,7 +1,8 @@
import AuthPageBrandingForm from "@app/components/AuthPageBrandingForm"; import AuthPageBrandingForm from "@app/components/AuthPageBrandingForm";
import AuthPageSettings from "@app/components/private/AuthPageSettings"; import AuthPageSettings from "@app/components/private/AuthPageSettings";
import { SettingsContainer } from "@app/components/Settings"; import { SettingsContainer } from "@app/components/Settings";
import { priv } from "@app/lib/api"; import { internal, priv } from "@app/lib/api";
import { authCookieHeader } from "@app/lib/api/cookies";
import { getCachedSubscription } from "@app/lib/api/getCachedSubscription"; import { getCachedSubscription } from "@app/lib/api/getCachedSubscription";
import { pullEnv } from "@app/lib/pullEnv"; import { pullEnv } from "@app/lib/pullEnv";
import { build } from "@server/build"; import { build } from "@server/build";
@@ -37,8 +38,9 @@ export default async function AuthPage(props: AuthPageProps) {
let loginPage: GetLoginPageResponse | null = null; let loginPage: GetLoginPageResponse | null = null;
try { try {
const res = await priv.get<AxiosResponse<GetLoginPageResponse>>( const res = await internal.get<AxiosResponse<GetLoginPageResponse>>(
`/org/${orgId}/login-page` `/org/${orgId}/login-page`,
await authCookieHeader()
); );
if (res.status === 200) { if (res.status === 200) {
loginPage = res.data.data; loginPage = res.data.data;
@@ -47,9 +49,9 @@ export default async function AuthPage(props: AuthPageProps) {
let loginPageBranding: GetLoginPageBrandingResponse | null = null; let loginPageBranding: GetLoginPageBrandingResponse | null = null;
try { try {
const res = await priv.get<AxiosResponse<GetLoginPageBrandingResponse>>( const res = await internal.get<
`/org/${orgId}/login-page-branding` AxiosResponse<GetLoginPageBrandingResponse>
); >(`/org/${orgId}/login-page-branding`, await authCookieHeader());
if (res.status === 200) { if (res.status === 200) {
loginPageBranding = res.data.data; loginPageBranding = res.data.data;
} }

View File

@@ -1,7 +1,7 @@
"use client"; "use client";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import * as React from "react"; import { useActionState, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import z from "zod"; import z from "zod";
import { import {
@@ -22,18 +22,25 @@ import {
SettingsSectionTitle SettingsSectionTitle
} from "./Settings"; } from "./Settings";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import AuthPageSettings, {
AuthPageSettingsRef import type { GetLoginPageBrandingResponse } from "@server/routers/loginPage/types";
} from "./private/AuthPageSettings";
import type {
GetLoginPageBrandingResponse,
GetLoginPageResponse
} from "@server/routers/loginPage/types";
import { Input } from "./ui/input"; import { Input } from "./ui/input";
import { XIcon } from "lucide-react"; import { Trash2, XIcon } from "lucide-react";
import { Button } from "./ui/button"; import { Button } from "./ui/button";
import { Separator } from "./ui/separator"; import { Separator } from "./ui/separator";
import { build } from "@server/build"; import { createApiClient, formatAxiosError } from "@app/lib/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { useRouter } from "next/navigation";
import { toast } from "@app/hooks/useToast";
import {
Credenza,
CredenzaBody,
CredenzaClose,
CredenzaContent,
CredenzaFooter,
CredenzaHeader,
CredenzaTitle
} from "./Credenza";
export type AuthPageCustomizationProps = { export type AuthPageCustomizationProps = {
orgId: string; orgId: string;
@@ -74,7 +81,21 @@ export default function AuthPageBrandingForm({
orgId, orgId,
branding branding
}: AuthPageCustomizationProps) { }: AuthPageCustomizationProps) {
const [, formAction, isSubmitting] = React.useActionState(onSubmit, null); const env = useEnvContext();
const api = createApiClient(env);
const router = useRouter();
const [, updateFormAction, isUpdatingBranding] = useActionState(
updateBranding,
null
);
const [, deleteFormAction, isDeletingBranding] = useActionState(
deleteBranding,
null
);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const t = useTranslations(); const t = useTranslations();
const form = useForm({ const form = useForm({
@@ -94,14 +115,70 @@ export default function AuthPageBrandingForm({
} }
}); });
async function onSubmit() { async function updateBranding() {
console.log({
dirty: form.formState.isDirty
});
const isValid = await form.trigger(); const isValid = await form.trigger();
const brandingData = form.getValues();
if (!isValid) return; if (!isValid) return;
// ... try {
// Update or existing auth page domain
const updateRes = await api.put(
`/org/${orgId}/login-page-branding`,
{
...brandingData
}
);
if (updateRes.status === 200 || updateRes.status === 201) {
// update the data from the API
router.refresh();
toast({
variant: "default",
title: t("success"),
description: t("authPageBrandingUpdated")
});
}
} catch (error) {
toast({
variant: "destructive",
title: t("authPageErrorUpdate"),
description: formatAxiosError(
error,
t("authPageErrorUpdateMessage")
)
});
}
}
async function deleteBranding() {
try {
// Update or existing auth page domain
const updateRes = await api.delete(
`/org/${orgId}/login-page-branding`
);
if (updateRes.status === 200) {
// update the data from the API
router.refresh();
form.reset();
setIsDeleteModalOpen(false);
toast({
variant: "default",
title: t("success"),
description: t("authPageBrandingRemoved")
});
}
} catch (error) {
toast({
variant: "destructive",
title: t("authPageErrorUpdate"),
description: formatAxiosError(
error,
t("authPageErrorUpdateMessage")
)
});
}
} }
return ( return (
@@ -120,7 +197,7 @@ export default function AuthPageBrandingForm({
<SettingsSectionForm> <SettingsSectionForm>
<Form {...form}> <Form {...form}>
<form <form
action={formAction} action={updateFormAction}
id="auth-page-branding-form" id="auth-page-branding-form"
className="flex flex-col gap-8 items-stretch" className="flex flex-col gap-8 items-stretch"
> >
@@ -293,22 +370,65 @@ export default function AuthPageBrandingForm({
</SettingsSectionForm> </SettingsSectionForm>
</SettingsSectionBody> </SettingsSectionBody>
<div className="flex justify-end gap-2 mt-6"> <Credenza
{/* {branding && ( open={isDeleteModalOpen}
<Button onOpenChange={setIsDeleteModalOpen}
type="submit" >
form="auth-page-branding-form" <CredenzaContent>
loading={isSubmitting} <CredenzaHeader>
disabled={isSubmitting} <CredenzaTitle>
> {t("authPageBrandingRemoveTitle")}
{t("saveSettings")} </CredenzaTitle>
</Button> </CredenzaHeader>
)} */} <CredenzaBody className="mb-0 space-y-0 flex flex-col gap-1">
<p>{t("authPageBrandingQuestionRemove")}</p>
<div className="font-bold text-destructive">
{t("cannotbeUndone")}
</div>
<form
action={deleteFormAction}
id="confirm-delete-branding-form"
className="sr-only"
></form>
</CredenzaBody>
<CredenzaFooter>
<CredenzaClose asChild>
<Button variant="outline">{t("close")}</Button>
</CredenzaClose>
<Button
variant={"destructive"}
type="submit"
form="confirm-delete-branding-form"
loading={isDeletingBranding}
disabled={isDeletingBranding}
>
{t("authPageBrandingDeleteConfirm")}
</Button>
</CredenzaFooter>
</CredenzaContent>
</Credenza>
<div className="flex justify-end gap-2 mt-6 items-center">
{branding && (
<Button
variant="destructive"
type="button"
loading={isUpdatingBranding || isDeletingBranding}
disabled={isUpdatingBranding || isDeletingBranding}
onClick={() => {
setIsDeleteModalOpen(true);
}}
className="gap-1"
>
{t("removeAuthPageBranding")}
<Trash2 size="14" />
</Button>
)}
<Button <Button
type="submit" type="submit"
form="auth-page-branding-form" form="auth-page-branding-form"
loading={isSubmitting} loading={isUpdatingBranding || isDeletingBranding}
disabled={isSubmitting} disabled={isUpdatingBranding || isDeletingBranding}
> >
{t("saveAuthPageBranding")} {t("saveAuthPageBranding")}
</Button> </Button>