No ledger entries yet.
- );
+ return No ledger entries yet.
;
}
return (
@@ -59,17 +57,11 @@ export function LedgerTable({ entries }: { entries: LedgerEntry[] }) {
// entry's actual sign — it reflects the perspective of THIS
// account, which the canonical flow cannot know.
const useEntrySign = amount !== 0 && Math.sign(amount) < 0;
- const prefix = useEntrySign
- ? txAmountPrefix("out")
- : txAmountPrefix(flow);
- const amountCls = useEntrySign
- ? txAmountClass("out")
- : txAmountClass(flow);
+ const prefix = useEntrySign ? txAmountPrefix("out") : txAmountPrefix(flow);
+ const amountCls = useEntrySign ? txAmountClass("out") : txAmountClass(flow);
return (
-
- {formatDate(entry.timestamp)}
-
+ {formatDate(entry.timestamp)}
{label}
{entry.memo}
diff --git a/src/components/frontend/banking/MyWalletCard.tsx b/src/components/frontend/banking/MyWalletCard.tsx
index 7dcd543..7a4dec4 100644
--- a/src/components/frontend/banking/MyWalletCard.tsx
+++ b/src/components/frontend/banking/MyWalletCard.tsx
@@ -30,16 +30,9 @@ export function MyWalletCard() {
My wallet
-
- You don't have a personal account yet.
-
+ You don't have a personal account yet.
{error && {error}
}
-
+
{loading ? "Creating..." : "Create wallet"}
diff --git a/src/components/frontend/banking/WalletHero.tsx b/src/components/frontend/banking/WalletHero.tsx
index bf4aa5f..7b7f789 100644
--- a/src/components/frontend/banking/WalletHero.tsx
+++ b/src/components/frontend/banking/WalletHero.tsx
@@ -8,10 +8,10 @@ import type { BankAccount, Resource } from "@/payload-types";
import { Button } from "@/components/ui/button";
import { formatAmount } from "@/lib/banking/format";
import {
+ type AccountStatus,
accountStatusBase,
accountStatusClass,
formatLastActivity,
- type AccountStatus,
} from "@/lib/banking/ui";
import { BankTransactionDialog } from "@/components/frontend/banking/BankTransactionDialog";
import { ensureMyAccount } from "@/app/(frontend)/logistics/banking/actions";
@@ -25,11 +25,7 @@ interface WalletHeroProps {
// Shared terminal-aesthetic class for small uppercase mono labels in the hero.
const labelCls = "font-mono text-[10px] uppercase tracking-wider text-muted-foreground";
-export function WalletHero({
- account,
- currentUser,
- currencyResource,
-}: WalletHeroProps) {
+export function WalletHero({ account, currentUser, currencyResource }: WalletHeroProps) {
const router = useRouter();
const [initializing, setInitializing] = useState(false);
const [initError, setInitError] = useState(null);
@@ -56,9 +52,7 @@ export function WalletHero({
<OPERATOR WALLET>
-
- {"> WALLET NOT INITIALIZED"}
-
+ {"> WALLET NOT INITIALIZED"}
Run initialization to provision your personal account.
@@ -85,8 +79,7 @@ export function WalletHero({
const accountId = account.id;
const lastActivity = formatLastActivity(account.updatedAt);
- const currency =
- typeof account.currency === "object" ? account.currency : currencyResource;
+ const currency = typeof account.currency === "object" ? account.currency : currencyResource;
const operatorLabel = currentUser.displayName ?? currentUser.username;
const statusKey = (account.status ?? "open") as AccountStatus;
@@ -106,9 +99,7 @@ export function WalletHero({
<OPERATOR WALLET>
-
- @{operatorLabel}
-
+ @{operatorLabel}
@@ -131,10 +122,7 @@ export function WalletHero({
-
+
diff --git a/src/components/frontend/blocks/NavProjects.tsx b/src/components/frontend/blocks/NavProjects.tsx
index 62b23b4..cd86d76 100644
--- a/src/components/frontend/blocks/NavProjects.tsx
+++ b/src/components/frontend/blocks/NavProjects.tsx
@@ -1,13 +1,7 @@
-"use client"
+"use client";
-import Link from "next/link"
-import {
- Folder,
- MoreHorizontal,
- Share,
- Trash2,
- type LucideIcon,
-} from "lucide-react"
+import Link from "next/link";
+import { Folder, type LucideIcon, MoreHorizontal, Share, Trash2 } from "lucide-react";
import {
DropdownMenu,
@@ -15,7 +9,7 @@ import {
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu"
+} from "@/components/ui/dropdown-menu";
import {
SidebarGroup,
SidebarGroupLabel,
@@ -24,18 +18,18 @@ import {
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
-} from "@/components/ui/sidebar"
+} from "@/components/ui/sidebar";
export function NavProjects({
projects,
}: {
projects: {
- name: string
- url: string
- icon: LucideIcon
- }[]
+ name: string;
+ url: string;
+ icon: LucideIcon;
+ }[];
}) {
- const { isMobile } = useSidebar()
+ const { isMobile } = useSidebar();
return (
@@ -86,5 +80,5 @@ export function NavProjects({
- )
+ );
}
diff --git a/src/components/frontend/blocks/NavSecondary.tsx b/src/components/frontend/blocks/NavSecondary.tsx
index ee6b359..ac2f0e3 100644
--- a/src/components/frontend/blocks/NavSecondary.tsx
+++ b/src/components/frontend/blocks/NavSecondary.tsx
@@ -1,6 +1,6 @@
-import * as React from "react"
-import Link from "next/link"
-import { type LucideIcon } from "lucide-react"
+import * as React from "react";
+import Link from "next/link";
+import { type LucideIcon } from "lucide-react";
import {
SidebarGroup,
@@ -8,17 +8,17 @@ import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
-} from "@/components/ui/sidebar"
+} from "@/components/ui/sidebar";
export function NavSecondary({
items,
...props
}: {
items: {
- title: string
- url: string
- icon: LucideIcon
- }[]
+ title: string;
+ url: string;
+ icon: LucideIcon;
+ }[];
} & React.ComponentPropsWithoutRef) {
return (
@@ -37,5 +37,5 @@ export function NavSecondary({
- )
+ );
}
diff --git a/src/components/frontend/blocks/NavUser.tsx b/src/components/frontend/blocks/NavUser.tsx
index 58ef6d3..90c2d15 100644
--- a/src/components/frontend/blocks/NavUser.tsx
+++ b/src/components/frontend/blocks/NavUser.tsx
@@ -1,15 +1,6 @@
"use client";
-import {
- BadgeCheck,
- Bell,
- Bird,
- ChevronsUpDown,
- CreditCard,
- FileTextIcon,
- LogOut,
- Sparkles,
-} from "lucide-react";
+import { BadgeCheck, Bird, ChevronsUpDown, FileTextIcon, LogOut } from "lucide-react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useState } from "react";
diff --git a/src/components/frontend/blocks/XPDisplay.tsx b/src/components/frontend/blocks/XPDisplay.tsx
index f10ce4f..192f4db 100644
--- a/src/components/frontend/blocks/XPDisplay.tsx
+++ b/src/components/frontend/blocks/XPDisplay.tsx
@@ -15,9 +15,7 @@ const XPDisplay = ({ xpLevel }: { xpLevel: ResolvedLevel }) => {
{levelName}
-
- {xp.toLocaleString()}
-
+ {xp.toLocaleString()}
diff --git a/src/components/frontend/dashboard/ActiveCampaigns.tsx b/src/components/frontend/dashboard/ActiveCampaigns.tsx
index 2c08909..5dbd5c8 100644
--- a/src/components/frontend/dashboard/ActiveCampaigns.tsx
+++ b/src/components/frontend/dashboard/ActiveCampaigns.tsx
@@ -1,11 +1,5 @@
import Link from "next/link";
-import {
- Item,
- ItemContent,
- ItemDescription,
- ItemHeader,
- ItemTitle,
-} from "@/components/ui/item";
+import { Item, ItemContent, ItemHeader, ItemTitle } from "@/components/ui/item";
import { ChevronRightIcon, FlagIcon } from "lucide-react";
interface CampaignEntry {
@@ -35,9 +29,7 @@ export function ActiveCampaigns({ campaigns }: ActiveCampaignsProps) {
-
- No active campaigns.
-
+ No active campaigns.
);
@@ -77,9 +69,7 @@ export function ActiveCampaigns({ campaigns }: ActiveCampaignsProps) {
{campaign.status === "inProgress" ? "Active" : campaign.status}
-
- {campaign.summary}
-
+ {campaign.summary}
diff --git a/src/components/frontend/dashboard/MissionBriefing.tsx b/src/components/frontend/dashboard/MissionBriefing.tsx
index 5c17b20..55e0d80 100644
--- a/src/components/frontend/dashboard/MissionBriefing.tsx
+++ b/src/components/frontend/dashboard/MissionBriefing.tsx
@@ -1,20 +1,7 @@
import Link from "next/link";
import type { Campaign, Map } from "@/payload-types";
-import { convertLexicalToPlaintext } from "@payloadcms/richtext-lexical/plaintext";
-import {
- Item,
- ItemContent,
- ItemDescription,
- ItemHeader,
- ItemTitle,
-} from "@/components/ui/item";
-import {
- CalendarDaysIcon,
- ClockIcon,
- CrosshairIcon,
- MapPinIcon,
- UsersIcon,
-} from "lucide-react";
+import { Item, ItemContent, ItemHeader, ItemTitle } from "@/components/ui/item";
+import { CalendarDaysIcon, ClockIcon, CrosshairIcon, MapPinIcon, UsersIcon } from "lucide-react";
interface MissionBriefingProps {
mission: {
@@ -60,18 +47,14 @@ export function MissionBriefing({ mission }: MissionBriefingProps) {
-
- No missions currently scheduled.
-
+ No missions currently scheduled.
);
}
const map =
- typeof mission.classification.map === "object"
- ? (mission.classification.map as Map)
- : null;
+ typeof mission.classification.map === "object" ? (mission.classification.map as Map) : null;
const campaign =
typeof mission.classification.campaign === "object"
? (mission.classification.campaign as Campaign)
@@ -89,10 +72,7 @@ export function MissionBriefing({ mission }: MissionBriefingProps) {
});
const durationHours = Math.floor(mission.classification.estimatedDuration / 60);
const durationMins = mission.classification.estimatedDuration % 60;
- const durationStr =
- durationMins > 0
- ? `${durationHours}h ${durationMins}m`
- : `${durationHours}h`;
+ const durationStr = durationMins > 0 ? `${durationHours}h ${durationMins}m` : `${durationHours}h`;
return (
-
@@ -112,9 +92,7 @@ export function MissionBriefing({ mission }: MissionBriefingProps) {
-
- {mission.codeName}
-
+ {mission.codeName}
@@ -125,9 +103,7 @@ export function MissionBriefing({ mission }: MissionBriefingProps) {
-
- {mission.summary}
-
+
{mission.summary}
@@ -150,9 +126,7 @@ export function MissionBriefing({ mission }: MissionBriefingProps) {
{mission.missionRoles.maxPlayers} slots
)}
- {campaign && (
- {campaign.name}
- )}
+ {campaign && {campaign.name} }
diff --git a/src/components/frontend/dashboard/ProfileSummary.tsx b/src/components/frontend/dashboard/ProfileSummary.tsx
index 507266f..0cc5968 100644
--- a/src/components/frontend/dashboard/ProfileSummary.tsx
+++ b/src/components/frontend/dashboard/ProfileSummary.tsx
@@ -1,14 +1,8 @@
import Link from "next/link";
-import type { Rank, Media, Assignment } from "@/payload-types";
+import type { Assignment, Rank } from "@/payload-types";
import type { ResolvedLevel } from "@/utils/xp/resolveLevel";
-import {
- Item,
- ItemContent,
- ItemDescription,
- ItemHeader,
- ItemTitle,
-} from "@/components/ui/item";
-import { BadgeCheckIcon, ShieldIcon, SparkleIcon, UserIcon } from "lucide-react";
+import { Item, ItemContent, ItemHeader, ItemTitle } from "@/components/ui/item";
+import { UserIcon } from "lucide-react";
interface ProfileSummaryProps {
displayName: string;
@@ -49,9 +43,7 @@ export function ProfileSummary({
/>
)}
-
- {displayName}
-
+
{displayName}
{rank.name}
|
@@ -83,9 +75,7 @@ export function ProfileSummary({
>
{a.callsign ? `[${a.callsign}] ` : ""}
{a.name}
-
- ({a.type})
-
+ ({a.type})
))}
diff --git a/src/components/frontend/dashboard/QuickStats.tsx b/src/components/frontend/dashboard/QuickStats.tsx
index 15c60cc..47fd6d5 100644
--- a/src/components/frontend/dashboard/QuickStats.tsx
+++ b/src/components/frontend/dashboard/QuickStats.tsx
@@ -1,16 +1,5 @@
-import {
- Item,
- ItemContent,
- ItemDescription,
- ItemMedia,
- ItemTitle,
-} from "@/components/ui/item";
-import {
- Building2Icon,
- CrosshairIcon,
- ShieldIcon,
- UsersIcon,
-} from "lucide-react";
+import { Item, ItemContent, ItemDescription, ItemMedia, ItemTitle } from "@/components/ui/item";
+import { Building2Icon, CrosshairIcon, ShieldIcon, UsersIcon } from "lucide-react";
interface QuickStatsProps {
activeCampaigns: number;
diff --git a/src/components/frontend/dashboard/RecentEvents.tsx b/src/components/frontend/dashboard/RecentEvents.tsx
index 3aa0c7a..0ce2408 100644
--- a/src/components/frontend/dashboard/RecentEvents.tsx
+++ b/src/components/frontend/dashboard/RecentEvents.tsx
@@ -1,11 +1,5 @@
-import type { User, GameStructure } from "@/payload-types";
-import {
- Item,
- ItemContent,
- ItemDescription,
- ItemHeader,
- ItemTitle,
-} from "@/components/ui/item";
+import type { GameStructure, User } from "@/payload-types";
+import { Item, ItemContent, ItemHeader, ItemTitle } from "@/components/ui/item";
import { BookOpenTextIcon, ScrollTextIcon } from "lucide-react";
import { formatType } from "@/utils/event-log/formatType";
@@ -48,9 +42,7 @@ export function RecentEvents({ events, currentUserId }: RecentEventsProps) {
-
- No recent events recorded.
-
+ No recent events recorded.
);
@@ -71,12 +63,9 @@ export function RecentEvents({ events, currentUserId }: RecentEventsProps) {
typeof event.actor === "object" && event.actor !== null
? (event.actor as User)
: null;
- const actorId =
- actor?.id ??
- (typeof event.actor === "number" ? event.actor : null);
+ const actorId = actor?.id ?? (typeof event.actor === "number" ? event.actor : null);
const isYou = actorId != null && actorId === currentUserId;
- const actorName =
- actor?.displayName ?? actor?.username ?? null;
+ const actorName = actor?.displayName ?? actor?.username ?? null;
const actorLabel = isYou ? "You" : actorName;
const isNarrative = event.system === false;
@@ -90,9 +79,7 @@ export function RecentEvents({ events, currentUserId }: RecentEventsProps) {
)}
-
- {timeAgo(event.timestamp)}
-
+ {timeAgo(event.timestamp)}
{actorLabel && (
{actorLabel}
diff --git a/src/components/frontend/dashboard/UpcomingMissions.tsx b/src/components/frontend/dashboard/UpcomingMissions.tsx
index bd4c20c..c8faf92 100644
--- a/src/components/frontend/dashboard/UpcomingMissions.tsx
+++ b/src/components/frontend/dashboard/UpcomingMissions.tsx
@@ -1,13 +1,7 @@
import Link from "next/link";
import type { Campaign, Map } from "@/payload-types";
-import {
- Item,
- ItemContent,
- ItemDescription,
- ItemHeader,
- ItemTitle,
-} from "@/components/ui/item";
-import { CalendarDaysIcon, ChevronRightIcon, CrosshairIcon } from "lucide-react";
+import { Item, ItemContent, ItemHeader, ItemTitle } from "@/components/ui/item";
+import { ChevronRightIcon, CrosshairIcon } from "lucide-react";
interface MissionEntry {
id: number;
@@ -38,9 +32,7 @@ export function UpcomingMissions({ missions }: UpcomingMissionsProps) {
-
- No missions scheduled.
-
+ No missions scheduled.
);
@@ -98,9 +90,7 @@ export function UpcomingMissions({ missions }: UpcomingMissionsProps) {
{mission.name}
{campaign && (
-
- {campaign.name}
-
+
{campaign.name}
)}
diff --git a/src/components/frontend/flappy/FlappyBird.tsx b/src/components/frontend/flappy/FlappyBird.tsx
index 603dbfd..72c5c77 100644
--- a/src/components/frontend/flappy/FlappyBird.tsx
+++ b/src/components/frontend/flappy/FlappyBird.tsx
@@ -2,7 +2,7 @@
import { useCallback, useEffect, useRef, useState } from "react";
-import { submitFlappyScore, type FlappyStats } from "@/app/(frontend)/flappy/actions";
+import { type FlappyStats, submitFlappyScore } from "@/app/(frontend)/flappy/actions";
import { playFlap, playHit, playXp } from "./sounds";
const CANVAS_W = 420;
diff --git a/src/components/frontend/helpdesk/CreateTicketDialog.tsx b/src/components/frontend/helpdesk/CreateTicketDialog.tsx
index c6c36ac..9cf26af 100644
--- a/src/components/frontend/helpdesk/CreateTicketDialog.tsx
+++ b/src/components/frontend/helpdesk/CreateTicketDialog.tsx
@@ -226,8 +226,9 @@ export function CreateTicketDialog() {
Important!
- Marking this ticket as critical means it requires immediate attention and may
- impact the stability or functionality of the system.
+ Marking this ticket as critical means it{" "}
+ requires immediate attention and may impact the stability or
+ functionality of the system.
Please ensure that the description clearly outlines the issue and any potential
diff --git a/src/components/frontend/helpdesk/TicketCard.tsx b/src/components/frontend/helpdesk/TicketCard.tsx
index 14b9a0c..ceb3e61 100644
--- a/src/components/frontend/helpdesk/TicketCard.tsx
+++ b/src/components/frontend/helpdesk/TicketCard.tsx
@@ -9,10 +9,7 @@ import {
ItemHeader,
ItemTitle,
} from "@/components/ui/item";
-import {
- TicketBadges,
- TicketCategoryBadge,
-} from "@/components/frontend/helpdesk/TicketBadges";
+import { TicketBadges, TicketCategoryBadge } from "@/components/frontend/helpdesk/TicketBadges";
interface TicketCardProps {
readonly ticket: Ticket;
@@ -50,7 +47,10 @@ export function TicketCard({ ticket }: TicketCardProps) {
return (
- -
+
-
#{ticket.id}
@@ -59,9 +59,7 @@ export function TicketCard({ ticket }: TicketCardProps) {
{ticket.title}
-
- {description || "No description provided."}
-
+ {description || "No description provided."}
diff --git a/src/components/frontend/helpdesk/TicketDetail.tsx b/src/components/frontend/helpdesk/TicketDetail.tsx
index 7b416e8..d6060d0 100644
--- a/src/components/frontend/helpdesk/TicketDetail.tsx
+++ b/src/components/frontend/helpdesk/TicketDetail.tsx
@@ -9,16 +9,18 @@ import type { Ticket, User } from "@/payload-types";
import { Button } from "@/components/ui/button";
import { Item, ItemContent, ItemHeader, ItemTitle } from "@/components/ui/item";
import { Label } from "@/components/ui/label";
-import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
import { TicketBadges, TicketCategoryBadge } from "@/components/frontend/helpdesk/TicketBadges";
import { TicketTimeline } from "@/components/frontend/helpdesk/TicketTimeline";
-import { replyToTicket, updateTicketStatus, assignTicket } from "@/app/(frontend)/helpdesk/actions";
-import {
- CLOSING_STATUSES,
- TICKET_STATUSES,
- type TicketStatus,
-} from "@/lib/tickets/ticketMeta";
+import { assignTicket, replyToTicket, updateTicketStatus } from "@/app/(frontend)/helpdesk/actions";
+import { CLOSING_STATUSES, TICKET_STATUSES, type TicketStatus } from "@/lib/tickets/ticketMeta";
import { CalendarClockIcon, HistoryIcon, SendIcon, Settings2Icon } from "lucide-react";
interface AssigneeOption {
@@ -37,7 +39,7 @@ type UserRelation = number | User | null | undefined;
type ControlKind = "status" | "assignee";
function relationId(relation: UserRelation): number | null {
- return typeof relation === "object" && relation ? relation.id : relation ?? null;
+ return typeof relation === "object" && relation ? relation.id : (relation ?? null);
}
function displayName(relation: UserRelation, fallback: string): string {
@@ -62,7 +64,9 @@ function isTicketStatus(value: string): value is TicketStatus {
function MetaItem({ label, value }: { readonly label: string; readonly value: string }) {
return (
- {label}
+
+ {label}
+
{value}
);
@@ -89,13 +93,17 @@ export function TicketDetail({
const [cancelBusy, setCancelBusy] = useState(false);
const cancelTimerRef = useRef | null>(null);
- useEffect(() => () => {
- if (cancelTimerRef.current) clearTimeout(cancelTimerRef.current);
- }, []);
+ useEffect(
+ () => () => {
+ if (cancelTimerRef.current) clearTimeout(cancelTimerRef.current);
+ },
+ [],
+ );
const reporterName = displayName(ticket.reporter, "Unknown reporter");
const assigneeName = displayName(ticket.assignee, "Unassigned");
- const canCancel = relationId(ticket.reporter) === currentUserId && !CLOSING_STATUSES.includes(ticket.status);
+ const canCancel =
+ relationId(ticket.reporter) === currentUserId && !CLOSING_STATUSES.includes(ticket.status);
const descriptionText = convertLexicalToPlaintext({ data: ticket.description });
async function applyControl(kind: ControlKind) {
@@ -170,58 +178,169 @@ export function TicketDetail({
-
-
+
+
+
+
-
Description
+
+
+
+ Description
+
+
- {descriptionText ?
:
No description provided.
}
+ {descriptionText ? (
+
+ ) : (
+
No description provided.
+ )}
{isStaff && (
-
-
Staff controls
+
+
+
+ Staff controls
+
+
- Status
- { if (isTicketStatus(value)) setStatus(value); }} disabled={controlBusy !== null}>
-
- {TICKET_STATUSES.map((option) => {option.label} )}
-
- void applyControl("status")} disabled={controlBusy !== null}>{controlBusy === "status" ? "Applying..." : "Apply"}
-
- Assignee
-
-
- Unassigned {assigneeOptions.map((option) => {option.label} )}
-
- void applyControl("assignee")} disabled={controlBusy !== null}>{controlBusy === "assignee" ? "Applying..." : "Apply"}
-
- {controlError && {controlError}
}
+
+
Status
+
+ {
+ if (isTicketStatus(value)) setStatus(value);
+ }}
+ disabled={controlBusy !== null}
+ >
+
+
+
+
+ {TICKET_STATUSES.map((option) => (
+
+ {option.label}
+
+ ))}
+
+
+ void applyControl("status")}
+ disabled={controlBusy !== null}
+ >
+ {controlBusy === "status" ? "Applying..." : "Apply"}
+
+
+
+
+
Assignee
+
+
+
+
+
+
+ Unassigned
+ {assigneeOptions.map((option) => (
+
+ {option.label}
+
+ ))}
+
+
+ void applyControl("assignee")}
+ disabled={controlBusy !== null}
+ >
+ {controlBusy === "assignee" ? "Applying..." : "Apply"}
+
+
+
+ {controlError && (
+
+ {controlError}
+
+ )}
)}
-
Activity {ticket.activity?.length ?? 0} entries
-
+
+
+
+ Activity
+
+
+ {ticket.activity?.length ?? 0} entries
+
+
+ -
+
+
+
+
-
-
Reply
{isStaff ? "Reply as staff" : "Send reply"}
+
+
+
+ Reply
+
+
+ {isStaff ? "Reply as staff" : "Send reply"}
+
+
-
- {canCancel && {cancelBusy ? "Canceling..." : confirmCancel ? "Confirm cancel?" : "Cancel ticket"}
}
+ {canCancel && (
+
+
+ {cancelBusy ? "Canceling..." : confirmCancel ? "Confirm cancel?" : "Cancel ticket"}
+
+
+ )}
);
}
diff --git a/src/components/frontend/helpdesk/TicketTimeline.tsx b/src/components/frontend/helpdesk/TicketTimeline.tsx
index de43598..1ceb283 100644
--- a/src/components/frontend/helpdesk/TicketTimeline.tsx
+++ b/src/components/frontend/helpdesk/TicketTimeline.tsx
@@ -18,7 +18,7 @@ function resolveUser(relation: UserRelation): User | null {
}
function userId(relation: UserRelation): number | null {
- return typeof relation === "object" && relation ? relation.id : relation ?? null;
+ return typeof relation === "object" && relation ? relation.id : (relation ?? null);
}
function displayName(relation: UserRelation, fallback: string): string {
diff --git a/src/components/frontend/intelligence/CampaignCard.tsx b/src/components/frontend/intelligence/CampaignCard.tsx
index 554fdb8..0ad8398 100644
--- a/src/components/frontend/intelligence/CampaignCard.tsx
+++ b/src/components/frontend/intelligence/CampaignCard.tsx
@@ -5,8 +5,10 @@ import type { Campaign } from "@/payload-types";
import { cn } from "@/lib/utils";
import { ClockIcon, CrosshairIcon, UsersIcon } from "lucide-react";
-interface CampaignCardCampaign
- extends Pick {
+interface CampaignCardCampaign extends Pick<
+ Campaign,
+ "id" | "name" | "summary" | "status" | "startDate" | "endDate"
+> {
readonly coverImageUrl: string | null;
readonly missionCount: number;
readonly avgPlayers: number;
diff --git a/src/components/frontend/intelligence/FactionCard.tsx b/src/components/frontend/intelligence/FactionCard.tsx
index ac0ddf4..733d2e3 100644
--- a/src/components/frontend/intelligence/FactionCard.tsx
+++ b/src/components/frontend/intelligence/FactionCard.tsx
@@ -76,9 +76,7 @@ export function FactionCard({ faction }: FactionCardProps) {
? convertLexicalToPlaintext({ data: faction.description })
: null;
const truncatedDescription =
- description && description.length > 150
- ? description.slice(0, 150) + "…"
- : description;
+ description && description.length > 150 ? description.slice(0, 150) + "…" : description;
const campaignCount = faction.associatedCampaigns?.length ?? 0;
const reputation = faction.reputation ?? 0;
@@ -105,9 +103,7 @@ export function FactionCard({ faction }: FactionCardProps) {
{truncatedDescription && (
-
- {truncatedDescription}
-
+
{truncatedDescription}
)}
{faction.playableFaction && (
@@ -134,9 +130,7 @@ export function FactionCard({ faction }: FactionCardProps) {
-
- {truncatedDescription ?? "No description provided."}
-
+ {truncatedDescription ?? "No description provided."}
);
diff --git a/src/components/frontend/intelligence/MissionAttendance.tsx b/src/components/frontend/intelligence/MissionAttendance.tsx
index 0ecd390..99fd7b7 100644
--- a/src/components/frontend/intelligence/MissionAttendance.tsx
+++ b/src/components/frontend/intelligence/MissionAttendance.tsx
@@ -111,25 +111,19 @@ export function MissionAttendance({
)}
{currentUserId !== null && attendanceClosed && (
-
- Attendance is closed for this mission.
-
+ Attendance is closed for this mission.
)}
{error && {error}
}
{states.map((response) => {
const meta = stateMeta[response];
const Icon = meta.icon;
- const people = groups[response]
- .slice()
- .sort((a, b) => a.name.localeCompare(b.name));
+ const people = groups[response].slice().sort((a, b) => a.name.localeCompare(b.name));
return (
{meta.label}
-
- ({people.length})
-
+ ({people.length})
{people.length > 0 && (
diff --git a/src/components/frontend/intelligence/MissionBriefingSection.tsx b/src/components/frontend/intelligence/MissionBriefingSection.tsx
index daa2323..c5bc9f6 100644
--- a/src/components/frontend/intelligence/MissionBriefingSection.tsx
+++ b/src/components/frontend/intelligence/MissionBriefingSection.tsx
@@ -36,9 +36,7 @@ export function MissionBriefingSection({ briefing }: MissionBriefingSectionProps
-
- No briefing available.
-
+ No briefing available.
);
diff --git a/src/components/frontend/intelligence/MissionFactions.tsx b/src/components/frontend/intelligence/MissionFactions.tsx
index 90a3007..0281789 100644
--- a/src/components/frontend/intelligence/MissionFactions.tsx
+++ b/src/components/frontend/intelligence/MissionFactions.tsx
@@ -29,9 +29,7 @@ export function MissionFactions({ factionsInvolved }: MissionFactionsProps) {
-
- No factions assigned.
-
+ No factions assigned.
);
@@ -47,15 +45,12 @@ export function MissionFactions({ factionsInvolved }: MissionFactionsProps) {
{factionsInvolved.map((entry, index) => {
- const faction =
- typeof entry.faction === "object" ? entry.faction : null;
+ const faction = typeof entry.faction === "object" ? entry.faction : null;
return (
-
-
- {faction?.name ?? `Faction #${entry.faction}`}
-
+ {faction?.name ?? `Faction #${entry.faction}`}
@@ -64,9 +59,7 @@ export function MissionFactions({ factionsInvolved }: MissionFactionsProps) {
{faction && (
-
- {faction.type}
-
+ {faction.type}
)}
diff --git a/src/components/frontend/intelligence/MissionFilters.tsx b/src/components/frontend/intelligence/MissionFilters.tsx
index 53b348f..6696a8c 100644
--- a/src/components/frontend/intelligence/MissionFilters.tsx
+++ b/src/components/frontend/intelligence/MissionFilters.tsx
@@ -1,10 +1,8 @@
"use client";
-import { useState, useMemo } from "react";
+import { useState } from "react";
import type { Campaign } from "@/payload-types";
-import { MissionCard } from "@/components/frontend/intelligence/MissionCard";
import { Button } from "@/components/ui/button";
-import { cn } from "@/lib/utils";
interface MissionFiltersProps {
campaigns: Campaign[];
@@ -21,20 +19,9 @@ const operationTypes = [
{ label: "External", value: "external" },
];
-const statuses = [
- "Concept",
- "Planning",
- "Ready",
- "Scheduled",
- "Active",
- "Completed",
- "Cancelled",
-];
+const statuses = ["Concept", "Planning", "Ready", "Scheduled", "Active", "Completed", "Cancelled"];
-export function MissionFilters({
- campaigns,
- onFilterChange,
-}: MissionFiltersProps) {
+export function MissionFilters({ campaigns, onFilterChange }: MissionFiltersProps) {
const [campaignId, setCampaignId] = useState
(null);
const [operationType, setOperationType] = useState(null);
const [status, setStatus] = useState(null);
diff --git a/src/components/frontend/intelligence/MissionHeader.tsx b/src/components/frontend/intelligence/MissionHeader.tsx
index 84a4b84..e5432e0 100644
--- a/src/components/frontend/intelligence/MissionHeader.tsx
+++ b/src/components/frontend/intelligence/MissionHeader.tsx
@@ -23,12 +23,7 @@ const operationTypeColors: Record = {
external: "bg-purple-500/20 text-purple-400",
};
-export function MissionHeader({
- name,
- codeName,
- operationType,
- status,
-}: MissionHeaderProps) {
+export function MissionHeader({ name, codeName, operationType, status }: MissionHeaderProps) {
return (
diff --git a/src/components/frontend/intelligence/MissionLogistics.tsx b/src/components/frontend/intelligence/MissionLogistics.tsx
index 9a5f505..80a445d 100644
--- a/src/components/frontend/intelligence/MissionLogistics.tsx
+++ b/src/components/frontend/intelligence/MissionLogistics.tsx
@@ -110,9 +110,7 @@ export function MissionLogistics({ logisticsAndSupport }: MissionLogisticsProps)
{assetsAvailable!.map((asset, index) => (
-
- {asset.assetType}
-
+ {asset.assetType}
{asset.assetCount ?? "—"}
@@ -134,9 +132,7 @@ export function MissionLogistics({ logisticsAndSupport }: MissionLogisticsProps)
)}
{!hasInsertion && !hasExtraction && !hasAssets && (
-
- No logistics details provided.
-
+ No logistics details provided.
)}
diff --git a/src/components/frontend/intelligence/MissionRoles.tsx b/src/components/frontend/intelligence/MissionRoles.tsx
index bdcfb3e..be449f7 100644
--- a/src/components/frontend/intelligence/MissionRoles.tsx
+++ b/src/components/frontend/intelligence/MissionRoles.tsx
@@ -35,9 +35,7 @@ export function MissionRoles({ roles, notes, maxPlayers }: MissionRolesProps) {
-
- No roles defined.
-
+ No roles defined.
);
@@ -51,8 +49,7 @@ export function MissionRoles({ roles, notes, maxPlayers }: MissionRolesProps) {
Roles
- Max Players:{" "}
- {maxPlayers}
+ Max Players: {maxPlayers}
@@ -90,8 +87,7 @@ export function MissionRoles({ roles, notes, maxPlayers }: MissionRolesProps) {
: "—"}
- {typeof role.minimumRank === "object" &&
- role.minimumRank !== null
+ {typeof role.minimumRank === "object" && role.minimumRank !== null
? role.minimumRank.name
: typeof role.minimumRank === "number"
? `#${role.minimumRank}`
@@ -102,9 +98,7 @@ export function MissionRoles({ roles, notes, maxPlayers }: MissionRolesProps) {
- {notes && (
-
{notes}
- )}
+ {notes &&
{notes}
}
);
}
diff --git a/src/components/frontend/intelligence/MissionsList.tsx b/src/components/frontend/intelligence/MissionsList.tsx
index 1e83a61..58624e4 100644
--- a/src/components/frontend/intelligence/MissionsList.tsx
+++ b/src/components/frontend/intelligence/MissionsList.tsx
@@ -1,7 +1,7 @@
"use client";
-import { useState, useMemo } from "react";
-import type { Mission, Campaign, Map } from "@/payload-types";
+import { useMemo, useState } from "react";
+import type { Campaign, Mission } from "@/payload-types";
import { MissionCard } from "@/components/frontend/intelligence/MissionCard";
import { MissionFilters } from "@/components/frontend/intelligence/MissionFilters";
@@ -29,10 +29,7 @@ export function MissionsList({ missions, campaigns }: MissionsListProps) {
if (filters.operationType && m.operationType !== filters.operationType) {
return false;
}
- if (
- filters.status &&
- m.ownershipAndStatus.status !== filters.status
- ) {
+ if (filters.status && m.ownershipAndStatus.status !== filters.status) {
return false;
}
return true;
diff --git a/src/components/frontend/locker/AddLockerItemDialog.tsx b/src/components/frontend/locker/AddLockerItemDialog.tsx
index f09c402..0486c0c 100644
--- a/src/components/frontend/locker/AddLockerItemDialog.tsx
+++ b/src/components/frontend/locker/AddLockerItemDialog.tsx
@@ -81,9 +81,7 @@ export function AddLockerItemDialog({ assets }: AddLockerItemDialogProps) {
Add Item to Locker
-
- Provision an asset into this member's locker.
-
+ Provision an asset into this member's locker.
diff --git a/src/components/frontend/locker/EquipmentEditorDialog.tsx b/src/components/frontend/locker/EquipmentEditorDialog.tsx
index fee2e87..51d7f5d 100644
--- a/src/components/frontend/locker/EquipmentEditorDialog.tsx
+++ b/src/components/frontend/locker/EquipmentEditorDialog.tsx
@@ -25,8 +25,8 @@ import {
getAssetTypeLabel,
getAttachmentOptionsForItem,
getSkinOptionsForItem,
- resolveAssetId,
type LockerGridItem,
+ resolveAssetId,
} from "@/lib/locker";
import { getAssetIcon } from "./LockerGrid";
import {
@@ -61,7 +61,7 @@ export function EquipmentEditorDialog({
}, [item, open]);
const resolvedItem = useMemo(
- () => (item ? items.find((i) => i.id === item.id) ?? item : null),
+ () => (item ? (items.find((i) => i.id === item.id) ?? item) : null),
[item, items],
);
@@ -167,18 +167,22 @@ export function EquipmentEditorDialog({
Customize {displayName}
- Install attachments, apply a skin, and give your {getAssetTypeLabel(activeItem.asset.assetType).toLowerCase()} a name.
- Installing consumes the item from your locker grid; removing returns it.
+ Install attachments, apply a skin, and give your{" "}
+ {getAssetTypeLabel(activeItem.asset.assetType).toLowerCase()} a name. Installing
+ consumes the item from your locker grid; removing returns it.
-
{getAssetIcon(activeItem.asset.assetType, "size-6")}
+
+ {getAssetIcon(activeItem.asset.assetType, "size-6")}
+
{activeItem.asset.name}
- {getAssetTypeLabel(activeItem.asset.assetType)} · {activeItem.quantity.toLocaleString()}×
+ {getAssetTypeLabel(activeItem.asset.assetType)} ·{" "}
+ {activeItem.quantity.toLocaleString()}×
@@ -193,7 +197,10 @@ export function EquipmentEditorDialog({
? [{ assetId: installed.id, quantity: 0, asset: installed }, ...options]
: options;
return (
-
+
{category.label}
@@ -219,8 +230,8 @@ export function EquipmentEditorDialog({
);
})}
- Compatible with this weapon based on its catalog entry. Replacing an attachment returns
- the old one to your locker.
+ Compatible with this weapon based on its catalog entry. Replacing an attachment
+ returns the old one to your locker.
)}
@@ -236,14 +247,17 @@ export function EquipmentEditorDialog({
>
None
{mergedSkinOptions.map((o) => (
- {o.asset.name} · {o.quantity > 0 ? `${o.quantity.toLocaleString()}×` : "applied"}
+ {o.asset.name} ·{" "}
+ {o.quantity > 0 ? `${o.quantity.toLocaleString()}×` : "applied"}
))}
@@ -251,7 +265,8 @@ export function EquipmentEditorDialog({
{installedSkin && (
- Currently applied: {installedSkin.name}. Choosing another skin returns the old one to your locker.
+ Currently applied: {installedSkin.name}. Choosing another skin returns the old one
+ to your locker.
)}
diff --git a/src/components/frontend/locker/LoadoutCard.tsx b/src/components/frontend/locker/LoadoutCard.tsx
index 0dcd11e..9f7edf1 100644
--- a/src/components/frontend/locker/LoadoutCard.tsx
+++ b/src/components/frontend/locker/LoadoutCard.tsx
@@ -4,7 +4,12 @@ import { CheckCircle2Icon, PencilIcon, TrashIcon } from "lucide-react";
import type { Asset, Loadout } from "@/payload-types";
import { Button } from "@/components/ui/button";
import { Item, ItemContent, ItemHeader, ItemTitle } from "@/components/ui/item";
-import { getAssetTypeLabel, LOADOUT_SLOTS, type LockerGridItem, type LoadoutSlots } from "@/lib/locker";
+import {
+ getAssetTypeLabel,
+ LOADOUT_SLOTS,
+ type LoadoutSlots,
+ type LockerGridItem,
+} from "@/lib/locker";
function resolveSlotValue(value: LoadoutSlots[keyof LoadoutSlots]): Asset | null {
if (value == null) return null;
@@ -48,11 +53,7 @@ export function LoadoutCard({ loadout, ownedItems, onEdit, onDelete, onEquip }:
{loadout.description}
)}
-
+
{loadout.equipped ? "Unequip" : "Equip"}
@@ -80,7 +81,9 @@ export function LoadoutCard({ loadout, ownedItems, onEdit, onDelete, onEquip }:
>
{slot.label}:
{slotDisplayName(asset, ownedItems)}
- ({getAssetTypeLabel(asset.assetType)})
+
+ ({getAssetTypeLabel(asset.assetType)})
+
);
})}
diff --git a/src/components/frontend/locker/LoadoutEditor.tsx b/src/components/frontend/locker/LoadoutEditor.tsx
index cbb9f93..c90f32d 100644
--- a/src/components/frontend/locker/LoadoutEditor.tsx
+++ b/src/components/frontend/locker/LoadoutEditor.tsx
@@ -22,16 +22,8 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
-import {
- LOADOUT_SLOTS,
- type LockerGridItem,
- type LoadoutSlots,
-} from "@/lib/locker";
-import {
- createLoadout,
- setLoadoutSlot,
- updateLoadout,
-} from "@/app/(frontend)/locker/actions";
+import { LOADOUT_SLOTS, type LoadoutSlots, type LockerGridItem } from "@/lib/locker";
+import { createLoadout, setLoadoutSlot, updateLoadout } from "@/app/(frontend)/locker/actions";
interface LoadoutEditorProps {
mode: "create" | "edit";
diff --git a/src/components/frontend/locker/LockerGrid.tsx b/src/components/frontend/locker/LockerGrid.tsx
index 94bf18b..0a1310a 100644
--- a/src/components/frontend/locker/LockerGrid.tsx
+++ b/src/components/frontend/locker/LockerGrid.tsx
@@ -3,18 +3,19 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
DndContext,
+ type DragEndEvent,
+ type DragMoveEvent,
DragOverlay,
+ type DragStartEvent,
PointerSensor,
useDraggable,
useSensor,
useSensors,
- type DragStartEvent,
- type DragMoveEvent,
- type DragEndEvent,
} from "@dnd-kit/core";
import { CSS } from "@dnd-kit/utilities";
import {
Backpack,
+ Crosshair,
FlaskConical,
Gem,
Glasses,
@@ -27,23 +28,22 @@ import {
Palette,
Shield,
Shirt,
+ Tag,
Target,
Wrench,
- Crosshair,
- Tag,
} from "lucide-react";
import type { Asset } from "@/payload-types";
import {
ATTACHMENT_CATEGORIES,
findLockerMergeTarget,
getAssetTypeColor,
- getLockerItemFootprint,
getLockerGridOccupancy,
+ getLockerItemFootprint,
isAttachmentCompatible,
isLockerPlacementValid,
- resolveAssetId,
type LockerGridItem,
type LockerItemEquipment,
+ resolveAssetId,
} from "@/lib/locker";
import { GridCell } from "@/components/frontend/storage/GridCell";
import type { ActionResult } from "@/app/(frontend)/locker/actions";
@@ -99,7 +99,9 @@ function assetThumbnailUrl(asset: Asset): string | null {
function itemContainsCell(item: LockerGridItem, x: number, y: number): boolean {
const fp = getLockerItemFootprint(item.asset, item.rotated);
- return x >= item.gridX && x < item.gridX + fp.width && y >= item.gridY && y < item.gridY + fp.height;
+ return (
+ x >= item.gridX && x < item.gridX + fp.width && y >= item.gridY && y < item.gridY + fp.height
+ );
}
export function LockerItemFace({
@@ -159,7 +161,9 @@ export function LockerItemFace({
{thumb ? (
) : (
- {getAssetIcon(asset.assetType, "size-5")}
+
+ {getAssetIcon(asset.assetType, "size-5")}
+
)}
{displayName}
@@ -467,11 +471,11 @@ export function LockerGrid({
}
if (onAttachItem) {
- const isAttachment = ATTACHMENT_CATEGORIES.some((c) => c.assetType === item.asset.assetType);
+ const isAttachment = ATTACHMENT_CATEGORIES.some(
+ (c) => c.assetType === item.asset.assetType,
+ );
const targetItem =
- items.find(
- (it) => it.id !== item.id && itemContainsCell(it, targetX, targetY),
- ) ?? null;
+ items.find((it) => it.id !== item.id && itemContainsCell(it, targetX, targetY)) ?? null;
if (
isAttachment &&
targetItem?.asset.assetType === "weapon" &&
@@ -508,7 +512,16 @@ export function LockerGrid({
await onMoveItem(item.id, targetX, targetY, pendingRotated);
}
},
- [items, gridWidth, gridHeight, onMoveItem, onMergeStacks, onAttachItem, cellSize, pendingRotated],
+ [
+ items,
+ gridWidth,
+ gridHeight,
+ onMoveItem,
+ onMergeStacks,
+ onAttachItem,
+ cellSize,
+ pendingRotated,
+ ],
);
const handleBackgroundClick = useCallback(() => {
@@ -516,7 +529,12 @@ export function LockerGrid({
}, [onSelectItem]);
return (
-
+
{layout.label}
-
- {asset ? asset.name : "Empty"}
-
+ {asset ? asset.name : "Empty"}
diff --git a/src/components/frontend/locker/WardrobePanel.tsx b/src/components/frontend/locker/WardrobePanel.tsx
index 43efc81..b41a937 100644
--- a/src/components/frontend/locker/WardrobePanel.tsx
+++ b/src/components/frontend/locker/WardrobePanel.tsx
@@ -15,12 +15,12 @@ import {
import { Wardrobe } from "./Wardrobe";
import { WardrobeSlotPicker } from "./WardrobeSlotPicker";
import {
- WARDROBE_SLOT_LAYOUT,
getAssetTypeLabel,
- getWardrobeSlot,
getLoadoutSlot,
+ getWardrobeSlot,
type LoadoutSlots,
type LockerGridItem,
+ WARDROBE_SLOT_LAYOUT,
} from "@/lib/locker";
import { equipLoadout, setLoadoutSlot } from "@/app/(frontend)/locker/actions";
import { getAssetIcon } from "./LockerGrid";
@@ -75,7 +75,7 @@ export function WardrobePanel({ userName, loadouts, equippedLoadout, items }: Wa
const pickerLayout = pickerSlot ? getWardrobeSlot(pickerSlot) : null;
const pickerConfig = pickerSlot ? getLoadoutSlot(pickerSlot) : null;
- const pickerValue = pickerSlot ? slots[pickerSlot as keyof LoadoutSlots] ?? null : null;
+ const pickerValue = pickerSlot ? (slots[pickerSlot as keyof LoadoutSlots] ?? null) : null;
const filled = equippedLoadout
? WARDROBE_SLOT_LAYOUT.filter((layout) => slots[layout.name] != null)
@@ -89,9 +89,7 @@ export function WardrobePanel({ userName, loadouts, equippedLoadout, items }: Wa
setPickerSlot(name)}
/>
@@ -124,7 +122,9 @@ export function WardrobePanel({ userName, loadouts, equippedLoadout, items }: Wa
>
@@ -149,7 +149,8 @@ export function WardrobePanel({ userName, loadouts, equippedLoadout, items }: Wa
{!equippedLoadout && (
- Choose a loadout above — it becomes the active loadout that the wardrobe reflects.
+ Choose a loadout above — it becomes the active loadout that the wardrobe
+ reflects.
)}
@@ -178,7 +179,9 @@ export function WardrobePanel({ userName, loadouts, equippedLoadout, items }: Wa
onClick={() => setPickerSlot(layout.name)}
className="flex items-center gap-2 rounded-md border border-border px-3 py-2 text-left text-sm hover:bg-muted transition-colors"
>
-
{getAssetIcon(asset.assetType, "size-4")}
+
+ {getAssetIcon(asset.assetType, "size-4")}
+
{layout.label}
diff --git a/src/components/frontend/locker/WardrobeSlotPicker.tsx b/src/components/frontend/locker/WardrobeSlotPicker.tsx
index e5e47da..03bea91 100644
--- a/src/components/frontend/locker/WardrobeSlotPicker.tsx
+++ b/src/components/frontend/locker/WardrobeSlotPicker.tsx
@@ -103,9 +103,7 @@ export function WardrobeSlotPicker({
onClick={() => handlePick(entry.assetId)}
className={cn(
"flex items-center gap-3 rounded-md border px-3 py-2 text-left text-sm transition-colors",
- selected
- ? "border-primary bg-primary/10"
- : "border-border hover:bg-muted",
+ selected ? "border-primary bg-primary/10" : "border-border hover:bg-muted",
)}
>
{getAssetIcon(entry.asset.assetType)}
@@ -122,11 +120,7 @@ export function WardrobeSlotPicker({
- onOpenChange(false)}
- >
+ onOpenChange(false)}>
Cancel
{currentId != null && (
diff --git a/src/components/frontend/logistics/ResourceCard.tsx b/src/components/frontend/logistics/ResourceCard.tsx
index ef50807..15d738d 100644
--- a/src/components/frontend/logistics/ResourceCard.tsx
+++ b/src/components/frontend/logistics/ResourceCard.tsx
@@ -86,15 +86,12 @@ const rarityLabels: Record = {
};
export function ResourceCard({ resource }: ResourceCardProps) {
- const icon =
- resource.icon && typeof resource.icon === "object" ? resource.icon : null;
+ const icon = resource.icon && typeof resource.icon === "object" ? resource.icon : null;
const description = resource.description
? convertLexicalToPlaintext({ data: resource.description })
: null;
const truncatedDescription =
- description && description.length > 150
- ? description.slice(0, 150) + "…"
- : description;
+ description && description.length > 150 ? description.slice(0, 150) + "…" : description;
return (
@@ -106,11 +103,7 @@ export function ResourceCard({ resource }: ResourceCardProps) {
{icon ? (
-
+
) : resource.color ? (
@@ -123,9 +116,7 @@ export function ResourceCard({ resource }: ResourceCardProps) {
{typeIcons[resource.type]}
)}
-
- {resource.codeName}
-
+ {resource.codeName}
{resource.name}
@@ -145,9 +136,7 @@ export function ResourceCard({ resource }: ResourceCardProps) {
{truncatedDescription && (
-
- {truncatedDescription}
-
+
{truncatedDescription}
)}
diff --git a/src/components/frontend/logistics/ShipmentActions.tsx b/src/components/frontend/logistics/ShipmentActions.tsx
index 142f629..d7b70fd 100644
--- a/src/components/frontend/logistics/ShipmentActions.tsx
+++ b/src/components/frontend/logistics/ShipmentActions.tsx
@@ -26,8 +26,7 @@ export function ShipmentActions({ shipment }: ShipmentActionsProps) {
const [error, setError] = useState(null);
const [showCancelDialog, setShowCancelDialog] = useState(false);
- const isActive =
- shipment.status === "dispatched" || shipment.status === "in_transit";
+ const isActive = shipment.status === "dispatched" || shipment.status === "in_transit";
if (!isActive) return null;
@@ -49,10 +48,7 @@ export function ShipmentActions({ shipment }: ShipmentActionsProps) {
setLoading(true);
setError(null);
- const result = await toggleAutoReturn(
- shipment.id,
- !shipment.autoReturn,
- );
+ const result = await toggleAutoReturn(shipment.id, !shipment.autoReturn);
if (result.success) {
router.refresh();
} else {
@@ -63,15 +59,8 @@ export function ShipmentActions({ shipment }: ShipmentActionsProps) {
return (
- {error && (
-
{error}
- )}
-
+ {error && {error} }
+
{shipment.autoReturn ? "Disable" : "Enable"} Auto-Return
@@ -86,23 +75,15 @@ export function ShipmentActions({ shipment }: ShipmentActionsProps) {
Cancel Shipment
- Are you sure you want to cancel this shipment? All cargo will be
- returned to the origin structure and the vehicle will be parked
- at the origin.
+ Are you sure you want to cancel this shipment? All cargo will be returned to the
+ origin structure and the vehicle will be parked at the origin.
- setShowCancelDialog(false)}
- >
+ setShowCancelDialog(false)}>
Keep Shipment
-
+
{loading ? "Cancelling..." : "Cancel Shipment"}
diff --git a/src/components/frontend/logistics/ShipmentCard.tsx b/src/components/frontend/logistics/ShipmentCard.tsx
index adbc1af..3f41487 100644
--- a/src/components/frontend/logistics/ShipmentCard.tsx
+++ b/src/components/frontend/logistics/ShipmentCard.tsx
@@ -1,7 +1,7 @@
"use client";
import Link from "next/link";
-import type { Shipment, GameStructure, GameVehicle, Vehicle } from "@/payload-types";
+import type { GameStructure, GameVehicle, Shipment, Vehicle } from "@/payload-types";
import {
Item,
ItemContent,
@@ -38,27 +38,19 @@ export function ShipmentCard({ shipment }: ShipmentCardProps) {
? (shipment.destination as GameStructure)
: null;
const vehicle =
- typeof shipment.transportVehicle === "object" &&
- shipment.transportVehicle !== null
+ typeof shipment.transportVehicle === "object" && shipment.transportVehicle !== null
? (shipment.transportVehicle as GameVehicle & { type: Vehicle })
: null;
- const vehicleType =
- vehicle && typeof vehicle.type === "object" ? vehicle.type : null;
+ const vehicleType = vehicle && typeof vehicle.type === "object" ? vehicle.type : null;
- const dispatchedAt = shipment.dispatchedAt
- ? new Date(shipment.dispatchedAt)
- : null;
- const estimatedArrival = shipment.estimatedArrival
- ? new Date(shipment.estimatedArrival)
- : null;
+ const dispatchedAt = shipment.dispatchedAt ? new Date(shipment.dispatchedAt) : null;
+ const estimatedArrival = shipment.estimatedArrival ? new Date(shipment.estimatedArrival) : null;
- const isActive =
- shipment.status === "dispatched" || shipment.status === "in_transit";
+ const isActive = shipment.status === "dispatched" || shipment.status === "in_transit";
const cargoCount = shipment.cargo?.length ?? 0;
- const totalItems =
- shipment.cargo?.reduce((sum, c) => sum + (c.amount ?? 0), 0) ?? 0;
+ const totalItems = shipment.cargo?.reduce((sum, c) => sum + (c.amount ?? 0), 0) ?? 0;
return (
@@ -83,16 +75,13 @@ export function ShipmentCard({ shipment }: ShipmentCardProps) {
>
{shipment.status === "in_transit"
? "In Transit"
- : shipment.status.charAt(0).toUpperCase() +
- shipment.status.slice(1)}
+ : shipment.status.charAt(0).toUpperCase() + shipment.status.slice(1)}
-
- {origin?.name ?? `Structure #${shipment.origin}`}
-
+
{origin?.name ?? `Structure #${shipment.origin}`}
{destination?.name ?? `Structure #${shipment.destination}`}
@@ -108,8 +97,7 @@ export function ShipmentCard({ shipment }: ShipmentCardProps) {
- {cargoCount} item type{cargoCount !== 1 ? "s" : ""} ({totalItems}{" "}
- total)
+ {cargoCount} item type{cargoCount !== 1 ? "s" : ""} ({totalItems} total)
{shipment.distance?.toFixed(1)} units · {shipment.fuelCost} fuel
@@ -122,7 +110,7 @@ export function ShipmentCard({ shipment }: ShipmentCardProps) {
? `Completed ${shipment.completedAt ? new Date(shipment.completedAt).toLocaleDateString() : ""}`
: shipment.status === "cancelled"
? "Cancelled"
- : shipment.failureReason ?? (isActive ? "In transit" : shipment.status)}
+ : (shipment.failureReason ?? (isActive ? "In transit" : shipment.status))}
View details
diff --git a/src/components/frontend/logistics/ShipmentProgressBar.tsx b/src/components/frontend/logistics/ShipmentProgressBar.tsx
index 0afa289..fcc89e0 100644
--- a/src/components/frontend/logistics/ShipmentProgressBar.tsx
+++ b/src/components/frontend/logistics/ShipmentProgressBar.tsx
@@ -1,16 +1,13 @@
"use client";
-import { useState, useEffect } from "react";
+import { useEffect, useState } from "react";
interface ShipmentProgressBarProps {
dispatchedAt: string;
estimatedArrival: string;
}
-export function ShipmentProgressBar({
- dispatchedAt,
- estimatedArrival,
-}: ShipmentProgressBarProps) {
+export function ShipmentProgressBar({ dispatchedAt, estimatedArrival }: ShipmentProgressBarProps) {
const [progress, setProgress] = useState(0);
const [countdown, setCountdown] = useState("");
@@ -52,9 +49,7 @@ export function ShipmentProgressBar({
style={{ width: `${Math.min(progress, 100)}%` }}
/>
-
- {Math.floor(progress)}%
-
+ {Math.floor(progress)}%
{countdown} remaining
diff --git a/src/components/frontend/logistics/ShipmentToasts.tsx b/src/components/frontend/logistics/ShipmentToasts.tsx
index 9fd5c38..699bc1c 100644
--- a/src/components/frontend/logistics/ShipmentToasts.tsx
+++ b/src/components/frontend/logistics/ShipmentToasts.tsx
@@ -3,7 +3,7 @@
import { useCallback, useEffect, useState } from "react";
import Link from "next/link";
import { ArrowRightIcon, ChevronDownIcon, ChevronUpIcon, TruckIcon } from "lucide-react";
-import type { Shipment, GameStructure, GameVehicle, Vehicle } from "@/payload-types";
+import type { GameStructure, GameVehicle, Shipment, Vehicle } from "@/payload-types";
import { ShipmentProgressBar } from "@/components/frontend/logistics/ShipmentProgressBar";
import { useGameTick } from "@/hooks/useGameTick";
@@ -81,9 +81,7 @@ export function ShipmentToasts() {
Active Shipments
-
- {count}
-
+ {count}
{vehicleType && (
-
- {vehicleType.name}
-
+
{vehicleType.name}
)}
{shipment.dispatchedAt && shipment.estimatedArrival && (
diff --git a/src/components/frontend/logistics/ShipmentsList.tsx b/src/components/frontend/logistics/ShipmentsList.tsx
index ec088e6..60901a0 100644
--- a/src/components/frontend/logistics/ShipmentsList.tsx
+++ b/src/components/frontend/logistics/ShipmentsList.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useState, useMemo } from "react";
+import { useMemo, useState } from "react";
import type { Shipment } from "@/payload-types";
import { ShipmentCard } from "@/components/frontend/logistics/ShipmentCard";
import { Button } from "@/components/ui/button";
diff --git a/src/components/frontend/logistics/VehicleCard.tsx b/src/components/frontend/logistics/VehicleCard.tsx
index 950f287..56a54ae 100644
--- a/src/components/frontend/logistics/VehicleCard.tsx
+++ b/src/components/frontend/logistics/VehicleCard.tsx
@@ -10,12 +10,7 @@ import {
ItemTitle,
} from "@/components/ui/item";
import { convertLexicalToPlaintext } from "@payloadcms/richtext-lexical/plaintext";
-import {
- ChevronRightIcon,
- HeartIcon,
- TimerIcon,
- TruckIcon,
-} from "lucide-react";
+import { ChevronRightIcon, HeartIcon, TimerIcon, TruckIcon } from "lucide-react";
interface VehicleCardProps {
vehicle: {
@@ -67,15 +62,12 @@ const categoryLabels: Record
= {
};
export function VehicleCard({ vehicle }: VehicleCardProps) {
- const image =
- vehicle.image && typeof vehicle.image === "object" ? vehicle.image : null;
+ const image = vehicle.image && typeof vehicle.image === "object" ? vehicle.image : null;
const description = vehicle.description
? convertLexicalToPlaintext({ data: vehicle.description })
: null;
const truncatedDescription =
- description && description.length > 150
- ? description.slice(0, 150) + "…"
- : description;
+ description && description.length > 150 ? description.slice(0, 150) + "…" : description;
const materialCount = vehicle.construction?.materials?.length ?? 0;
const constructionTime = vehicle.construction?.constructionTime;
@@ -92,11 +84,7 @@ export function VehicleCard({ vehicle }: VehicleCardProps) {
{image ? (
-
+
) : (
@@ -105,9 +93,7 @@ export function VehicleCard({ vehicle }: VehicleCardProps) {
)}
{vehicle.classname && (
-
- {vehicle.classname}
-
+ {vehicle.classname}
)}
{vehicle.name}
@@ -126,9 +112,7 @@ export function VehicleCard({ vehicle }: VehicleCardProps) {
{truncatedDescription && (
-
- {truncatedDescription}
-
+
{truncatedDescription}
)}
{materialCount > 0 && (
@@ -136,8 +120,7 @@ export function VehicleCard({ vehicle }: VehicleCardProps) {
{materialCount} material{materialCount !== 1 ? "s" : ""}
{constructionTime != null && (
<>
- ,{" "}
-
+ ,
{constructionTime} tick{constructionTime !== 1 ? "s" : ""}
>
)}
@@ -158,9 +141,7 @@ export function VehicleCard({ vehicle }: VehicleCardProps) {
-
- {truncatedDescription ?? vehicle.classname ?? "Vehicle"}
-
+ {truncatedDescription ?? vehicle.classname ?? "Vehicle"}
View details
diff --git a/src/components/frontend/logistics/VehicleFilters.tsx b/src/components/frontend/logistics/VehicleFilters.tsx
index af9bb74..eeecd56 100644
--- a/src/components/frontend/logistics/VehicleFilters.tsx
+++ b/src/components/frontend/logistics/VehicleFilters.tsx
@@ -2,7 +2,6 @@
import { useState } from "react";
import { Button } from "@/components/ui/button";
-import { cn } from "@/lib/utils";
interface VehicleFiltersProps {
categories: string[];
diff --git a/src/components/frontend/market/ChatBubbleList.tsx b/src/components/frontend/market/ChatBubbleList.tsx
index 3d30d60..0ce76d4 100644
--- a/src/components/frontend/market/ChatBubbleList.tsx
+++ b/src/components/frontend/market/ChatBubbleList.tsx
@@ -41,11 +41,7 @@ export function ChatBubbleList({
{bubble.from === "buyer" ? buyerLabel : sellerLabel}
-
+
{bubble.text}
diff --git a/src/components/frontend/market/CreateListingDialog.tsx b/src/components/frontend/market/CreateListingDialog.tsx
index 3205913..661c340 100644
--- a/src/components/frontend/market/CreateListingDialog.tsx
+++ b/src/components/frontend/market/CreateListingDialog.tsx
@@ -74,9 +74,10 @@ export function CreateListingDialog({ locker, currencyLabel }: CreateListingDial
quantity,
tradeable: asset.tradeable === true,
isLive: asset.isLive === true,
- basePrice: typeof asset.marketTrading?.perceivedValue?.baseBuyPrice === "number"
- ? asset.marketTrading.perceivedValue.baseBuyPrice
- : null,
+ basePrice:
+ typeof asset.marketTrading?.perceivedValue?.baseBuyPrice === "number"
+ ? asset.marketTrading.perceivedValue.baseBuyPrice
+ : null,
});
}
}
@@ -164,14 +165,20 @@ export function CreateListingDialog({ locker, currencyLabel }: CreateListingDial
Sell Item
- List items from your locker on the market. Listings last 30 days and stock is
- returned if they expire.
+ List items from your locker on the market. Listings last 30 days and stock is returned
+ if they expire.
Item
- { setAssetId(v); setQuantity("1"); }}>
+ {
+ setAssetId(v);
+ setQuantity("1");
+ }}
+ >
@@ -216,7 +223,9 @@ export function CreateListingDialog({ locker, currencyLabel }: CreateListingDial
step="any"
value={price}
onChange={(e) => setPrice(e.target.value)}
- placeholder={selected.basePrice != null ? `Suggested ~${selected.basePrice}` : "0"}
+ placeholder={
+ selected.basePrice != null ? `Suggested ~${selected.basePrice}` : "0"
+ }
/>
@@ -255,7 +264,9 @@ export function CreateListingDialog({ locker, currencyLabel }: CreateListingDial
{selected && selected.basePrice != null && (
- Tip
+
+ Tip
+
Resting valuation is {selected.basePrice.toLocaleString()} per unit.
)}
diff --git a/src/components/frontend/market/ListingCard.tsx b/src/components/frontend/market/ListingCard.tsx
index 76302e8..16d2896 100644
--- a/src/components/frontend/market/ListingCard.tsx
+++ b/src/components/frontend/market/ListingCard.tsx
@@ -6,7 +6,16 @@ import type { Asset, MarketListing } from "@/payload-types";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
-import { Item, ItemActions, ItemContent, ItemDescription, ItemFooter, ItemHeader, ItemMedia, ItemTitle } from "@/components/ui/item";
+import {
+ Item,
+ ItemActions,
+ ItemContent,
+ ItemDescription,
+ ItemFooter,
+ ItemHeader,
+ ItemMedia,
+ ItemTitle,
+} from "@/components/ui/item";
import { getAssetTypeLabel } from "@/lib/locker";
import { getRarityLabel, isNegotiable } from "@/lib/market";
import { formatAmount } from "@/lib/banking/format";
@@ -42,7 +51,12 @@ function sellerName(listing: MarketListing): string {
return npcLabel(listing);
}
-export function ListingCard({ listing, currentUserId, currencyLabel, canManage }: ListingCardProps) {
+export function ListingCard({
+ listing,
+ currentUserId,
+ currencyLabel,
+ canManage,
+}: ListingCardProps) {
const router = useRouter();
const [busy, setBusy] = useState<"buy" | "cancel" | null>(null);
const [error, setError] = useState(null);
@@ -51,7 +65,8 @@ export function ListingCard({ listing, currentUserId, currencyLabel, canManage }
const asset = listing.asset as Asset;
const assetName = typeof asset === "object" && asset ? asset.name : `Asset #${listing.asset}`;
const rarity = typeof asset === "object" && asset ? getRarityLabel(asset) : null;
- const assetType = typeof asset === "object" && asset ? getAssetTypeLabel(asset.assetType) : "Item";
+ const assetType =
+ typeof asset === "object" && asset ? getAssetTypeLabel(asset.assetType) : "Item";
const stock = listing.quantity ?? 1;
const negotiable = isNegotiable(listing);
const isOwn = (() => {
@@ -170,7 +185,11 @@ export function ListingCard({ listing, currentUserId, currencyLabel, canManage }
{busy === "buy" ? "Buying..." : stock > 1 ? `Buy ${selectedQty}` : "Buy"}
{negotiable && !isOwn && (
-
+
)}
{canCancel && (
diff --git a/src/components/frontend/market/MakeOfferDialog.tsx b/src/components/frontend/market/MakeOfferDialog.tsx
index a11b4da..f7f804c 100644
--- a/src/components/frontend/market/MakeOfferDialog.tsx
+++ b/src/components/frontend/market/MakeOfferDialog.tsx
@@ -129,7 +129,10 @@ export function MakeOfferDialog({ listing, currentUserId, currencyLabel }: MakeO
}
}
- async function run(action: () => Promise<{ success: boolean; error?: string; data?: unknown }>, key: string) {
+ async function run(
+ action: () => Promise<{ success: boolean; error?: string; data?: unknown }>,
+ key: string,
+ ) {
setBusy(key);
setError(null);
const res = await action();
@@ -278,9 +281,7 @@ export function MakeOfferDialog({ listing, currentUserId, currencyLabel }: MakeO
)}
{stock}× in stock · total{" "}
-
- {fmt((Number(amount) || 0) * clampedQty)}
-
+ {fmt((Number(amount) || 0) * clampedQty)}
{error &&
{error}
}
>
@@ -289,8 +290,8 @@ export function MakeOfferDialog({ listing, currentUserId, currencyLabel }: MakeO
{outcome?.kind === "pending" && (
- Offer of {fmt(negotiation?.amount ?? 0)} each
- for {negotiation?.quantity ?? stock}× sent. Waiting for the{" "}
+ Offer of {fmt(negotiation?.amount ?? 0)} each for{" "}
+ {negotiation?.quantity ?? stock}× sent. Waiting for the{" "}
{isVendor ? "vendor" : "seller"} to respond.
{error &&
{error}
}
@@ -386,7 +387,9 @@ export function MakeOfferDialog({ listing, currentUserId, currencyLabel }: MakeO
>
)}
- {(outcome?.kind === "accepted" || outcome?.kind === "rejected" || outcome?.kind === "closed") && (
+ {(outcome?.kind === "accepted" ||
+ outcome?.kind === "rejected" ||
+ outcome?.kind === "closed") && (
setOpen(false)} disabled={acceptLocked}>
{acceptLocked ? `Closing in ${acceptRemaining}s…` : "Close"}
diff --git a/src/components/frontend/market/MarketView.tsx b/src/components/frontend/market/MarketView.tsx
index d9502d4..2aceb5e 100644
--- a/src/components/frontend/market/MarketView.tsx
+++ b/src/components/frontend/market/MarketView.tsx
@@ -181,9 +181,7 @@ export function MarketView({
My listings
-
- {myListings.length} active
-
+
{myListings.length} active
{myListings.map((listing) => (
diff --git a/src/components/frontend/market/NegotiationHistoryDialog.tsx b/src/components/frontend/market/NegotiationHistoryDialog.tsx
index acea842..fc783ca 100644
--- a/src/components/frontend/market/NegotiationHistoryDialog.tsx
+++ b/src/components/frontend/market/NegotiationHistoryDialog.tsx
@@ -17,7 +17,7 @@ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/component
import { formatAmount } from "@/lib/banking/format";
import { buildChatBubbles } from "@/lib/market/chatBubbles";
import { ChatBubbleList } from "@/components/frontend/market/ChatBubbleList";
-import { HistoryIcon, ChevronDownIcon } from "lucide-react";
+import { ChevronDownIcon, HistoryIcon } from "lucide-react";
import { cn } from "@/lib/utils";
const STATUS_LABELS: Record = {
diff --git a/src/components/frontend/market/NegotiationPanel.tsx b/src/components/frontend/market/NegotiationPanel.tsx
index 52d71ca..d90dc9e 100644
--- a/src/components/frontend/market/NegotiationPanel.tsx
+++ b/src/components/frontend/market/NegotiationPanel.tsx
@@ -40,10 +40,7 @@ function assetName(listing: MarketListing | null): string {
return typeof asset === "object" && asset ? asset.name : `Asset #${listing.asset}`;
}
-function counterpartyLabel(
- negotiation: MarketNegotiation,
- role: "buyer" | "seller",
-): string {
+function counterpartyLabel(negotiation: MarketNegotiation, role: "buyer" | "seller"): string {
if (role === "buyer") {
const listing = listingOf(negotiation);
if (listing?.isAutoGenerated) {
@@ -55,9 +52,11 @@ function counterpartyLabel(
}
const seller = listing?.seller as unknown;
if (typeof seller === "object" && seller && "username" in (seller as object)) {
- return (seller as { username?: string; displayName?: string }).displayName ||
+ return (
+ (seller as { username?: string; displayName?: string }).displayName ||
(seller as { username?: string }).username ||
- "Seller";
+ "Seller"
+ );
}
return "Seller";
}
@@ -128,7 +127,9 @@ function NegotiationRow({
{counterpartyLabel(negotiation, role)} · {listing?.quantity ?? "?"}× in stock
-
{STATUS_LABELS[negotiation.status] ?? negotiation.status}
+
+ {STATUS_LABELS[negotiation.status] ?? negotiation.status}
+
@@ -223,7 +224,11 @@ interface NegotiationPanelProps {
currencyLabel: string | null;
}
-export function NegotiationPanel({ negotiations, currentUserId, currencyLabel }: NegotiationPanelProps) {
+export function NegotiationPanel({
+ negotiations,
+ currentUserId,
+ currencyLabel,
+}: NegotiationPanelProps) {
const open = negotiations.filter((n) => n.status === "open");
const closed = negotiations.filter((n) => n.status !== "open");
@@ -255,9 +260,8 @@ export function NegotiationPanel({ negotiations, currentUserId, currencyLabel }:
{open.map((negotiation) => {
const role: "buyer" | "seller" =
negotiation.buyer &&
- (typeof negotiation.buyer === "object"
- ? negotiation.buyer.id
- : negotiation.buyer) === currentUserId
+ (typeof negotiation.buyer === "object" ? negotiation.buyer.id : negotiation.buyer) ===
+ currentUserId
? "buyer"
: "seller";
return (
@@ -276,7 +280,8 @@ export function NegotiationPanel({ negotiations, currentUserId, currencyLabel }:
{closed.slice(0, 6).map((negotiation) => (
- {assetName(listingOf(negotiation))} · {STATUS_LABELS[negotiation.status] ?? negotiation.status}
+ {assetName(listingOf(negotiation))} ·{" "}
+ {STATUS_LABELS[negotiation.status] ?? negotiation.status}
))}
{closed.length > 6 && (
diff --git a/src/components/frontend/market/PatienceMeter.tsx b/src/components/frontend/market/PatienceMeter.tsx
index c9e5644..7371c4b 100644
--- a/src/components/frontend/market/PatienceMeter.tsx
+++ b/src/components/frontend/market/PatienceMeter.tsx
@@ -27,7 +27,12 @@ export function PatienceMeter({ patience, className }: PatienceMeterProps) {
Vendor patience
- = 80 ? "text-red-400" : "text-muted-foreground")}>
+ = 80 ? "text-red-400" : "text-muted-foreground",
+ )}
+ >
{value}%
diff --git a/src/components/frontend/roster/RosterView.tsx b/src/components/frontend/roster/RosterView.tsx
index 876bcdb..3a2c5e5 100644
--- a/src/components/frontend/roster/RosterView.tsx
+++ b/src/components/frontend/roster/RosterView.tsx
@@ -2,9 +2,9 @@
import Link from "next/link";
import { cn, pluralize } from "@/lib/utils";
-import type { RosterMember, RosterAssignment } from "@/app/(frontend)/roster/page";
+import type { RosterAssignment, RosterMember } from "@/app/(frontend)/roster/page";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
-import { NetworkIcon, Building2Icon, ShieldIcon, ListIcon, CrownIcon } from "lucide-react";
+import { Building2Icon, CrownIcon, ListIcon, NetworkIcon, ShieldIcon } from "lucide-react";
const TYPE_ORDER: Record
= {
division: 0,
diff --git a/src/components/frontend/storage/CapacityBar.tsx b/src/components/frontend/storage/CapacityBar.tsx
index 0c182a9..3297c32 100644
--- a/src/components/frontend/storage/CapacityBar.tsx
+++ b/src/components/frontend/storage/CapacityBar.tsx
@@ -42,9 +42,7 @@ export function CapacityBar({ utilization, currentMass, maxMass, className }: Ca
Capacity
-
- {Math.round(utilization)}%
-
+ {Math.round(utilization)}%
diff --git a/src/components/frontend/storage/EventLedger.tsx b/src/components/frontend/storage/EventLedger.tsx
index 245fee3..a96227c 100644
--- a/src/components/frontend/storage/EventLedger.tsx
+++ b/src/components/frontend/storage/EventLedger.tsx
@@ -1,7 +1,7 @@
"use client";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
-import { Item, ItemContent, ItemHeader, ItemTitle, ItemDescription } from "@/components/ui/item";
+import { Item, ItemContent, ItemDescription, ItemHeader, ItemTitle } from "@/components/ui/item";
import { BookOpenTextIcon, ScrollTextIcon } from "lucide-react";
import { useGameTick } from "@/hooks/useGameTick";
import { formatType } from "@/utils/event-log/formatType";
@@ -9,7 +9,6 @@ import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
- DropdownMenuLabel,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
@@ -140,9 +139,7 @@ export function EventLedger({
{eventTypes.length > 1 && (
-
- Filter event logs...
-
+ Filter event logs...
-
+
{resource.icon && typeof resource.icon === "object" && "url" in resource.icon && (
@@ -78,18 +79,15 @@ export function GridToolbar({
{res.name}
- {selectedItem.amount.toLocaleString()} {pluralize(selectedItem.amount, res.unitOfMeasure)}
+ {selectedItem.amount.toLocaleString()}{" "}
+ {pluralize(selectedItem.amount, res.unitOfMeasure)}
{fp && ` · ${fp.width}x${fp.height}`}
{selectedItem.rotated && " · rotated"}
{res.rotatable !== false && (
- onRotate(selectedItem.id)}
- >
+ onRotate(selectedItem.id)}>
)}
@@ -103,11 +101,7 @@ export function GridToolbar({
)}
- onRemove(selectedItem.id)}
- >
+ onRemove(selectedItem.id)}>
diff --git a/src/components/frontend/storage/InventoryGrid.tsx b/src/components/frontend/storage/InventoryGrid.tsx
index e232376..5b08fb6 100644
--- a/src/components/frontend/storage/InventoryGrid.tsx
+++ b/src/components/frontend/storage/InventoryGrid.tsx
@@ -1,27 +1,35 @@
"use client";
-import { useState, useCallback, useMemo, useRef, useEffect } from "react";
+import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
DndContext,
+ type DragEndEvent,
+ type DragMoveEvent,
DragOverlay,
+ type DragStartEvent,
PointerSensor,
useSensor,
useSensors,
- type DragStartEvent,
- type DragMoveEvent,
- type DragEndEvent,
} from "@dnd-kit/core";
-import { GameStructure, Structure, Resource } from "@/payload-types";
+import { GameStructure, Resource, Structure } from "@/payload-types";
import {
- getItemFootprint,
- isValidPlacement,
getGridOccupancy,
+ getItemFootprint,
type GridItem,
+ isValidPlacement,
} from "@/lib/logistics";
import { GridItemComponent } from "./GridItem";
import { GridCell } from "./GridCell";
-function DragOverlayContent({ item, cellSize, rotated }: { item: GridItem; cellSize: number; rotated?: boolean }) {
+function DragOverlayContent({
+ item,
+ cellSize,
+ rotated,
+}: {
+ item: GridItem;
+ cellSize: number;
+ rotated?: boolean;
+}) {
const resource = item.resource as Resource;
const effective = rotated ?? item.rotated;
const { width, height } = getItemFootprint(resource, effective);
@@ -60,7 +68,13 @@ function DragOverlayContent({ item, cellSize, rotated }: { item: GridItem; cellS
);
}
-function findMergeTarget(items: GridItem[], dragged: GridItem, targetX: number, targetY: number, rotated?: boolean): GridItem | null {
+function findMergeTarget(
+ items: GridItem[],
+ dragged: GridItem,
+ targetX: number,
+ targetY: number,
+ rotated?: boolean,
+): GridItem | null {
const effective = rotated ?? dragged.rotated;
const fp = getItemFootprint(dragged.resource as Resource, effective);
const targetCells = new Set
();
@@ -172,32 +186,37 @@ export function InventoryGrid({
cells.add(`${hoverPosition.x + dx},${hoverPosition.y + dy}`);
}
}
- const mergeTarget = findMergeTarget(storedResources, activeItem, hoverPosition.x, hoverPosition.y, pendingRotated);
- const valid = mergeTarget !== null || isValidPlacement(
+ const mergeTarget = findMergeTarget(
storedResources,
- activeItem.id,
+ activeItem,
hoverPosition.x,
hoverPosition.y,
- fp.width,
- fp.height,
- gridWidth,
- gridHeight,
+ pendingRotated,
);
+ const valid =
+ mergeTarget !== null ||
+ isValidPlacement(
+ storedResources,
+ activeItem.id,
+ hoverPosition.x,
+ hoverPosition.y,
+ fp.width,
+ fp.height,
+ gridWidth,
+ gridHeight,
+ );
return { cells, valid };
}, [hoverPosition, activeItem, storedResources, gridWidth, gridHeight, pendingRotated]);
- const handleDragStart = useCallback(
- (event: DragStartEvent) => {
- const data = event.active.data.current;
- if (data?.item) {
- const item = data.item as GridItem;
- setActiveItem(item);
- setPendingRotated(item.rotated ?? false);
- }
- setHoverPosition(null);
- },
- [],
- );
+ const handleDragStart = useCallback((event: DragStartEvent) => {
+ const data = event.active.data.current;
+ if (data?.item) {
+ const item = data.item as GridItem;
+ setActiveItem(item);
+ setPendingRotated(item.rotated ?? false);
+ }
+ setHoverPosition(null);
+ }, []);
const handleDragMove = useCallback(
(event: DragMoveEvent) => {
@@ -277,13 +296,13 @@ export function InventoryGrid({
}, [onSelectItem]);
return (
-
-
diff --git a/src/components/frontend/storage/ManageStorageButton.tsx b/src/components/frontend/storage/ManageStorageButton.tsx
index c9df99c..b2e8838 100644
--- a/src/components/frontend/storage/ManageStorageButton.tsx
+++ b/src/components/frontend/storage/ManageStorageButton.tsx
@@ -18,11 +18,7 @@ export function ManageStorageButton({ structureId }: ManageStorageButtonProps) {
Manage
-
+
>
);
}
diff --git a/src/components/frontend/storage/ManageStorageDialog.tsx b/src/components/frontend/storage/ManageStorageDialog.tsx
index 1ee6b33..c665b16 100644
--- a/src/components/frontend/storage/ManageStorageDialog.tsx
+++ b/src/components/frontend/storage/ManageStorageDialog.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useState, useEffect, useTransition, useCallback } from "react";
+import { useCallback, useEffect, useState, useTransition } from "react";
import { useRouter } from "next/navigation";
import {
Dialog,
@@ -31,11 +31,11 @@ import {
isResourceProhibited,
isResourceWhitelisted,
} from "@/lib/storageRules";
-import { GameStructure, Structure, Resource, GameVehicle, Vehicle } from "@/payload-types";
+import { GameStructure, GameVehicle, Resource, Structure, Vehicle } from "@/payload-types";
import {
ArrowDownToLineIcon,
- ArrowUpFromLineIcon,
ArrowRightLeftIcon,
+ ArrowUpFromLineIcon,
Loader2Icon,
TruckIcon,
} from "lucide-react";
@@ -48,21 +48,13 @@ interface ManageStorageDialogProps {
onOpenChange: (open: boolean) => void;
}
-export function ManageStorageDialog({
- structureId,
- open,
- onOpenChange,
-}: ManageStorageDialogProps) {
- const [structure, setStructure] = useState<
- (GameStructure & { type: Structure }) | null
- >(null);
- const [allStructures, setAllStructures] = useState<
- (GameStructure & { type: Structure })[]
- >([]);
+export function ManageStorageDialog({ structureId, open, onOpenChange }: ManageStorageDialogProps) {
+ const [structure, setStructure] = useState<(GameStructure & { type: Structure }) | null>(null);
+ const [allStructures, setAllStructures] = useState<(GameStructure & { type: Structure })[]>([]);
const [allResources, setAllResources] = useState([]);
- const [availableVehicles, setAvailableVehicles] = useState<
- (GameVehicle & { type: Vehicle })[]
- >([]);
+ const [availableVehicles, setAvailableVehicles] = useState<(GameVehicle & { type: Vehicle })[]>(
+ [],
+ );
const [loading, setLoading] = useState(false);
const [isPending, startTransition] = useTransition();
const router = useRouter();
@@ -91,7 +83,9 @@ export function ManageStorageDialog({
fetch(`/api/game-structures/${structureId}?depth=2`),
fetch(`/api/game-structures?depth=2&limit=500`),
fetch(`/api/resources?depth=0&limit=500`),
- fetch(`/api/game-vehicles?depth=2&limit=500&where[deployedAt][equals]=${structureId}&where[status][equals]=idle`),
+ fetch(
+ `/api/game-vehicles?depth=2&limit=500&where[deployedAt][equals]=${structureId}&where[status][equals]=idle`,
+ ),
]);
const anyUnauthorized = [structRes, allRes, resourcesRes, vehiclesRes].some(
@@ -111,9 +105,7 @@ export function ManageStorageDialog({
const vehiclesData = await vehiclesRes.json();
setStructure(structData);
setAllStructures(
- allData.docs.filter(
- (s: GameStructure & { type: Structure }) => s.id !== structureId,
- ),
+ allData.docs.filter((s: GameStructure & { type: Structure }) => s.id !== structureId),
);
setAllResources(resourcesData.docs ?? []);
setAvailableVehicles(vehiclesData.docs ?? []);
@@ -177,18 +169,12 @@ export function ManageStorageDialog({
): number => {
const grid = (s.storedResources ?? []).reduce((sum, e) => {
const res = e.resource;
- const rid =
- typeof res === "object" && res !== null && "id" in res
- ? res.id
- : (res as number);
+ const rid = typeof res === "object" && res !== null && "id" in res ? res.id : (res as number);
return sum + (rid === resourceId ? e.amount : 0);
}, 0);
const voidAmount = (s.voidStorage ?? []).reduce((sum, e) => {
const res = e.resource;
- const rid =
- typeof res === "object" && res !== null && "id" in res
- ? res.id
- : (res as number);
+ const rid = typeof res === "object" && res !== null && "id" in res ? res.id : (res as number);
return sum + (rid === resourceId ? e.amount : 0);
}, 0);
return grid + voidAmount;
@@ -230,15 +216,11 @@ export function ManageStorageDialog({
reasons.push({ value: allowance, label: "storage allowance" });
}
if (reasons.length === 0) return undefined;
- const binding = reasons.reduce(
- (min, r) => (r.value < min.value ? r : min),
- reasons[0],
- );
+ const binding = reasons.reduce((min, r) => (r.value < min.value ? r : min), reasons[0]);
return { max: binding.value, reason: binding.label };
};
- const getDepositMax = (): number | undefined =>
- getDepositLimitInfoFor(depositResourceId)?.max;
+ const getDepositMax = (): number | undefined => getDepositLimitInfoFor(depositResourceId)?.max;
const getWithdrawMax = (): number | undefined => {
const stored = findStoredResource(withdrawResourceId);
@@ -263,9 +245,7 @@ export function ManageStorageDialog({
if (destMaxMass != null) {
const massPerUnit = stored.resource.massPerUnit ?? 0;
if (massPerUnit > 0) {
- caps.push(
- Math.floor((destMaxMass - getMassOf(dest)) / massPerUnit),
- );
+ caps.push(Math.floor((destMaxMass - getMassOf(dest)) / massPerUnit));
}
}
return Math.min(...caps);
@@ -273,8 +253,7 @@ export function ManageStorageDialog({
return stored.amount;
};
- const getTransferMax = (): number | undefined =>
- getTransferMaxFor(transferToId);
+ const getTransferMax = (): number | undefined => getTransferMaxFor(transferToId);
const clampAmount = (value: string, max: number | undefined): string => {
const num = Number(value);
@@ -333,13 +312,19 @@ export function ManageStorageDialog({
};
return (
-
+
- {tab === "deposit" ? "After deposit" : tab === "withdraw" ? "After withdrawal" : "After transfer"}
+ {tab === "deposit"
+ ? "After deposit"
+ : tab === "withdraw"
+ ? "After withdrawal"
+ : "After transfer"}
{Math.round(previewPercent)}%
@@ -347,9 +332,12 @@ export function ManageStorageDialog({
- {currentMass.toLocaleString()} kg → {previewMass.toLocaleString()} kg
+
+ {currentMass.toLocaleString()} kg → {previewMass.toLocaleString()} kg
+
- {delta >= 0 ? "+" : ""}{delta.toLocaleString()} kg
+ {delta >= 0 ? "+" : ""}
+ {delta.toLocaleString()} kg
{isOverCapacity && " (over capacity!)"}
@@ -474,9 +462,7 @@ export function ManageStorageDialog({
Manage Storage
-
- {structure?.name ?? "Loading..."}
-
+ {structure?.name ?? "Loading..."}
{loading ? (
@@ -505,7 +491,8 @@ export function ManageStorageDialog({
Cargo
- {shipmentAmount}× {allResources.find((r) => r.id === Number(shipmentResourceId))?.name ?? "Unknown"}
+ {shipmentAmount}×{" "}
+ {allResources.find((r) => r.id === Number(shipmentResourceId))?.name ?? "Unknown"}
@@ -531,67 +518,81 @@ export function ManageStorageDialog({
) : (
- No idle vehicles stationed at this structure. Deploy a vehicle here first to create shipments.
+
+ No idle vehicles stationed at this structure. Deploy a vehicle here first to
+ create shipments.
+
)}
- {shipmentVehicleId && (() => {
- const selectedVehicle = availableVehicles.find((v) => v.id === Number(shipmentVehicleId));
- const vType = selectedVehicle && typeof selectedVehicle.type === "object" ? selectedVehicle.type : null;
- if (!selectedVehicle || !vType) return null;
-
- const destStructure = allStructures.find((s) => s.id === Number(shipmentDestId));
- const originCoords = structure?.coordinates as [number, number] | null;
- const destCoords = destStructure?.coordinates as [number, number] | null;
- let distance = 0;
- if (originCoords && destCoords) {
- distance = Math.sqrt(
- (destCoords[0] - originCoords[0]) ** 2 +
- (destCoords[1] - originCoords[1]) ** 2,
+ {shipmentVehicleId &&
+ (() => {
+ const selectedVehicle = availableVehicles.find(
+ (v) => v.id === Number(shipmentVehicleId),
);
- }
+ const vType =
+ selectedVehicle && typeof selectedVehicle.type === "object"
+ ? selectedVehicle.type
+ : null;
+ if (!selectedVehicle || !vType) return null;
- const consumptionRate = vType.fuel?.fuelConsumptionRate ?? 1;
- const fuelCost = Math.ceil(distance * consumptionRate);
- const speed = vType.maxSpeedOnRoad ?? vType.maxSpeedOffRoad ?? vType.maxAirspeed ?? 50;
- const transitHours = distance / speed;
- const canAfford = (selectedVehicle.currentFuel ?? 0) >= fuelCost;
+ const destStructure = allStructures.find((s) => s.id === Number(shipmentDestId));
+ const originCoords = structure?.coordinates as [number, number] | null;
+ const destCoords = destStructure?.coordinates as [number, number] | null;
+ let distance = 0;
+ if (originCoords && destCoords) {
+ distance = Math.sqrt(
+ (destCoords[0] - originCoords[0]) ** 2 + (destCoords[1] - originCoords[1]) ** 2,
+ );
+ }
- return (
-
-
-
- Distance
- {distance.toFixed(1)} units
-
-
- Fuel Cost
-
- {fuelCost} units
-
-
-
- Transit Time
-
- {transitHours < 1
- ? `${Math.ceil(transitHours * 60)} min`
- : `${transitHours.toFixed(1)} hr`}
-
-
-
-
Vehicle Fuel
-
{selectedVehicle.currentFuel?.toFixed(1)} / {vType.fuel?.fuelCapacity}
+ const consumptionRate = vType.fuel?.fuelConsumptionRate ?? 1;
+ const fuelCost = Math.ceil(distance * consumptionRate);
+ const speed =
+ vType.maxSpeedOnRoad ?? vType.maxSpeedOffRoad ?? vType.maxAirspeed ?? 50;
+ const transitHours = distance / speed;
+ const canAfford = (selectedVehicle.currentFuel ?? 0) >= fuelCost;
+
+ return (
+
+
+
+ Distance
+ {distance.toFixed(1)} units
+
+
+ Fuel Cost
+
+ {fuelCost} units
+
+
+
+ Transit Time
+
+ {transitHours < 1
+ ? `${Math.ceil(transitHours * 60)} min`
+ : `${transitHours.toFixed(1)} hr`}
+
+
+
+ Vehicle Fuel
+
+ {selectedVehicle.currentFuel?.toFixed(1)} / {vType.fuel?.fuelCapacity}
+
+
+ {!canAfford && (
+
+
+ Vehicle does not have enough fuel for this trip. Need {fuelCost} units,
+ has {selectedVehicle.currentFuel?.toFixed(1)}.
+
+
+ )}
- {!canAfford && (
-
- Vehicle does not have enough fuel for this trip. Need {fuelCost} units, has {selectedVehicle.currentFuel?.toFixed(1)}.
-
- )}
-
- );
- })()}
+ );
+ })()}
{isPending ? (
@@ -628,269 +634,273 @@ export function ManageStorageDialog({
{Math.round(Math.min((currentMass / maxMass) * 100, 100))}%
-
+
{currentMass.toLocaleString()} kg used
{maxMass.toLocaleString()} kg max
)}
-
-
-
-
- Deposit
-
-
-
- Withdraw
-
-
-
- Transfer
-
-
+
+
+
+
+ Deposit
+
+
+
+ Withdraw
+
+
+
+ Transfer
+
+
-
-
-
Resource
-
{
- setDepositResourceId(val);
- setDepositAmount(
- clampAmount(depositAmount, getDepositLimitInfoFor(val)?.max),
- );
- }}
+
+
+ Resource
+ {
+ setDepositResourceId(val);
+ setDepositAmount(
+ clampAmount(depositAmount, getDepositLimitInfoFor(val)?.max),
+ );
+ }}
+ >
+
+
+
+
+ {allResources
+ .filter((r) => isResourceAllowed(r.id))
+ .map((resource) => {
+ const cap = getAllowedDepositCap(resource.id);
+ return (
+
+ {resource.name}
+ {cap !== undefined && (
+
+ {" "}
+ — {cap.toLocaleString()} max
+
+ )}
+
+ );
+ })}
+
+
+
+
+ Amount
+ setDepositAmount(clampAmount(e.target.value, getDepositMax()))}
+ />
+ {getDepositLimitInfoFor(depositResourceId) !== undefined && (
+
+ Max: {getDepositLimitInfoFor(depositResourceId)!.max.toLocaleString()}{" "}
+ (limited by {getDepositLimitInfoFor(depositResourceId)!.reason})
+
+ )}
+
+ {renderCapacityPreview("deposit")}
+
-
-
-
-
- {allResources
- .filter((r) => isResourceAllowed(r.id))
- .map((resource) => {
- const cap = getAllowedDepositCap(resource.id);
+ {isPending ? (
+
+ ) : (
+
+ )}
+ Deposit
+
+
+
+
+
+ Resource
+ {
+ setWithdrawResourceId(val);
+ const stored = findStoredResource(val);
+ setWithdrawAmount(clampAmount(withdrawAmount, stored?.amount));
+ }}
+ >
+
+
+
+
+ {storedResources.map((entry) => {
+ const resource = entry.resource as Resource;
+ if (!resource || typeof resource === "number") return null;
return (
-
- {resource.name}
- {cap !== undefined && (
-
- {" "}
- — {cap.toLocaleString()} max
-
- )}
+
+ {resource.name} ({entry.amount.toLocaleString()}{" "}
+ {pluralize(entry.amount, resource.unitOfMeasure)})
);
})}
-
-
-
-
- Amount
- setDepositAmount(clampAmount(e.target.value, getDepositMax()))}
- />
- {getDepositLimitInfoFor(depositResourceId) !== undefined && (
-
- Max:{" "}
- {getDepositLimitInfoFor(depositResourceId)!.max.toLocaleString()}{" "}
- (limited by{" "}
- {getDepositLimitInfoFor(depositResourceId)!.reason})
-
- )}
-
- {renderCapacityPreview("deposit")}
-
- {isPending ? (
-
- ) : (
-
- )}
- Deposit
-
-
-
-
-
- Resource
- {
- setWithdrawResourceId(val);
- const stored = findStoredResource(val);
- setWithdrawAmount(clampAmount(withdrawAmount, stored?.amount));
- }}
- >
-
-
-
-
- {storedResources.map((entry) => {
- const resource = entry.resource as Resource;
- if (!resource || typeof resource === "number") return null;
- return (
-
- {resource.name} ({entry.amount.toLocaleString()} {pluralize(entry.amount, resource.unitOfMeasure)})
-
- );
- })}
-
-
-
-
- Amount
- setWithdrawAmount(clampAmount(e.target.value, getWithdrawMax()))}
- />
- {getWithdrawMax() !== undefined && (
-
- Max: {getWithdrawMax()!.toLocaleString()} stored
-
- )}
-
- {renderCapacityPreview("withdraw")}
-
- {isPending ? (
-
- ) : (
-
- )}
- Withdraw
-
-
-
-
-
- Destination
- {
- setTransferToId(val);
- setTransferAmount(
- clampAmount(transferAmount, getTransferMaxFor(val)),
- );
- }}
- >
-
-
-
-
- {allStructures.map((s) => (
-
- {s.name}
-
- ))}
-
-
-
-
- Resource
- {
- setTransferResourceId(val);
- const stored = findStoredResource(val);
- setTransferAmount(clampAmount(transferAmount, stored?.amount));
- }}
- >
-
-
-
-
- {storedResources.map((entry) => {
- const resource = entry.resource as Resource;
- if (!resource || typeof resource === "number") return null;
- return (
-
- {resource.name} ({entry.amount.toLocaleString()} {pluralize(entry.amount, resource.unitOfMeasure)})
-
- );
- })}
-
-
-
-
- Amount
- setTransferAmount(clampAmount(e.target.value, getTransferMax()))}
- />
- {getTransferMax() !== undefined && (
-
- Max: {getTransferMax()!.toLocaleString()} stored
-
- )}
- {transferToId && transferResourceId && (() => {
- const dest = allStructures.find((s) => s.id === Number(transferToId));
- const destType = dest?.type;
- if (!dest || !destType) return null;
- const stored = findStoredResource(transferResourceId);
- if (!stored) return null;
- const resId = stored.resource.id;
- if (isResourceProhibited(destType, resId)) {
- return (
-
- Destination prohibits this resource.
-
- );
- }
- if (!isResourceWhitelisted(destType, resId)) {
- return (
-
- Destination only accepts items on its allowed storage list — this resource is not permitted.
-
- );
- }
- const cap = getResourceStorageCap(destType, resId);
- const destStored = getStoredAmountOf(dest, resId);
- return (
+
+
+
+
+ Amount
+
+ setWithdrawAmount(clampAmount(e.target.value, getWithdrawMax()))
+ }
+ />
+ {getWithdrawMax() !== undefined && (
- {cap != null
- ? `Destination allowance: ${Math.max(0, cap - destStored).toLocaleString()} / ${cap.toLocaleString()}`
- : "Destination has no per-item storage limit"}
+ Max: {getWithdrawMax()!.toLocaleString()} stored
- );
- })()}
-
- {renderCapacityPreview("transfer")}
-
- {isPending ? (
-
- ) : (
-
- )}
- Transfer
-
-
-
+ )}
+
+ {renderCapacityPreview("withdraw")}
+
+ {isPending ? (
+
+ ) : (
+
+ )}
+ Withdraw
+
+
+
+
+
+ Destination
+ {
+ setTransferToId(val);
+ setTransferAmount(clampAmount(transferAmount, getTransferMaxFor(val)));
+ }}
+ >
+
+
+
+
+ {allStructures.map((s) => (
+
+ {s.name}
+
+ ))}
+
+
+
+
+ Resource
+ {
+ setTransferResourceId(val);
+ const stored = findStoredResource(val);
+ setTransferAmount(clampAmount(transferAmount, stored?.amount));
+ }}
+ >
+
+
+
+
+ {storedResources.map((entry) => {
+ const resource = entry.resource as Resource;
+ if (!resource || typeof resource === "number") return null;
+ return (
+
+ {resource.name} ({entry.amount.toLocaleString()}{" "}
+ {pluralize(entry.amount, resource.unitOfMeasure)})
+
+ );
+ })}
+
+
+
+
+ Amount
+
+ setTransferAmount(clampAmount(e.target.value, getTransferMax()))
+ }
+ />
+ {getTransferMax() !== undefined && (
+
+ Max: {getTransferMax()!.toLocaleString()} stored
+
+ )}
+ {transferToId &&
+ transferResourceId &&
+ (() => {
+ const dest = allStructures.find((s) => s.id === Number(transferToId));
+ const destType = dest?.type;
+ if (!dest || !destType) return null;
+ const stored = findStoredResource(transferResourceId);
+ if (!stored) return null;
+ const resId = stored.resource.id;
+ if (isResourceProhibited(destType, resId)) {
+ return (
+
+ Destination prohibits this resource.
+
+ );
+ }
+ if (!isResourceWhitelisted(destType, resId)) {
+ return (
+
+ Destination only accepts items on its allowed storage list — this
+ resource is not permitted.
+
+ );
+ }
+ const cap = getResourceStorageCap(destType, resId);
+ const destStored = getStoredAmountOf(dest, resId);
+ return (
+
+ {cap != null
+ ? `Destination allowance: ${Math.max(0, cap - destStored).toLocaleString()} / ${cap.toLocaleString()}`
+ : "Destination has no per-item storage limit"}
+
+ );
+ })()}
+
+ {renderCapacityPreview("transfer")}
+
+ {isPending ? (
+
+ ) : (
+
+ )}
+ Transfer
+
+
+
>
)}
diff --git a/src/components/frontend/storage/ResourceList.tsx b/src/components/frontend/storage/ResourceList.tsx
index 6be8d26..de9c3e0 100644
--- a/src/components/frontend/storage/ResourceList.tsx
+++ b/src/components/frontend/storage/ResourceList.tsx
@@ -1,4 +1,4 @@
-import { Resource, GameStructure } from "@/payload-types";
+import { GameStructure, Resource } from "@/payload-types";
import { convertLexicalToPlaintext } from "@payloadcms/richtext-lexical/plaintext";
import { pluralize } from "@/lib/utils";
import {
@@ -10,7 +10,7 @@ import {
ItemSeparator,
ItemTitle,
} from "@/components/ui/item";
-import { PackageOpenIcon, DropletIcon, ZapIcon, BrainIcon } from "lucide-react";
+import { BrainIcon, DropletIcon, PackageOpenIcon, ZapIcon } from "lucide-react";
interface ResourceListProps {
storedResources: GameStructure["storedResources"];
@@ -77,7 +77,7 @@ export function ResourceList({ storedResources, maxCapacityMass }: ResourceListP
{resource.icon ? (
@@ -93,18 +93,18 @@ export function ResourceList({ storedResources, maxCapacityMass }: ResourceListP
/>
{resource.name}
- {entry.amount.toLocaleString()} {pluralize(entry.amount, resource.unitOfMeasure)}
+ {entry.amount.toLocaleString()}{" "}
+ {pluralize(entry.amount, resource.unitOfMeasure)}
{description && (
-
- {description}
-
+ {description}
)}
- {entry.amount.toLocaleString()} {pluralize(entry.amount, resource.unitOfMeasure)}
+ {entry.amount.toLocaleString()}{" "}
+ {pluralize(entry.amount, resource.unitOfMeasure)}
{mass > 0 && `${mass.toLocaleString()} kg mass`}
diff --git a/src/components/frontend/storage/StorageFilters.tsx b/src/components/frontend/storage/StorageFilters.tsx
index 13f2c42..49234f3 100644
--- a/src/components/frontend/storage/StorageFilters.tsx
+++ b/src/components/frontend/storage/StorageFilters.tsx
@@ -8,11 +8,19 @@ import { getCategoryLabel } from "@/lib/logistics";
interface StorageFiltersProps {
categories: Structure["category"][];
factions: Faction[];
- onFilterChange: (filters: { category: Structure["category"] | null; factionId: number | null }) => void;
+ onFilterChange: (filters: {
+ category: Structure["category"] | null;
+ factionId: number | null;
+ }) => void;
className?: string;
}
-export function StorageFilters({ categories, factions, onFilterChange, className }: StorageFiltersProps) {
+export function StorageFilters({
+ categories,
+ factions,
+ onFilterChange,
+ className,
+}: StorageFiltersProps) {
const [selectedCategory, setSelectedCategory] = useState
(null);
const [selectedFaction, setSelectedFaction] = useState(null);
diff --git a/src/components/frontend/storage/StorageGrid.tsx b/src/components/frontend/storage/StorageGrid.tsx
index 71b9493..ebffb1d 100644
--- a/src/components/frontend/storage/StorageGrid.tsx
+++ b/src/components/frontend/storage/StorageGrid.tsx
@@ -1,7 +1,7 @@
"use client";
import { useState } from "react";
-import { GameStructure, Faction, Structure } from "@/payload-types";
+import { Faction, GameStructure, Structure } from "@/payload-types";
import { StructureCard } from "@/components/frontend/storage/StructureCard";
import { StorageFilters } from "@/components/frontend/storage/StorageFilters";
@@ -21,7 +21,11 @@ export function StorageGrid({ structures, categories, factions }: StorageGridPro
if (filters.category && s.type.category !== filters.category) return false;
if (filters.factionId) {
const factionId =
- typeof s.faction === "object" && s.faction ? s.faction.id : typeof s.faction === "number" ? s.faction : null;
+ typeof s.faction === "object" && s.faction
+ ? s.faction.id
+ : typeof s.faction === "number"
+ ? s.faction
+ : null;
if (factionId !== filters.factionId) return false;
}
return true;
@@ -29,11 +33,7 @@ export function StorageGrid({ structures, categories, factions }: StorageGridPro
return (
-
+
{filtered.length === 0 ? (
No structures found.
diff --git a/src/components/frontend/storage/StorageRules.tsx b/src/components/frontend/storage/StorageRules.tsx
index 718125c..7b19be8 100644
--- a/src/components/frontend/storage/StorageRules.tsx
+++ b/src/components/frontend/storage/StorageRules.tsx
@@ -1,16 +1,6 @@
import { Structure } from "@/payload-types";
-import {
- Item,
- ItemContent,
- ItemHeader,
- ItemTitle,
-} from "@/components/ui/item";
-import {
- CheckCircleIcon,
- XCircleIcon,
- ShieldCheckIcon,
- ListRestartIcon,
-} from "lucide-react";
+import { Item, ItemContent, ItemHeader, ItemTitle } from "@/components/ui/item";
+import { CheckCircleIcon, ListRestartIcon, ShieldCheckIcon, XCircleIcon } from "lucide-react";
interface StorageRulesProps {
allowedStorage: Structure["allowedStorage"];
@@ -42,8 +32,8 @@ export function StorageRules({
- Whitelist mode — only the items listed under "Allowed" can be
- stored here. All other items are rejected.
+ Whitelist mode — only the items listed under "Allowed" can be stored here. All other
+ items are rejected.
)}
@@ -68,9 +58,7 @@ export function StorageRules({
>
{name}
- {rule.amount && (
-
×{rule.amount}
- )}
+ {rule.amount &&
×{rule.amount} }
);
})}
diff --git a/src/components/frontend/storage/StructureCard.tsx b/src/components/frontend/storage/StructureCard.tsx
index 8089ec6..e43655c 100644
--- a/src/components/frontend/storage/StructureCard.tsx
+++ b/src/components/frontend/storage/StructureCard.tsx
@@ -11,21 +11,15 @@ import {
} from "@/components/ui/item";
import { CapacityBar } from "@/components/frontend/storage/CapacityBar";
import {
+ calculateStoredMass,
+ calculateUtilization,
getCategoryLabel,
getFunctionTypeLabel,
getGridSlots,
getResourceCount,
- calculateStoredMass,
- calculateUtilization,
} from "@/lib/logistics";
-import { GameStructure, Faction, Structure } from "@/payload-types";
-import {
- BoxIcon,
- Building2Icon,
- ChevronRightIcon,
- Grid3x3Icon,
- MapPinIcon,
-} from "lucide-react";
+import { Faction, GameStructure, Structure } from "@/payload-types";
+import { BoxIcon, Building2Icon, ChevronRightIcon, Grid3x3Icon, MapPinIcon } from "lucide-react";
interface StructureCardProps {
structure: GameStructure & {
@@ -46,7 +40,10 @@ export function StructureCard({ structure }: StructureCardProps) {
return (
-
-
+
-
@@ -89,11 +86,7 @@ export function StructureCard({ structure }: StructureCardProps) {
{structureType.xSize}×{structureType.ySize} grid
)}
- {functionLabel && (
-
- {functionLabel}
-
- )}
+ {functionLabel && {functionLabel} }
{structure.coordinates && (
diff --git a/src/components/frontend/storage/StructureStorageView.tsx b/src/components/frontend/storage/StructureStorageView.tsx
index 22104b6..cee4943 100644
--- a/src/components/frontend/storage/StructureStorageView.tsx
+++ b/src/components/frontend/storage/StructureStorageView.tsx
@@ -1,8 +1,8 @@
"use client";
-import { useState, useCallback } from "react";
+import { useCallback, useState } from "react";
import { useRouter } from "next/navigation";
-import { GameStructure, Structure, Resource } from "@/payload-types";
+import { GameStructure, Resource, Structure } from "@/payload-types";
import { InventoryGrid } from "./InventoryGrid";
import { GridToolbar } from "./GridToolbar";
import { ResourceList } from "./ResourceList";
@@ -13,27 +13,21 @@ import {
ItemContent,
ItemDescription,
ItemHeader,
- ItemSeparator,
ItemMedia,
+ ItemSeparator,
ItemTitle,
} from "@/components/ui/item";
-import {
- PackageOpenIcon,
- AlertTriangleIcon,
- DropletIcon,
- ZapIcon,
- BrainIcon,
-} from "lucide-react";
+import { AlertTriangleIcon, BrainIcon, DropletIcon, PackageOpenIcon, ZapIcon } from "lucide-react";
import { EventLedger } from "./EventLedger";
import {
- moveGridItem,
- mergeGridStacks,
- rotateGridItem,
- removeGridItem,
- splitGridStack,
autoArrangeGrid,
+ mergeGridStacks,
+ moveGridItem,
+ removeGridItem,
removeResource,
retrieveFromVoid,
+ rotateGridItem,
+ splitGridStack,
} from "@/app/(frontend)/logistics/structures/actions";
function getResourceIcon(type: Resource["type"]) {
@@ -216,7 +210,8 @@ export function StructureStorageView({ structure }: StructureStorageViewProps) {
- {voidStorageItems.length} item{voidStorageItems.length !== 1 ? "s" : ""} — these no longer fit in the grid.
+ {voidStorageItems.length} item{voidStorageItems.length !== 1 ? "s" : ""} — these no
+ longer fit in the grid.
{resource.icon ? (
@@ -260,7 +255,8 @@ export function StructureStorageView({ structure }: StructureStorageViewProps) {
- {entry.amount.toLocaleString()} {pluralize(entry.amount, resource.unitOfMeasure)}
+ {entry.amount.toLocaleString()}{" "}
+ {pluralize(entry.amount, resource.unitOfMeasure)}
{mass > 0 && `${mass.toLocaleString()} kg`}