add game name to page, make detail icons use same classes

This commit is contained in:
ppc
2024-09-05 10:45:50 +01:00
parent e467329db8
commit f4dd845ee6
5 changed files with 90 additions and 81 deletions
+8 -9
View File
@@ -8,20 +8,19 @@ 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)");
return (
<AMCard title={props.card.name} actionIcon={props.actionIcon} actionClicked={props.actionClicked} actionDisabled={props.actionDisabled}>
<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"/>
<AMCardDetail icon={History}>
<TimeAgo date={props.card.lastUsed}/>
</AMCardDetail>
)}
<AMCardDetail>
<CreditCard className="h-5 w-5 cursor-pointer" onClick={() => navigator.clipboard.writeText(props.card.id)}/>
<code>{isDesktop
? props.card.id.match(/.{1,4}/g)?.join(" ")
: `•••• ${props.card.id.substring(props.card.id.length - 4)}`}
<AMCardDetail icon={CreditCard} iconClicked={() => navigator.clipboard.writeText(props.card.id)}>
<code>
{isDesktop
? props.card.id.match(/.{1,4}/g)?.join(" ")
: `•••• ${props.card.id.substring(props.card.id.length - 4)}`}
</code>
</AMCardDetail>
</AMCard>
+5 -3
View File
@@ -1,5 +1,6 @@
import React from "react";
import {ChevronRight} from "lucide-react";
import {ChevronRight, LucideIcon} from "lucide-react";
import {Card} from "@/components/ui/card.tsx";
import {Button} from "@/components/ui/button.tsx";
@@ -18,7 +19,7 @@ export function AMCard(props: AMCardActionProps & { title: string, titlePerforms
onClick={() => props.titlePerformsAction && props.actionClicked ? props.actionClicked() : null}>
{props.title}
</h4>
<div className="text-sm lg:text-md flex flex-auto flex-wrap gap-3">
<div className="text-sm lg:text-md flex flex-auto flex-wrap gap-4">
{props.children}
</div>
</div>
@@ -32,9 +33,10 @@ export function AMCard(props: AMCardActionProps & { title: string, titlePerforms
)
}
export function AMCardDetail(props: { children?: Readonly<React.ReactNode> }) {
export function AMCardDetail(props: { icon: LucideIcon, iconClicked?: () => void | Promise<any>, children?: Readonly<React.ReactNode> }) {
return (
<div className="inline-flex items-center max-w-full gap-2 text-gray-500 select-none md:select-text">
{React.createElement(props.icon, {className: "h-5 w-5", onClick: props.iconClicked})}
{props.children}
</div>
)
+45
View File
@@ -0,0 +1,45 @@
import {useMemo} from "react";
import TimeAgo from "react-timeago";
import {getGameName} from "amnet-shared";
import {useNavigate} from "react-router-dom";
import {Earth, History, Joystick, Trash} from "lucide-react";
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuSeparator,
ContextMenuTrigger
} from "@/components/ui/context-menu.tsx";
import {AimeServer, db} from "@/lib/database.ts";
import {AMCard, AMCardDetail} from "@/components/am-card.tsx";
export function AMServer(props: { server: AimeServer }) {
const navigate: (p: string) => void = useNavigate();
const gameName = useMemo(() => props.server.gameId ? getGameName(props.server.gameId) : null, [props.server.gameId]);
return (
<ContextMenu key={props.server.id}>
<ContextMenuTrigger>
<AMCard title={props.server.serverName} titlePerformsAction actionClicked={() => navigate(`/servers/${props.server.id}`)}>
<AMCardDetail icon={Earth}>
<code className="text-ellipsis whitespace-nowrap overflow-hidden">{props.server.serverAddress}</code>
</AMCardDetail>
<AMCardDetail icon={History}>
{props.server.lastConnected > 0 ? <TimeAgo date={props.server.lastConnected}/> :
<span>never</span>}
</AMCardDetail>
{gameName && <AMCardDetail icon={Joystick}>{gameName}</AMCardDetail>}
</AMCard>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem inset onClick={() => navigate(`/servers/${props.server.id}`)}>
Select Server
</ContextMenuItem>
<ContextMenuSeparator/>
<ContextMenuItem className="text-red-500" onClick={() => db.servers.delete(props.server.id)}>
<Trash className="mr-2 h-4 w-4"/> Remove Server
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>)
}
+7 -7
View File
@@ -1,11 +1,9 @@
import {useLiveQuery} from "dexie-react-hooks";
import {useEffect, useMemo, useState} from "react";
import {useNavigate, useParams} from "react-router-dom";
import {performCardIn, fetchServerInfo, getGameName} from "amnet-shared";
import {CircleDot, CircleCheckBig, Home, Nfc, Wallet, WifiOff} from "lucide-react";
import {AimeCard, AimeServer, db} from "@/lib/database";
import {performCardIn, fetchServerInfo} from "amnet-shared";
import {Button} from "@/components/ui/button";
import {useToast} from "@/components/ui/use-toast";
import {Alert, AlertDescription, AlertTitle} from "@/components/ui/alert";
@@ -21,6 +19,8 @@ import {
AlertDialogTrigger
} from "@/components/ui/alert-dialog.tsx";
import {AimeCard, AimeServer, db} from "@/lib/database";
import {AMHeader} from "@/components/am-header";
import {AMFooter} from "@/components/am-footer";
import {Separator} from "@/components/ui/separator";
@@ -98,6 +98,7 @@ function ServerCardInPage() {
const [connectionError, setConnectionError] = useState(false);
const shareUrl = useMemo(() => `${location.origin}/#/servers/add?address=${encodeURIComponent(server?.serverAddress ?? "")}`, [server]);
const gameName = useMemo(() => server?.gameId ? getGameName(server.gameId) : null, [server]);
// select newest card
useEffect(() => setSelectedCard(cards?.sort((a, b) => b.lastUsed - a.lastUsed)[0] ?? null), [cards]);
@@ -170,7 +171,7 @@ function ServerCardInPage() {
{server && (
<div className="flex flex-col gap-4 w-full flex-grow">
{selectedCard && (<>
<h4 className="text-2xl font-bold">Selected Card</h4>
<h4 className="text-2xl font-bold">{gameName ? `Play ${gameName}` : "Selected Card"}</h4>
<AimeCardEntry card={selectedCard}>
<Button size="icon" variant="ghost" disabled>
@@ -198,8 +199,7 @@ function ServerCardInPage() {
</div>
)}
<AMFooter
className="flex flex-col-reverse sm:flex-row sm:flex-wrap justify-center sm:justify-between sm:gap-2 gap-4">
<AMFooter className="flex flex-col-reverse sm:flex-row sm:flex-wrap justify-center sm:justify-between sm:gap-2 gap-4">
{server?.id && (
<div className="flex items-center justify-center sm:gap-5 gap-2">
{location.protocol === "https:" && (<>
@@ -252,4 +252,4 @@ function ServerCardInPage() {
);
}
export default ServerCardInPage;
export default ServerCardInPage;
+25 -62
View File
@@ -1,21 +1,19 @@
import {z} from "zod";
import TimeAgo from "react-timeago";
import {useForm} from "react-hook-form";
import {useLiveQuery} from "dexie-react-hooks";
import {AMNET_API_VERSION} from "amnet-shared";
import React, {useEffect, useState} from "react";
import {useLocation, useNavigate} from 'react-router-dom';
import {zodResolver} from "@hookform/resolvers/zod";
import {
Earth,
History,
Plus,
ShieldAlert,
SquareArrowOutUpRight,
Trash,
WalletCards
} from "lucide-react";
import {AimeServer, db} from "@/lib/database";
import {zodResolver} from "@hookform/resolvers/zod";
import {Input} from "@/components/ui/input";
import {Button} from "@/components/ui/button";
@@ -23,26 +21,38 @@ import {LoadingIndicator} from "@/components/loading-indicator";
import {Alert, AlertDescription, AlertTitle} from "@/components/ui/alert";
import {Form, FormControl, FormField, FormItem, FormMessage} from "@/components/ui/form";
import {HttpsMixedModeRequestWarning} from "@/components/https-mixed-mode-request-warning";
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuSeparator,
ContextMenuTrigger
} from "@/components/ui/context-menu";
import {AMFooter} from "@/components/am-footer";
import {AMHeader} from "@/components/am-header";
import {AMNET_API_VERSION} from "amnet-shared";
import {AMCard, AMCardDetail} from "@/components/am-card";
import {AMServer} from "@/components/am-server.tsx";
const serverAdditionSchema = z.object({
address: z.string().startsWith("http")
});
function ServerList(props: { servers: AimeServer[] | undefined }) {
function ServerListing() {
const navigate = useNavigate();
const servers = useLiveQuery(() => db.servers.toArray());
return (
<main className="flex flex-col gap-5 container min-h-screen py-10">
<AMHeader title="Servers">
<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>
</Button>
</AMHeader>
<div className="flex flex-col gap-4 w-full flex-grow">
<ServerList servers={servers}/>
</div>
<AMFooter/>
</main>
);
}
function ServerList(props: { servers: AimeServer[] | undefined }) {
if (!props.servers) {
return (<LoadingIndicator/>);
}
@@ -65,32 +75,7 @@ function ServerList(props: { servers: AimeServer[] | undefined }) {
<HttpsMixedModeRequestWarning/>
<ServerAddForm/>
{props.servers.map(s =>
<ContextMenu key={s.id}>
<ContextMenuTrigger>
<AMCard title={s.serverName} titlePerformsAction 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.serverAddress}</code>
</AMCardDetail>
</AMCard>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem inset onClick={() => navigate(`/servers/${s.id}`)}>
Select Server
</ContextMenuItem>
<ContextMenuSeparator/>
<ContextMenuItem className="text-red-500" onClick={() => db.servers.delete(s.id)}>
<Trash className="mr-2 h-4 w-4"/> Remove Server
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>)}
{props.servers.map(s => <AMServer key={s.id} server={s}/>)}
</>);
}
@@ -224,26 +209,4 @@ function ServerAddForm() {
);
}
function ServerListing() {
const navigate = useNavigate();
const servers = useLiveQuery(() => db.servers.toArray());
return (
<main className="flex flex-col gap-5 container min-h-screen py-10">
<AMHeader title="Servers">
<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>
</Button>
</AMHeader>
<div className="flex flex-col gap-4 w-full flex-grow">
<ServerList servers={servers}/>
</div>
<AMFooter/>
</main>
);
}
export default ServerListing;