refactor: consolidate shared application helpers
This commit is contained in:
@@ -21,7 +21,7 @@ import {
|
||||
import { Plus, Trash, ArrowUp, ArrowDown, Loader2, Image as ImageIcon, X, Check, GripVertical, AlertCircle } from "lucide-react";
|
||||
import Image from 'next/image';
|
||||
import { RichTextEditor } from "@/components/rich-text-editor";
|
||||
import { getErrorMessage } from "@/lib/errors";
|
||||
import { getErrorMessage } from "@/lib/utils";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
import { getErrorMessage } from "@/lib/errors";
|
||||
import { getErrorMessage } from "@/lib/utils";
|
||||
import {
|
||||
isValidDiscordWebhookUrl,
|
||||
previewWebhookTemplate,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { db } from "@/db";
|
||||
import { cdnImageResponse } from "@/lib/cdn-response";
|
||||
import { cdnImageResponse } from "@/lib/cdn-images";
|
||||
|
||||
export async function GET(
|
||||
_request: Request,
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { DropdownMenuAvatar, DiscordMemberProfile } from "@/components/dropdown-menu-avatar";
|
||||
import { getErrorMessage } from "@/lib/errors";
|
||||
import { getErrorMessage } from "@/lib/utils";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
AlertDialogTrigger,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { getErrorMessage } from "@/lib/errors";
|
||||
import { getErrorMessage } from "@/lib/utils";
|
||||
|
||||
interface DeleteFormButtonProps {
|
||||
formId: string;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import type { ComponentProps, MouseEvent } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { flushPendingSaves } from "@/lib/admin-form-save-bridge";
|
||||
import { flushPendingSaves } from "@/hooks/use-debounced-autosave";
|
||||
|
||||
export function GuardedLink({
|
||||
href,
|
||||
|
||||
@@ -1,10 +1,24 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { registerPendingSave } from "@/lib/admin-form-save-bridge";
|
||||
|
||||
export type AutoSaveStatus = "clean" | "dirty" | "saving" | "saved" | "error";
|
||||
|
||||
type FlushSave = () => Promise<void>;
|
||||
|
||||
const pendingSaves = new Set<FlushSave>();
|
||||
|
||||
export function registerPendingSave(flush: FlushSave) {
|
||||
pendingSaves.add(flush);
|
||||
return () => {
|
||||
pendingSaves.delete(flush);
|
||||
};
|
||||
}
|
||||
|
||||
export async function flushPendingSaves() {
|
||||
await Promise.all([...pendingSaves].map((flush) => flush()));
|
||||
}
|
||||
|
||||
export function useDebouncedAutoSave({
|
||||
save,
|
||||
delay = 1000,
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
"use client";
|
||||
|
||||
type FlushSave = () => Promise<void>;
|
||||
|
||||
const pendingSaves = new Set<FlushSave>();
|
||||
|
||||
export function registerPendingSave(flush: FlushSave) {
|
||||
pendingSaves.add(flush);
|
||||
return () => {
|
||||
pendingSaves.delete(flush);
|
||||
};
|
||||
}
|
||||
|
||||
export async function flushPendingSaves() {
|
||||
await Promise.all([...pendingSaves].map((flush) => flush()));
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { cdnImageResponse } from "@/lib/cdn-response";
|
||||
import {
|
||||
cdnImageResponse,
|
||||
MAX_FORM_ANSWER_IMAGE_BYTES,
|
||||
validateImageUploadFile,
|
||||
} from "@/lib/cdn-images";
|
||||
|
||||
@@ -27,3 +27,20 @@ export function validateImageUploadFile(file: Pick<File, "name" | "type" | "size
|
||||
export function cdnUrl(id: string | null | undefined) {
|
||||
return id ? `/cdn/${id}` : "";
|
||||
}
|
||||
|
||||
export type CdnResponseRecord = {
|
||||
data: Buffer;
|
||||
type: string;
|
||||
size: number;
|
||||
};
|
||||
|
||||
export function cdnImageResponse(record: CdnResponseRecord) {
|
||||
return new Response(new Uint8Array(record.data), {
|
||||
headers: {
|
||||
"Content-Type": record.type,
|
||||
"Content-Length": String(record.size),
|
||||
"Cache-Control": "public, max-age=31536000, immutable",
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
export type CdnResponseRecord = {
|
||||
data: Buffer;
|
||||
type: string;
|
||||
size: number;
|
||||
};
|
||||
|
||||
export function cdnImageResponse(record: CdnResponseRecord) {
|
||||
return new Response(new Uint8Array(record.data), {
|
||||
headers: {
|
||||
"Content-Type": record.type,
|
||||
"Content-Length": String(record.size),
|
||||
"Cache-Control": "public, max-age=31536000, immutable",
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export function getErrorMessage(error: unknown, fallback: string) {
|
||||
return error instanceof Error ? error.message : fallback;
|
||||
}
|
||||
@@ -5,6 +5,10 @@ export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
||||
export function getErrorMessage(error: unknown, fallback: string) {
|
||||
return error instanceof Error ? error.message : fallback;
|
||||
}
|
||||
|
||||
export const stripHtml = (html: string) => {
|
||||
return (html || "").replace(/<[^>]*>?/gm, '').trim();
|
||||
};
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
"start": "next start -H 0.0.0.0",
|
||||
"lint": "eslint",
|
||||
"up": "docker compose up -d --build",
|
||||
"logs": "docker compose logs -f erika",
|
||||
"logs": "docker compose logs -f app",
|
||||
"log": "docker compose logs -f",
|
||||
"db:generate": "drizzle-kit generate",
|
||||
"db:migrate": "drizzle-kit migrate",
|
||||
|
||||
Reference in New Issue
Block a user