mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
@@ -28,6 +28,11 @@ const ALLOWED_DOCUMENT_EXTENSIONS = new Set([
|
||||
"zip", "json", "rtf", "txt", "csv", "md",
|
||||
]);
|
||||
|
||||
// Matches asset_upload.middleware.js on the backend — rejecting an oversized
|
||||
// file here means the queue shows a clear "invalid" reason instantly instead
|
||||
// of a job that uploads for a while and then fails with a generic error.
|
||||
export const MAX_ASSET_FILE_SIZE = 500 * 1024 * 1024; // 500 MB
|
||||
|
||||
export function fileExtension(filename = "") {
|
||||
const dot = filename.lastIndexOf(".");
|
||||
return dot > 0 ? filename.slice(dot + 1).toLowerCase() : "";
|
||||
@@ -35,6 +40,13 @@ export function fileExtension(filename = "") {
|
||||
|
||||
// { ok: true } or { ok: false, reason }
|
||||
export function validateAssetFile(file) {
|
||||
if (file.size > MAX_ASSET_FILE_SIZE) {
|
||||
return {
|
||||
ok: false,
|
||||
reason: `File exceeds the ${MAX_ASSET_FILE_SIZE / (1024 * 1024)} MB size limit.`,
|
||||
};
|
||||
}
|
||||
|
||||
const mime = file.type || "";
|
||||
|
||||
if (mime.startsWith("image/") || mime.startsWith("video/") || mime.startsWith("audio/")) {
|
||||
|
||||
+25
-14
@@ -75,6 +75,10 @@ export const BADGE_STYLES = {
|
||||
// ── Asset — is_public ─────────────────────────
|
||||
"true": "bg-emerald-100 text-emerald-700 border-emerald-200",
|
||||
"false": "bg-zinc-100 text-zinc-600 border-zinc-200",
|
||||
|
||||
// ── Generic boolean labels (BOOLEAN_FIELD_LABELS) ─────────
|
||||
"Yes": "bg-emerald-100 text-emerald-700 border-emerald-200",
|
||||
"No": "bg-zinc-100 text-zinc-600 border-zinc-200",
|
||||
};
|
||||
|
||||
function EnumBadge({ value }) {
|
||||
@@ -94,24 +98,28 @@ export function formatDate(value) {
|
||||
catch { return value; }
|
||||
}
|
||||
|
||||
// Human-readable labels for boolean-backed enum fields — [falseLabel, trueLabel].
|
||||
// Shared by renderCell (table cells) and ColumnFilter (filter dropdown), so a
|
||||
// "true"/"false" boolean column never surfaces its raw value to the admin.
|
||||
export const BOOLEAN_FIELD_LABELS = {
|
||||
is_active: ["Not Active", "Active"],
|
||||
is_verified: ["Not Verified", "Verified"],
|
||||
is_public: ["Private", "Public"],
|
||||
is_banned: ["Not Banned", "Banned"],
|
||||
is_required: ["No", "Yes"],
|
||||
};
|
||||
|
||||
function renderCell(attr, value) {
|
||||
if (value === null || value === undefined || value === "") {
|
||||
return <span className="text-muted-foreground/40">-</span>;
|
||||
}
|
||||
|
||||
|
||||
switch (attr.type) {
|
||||
case "enum":
|
||||
if (attr.field === "is_active")
|
||||
return <EnumBadge value={value ? "Active" : "Not Active"} />;
|
||||
else if (attr.field === "is_verified")
|
||||
return <EnumBadge value={value ? "Verified" : "Not Verified"} />;
|
||||
else if (attr.field === "is_public")
|
||||
return <EnumBadge value={value ? "Public" : "Private"} />;
|
||||
else if (attr.field === "is_banned")
|
||||
return <EnumBadge value={value ? "Banned" : "Not Banned"} />;
|
||||
else
|
||||
return <EnumBadge value={value} />;
|
||||
case "enum": {
|
||||
const boolLabels = BOOLEAN_FIELD_LABELS[attr.field];
|
||||
if (boolLabels) return <EnumBadge value={boolLabels[value ? 1 : 0]} />;
|
||||
return <EnumBadge value={value} />;
|
||||
}
|
||||
case "date": return <span className="text-xs text-muted-foreground">{formatDate(value)}</span>;
|
||||
case "number": return <span className="font-mono text-xs font-semibold text-muted-foreground">{value}</span>;
|
||||
default: return <span className="text-sm">{value}</span>;
|
||||
@@ -131,13 +139,16 @@ export function ColumnFilter({ column, attr }) {
|
||||
// ENUM (AUTO APPLY)
|
||||
// ─────────────────────────────────────────────
|
||||
if (attr.type === "enum") {
|
||||
const boolLabels = BOOLEAN_FIELD_LABELS[attr.field];
|
||||
const choiceLabel = (c) => boolLabels ? boolLabels[c === "true" ? 1 : 0] : c;
|
||||
|
||||
return (
|
||||
<Select
|
||||
value={inputValue || "__all__"}
|
||||
onValueChange={(v) => {
|
||||
const value = v === "__all__" ? "" : v;
|
||||
setInputValue(value);
|
||||
column.setFilterValue(value);
|
||||
column.setFilterValue(value);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-7 text-xs w-full">
|
||||
@@ -148,7 +159,7 @@ export function ColumnFilter({ column, attr }) {
|
||||
<SelectItem value="__all__">All</SelectItem>
|
||||
{attr.options?.choices?.map((c) => (
|
||||
<SelectItem key={c} value={c}>
|
||||
{c}
|
||||
{choiceLabel(c)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
|
||||
Reference in New Issue
Block a user