mirror of
https://gitea.tendokyu.moe/ppc/amnet.git
synced 2026-09-27 17:27:55 +03:00
abstract card design to component, fix text truncation
This commit is contained in:
@@ -3,29 +3,27 @@ import TimeAgo from "react-timeago";
|
||||
import {CreditCard, History} from "lucide-react";
|
||||
|
||||
import {AimeCard} from "@/lib/database";
|
||||
import {Card} from "@/components/ui/card";
|
||||
import {useMediaQuery} from "@/hooks/use-media-query.tsx";
|
||||
import {AMCard, AMCardActionProps, AMCardDetail} from "@/components/am-card.tsx";
|
||||
|
||||
export function AimeCardEntry(props: AMCardActionProps & { card: AimeCard, children?: Readonly<React.ReactNode> }) {
|
||||
const isDesktop = useMediaQuery("(min-width: 512px)");
|
||||
|
||||
export function AimeCardEntry(props: { card: AimeCard, children?: Readonly<React.ReactNode> }) {
|
||||
return (
|
||||
<Card>
|
||||
<div className="flex p-4 items-center gap-3">
|
||||
<div className="flex-grow flex flex-col gap-4">
|
||||
<h4 className="text-2xl font-semibold select-none">{props.card.name}</h4>
|
||||
<div className="text-md flex gap-3 flex-wrap">
|
||||
{props.card.lastUsed > 0 && (
|
||||
<div className="flex items-center gap-2 text-gray-500">
|
||||
<History className="h-5 w-5"/>
|
||||
<TimeAgo date={props.card.lastUsed}/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-2 text-gray-500">
|
||||
<CreditCard className="h-5 w-5"/>
|
||||
<code>{props.card.id.match(/.{1,4}/g)?.join(" ")}</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{props.children}
|
||||
</div>
|
||||
</Card>
|
||||
<AMCard title={props.card.name} actionIcon={props.actionIcon} actionClicked={props.actionClicked} actionDisabled={props.actionDisabled}>
|
||||
{props.card.lastUsed > 0 && (
|
||||
<AMCardDetail>
|
||||
<History className="h-5 w-5"/>
|
||||
<TimeAgo date={props.card.lastUsed}/>
|
||||
</AMCardDetail>
|
||||
)}
|
||||
<AMCardDetail>
|
||||
<CreditCard className="h-5 w-5"/>
|
||||
<code>{isDesktop
|
||||
? props.card.id.match(/.{1,4}/g)?.join(" ")
|
||||
: `•••• ${props.card.id.substring(props.card.id.length - 4)}`}
|
||||
</code>
|
||||
</AMCardDetail>
|
||||
</AMCard>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import React from "react";
|
||||
import {ChevronRight} from "lucide-react";
|
||||
import {Card} from "@/components/ui/card.tsx";
|
||||
import {Button} from "@/components/ui/button.tsx";
|
||||
|
||||
export interface AMCardActionProps {
|
||||
actionIcon?: Readonly<React.ReactNode>,
|
||||
actionClicked?: () => void,
|
||||
actionDisabled?: boolean,
|
||||
}
|
||||
|
||||
export function AMCard(props: AMCardActionProps & { title: string, children: Readonly<React.ReactNode> }) {
|
||||
return (
|
||||
<Card>
|
||||
<div className="grid grid-cols-[1fr_40px] items-center gap-1 p-4 pr-2 sm:p-6">
|
||||
<div className="max-w-full overflow-hidden space-y-3">
|
||||
<h4 className="text-2xl font-semibold select-none text-ellipsis whitespace-nowrap overflow-hidden">
|
||||
{props.title}
|
||||
</h4>
|
||||
<div className="text-sm lg:text-md flex flex-auto flex-wrap gap-3">
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
{props.actionClicked && (
|
||||
<Button className="flex-shrink-0" variant="ghost" size="icon" disabled={props.actionDisabled} onClick={props.actionClicked}>
|
||||
{props.actionIcon ?? <ChevronRight className="h-4 w-4"/>}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export function AMCardDetail(props: { children?: Readonly<React.ReactNode> }) {
|
||||
return (
|
||||
<div className="inline-flex items-center max-w-full gap-2 text-gray-500">
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export function Footer() {
|
||||
export function AMFooter() {
|
||||
return (<footer>
|
||||
<span className="text-gray-500 text-sm">AimeNet © ppc {new Date().getUTCFullYear()}</span>
|
||||
</footer>);
|
||||
@@ -6,12 +6,11 @@ import {ChevronRight, Circle, CircleCheckBig, Home, Plus, ServerOff, Wallet} fro
|
||||
import {AimeCard, AimeServer, db} from "@/lib/database";
|
||||
import {getServerInfo, serverCardIn} from "@/lib/aime-net";
|
||||
|
||||
import {Footer} from "@/components/footer";
|
||||
import {AMFooter} from "@/components/am-footer.tsx";
|
||||
import {Button} from "@/components/ui/button";
|
||||
import {useToast} from "@/components/ui/use-toast";
|
||||
import {Separator} from "@/components/ui/separator";
|
||||
import {Alert, AlertDescription, AlertTitle} from "@/components/ui/alert";
|
||||
import {Tooltip, TooltipContent, TooltipProvider, TooltipTrigger} from "@/components/ui/tooltip";
|
||||
|
||||
import {AimeCardEntry} from "@/components/aime-card";
|
||||
import {LoadingIndicator} from "@/components/loading-indicator";
|
||||
@@ -43,6 +42,7 @@ async function performCardInAction(card: AimeCard, server: AimeServer, setCardIn
|
||||
setTimeout(() => setCardInStatus(false), 1000);
|
||||
}
|
||||
}
|
||||
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
|
||||
function CardList(props: { cards: ReadonlyArray<AimeCard> | null, cardSelected: (card: AimeCard) => void }) {
|
||||
@@ -62,27 +62,18 @@ function CardList(props: { cards: ReadonlyArray<AimeCard> | null, cardSelected:
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{props.cards?.length ? props.cards.map(x => <AimeCardEntry card={x} key={x.id}>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button size="icon" variant="ghost" onClick={() => props.cardSelected(x)}>
|
||||
<Circle className="w-5 h-5"/>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="left">
|
||||
<p>Select Card</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</AimeCardEntry>) : <NoCardsNotification/>}
|
||||
{!props.cards?.length
|
||||
? <NoCardsNotification/>
|
||||
: props.cards.map(x => <AimeCardEntry key={x.id} card={x}
|
||||
actionIcon={<Circle className="w-5 h-5"/>}
|
||||
actionClicked={() => props.cardSelected(x)}/>)}
|
||||
</>);
|
||||
}
|
||||
|
||||
function ServerCardInPage() {
|
||||
const {toast} = useToast();
|
||||
const {serverId} = useParams<{ serverId: string }>();
|
||||
|
||||
|
||||
const navigate = useNavigate();
|
||||
const cards = useLiveQuery(() => db.cards.toArray());
|
||||
|
||||
@@ -182,7 +173,7 @@ function ServerCardInPage() {
|
||||
}}/>
|
||||
</div>
|
||||
)}
|
||||
<Footer/>
|
||||
<AMFooter/>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@ import {Plus, Server, Trash} from "lucide-react";
|
||||
|
||||
import {AimeCard, db} from "@/lib/database";
|
||||
|
||||
import {Footer} from "@/components/footer";
|
||||
import {AMFooter} from "@/components/am-footer.tsx";
|
||||
import {Button} from "@/components/ui/button";
|
||||
import {Separator} from "@/components/ui/separator";
|
||||
import {
|
||||
@@ -75,7 +75,7 @@ function CardManagerPage() {
|
||||
{cards ? <CardList cards={cards}/> : <LoadingIndicator/>}
|
||||
</div>
|
||||
|
||||
<Footer/>
|
||||
<AMFooter/>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,19 +6,18 @@ import {useNavigate} from 'react-router-dom';
|
||||
import {useLiveQuery} from "dexie-react-hooks";
|
||||
import {zodResolver} from "@hookform/resolvers/zod";
|
||||
import {
|
||||
ChevronRight,
|
||||
Earth,
|
||||
History,
|
||||
Plus,
|
||||
ShieldAlert,
|
||||
SquareArrowOutUpRight,
|
||||
Trash, WalletCards
|
||||
Trash,
|
||||
WalletCards
|
||||
} from "lucide-react";
|
||||
|
||||
import {AimeServer, db} from "@/lib/database";
|
||||
|
||||
import {Card} from "@/components/ui/card";
|
||||
import {Footer} from "@/components/footer";
|
||||
import {AMFooter} from "@/components/am-footer.tsx";
|
||||
import {Input} from "@/components/ui/input";
|
||||
import {Button} from "@/components/ui/button";
|
||||
import {Separator} from "@/components/ui/separator";
|
||||
@@ -32,6 +31,7 @@ import {
|
||||
ContextMenuSeparator,
|
||||
ContextMenuTrigger
|
||||
} from "@/components/ui/context-menu";
|
||||
import {AMCard, AMCardDetail} from "@/components/am-card.tsx";
|
||||
|
||||
const serverAdditionSchema = z.object({
|
||||
address: z.string().startsWith("http")
|
||||
@@ -60,28 +60,18 @@ function ServerList(props: { servers: AimeServer[] | undefined }) {
|
||||
{props.servers.map(s =>
|
||||
<ContextMenu key={s.id}>
|
||||
<ContextMenuTrigger>
|
||||
<Card>
|
||||
<div className="flex p-6 items-center gap-3">
|
||||
<div className="flex-grow flex flex-col gap-4">
|
||||
<h4 className="text-2xl font-semibold select-none">{s.name}</h4>
|
||||
<div className="text-md flex gap-3 flex-wrap">
|
||||
{s.lastConnected > 0 && (
|
||||
<div className="flex items-center gap-2 text-gray-500">
|
||||
<History className="h-5 w-5"/>
|
||||
<TimeAgo date={s.lastConnected}/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-2 text-gray-500">
|
||||
<Earth className="h-5 w-5"/>
|
||||
<code>{s.address}</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="ghost" size="icon" onClick={() => navigate(`/servers/${s.id}`)}>
|
||||
<ChevronRight className="h-4 w-4"/>
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
<AMCard title={s.name} actionClicked={() => navigate(`/servers/${s.id}`)}>
|
||||
{s.lastConnected > 0 && (
|
||||
<AMCardDetail>
|
||||
<History className="h-5 w-5"/>
|
||||
<TimeAgo date={s.lastConnected}/>
|
||||
</AMCardDetail>
|
||||
)}
|
||||
<AMCardDetail>
|
||||
<Earth className="h-5 w-5"/>
|
||||
<code className="text-ellipsis whitespace-nowrap overflow-hidden">{s.address}</code>
|
||||
</AMCardDetail>
|
||||
</AMCard>
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent>
|
||||
<ContextMenuItem inset onClick={() => navigate(`/servers/${s.id}`)}>
|
||||
@@ -203,7 +193,7 @@ function ServerListing() {
|
||||
return (
|
||||
<main className="flex flex-col gap-5 container min-h-screen py-10">
|
||||
<div className="flex items-center gap-1">
|
||||
<h2 className="text-4xl font-bold select-none flex-grow">Servers</h2>
|
||||
<h2 className="text-4xl font-bold select-none flex-grow">AimeNet</h2>
|
||||
<Button variant="ghost" size={"default"} onClick={() => navigate('/cards')}>
|
||||
<WalletCards className="h-4 w-4 sm:mr-2"/>
|
||||
<span className="hidden sm:block">Manage Cards</span>
|
||||
@@ -215,7 +205,7 @@ function ServerListing() {
|
||||
<ServerList servers={servers}/>
|
||||
</div>
|
||||
|
||||
<Footer/>
|
||||
<AMFooter/>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user