mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-01 08:16:44 +00:00
✨ save and update branding
This commit is contained in:
@@ -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)",
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user