mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
ready
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
// Column definitions and pinning for the standalone Lesson Library table.
|
// Column definitions and pinning for the standalone Lesson Library table.
|
||||||
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Clock } from "lucide-react";
|
import { Clock, Layers } from "lucide-react";
|
||||||
import { buildColumns } from "@/utils/table.util";
|
import { buildColumns } from "@/utils/table.util";
|
||||||
import { buildSelectionColumn } from "@/components/generic/Table/buildSelectionColumn";
|
import { buildSelectionColumn } from "@/components/generic/Table/buildSelectionColumn";
|
||||||
import { buildRowActionsColumn } from "@/components/generic/Table/buildRowActionsColumn";
|
import { buildRowActionsColumn } from "@/components/generic/Table/buildRowActionsColumn";
|
||||||
@@ -13,13 +13,23 @@ export const columnPinning = {
|
|||||||
left: [],
|
left: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const COURSE_STATUS_BADGE = {
|
|
||||||
published: "default",
|
|
||||||
draft: "secondary",
|
|
||||||
standalone: "outline",
|
|
||||||
};
|
|
||||||
|
|
||||||
const cellOverrides = {
|
const cellOverrides = {
|
||||||
|
title: (info) => {
|
||||||
|
const inUnit = parseInt(info.row.original.unit_count ?? 0, 10) > 0;
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-1.5 min-w-0">
|
||||||
|
{inUnit && (
|
||||||
|
<Badge variant="secondary" className="gap-1 text-[10px] font-medium shrink-0">
|
||||||
|
<Layers className="h-3 w-3" />
|
||||||
|
Unit
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
<span className="block truncate max-w-56 text-sm" title={info.getValue()}>
|
||||||
|
{info.getValue()}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
duration_seconds: (info) => {
|
duration_seconds: (info) => {
|
||||||
const seconds = parseInt(info.getValue() ?? 0, 10);
|
const seconds = parseInt(info.getValue() ?? 0, 10);
|
||||||
return (
|
return (
|
||||||
@@ -39,11 +49,6 @@ const cellOverrides = {
|
|||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
course_status: (info) => (
|
|
||||||
<Badge variant={COURSE_STATUS_BADGE[info.getValue()] ?? "outline"} className="text-xs capitalize">
|
|
||||||
{info.getValue()}
|
|
||||||
</Badge>
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function buildDataColumns(attributes, rowActions) {
|
export function buildDataColumns(attributes, rowActions) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// Column definitions and pinning for the standalone Unit Library table.
|
// Column definitions and pinning for the standalone Unit Library table.
|
||||||
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Clock } from "lucide-react";
|
import { Clock, GraduationCap } from "lucide-react";
|
||||||
import { buildColumns } from "@/utils/table.util";
|
import { buildColumns } from "@/utils/table.util";
|
||||||
import { buildSelectionColumn } from "@/components/generic/Table/buildSelectionColumn";
|
import { buildSelectionColumn } from "@/components/generic/Table/buildSelectionColumn";
|
||||||
import { buildRowActionsColumn } from "@/components/generic/Table/buildRowActionsColumn";
|
import { buildRowActionsColumn } from "@/components/generic/Table/buildRowActionsColumn";
|
||||||
@@ -13,13 +13,23 @@ export const columnPinning = {
|
|||||||
left: [],
|
left: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const COURSE_STATUS_BADGE = {
|
|
||||||
published: "default",
|
|
||||||
draft: "secondary",
|
|
||||||
standalone: "outline",
|
|
||||||
};
|
|
||||||
|
|
||||||
const cellOverrides = {
|
const cellOverrides = {
|
||||||
|
title: (info) => {
|
||||||
|
const inCourse = parseInt(info.row.original.course_count ?? 0, 10) > 0;
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-1.5 min-w-0">
|
||||||
|
{inCourse && (
|
||||||
|
<Badge variant="secondary" className="gap-1 text-[10px] font-medium shrink-0">
|
||||||
|
<GraduationCap className="h-3 w-3" />
|
||||||
|
Course
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
<span className="block truncate max-w-56 text-sm" title={info.getValue()}>
|
||||||
|
{info.getValue()}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
duration_seconds: (info) => {
|
duration_seconds: (info) => {
|
||||||
const seconds = parseInt(info.getValue() ?? 0, 10);
|
const seconds = parseInt(info.getValue() ?? 0, 10);
|
||||||
return (
|
return (
|
||||||
@@ -36,19 +46,6 @@ const cellOverrides = {
|
|||||||
{info.getValue() ?? 0} lesson{(info.getValue() ?? 0) === 1 ? "" : "s"}
|
{info.getValue() ?? 0} lesson{(info.getValue() ?? 0) === 1 ? "" : "s"}
|
||||||
</Badge>
|
</Badge>
|
||||||
),
|
),
|
||||||
course_count: (info) => {
|
|
||||||
const n = parseInt(info.getValue() ?? 0, 10);
|
|
||||||
return (
|
|
||||||
<Badge variant="secondary" className="text-xs tabular-nums">
|
|
||||||
{n} course{n === 1 ? "" : "s"}
|
|
||||||
</Badge>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
course_status: (info) => (
|
|
||||||
<Badge variant={COURSE_STATUS_BADGE[info.getValue()] ?? "outline"} className="text-xs capitalize">
|
|
||||||
{info.getValue()}
|
|
||||||
</Badge>
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function buildDataColumns(attributes, rowActions) {
|
export function buildDataColumns(attributes, rowActions) {
|
||||||
|
|||||||
@@ -14,12 +14,6 @@ export function buildRowActions({ onView, onEdit, onManageLessons, onArchive, on
|
|||||||
icon: <Eye className="h-3.5 w-3.5" />,
|
icon: <Eye className="h-3.5 w-3.5" />,
|
||||||
onClick: (row) => onView(row),
|
onClick: (row) => onView(row),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: "edit",
|
|
||||||
label: "Edit Unit",
|
|
||||||
icon: <Pencil className="h-3.5 w-3.5" />,
|
|
||||||
onClick: (row) => onEdit(row),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: "manage_lessons",
|
key: "manage_lessons",
|
||||||
label: "Manage Lessons",
|
label: "Manage Lessons",
|
||||||
|
|||||||
@@ -185,12 +185,6 @@ export default function AddAsset() {
|
|||||||
hasFileError = true;
|
hasFileError = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thumbnail is required for video, optional for audio
|
|
||||||
if (isVideo && !thumbnailRef.current) {
|
|
||||||
setError("_thumbnail", { message: "A thumbnail is required for video uploads." });
|
|
||||||
hasFileError = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasFileError) return;
|
if (hasFileError) return;
|
||||||
|
|
||||||
setProgress({ phase: "uploading", pct: 0 });
|
setProgress({ phase: "uploading", pct: 0 });
|
||||||
@@ -259,15 +253,14 @@ export default function AddAsset() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ── Thumbnail (video required / audio optional) ── */}
|
{/* ── Thumbnail (optional for both video and audio) ── */}
|
||||||
{(isVideo || isAudio) && (
|
{(isVideo || isAudio) && (
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<Label>
|
<Label>
|
||||||
Thumbnail{" "}
|
Thumbnail{" "}
|
||||||
{isVideo
|
<span className="text-muted-foreground text-xs">
|
||||||
? <span className="text-destructive">*</span>
|
(optional — {isVideo ? "video preview" : "album / cover art"})
|
||||||
: <span className="text-muted-foreground text-xs">(optional — album / cover art)</span>
|
</span>
|
||||||
}
|
|
||||||
</Label>
|
</Label>
|
||||||
<DropZone
|
<DropZone
|
||||||
key={thumbKey}
|
key={thumbKey}
|
||||||
@@ -350,23 +343,15 @@ export default function AddAsset() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Storage Provider */}
|
{/* Storage Provider — TEMPORARY: locked to S3 (see zrok tunnel note above; revert after VPS migration) */}
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<Label>Storage</Label>
|
<Label>Storage</Label>
|
||||||
<Controller
|
<Select value="s3" disabled>
|
||||||
name="storage_provider"
|
<SelectTrigger className="disabled:opacity-60 disabled:cursor-not-allowed"><SelectValue /></SelectTrigger>
|
||||||
control={control}
|
<SelectContent>
|
||||||
render={({ field }) => (
|
<SelectItem value="s3">S3</SelectItem>
|
||||||
<Select value={field.value} onValueChange={field.onChange}>
|
</SelectContent>
|
||||||
<SelectTrigger><SelectValue /></SelectTrigger>
|
</Select>
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="chibisafe">Chibisafe</SelectItem>
|
|
||||||
<SelectItem value="local">Local</SelectItem>
|
|
||||||
<SelectItem value="s3">S3</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -131,7 +131,8 @@ export default function AddAssetsBulk() {
|
|||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
|
|
||||||
const [isPublic, setIsPublic] = useState("false");
|
const [isPublic, setIsPublic] = useState("false");
|
||||||
const [storageProvider, setStorageProvider] = useState("s3");
|
// TEMPORARY: locked to S3 (see zrok tunnel note; revert after VPS migration)
|
||||||
|
const storageProvider = "s3";
|
||||||
|
|
||||||
const total = jobs.length;
|
const total = jobs.length;
|
||||||
const done = jobs.filter((j) => j.status === "uploaded").length;
|
const done = jobs.filter((j) => j.status === "uploaded").length;
|
||||||
@@ -177,11 +178,10 @@ export default function AddAssetsBulk() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex-1 space-y-1.5">
|
<div className="flex-1 space-y-1.5">
|
||||||
<label className="text-sm font-medium">Storage</label>
|
<label className="text-sm font-medium">Storage</label>
|
||||||
<Select value={storageProvider} onValueChange={setStorageProvider}>
|
{/* TEMPORARY: locked to S3 (see zrok tunnel note; revert after VPS migration) */}
|
||||||
<SelectTrigger><SelectValue /></SelectTrigger>
|
<Select value={storageProvider} disabled>
|
||||||
|
<SelectTrigger className="disabled:opacity-60 disabled:cursor-not-allowed"><SelectValue /></SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="chibisafe">Chibisafe</SelectItem>
|
|
||||||
<SelectItem value="local">Local</SelectItem>
|
|
||||||
<SelectItem value="s3">S3</SelectItem>
|
<SelectItem value="s3">S3</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ function InviteLinkDialog({ open, onOpenChange, group }) {
|
|||||||
{/* Invite link */}
|
{/* Invite link */}
|
||||||
<div className="w-full flex flex-col gap-2">
|
<div className="w-full flex flex-col gap-2">
|
||||||
<div className="flex items-center gap-2 rounded-lg border bg-muted px-3 py-2 min-w-0">
|
<div className="flex items-center gap-2 rounded-lg border bg-muted px-3 py-2 min-w-0">
|
||||||
<p className="font-mono text-xs text-muted-foreground truncate flex-1">
|
<p className="font-mono text-xs text-muted-foreground truncate w-64">
|
||||||
{inviteUrl}
|
{inviteUrl}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -62,9 +62,9 @@ function ReferDialog({ open, onOpenChange }) {
|
|||||||
const handleDownload = () => {
|
const handleDownload = () => {
|
||||||
const canvas = document.querySelector("[data-qr='refer-invite']")
|
const canvas = document.querySelector("[data-qr='refer-invite']")
|
||||||
if (!canvas) return
|
if (!canvas) return
|
||||||
const url = canvas.toDataURL("image/png")
|
const url = canvas.toDataURL("image/png")
|
||||||
const link = document.createElement("a")
|
const link = document.createElement("a")
|
||||||
link.href = url
|
link.href = url
|
||||||
link.download = `QR_${group.group_code}.png`
|
link.download = `QR_${group.group_code}.png`
|
||||||
link.click()
|
link.click()
|
||||||
}
|
}
|
||||||
@@ -112,7 +112,7 @@ function ReferDialog({ open, onOpenChange }) {
|
|||||||
{/* Invite link */}
|
{/* Invite link */}
|
||||||
<div className="w-full flex flex-col gap-2">
|
<div className="w-full flex flex-col gap-2">
|
||||||
<div className="flex items-center gap-2 rounded-lg border bg-muted px-3 py-2 min-w-0">
|
<div className="flex items-center gap-2 rounded-lg border bg-muted px-3 py-2 min-w-0">
|
||||||
<p className="font-mono text-xs text-muted-foreground truncate flex-1">
|
<p className="font-mono text-xs text-muted-foreground truncate w-64">
|
||||||
{inviteUrl}
|
{inviteUrl}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -175,10 +175,10 @@ function ClientNav() {
|
|||||||
|
|
||||||
// Derive primitive deps — effect only fires when the actual asset changes,
|
// Derive primitive deps — effect only fires when the actual asset changes,
|
||||||
// not on every tierMap/tierSlug reference churn.
|
// not on every tierMap/tierSlug reference churn.
|
||||||
const badgeAsset = tierMap[tierSlug]?.badgeAsset ?? null
|
const badgeAsset = tierMap[tierSlug]?.badgeAsset ?? null
|
||||||
const badgeAssetId = badgeAsset?.asset_id ?? null
|
const badgeAssetId = badgeAsset?.asset_id ?? null
|
||||||
const badgeProvider = badgeAsset?.storage_provider ?? null
|
const badgeProvider = badgeAsset?.storage_provider ?? null
|
||||||
const badgeFileUrl = badgeAsset?.file_url ?? null
|
const badgeFileUrl = badgeAsset?.file_url ?? null
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!badgeAssetId) { setBadgeImgUrl(null); return }
|
if (!badgeAssetId) { setBadgeImgUrl(null); return }
|
||||||
@@ -220,83 +220,83 @@ function ClientNav() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<nav className="bg-card w-full border-b border-default">
|
<nav className="bg-card w-full border-b border-default">
|
||||||
<div className="flex flex-wrap items-center justify-between mx-auto py-3 px-6">
|
<div className="flex flex-wrap items-center justify-between mx-auto py-3 px-6">
|
||||||
|
|
||||||
{/* Logo */}
|
{/* Logo */}
|
||||||
<div className="flex gap-4 items-center">
|
<div className="flex gap-4 items-center">
|
||||||
<div className="w-40 cursor-pointer" onClick={() => navigate("/")}>
|
<div className="w-40 cursor-pointer" onClick={() => navigate("/")}>
|
||||||
<img src="/philpro-white.png" alt="Philproperties" className="object-cover dark:hidden" />
|
<img src="/philpro-white.png" alt="Philproperties" className="object-cover dark:hidden" />
|
||||||
<img src="/philpro-dark.png" alt="Philproperties" className="object-cover hidden dark:block" />
|
<img src="/philpro-dark.png" alt="Philproperties" className="object-cover hidden dark:block" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<svg
|
<svg
|
||||||
data-testid="geist-icon"
|
data-testid="geist-icon"
|
||||||
height="16"
|
height="16"
|
||||||
width="16"
|
width="16"
|
||||||
viewBox="0 0 16 16"
|
viewBox="0 0 16 16"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
className="xs:hidden sm:block fill-slate-300"
|
className="xs:hidden sm:block fill-slate-300"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fillRule="evenodd"
|
fillRule="evenodd"
|
||||||
clipRule="evenodd"
|
clipRule="evenodd"
|
||||||
d="M4.01526 15.3939L4.3107 14.7046L10.3107 0.704556L10.6061 0.0151978L11.9849 0.606077L11.6894 1.29544L5.68942 15.2954L5.39398 15.9848L4.01526 15.3939Z"
|
d="M4.01526 15.3939L4.3107 14.7046L10.3107 0.704556L10.6061 0.0151978L11.9849 0.606077L11.6894 1.29544L5.68942 15.2954L5.39398 15.9848L4.01526 15.3939Z"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div id="name" className="lg:-ml-1 font-medium text-sm flex items-center gap-2">
|
<div id="name" className="lg:-ml-1 font-medium text-sm flex items-center gap-2">
|
||||||
{tierBadge && (
|
{tierBadge && (
|
||||||
<div className={`xs:hidden md:inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-xs font-semibold ${tierBadge.cls}`}>
|
<div className={`xs:hidden md:inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-xs font-semibold ${tierBadge.cls}`}>
|
||||||
{badgeImgUrl
|
{badgeImgUrl
|
||||||
? <img src={badgeImgUrl} className="size-3.5 rounded-full object-cover" />
|
? <img src={badgeImgUrl} className="size-3.5 rounded-full object-cover" />
|
||||||
: TierIcon && <TierIcon className="size-3" />
|
: TierIcon && <TierIcon className="size-3" />
|
||||||
}
|
}
|
||||||
{tierBadge.label}
|
{tierBadge.label}
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Right — bell + user menu */}
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<ClientNotificationBell />
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Avatar className="cursor-pointer">
|
|
||||||
<AvatarImage src={avatarUrl} />
|
|
||||||
<AvatarFallback className={`text-sm font-semibold ${avatarColor}`}>
|
|
||||||
{initials || "PH"}
|
|
||||||
</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
|
|
||||||
<DropdownMenuContent align="end" className="w-64">
|
|
||||||
|
|
||||||
{/* User info */}
|
|
||||||
<DropdownMenuGroup className="p-1.5">
|
|
||||||
<div className="w-full truncate text-start text-sm font-medium">
|
|
||||||
{fullName || "—"}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full truncate text-start text-[13px] font-medium text-muted-foreground">
|
)}
|
||||||
{email}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenuGroup>
|
|
||||||
|
|
||||||
<DropdownMenuSeparator />
|
{/* Right — bell + user menu */}
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<ClientNotificationBell />
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Avatar className="cursor-pointer">
|
||||||
|
<AvatarImage src={avatarUrl} />
|
||||||
|
<AvatarFallback className={`text-sm font-semibold ${avatarColor}`}>
|
||||||
|
{initials || "PH"}
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
|
||||||
<DropdownMenuGroup>
|
<DropdownMenuContent align="end" className="w-64">
|
||||||
<DropdownMenuItem className="bg-gradient-to-r from-[#0e7490] via-[#3b82f6] to-[#4f46e5] text-white" onClick={() => navigate("/plans")}>
|
|
||||||
<Zap /> Plans
|
{/* User info */}
|
||||||
</DropdownMenuItem>
|
<DropdownMenuGroup className="p-1.5">
|
||||||
<DropdownMenuItem onClick={() => navigate("/profile")}>
|
<div className="w-full truncate text-start text-sm font-medium">
|
||||||
<User /> Profile
|
{fullName || "—"}
|
||||||
</DropdownMenuItem>
|
</div>
|
||||||
<DropdownMenuItem onClick={() => navigate("/settings")}>
|
<div className="w-full truncate text-start text-[13px] font-medium text-muted-foreground">
|
||||||
<Settings /> Account Settings
|
{email}
|
||||||
</DropdownMenuItem>
|
</div>
|
||||||
{/* <DropdownMenuItem>
|
</DropdownMenuGroup>
|
||||||
|
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
|
||||||
|
<DropdownMenuGroup>
|
||||||
|
<DropdownMenuItem className="bg-gradient-to-r from-[#0e7490] via-[#3b82f6] to-[#4f46e5] text-white" onClick={() => navigate("/plans")}>
|
||||||
|
<Zap /> Plans
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => navigate("/profile")}>
|
||||||
|
<User /> Profile
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => navigate("/settings")}>
|
||||||
|
<Settings /> Account Settings
|
||||||
|
</DropdownMenuItem>
|
||||||
|
{/* <DropdownMenuItem>
|
||||||
<TableOfContents /> Documentation
|
<TableOfContents /> Documentation
|
||||||
<DropdownMenuShortcut>
|
<DropdownMenuShortcut>
|
||||||
<SquareArrowOutUpRight />
|
<SquareArrowOutUpRight />
|
||||||
@@ -308,29 +308,29 @@ function ClientNav() {
|
|||||||
<DropdownMenuItem onSelect={() => setReferOpen(true)}>
|
<DropdownMenuItem onSelect={() => setReferOpen(true)}>
|
||||||
<Gift /> Refer
|
<Gift /> Refer
|
||||||
</DropdownMenuItem> */}
|
</DropdownMenuItem> */}
|
||||||
<DropdownMenuItem onSelect={(e) => e.preventDefault()}>
|
<DropdownMenuItem onSelect={(e) => e.preventDefault()}>
|
||||||
Theme
|
Theme
|
||||||
<DropdownMenuShortcut>
|
<DropdownMenuShortcut>
|
||||||
<ThemeSwitcher />
|
<ThemeSwitcher />
|
||||||
</DropdownMenuShortcut>
|
</DropdownMenuShortcut>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuGroup>
|
</DropdownMenuGroup>
|
||||||
|
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
|
|
||||||
<DropdownMenuGroup>
|
<DropdownMenuGroup>
|
||||||
<DropdownMenuItem variant="destructive" onClick={handleLogout}>
|
<DropdownMenuItem variant="destructive" onClick={handleLogout}>
|
||||||
<LogOut /> Log out
|
<LogOut /> Log out
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuGroup>
|
</DropdownMenuGroup>
|
||||||
|
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</nav>
|
||||||
</nav>
|
|
||||||
|
|
||||||
<ReferDialog open={referOpen} onOpenChange={setReferOpen} />
|
<ReferDialog open={referOpen} onOpenChange={setReferOpen} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ function renderCell(attr, value) {
|
|||||||
}
|
}
|
||||||
case "date": return <span className="text-xs text-muted-foreground">{formatDate(value)}</span>;
|
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>;
|
case "number": return <span className="font-mono text-xs font-semibold text-muted-foreground">{value}</span>;
|
||||||
default: return <span className="text-sm">{value}</span>;
|
default: return <span className="block truncate max-w-56 text-sm" title={String(value)}>{value}</span>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user