mirror of
https://github.com/fosrl/pangolin.git
synced 2026-01-28 22:00:51 +00:00
error response improvements to logo url
This commit is contained in:
@@ -37,27 +37,55 @@ const paramsSchema = z.strictObject({
|
|||||||
const bodySchema = z.strictObject({
|
const bodySchema = z.strictObject({
|
||||||
logoUrl: z
|
logoUrl: z
|
||||||
.union([
|
.union([
|
||||||
z.string().length(0),
|
z.literal(""),
|
||||||
z.url().refine(
|
z
|
||||||
async (url) => {
|
.url("Must be a valid URL")
|
||||||
|
.superRefine(async (url, ctx) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url);
|
const response = await fetch(url, {
|
||||||
return (
|
method: "HEAD"
|
||||||
response.status === 200 &&
|
}).catch(() => {
|
||||||
(
|
// If HEAD fails (CORS or method not allowed), try GET
|
||||||
response.headers.get("content-type") ?? ""
|
return fetch(url, { method: "GET" });
|
||||||
).startsWith("image/")
|
});
|
||||||
);
|
|
||||||
|
if (response.status !== 200) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: "custom",
|
||||||
|
message: `Failed to load image. Please check that the URL is accessible.`
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType =
|
||||||
|
response.headers.get("content-type") ?? "";
|
||||||
|
if (!contentType.startsWith("image/")) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: "custom",
|
||||||
|
message: `URL does not point to an image. Please provide a URL to an image file (e.g., .png, .jpg, .svg).`
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return false;
|
let errorMessage =
|
||||||
|
"Unable to verify image URL. Please check that the URL is accessible and points to an image file.";
|
||||||
|
|
||||||
|
if (error instanceof TypeError && error.message.includes("fetch")) {
|
||||||
|
errorMessage =
|
||||||
|
"Network error: Unable to reach the URL. Please check your internet connection and verify the URL is correct.";
|
||||||
|
} else if (error instanceof Error) {
|
||||||
|
errorMessage = `Error verifying URL: ${error.message}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.addIssue({
|
||||||
|
code: "custom",
|
||||||
|
message: errorMessage
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
{
|
|
||||||
error: "Invalid logo URL, must be a valid image URL"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
])
|
])
|
||||||
.optional(),
|
.transform((val) => (val === "" ? null : val))
|
||||||
|
.nullish(),
|
||||||
logoWidth: z.coerce.number<number>().min(1),
|
logoWidth: z.coerce.number<number>().min(1),
|
||||||
logoHeight: z.coerce.number<number>().min(1),
|
logoHeight: z.coerce.number<number>().min(1),
|
||||||
resourceTitle: z.string(),
|
resourceTitle: z.string(),
|
||||||
@@ -117,9 +145,8 @@ export async function upsertLoginPageBranding(
|
|||||||
typeof loginPageBranding
|
typeof loginPageBranding
|
||||||
>;
|
>;
|
||||||
|
|
||||||
if ((updateData.logoUrl ?? "").trim().length === 0) {
|
// Empty strings are transformed to null by the schema, which will clear the logo URL in the database
|
||||||
updateData.logoUrl = undefined;
|
// We keep it as null (not undefined) because undefined fields are omitted from Drizzle updates
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
build !== "saas" &&
|
build !== "saas" &&
|
||||||
|
|||||||
@@ -43,25 +43,52 @@ export type AuthPageCustomizationProps = {
|
|||||||
|
|
||||||
const AuthPageFormSchema = z.object({
|
const AuthPageFormSchema = z.object({
|
||||||
logoUrl: z.union([
|
logoUrl: z.union([
|
||||||
z.string().length(0),
|
z.literal(""),
|
||||||
z.url().refine(
|
z.url("Must be a valid URL").superRefine(async (url, ctx) => {
|
||||||
async (url) => {
|
try {
|
||||||
try {
|
const response = await fetch(url, {
|
||||||
const response = await fetch(url);
|
method: "HEAD"
|
||||||
return (
|
}).catch(() => {
|
||||||
response.status === 200 &&
|
// If HEAD fails (CORS or method not allowed), try GET
|
||||||
(response.headers.get("content-type") ?? "").startsWith(
|
return fetch(url, { method: "GET" });
|
||||||
"image/"
|
});
|
||||||
)
|
|
||||||
);
|
if (response.status !== 200) {
|
||||||
} catch (error) {
|
ctx.addIssue({
|
||||||
return false;
|
code: "custom",
|
||||||
|
message: `Failed to load image. Please check that the URL is accessible.`
|
||||||
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
const contentType = response.headers.get("content-type") ?? "";
|
||||||
error: "Invalid logo URL, must be a valid image URL"
|
if (!contentType.startsWith("image/")) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: "custom",
|
||||||
|
message: `URL does not point to an image. Please provide a URL to an image file (e.g., .png, .jpg, .svg).`
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
let errorMessage =
|
||||||
|
"Unable to verify image URL. Please check that the URL is accessible and points to an image file.";
|
||||||
|
|
||||||
|
if (
|
||||||
|
error instanceof TypeError &&
|
||||||
|
error.message.includes("fetch")
|
||||||
|
) {
|
||||||
|
errorMessage =
|
||||||
|
"Network error: Unable to reach the URL. Please check your internet connection and verify the URL is correct.";
|
||||||
|
} else if (error instanceof Error) {
|
||||||
|
errorMessage = `Error verifying URL: ${error.message}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message: errorMessage
|
||||||
|
});
|
||||||
}
|
}
|
||||||
)
|
})
|
||||||
]),
|
]),
|
||||||
logoWidth: z.coerce.number<number>().min(1),
|
logoWidth: z.coerce.number<number>().min(1),
|
||||||
logoHeight: z.coerce.number<number>().min(1),
|
logoHeight: z.coerce.number<number>().min(1),
|
||||||
@@ -405,9 +432,7 @@ export default function AuthPageBrandingForm({
|
|||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
type="submit"
|
type="submit"
|
||||||
loading={
|
loading={isDeletingBranding}
|
||||||
isUpdatingBranding || isDeletingBranding
|
|
||||||
}
|
|
||||||
disabled={
|
disabled={
|
||||||
isUpdatingBranding ||
|
isUpdatingBranding ||
|
||||||
isDeletingBranding ||
|
isDeletingBranding ||
|
||||||
@@ -422,7 +447,7 @@ export default function AuthPageBrandingForm({
|
|||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
form="auth-page-branding-form"
|
form="auth-page-branding-form"
|
||||||
loading={isUpdatingBranding || isDeletingBranding}
|
loading={isUpdatingBranding}
|
||||||
disabled={
|
disabled={
|
||||||
isUpdatingBranding ||
|
isUpdatingBranding ||
|
||||||
isDeletingBranding ||
|
isDeletingBranding ||
|
||||||
|
|||||||
Reference in New Issue
Block a user