chore(history): squash 16 commit(s) from 2026-09-26
- chore(backend): sync Cargo.lock with gateway dev-dependency - feat(frontend): ClickGui replica matching the in-game GUI — category panels, Modules/Settings switch, theme cards - feat(frontend): floating glass top bar in the mod's UI language - fix(frontend): soft night backdrop for the ClickGui, editor that fits one screen - chore(frontend): replace em dashes in UI copy with lighter separators - refactor(frontend): split landing sections and theme editor into subfolders (4-files-per-folder rule) - feat(frontend): login and registration in the mod's glass panel style - feat(frontend): link the game with a device code - feat(frontend): account page with avatar upload and linked games - feat(frontend): my configs with share codes and publishing - feat(frontend): public showcase listing and detail pages - chore(frontend): drop em dashes from visible copy - feat(frontend): atmospheric backdrop, glass download block and ClickGui-style FAQ on the landing - feat(frontend): Konami code, DevTools console greeting and ASCII 404 block - feat: public player profiles at /u/:id and author link in the showcase - fix(frontend): stop the theme editor overflowing horizontally at 375px
This commit is contained in:
parent
7f4b532f99
commit
ff2edc8eb6
116 changed files with 3534 additions and 1020 deletions
72
frontend/src/features/account/components/DeviceList.tsx
Normal file
72
frontend/src/features/account/components/DeviceList.tsx
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Link } from 'react-router'
|
||||
import { Button } from '../../../shared/ui/Button'
|
||||
import { listDevices, revokeDevice, type DeviceLink } from '../api'
|
||||
|
||||
function relativeDate(iso: string): string {
|
||||
return new Intl.DateTimeFormat('ru-RU', { dateStyle: 'long' }).format(new Date(iso))
|
||||
}
|
||||
|
||||
function DeviceRow({ device }: { device: DeviceLink }) {
|
||||
const { t } = useTranslation('account')
|
||||
const client = useQueryClient()
|
||||
const [confirming, setConfirming] = useState(false)
|
||||
const mutation = useMutation({
|
||||
mutationFn: () => revokeDevice(device.id),
|
||||
onSuccess: () => client.invalidateQueries({ queryKey: ['devices'] }),
|
||||
})
|
||||
|
||||
return (
|
||||
<li className="flex items-center justify-between gap-4 rounded-(--radius-panel) border border-shoal bg-slate/60 px-4 py-3">
|
||||
<div>
|
||||
<p className="text-sm text-snow">{t('devices.linkedAt', { date: relativeDate(device.linked_at) })}</p>
|
||||
<p className="text-sm text-frost">
|
||||
{t('devices.lastSeen', { when: device.last_seen ? relativeDate(device.last_seen) : t('devices.neverSeen') })}
|
||||
</p>
|
||||
</div>
|
||||
{confirming ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="danger" busy={mutation.isPending} onClick={() => mutation.mutate()}>
|
||||
{t('devices.confirmRevoke')}
|
||||
</Button>
|
||||
<Button variant="quiet" onClick={() => setConfirming(false)}>
|
||||
{t('devices.cancel')}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<Button variant="danger" onClick={() => setConfirming(true)}>
|
||||
{t('devices.revoke')}
|
||||
</Button>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
/** ClickGui-like rows listing devices the mod linked, each with a two-step confirm to revoke. */
|
||||
export function DeviceList() {
|
||||
const { t } = useTranslation('account')
|
||||
const query = useQuery({ queryKey: ['devices'], queryFn: listDevices })
|
||||
const devices = query.data ?? []
|
||||
|
||||
return (
|
||||
<div className="rounded-(--radius-panel) border border-shoal bg-slate/40 p-5">
|
||||
<h2 className="font-display text-lg">{t('devices.heading')}</h2>
|
||||
{devices.length === 0 ? (
|
||||
<p className="mt-3 text-sm text-frost">
|
||||
{t('devices.empty')}{' '}
|
||||
<Link to="/link" className="text-ice underline-offset-4 hover:underline">
|
||||
{t('devices.emptyLink')}
|
||||
</Link>
|
||||
</p>
|
||||
) : (
|
||||
<ul className="mt-3 flex flex-col gap-2">
|
||||
{devices.map((device) => (
|
||||
<DeviceRow key={device.id} device={device} />
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue