From c3866ba443dbd43119f5d8e29472b38e6e7858bf Mon Sep 17 00:00:00 2001 From: Marcus Paeschke Date: Tue, 12 Aug 2025 11:40:41 +0200 Subject: [PATCH 1/6] Issue #109: fix datetime in og:image tag for social media previews. --- app/routes/view.$certUuid.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/routes/view.$certUuid.tsx b/app/routes/view.$certUuid.tsx index 2e586fc..9cc7e8d 100644 --- a/app/routes/view.$certUuid.tsx +++ b/app/routes/view.$certUuid.tsx @@ -51,7 +51,7 @@ export function meta({ data }: Route.MetaArgs) { }, { property: "og:image", - content: `${data?.domain}/cert/${data?.certificate.uuid}/social-preview.png?t=${data?.certificate.updatedAt}`, + content: `${data?.domain}/cert/${data?.certificate.uuid}/social-preview.png?t=${data?.certificate.updatedAt.getTime()}`, }, { property: "og:url", From 4aeb1ae874192ff513f3dabc159737762ac8e984 Mon Sep 17 00:00:00 2001 From: Marcus Paeschke Date: Tue, 12 Aug 2025 11:45:18 +0200 Subject: [PATCH 2/6] Fix: Delete typeface from DB even when file is missing on disk storage --- app/lib/typeface.server.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/lib/typeface.server.ts b/app/lib/typeface.server.ts index c6418eb..369fc41 100644 --- a/app/lib/typeface.server.ts +++ b/app/lib/typeface.server.ts @@ -30,7 +30,12 @@ export async function saveTypefaceUpload( } export async function deleteTypefaceTTF(typefaceId: number) { - return await unlink(`${typefaceDir}/${typefaceId}.ttf`); + return await unlink(`${typefaceDir}/${typefaceId}.ttf`).catch((error) => { + console.log( + `Encountered the following error when trying to delete the typeface file in storage for ID ${typefaceId}:`, + ); + console.error(error); + }); } export async function deleteTypeface(typefaceId: number) { From cc589ceae5be15634d975866650b7285b007d38b Mon Sep 17 00:00:00 2001 From: Marcus Paeschke Date: Tue, 12 Aug 2025 11:53:29 +0200 Subject: [PATCH 3/6] Catch similar file deletion errors (with log and proceed) --- app/lib/organisation.server.ts | 9 ++++++++- app/lib/pdf.server.ts | 14 ++++++++++++-- app/lib/program.server.ts | 9 ++++++++- app/lib/social.server.ts | 25 ++++++++++++++++++++++--- app/lib/template.server.ts | 23 ++++++++++++++++++++--- app/lib/typeface.server.ts | 2 +- app/lib/user.server.ts | 9 ++++++++- 7 files changed, 79 insertions(+), 12 deletions(-) diff --git a/app/lib/organisation.server.ts b/app/lib/organisation.server.ts index f8d7688..a8599c5 100644 --- a/app/lib/organisation.server.ts +++ b/app/lib/organisation.server.ts @@ -177,5 +177,12 @@ export async function deleteOrganisationLogo(logo: OrganisationLogo) { extension = "unkown"; } - return await unlink(`${logoDir}/_org.${logo.id}.logo.${extension}`); + return await unlink(`${logoDir}/_org.${logo.id}.logo.${extension}`).catch( + (error) => { + console.error( + `Encountered the following error when trying to delete the organisation logo file in storage for ID ${logo.id}:`, + ); + console.error(error); + }, + ); } diff --git a/app/lib/pdf.server.ts b/app/lib/pdf.server.ts index 8defaca..16473c1 100644 --- a/app/lib/pdf.server.ts +++ b/app/lib/pdf.server.ts @@ -441,11 +441,21 @@ export async function duplicateTemplate( } export async function deleteCertificatePreview(certificateId: number) { - return await unlink(`${previewDir}/${certificateId}.png`); + return await unlink(`${previewDir}/${certificateId}.png`).catch((error) => { + console.error( + `Encountered the following error when trying to delete the certificate preview file in storage for ID ${certificateId}:`, + ); + console.error(error); + }); } export async function deleteCertificatePDF(certificateId: number) { - return await unlink(`${certDir}/${certificateId}.pdf`); + return await unlink(`${certDir}/${certificateId}.pdf`).catch((error) => { + console.error( + `Encountered the following error when trying to delete the certificate PDF file in storage for ID ${certificateId}:`, + ); + console.error(error); + }); } export async function deleteCertificate(certificateId: number) { diff --git a/app/lib/program.server.ts b/app/lib/program.server.ts index 04b3288..8426ebd 100644 --- a/app/lib/program.server.ts +++ b/app/lib/program.server.ts @@ -130,5 +130,12 @@ export async function deleteProgramLogo(logo: ProgramLogo) { extension = "unkown"; } - return await unlink(`${logoDir}/${logo.id}.logo.${extension}`); + return await unlink(`${logoDir}/${logo.id}.logo.${extension}`).catch( + (error) => { + console.error( + `Encountered the following error when trying to delete the program logo file in storage for ID ${logo.id}:`, + ); + console.error(error); + }, + ); } diff --git a/app/lib/social.server.ts b/app/lib/social.server.ts index 708aa01..00eda3c 100644 --- a/app/lib/social.server.ts +++ b/app/lib/social.server.ts @@ -183,12 +183,31 @@ export async function deleteSocialBackground(social: SocialPreview) { extension = "unkown"; } - return await unlink(`${socialDir}/${social.id}.background.${extension}`); + return await unlink( + `${socialDir}/${social.id}.background.${extension}`, + ).catch((error) => { + console.error( + `Encountered the following error when trying to delete the social preview background image file in storage for ID ${social.id}:`, + ); + console.error(error); + }); } export async function deleteSocialComposites(socialId: number) { - await unlink(`${socialDir}/${socialId}.noPhoto.png`); - return await unlink(`${socialDir}/${socialId}.withPhoto.png`); + await unlink(`${socialDir}/${socialId}.noPhoto.png`).catch((error) => { + console.error( + `Encountered the following error when trying to delete the social preview without a photo file in storage for ID ${socialId}:`, + ); + console.error(error); + }); + return await unlink(`${socialDir}/${socialId}.withPhoto.png`).catch( + (error) => { + console.log( + `Encountered the following error when trying to delete the social preview with a photo file in storage for ID ${socialId}:`, + ); + console.error(error); + }, + ); } export const defaultLayout = { diff --git a/app/lib/template.server.ts b/app/lib/template.server.ts index 3d32868..67e2a1d 100644 --- a/app/lib/template.server.ts +++ b/app/lib/template.server.ts @@ -9,12 +9,29 @@ const previewDir = resolve(__dirname, "../../storage/previews"); const templateDir = resolve(__dirname, "../../storage/templates"); export async function deleteTemplatePreview(templateId: number) { - return await unlink(`${previewDir}/tpl-${templateId}.png`); + return await unlink(`${previewDir}/tpl-${templateId}.png`).catch( + (error) => { + console.error( + `Encountered the following error when trying to delete the template preview image file in storage for ID ${templateId}:`, + ); + console.error(error); + }, + ); } export async function deleteTemplatePDF(templateId: number) { - await unlink(`${templateDir}/${templateId}.sample.pdf`); - return await unlink(`${templateDir}/${templateId}.pdf`); + await unlink(`${templateDir}/${templateId}.sample.pdf`).catch((error) => { + console.error( + `Encountered the following error when trying to delete the template preview sample PDF file in storage for ID ${templateId}:`, + ); + console.error(error); + }); + return await unlink(`${templateDir}/${templateId}.pdf`).catch((error) => { + console.error( + `Encountered the following error when trying to delete the template PDF file in storage for ID ${templateId}:`, + ); + console.error(error); + }); } export async function deleteTemplate(templateId: number, programId: number) { diff --git a/app/lib/typeface.server.ts b/app/lib/typeface.server.ts index 369fc41..45e7d9c 100644 --- a/app/lib/typeface.server.ts +++ b/app/lib/typeface.server.ts @@ -31,7 +31,7 @@ export async function saveTypefaceUpload( export async function deleteTypefaceTTF(typefaceId: number) { return await unlink(`${typefaceDir}/${typefaceId}.ttf`).catch((error) => { - console.log( + console.error( `Encountered the following error when trying to delete the typeface file in storage for ID ${typefaceId}:`, ); console.error(error); diff --git a/app/lib/user.server.ts b/app/lib/user.server.ts index da03ac1..f29c054 100644 --- a/app/lib/user.server.ts +++ b/app/lib/user.server.ts @@ -230,7 +230,14 @@ export async function readPhoto(userPhoto: UserPhoto) { } export async function deleteUserPhoto(userPhoto: UserPhoto) { - await unlink(`${userPhotoDir}/${userPhoto.id}.transparent.png`); + await unlink(`${userPhotoDir}/${userPhoto.id}.transparent.png`).catch( + (error) => { + console.error( + `Encountered the following error when trying to delete the transparent photo file in storage for UserPhoto ID ${userPhoto.id}:`, + ); + console.error(error); + }, + ); return await prisma.userPhoto .delete({ where: { From 8c4715a712f5ca4eda5eea39f43329f165ca1ad9 Mon Sep 17 00:00:00 2001 From: Marcus Paeschke Date: Tue, 12 Aug 2025 12:24:57 +0200 Subject: [PATCH 4/6] Remove unnecessary tailwind icon sizes --- app/components/certificate-refresh.tsx | 4 +++- app/components/certificate-send-notification.tsx | 4 ++-- app/components/layout-editor.tsx | 4 ++-- app/components/task-runner.tsx | 6 +++--- ...programId.batch.$batchId.certificates.$certId.edit.tsx | 2 +- ...gramId.batch.$batchId.certificates.$certId.preview.tsx | 2 +- ...org.program.$programId.batch.$batchId.certificates.tsx | 2 +- app/routes/org.program.$programId.batch.$batchId.edit.tsx | 2 +- app/routes/org.program.$programId.batch.tsx | 2 +- app/routes/org.program.$programId.edit.tsx | 2 +- app/routes/org.program.$programId.settings.tsx | 4 ++-- ...ogram.$programId.templates.$templateId.edit-layout.tsx | 4 ++-- ...program.$programId.templates.$templateId.edit-meta.tsx | 2 +- app/routes/org.program.$programId.templates.tsx | 2 +- app/routes/org.program.$programId.user._index.tsx | 4 ++-- app/routes/org.program._index.tsx | 8 ++++---- app/routes/org.settings.tsx | 2 +- app/routes/org.typeface._index.tsx | 2 +- app/routes/org.user._index.tsx | 8 +++++--- app/routes/user.photo.tsx | 2 +- 20 files changed, 36 insertions(+), 32 deletions(-) diff --git a/app/components/certificate-refresh.tsx b/app/components/certificate-refresh.tsx index 4cf6ab2..8617073 100644 --- a/app/components/certificate-refresh.tsx +++ b/app/components/certificate-refresh.tsx @@ -24,7 +24,9 @@ export function CertificateRefresh({ certificate }: CertificateRefreshProps) { disabled={fetcher.state !== "idle"} > diff --git a/app/components/certificate-send-notification.tsx b/app/components/certificate-send-notification.tsx index 653b688..fe557fa 100644 --- a/app/components/certificate-send-notification.tsx +++ b/app/components/certificate-send-notification.tsx @@ -20,9 +20,9 @@ export function CertificateSendNotification({ variant={wasSent ? "outline" : "default"} > {fetcher.state !== "idle" ? ( - + ) : ( - + )} {wasSent ? "Resend" : "Send"} diff --git a/app/components/layout-editor.tsx b/app/components/layout-editor.tsx index d5c8f52..77dec13 100644 --- a/app/components/layout-editor.tsx +++ b/app/components/layout-editor.tsx @@ -466,7 +466,7 @@ function TextBlock({ blockId, settings, fonts, onChangeBlock, onDelete }: any) { onChangeBlock(updateBlock); }} > - Add Segment + Add Segment ); @@ -510,7 +510,7 @@ export function LayoutEditor({ layout, fonts, onChange }: any) { onChange(updateLayout); }} > - Add Block + Add Block ); diff --git a/app/components/task-runner.tsx b/app/components/task-runner.tsx index 7fa19bd..152e144 100644 --- a/app/components/task-runner.tsx +++ b/app/components/task-runner.tsx @@ -112,18 +112,18 @@ export function TaskRunner({ > {isRunning ? ( <> - + {pauseLabel ?? "Pause"} ) : ( <> - + {startLabel ?? "Start"} )}
diff --git a/app/routes/org.program.$programId.batch.$batchId.certificates.$certId.edit.tsx b/app/routes/org.program.$programId.batch.$batchId.certificates.$certId.edit.tsx index aa3c2b1..c89e0d0 100644 --- a/app/routes/org.program.$programId.batch.$batchId.certificates.$certId.edit.tsx +++ b/app/routes/org.program.$programId.batch.$batchId.certificates.$certId.edit.tsx @@ -211,7 +211,7 @@ export default function EditCertificateDialog({ diff --git a/app/routes/org.program.$programId.batch.$batchId.certificates.$certId.preview.tsx b/app/routes/org.program.$programId.batch.$batchId.certificates.$certId.preview.tsx index 26d4cfb..90c69bf 100644 --- a/app/routes/org.program.$programId.batch.$batchId.certificates.$certId.preview.tsx +++ b/app/routes/org.program.$programId.batch.$batchId.certificates.$certId.preview.tsx @@ -45,7 +45,7 @@ export default function CertificatePage({ loaderData }: Route.ComponentProps) {
diff --git a/app/routes/org.program.$programId.batch.$batchId.certificates.tsx b/app/routes/org.program.$programId.batch.$batchId.certificates.tsx index 98efc25..dd91938 100644 --- a/app/routes/org.program.$programId.batch.$batchId.certificates.tsx +++ b/app/routes/org.program.$programId.batch.$batchId.certificates.tsx @@ -194,7 +194,7 @@ export default function BatchCertificatesPage({
diff --git a/app/routes/org.program.$programId.batch.$batchId.edit.tsx b/app/routes/org.program.$programId.batch.$batchId.edit.tsx index ef97367..c998ca0 100644 --- a/app/routes/org.program.$programId.batch.$batchId.edit.tsx +++ b/app/routes/org.program.$programId.batch.$batchId.edit.tsx @@ -129,7 +129,7 @@ export default function EditBatchDialog({ loaderData }: Route.ComponentProps) { Delete this batch diff --git a/app/routes/org.program.$programId.batch.tsx b/app/routes/org.program.$programId.batch.tsx index 92fe04c..7943655 100644 --- a/app/routes/org.program.$programId.batch.tsx +++ b/app/routes/org.program.$programId.batch.tsx @@ -131,7 +131,7 @@ export default function BatchPage({ to={`${currentBatch.id}/edit`} aria-label="Edit batch settings" > - + diff --git a/app/routes/org.program.$programId.edit.tsx b/app/routes/org.program.$programId.edit.tsx index ae6495d..51f9636 100644 --- a/app/routes/org.program.$programId.edit.tsx +++ b/app/routes/org.program.$programId.edit.tsx @@ -135,7 +135,7 @@ export default function EditBatchDialog({ loaderData }: Route.ComponentProps) { Delete this program diff --git a/app/routes/org.program.$programId.settings.tsx b/app/routes/org.program.$programId.settings.tsx index 26673c1..b5b9ef9 100644 --- a/app/routes/org.program.$programId.settings.tsx +++ b/app/routes/org.program.$programId.settings.tsx @@ -173,7 +173,7 @@ export default function ProgramSettings() { className="flex grow" > )} @@ -191,7 +191,7 @@ export default function ProgramSettings() {
diff --git a/app/routes/org.program.$programId.templates.$templateId.edit-layout.tsx b/app/routes/org.program.$programId.templates.$templateId.edit-layout.tsx index df27196..e6e7ae4 100644 --- a/app/routes/org.program.$programId.templates.$templateId.edit-layout.tsx +++ b/app/routes/org.program.$programId.templates.$templateId.edit-layout.tsx @@ -130,14 +130,14 @@ export default function TemplateEditorPage({ aria-label="Toggle visual editor" className="data-[state=on]:text-primary data-[state=off]:text-muted-foreground" > - + - + {" "} diff --git a/app/routes/org.program.$programId.templates.$templateId.edit-meta.tsx b/app/routes/org.program.$programId.templates.$templateId.edit-meta.tsx index 17257c1..75a958b 100644 --- a/app/routes/org.program.$programId.templates.$templateId.edit-meta.tsx +++ b/app/routes/org.program.$programId.templates.$templateId.edit-meta.tsx @@ -183,7 +183,7 @@ export default function EditTemplateDialog({ Delete this template diff --git a/app/routes/org.program.$programId.templates.tsx b/app/routes/org.program.$programId.templates.tsx index 1837fca..e87e96a 100644 --- a/app/routes/org.program.$programId.templates.tsx +++ b/app/routes/org.program.$programId.templates.tsx @@ -113,7 +113,7 @@ export default function ProgramTemplatesPage({ to={`${params.templateId}/edit-meta`} aria-label="Edit template settings" > - + diff --git a/app/routes/org.program.$programId.user._index.tsx b/app/routes/org.program.$programId.user._index.tsx index 1823c91..24864c2 100644 --- a/app/routes/org.program.$programId.user._index.tsx +++ b/app/routes/org.program.$programId.user._index.tsx @@ -197,7 +197,7 @@ export default function UserIndexPage({ loaderData }: Route.ComponentProps) { @@ -231,7 +231,7 @@ export default function UserIndexPage({ loaderData }: Route.ComponentProps) { variant="outline" aria-label="Remove program manager permissions" > - Remove + Remove Remove access diff --git a/app/routes/org.program._index.tsx b/app/routes/org.program._index.tsx index a3f3467..39b2987 100644 --- a/app/routes/org.program._index.tsx +++ b/app/routes/org.program._index.tsx @@ -80,7 +80,7 @@ export default function OrgIndex({ loaderData }: Route.ComponentProps) { @@ -89,7 +89,7 @@ export default function OrgIndex({ loaderData }: Route.ComponentProps) { @@ -102,7 +102,7 @@ export default function OrgIndex({ loaderData }: Route.ComponentProps) { to={`${program.id}/user`} aria-label="Manage access" > - + @@ -115,7 +115,7 @@ export default function OrgIndex({ loaderData }: Route.ComponentProps) { to={`${program.id}/settings`} aria-label="Edit program" > - + diff --git a/app/routes/org.settings.tsx b/app/routes/org.settings.tsx index 072571c..fc61252 100644 --- a/app/routes/org.settings.tsx +++ b/app/routes/org.settings.tsx @@ -198,7 +198,7 @@ export default function OrgSettings({ loaderData }: Route.ComponentProps) { {org.logo && (
)} diff --git a/app/routes/org.typeface._index.tsx b/app/routes/org.typeface._index.tsx index 165f3d0..bff2fdc 100644 --- a/app/routes/org.typeface._index.tsx +++ b/app/routes/org.typeface._index.tsx @@ -86,7 +86,7 @@ export default function TypefaceIndexPage({ Delete typeface diff --git a/app/routes/org.user._index.tsx b/app/routes/org.user._index.tsx index bdf03c3..d510b52 100644 --- a/app/routes/org.user._index.tsx +++ b/app/routes/org.user._index.tsx @@ -182,7 +182,7 @@ export default function UserIndexPage({ loaderData }: Route.ComponentProps) { @@ -198,7 +198,9 @@ export default function UserIndexPage({ loaderData }: Route.ComponentProps) { {u.firstName} {u.lastName} - {u.email} + + {u.email} + {u.isSuperAdmin ? ( "Super Admin" @@ -217,7 +219,7 @@ export default function UserIndexPage({ loaderData }: Route.ComponentProps) { diff --git a/app/routes/user.photo.tsx b/app/routes/user.photo.tsx index 46f3b17..378bc2d 100644 --- a/app/routes/user.photo.tsx +++ b/app/routes/user.photo.tsx @@ -524,7 +524,7 @@ export default function UserUploadPictureDialog({ variant="destructive" size="icon" > - + From 2b4027095ea10b6aeedc25dc4153cbfc45c8b1a4 Mon Sep 17 00:00:00 2001 From: Marcus Paeschke Date: Tue, 12 Aug 2025 12:25:17 +0200 Subject: [PATCH 5/6] Improve checkbox display in multi-select --- app/components/ui/multi-select.tsx | 28 +++++++++++++++------------- app/tailwind.css | 3 +++ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/app/components/ui/multi-select.tsx b/app/components/ui/multi-select.tsx index cf7a3cd..8fadfa7 100644 --- a/app/components/ui/multi-select.tsx +++ b/app/components/ui/multi-select.tsx @@ -235,11 +235,11 @@ export const MultiSelect = React.forwardRef< }} > {IconComponent && ( - + )} {option?.label} { event.stopPropagation(); toggleOption(value); @@ -263,9 +263,11 @@ export const MultiSelect = React.forwardRef< animationDuration: `${animation}s`, }} > - {`+ ${selectedValues.length - maxCount} more`} + {`+ ${ + selectedValues.length - maxCount + } more`} { event.stopPropagation(); clearExtraOptions(); @@ -276,7 +278,7 @@ export const MultiSelect = React.forwardRef<
{ event.stopPropagation(); handleClear(); @@ -286,7 +288,7 @@ export const MultiSelect = React.forwardRef< orientation="vertical" className="flex min-h-6 h-full" /> - +
) : ( @@ -294,7 +296,7 @@ export const MultiSelect = React.forwardRef< {placeholder} - + )} @@ -320,10 +322,10 @@ export const MultiSelect = React.forwardRef< >
@@ -345,16 +347,16 @@ export const MultiSelect = React.forwardRef< >
- +
{option.icon && ( - + )} {option.label} diff --git a/app/tailwind.css b/app/tailwind.css index a16366b..8973d78 100644 --- a/app/tailwind.css +++ b/app/tailwind.css @@ -36,6 +36,7 @@ body { --color-destructive: var(--destructive); --color-border: var(--border); --color-input: var(--input); + --color-input-foreground: var(--input-foreground); --color-ring: var(--ring); --color-chart-1: var(--chart-1); --color-chart-2: var(--chart-2); @@ -107,6 +108,7 @@ body { --destructive: oklch(0.577 0.245 27.325); --border: oklch(0.929 0.013 255.508); --input: oklch(0.929 0.013 255.508); + --input-foreground: oklch(0 0 0); --ring: oklch(0.704 0.04 256.788); --chart-1: oklch(0.646 0.222 41.116); --chart-2: oklch(0.6 0.118 184.704); @@ -140,6 +142,7 @@ body { --destructive: oklch(0.704 0.191 22.216); --border: oklch(1 0 0 / 10%); --input: oklch(1 0 0 / 15%); + --input-foreground: oklch(1 0 0); --ring: oklch(0.551 0.027 264.364); --chart-1: oklch(0.488 0.243 264.376); --chart-2: oklch(0.696 0.17 162.48); From 1cf13532b914d42332482b0262a27b7235e59d8e Mon Sep 17 00:00:00 2001 From: Marcus Paeschke Date: Tue, 12 Aug 2025 12:33:05 +0200 Subject: [PATCH 6/6] Specify accept MIME types for file inputs --- app/routes/org.program.$programId.social.tsx | 1 + .../org.program.$programId.templates.$templateId.duplicate.tsx | 2 +- .../org.program.$programId.templates.$templateId.edit-meta.tsx | 2 +- app/routes/org.program.$programId.templates.create.tsx | 1 + app/routes/org.typeface.create.tsx | 1 + 5 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/routes/org.program.$programId.social.tsx b/app/routes/org.program.$programId.social.tsx index 0828712..0e70c35 100644 --- a/app/routes/org.program.$programId.social.tsx +++ b/app/routes/org.program.$programId.social.tsx @@ -147,6 +147,7 @@ export default function ProgramSocialPage({ - +