SPB Git

Repository Public

Shares.tsx 3.6 KB 56 lines tsx

1// Every share link of the account, whatever it targets.2import { useQuery } from '@tanstack/react-query';3import { useState } from 'react';4import { Link } from 'wouter';5import { get } from '../api.ts';6import { Glyph } from '../glyphs.tsx';7import { useT } from '../i18n.ts';8import { ShareDialog } from '../sharing/ShareDialog.tsx';9import { ShareItem } from '../sharing/ShareParts.tsx';10import type { Share } from '../types.ts';11import { cx, EmptyState, ErrorState, Loading } from '../ui.tsx';1213const STATUSES: (Share['status'] | 'all')[] = ['all', 'active', 'expired', 'exhausted', 'revoked', 'target_missing'];1415export function Shares() {16  const t = useT();17  const q = useQuery({ queryKey: ['shares', 'all'], queryFn: () => get<Share[]>('/shares'), refetchOnMount: 'always' });18  const [filter, setFilter] = useState<Share['status'] | 'all'>('all');19  const [manage, setManage] = useState<Share | null>(null);20  const all = q.data ?? [];21  const shown = filter === 'all' ? all : all.filter((s) => s.status === filter);22  const count = (s: Share['status'] | 'all') => (s === 'all' ? all.length : all.filter((x) => x.status === s).length);23  return (24    <div className="pb-dock mx-auto w-full max-w-3xl px-4 pt-3 sm:px-6 sm:pt-7" data-testid="shares-screen">25      <header className="px-1">26        <h1 className="text-[30px] leading-[1.1] font-bold tracking-[-0.03em] sm:text-[32px]">{t('sharing.shares.title')}</h1>27        <p className="mt-1.5 text-[15px] text-ink-2">{t('sharing.shares.intro')}</p>28      </header>29      {q.isLoading ? <Loading /> : q.error ? <ErrorState error={q.error} onRetry={() => void q.refetch()} /> : !all.length ? <EmptyState icon="link" title={t('sharing.shares.empty')} text={t('sharing.shares.emptyText')} /> : (30        <>31          <div role="group" aria-label={t('sharing.shares.filter')} className="mt-4 flex flex-wrap gap-1.5">32            {STATUSES.filter((s) => s === 'all' || count(s) > 0).map((s) => (33              <button key={s} type="button" aria-pressed={filter === s} onClick={() => setFilter(s)}34                className={cx('press h-10 rounded-full px-4 text-sm font-medium', filter === s ? 'grad shadow-sm' : 'bg-surface text-ink-2 ring-1 ring-inset ring-line hover:bg-hover')}>35                {s === 'all' ? t('sharing.shares.all') : t(`sharing.status.${s}`)} <span className="tabular-nums opacity-70">{count(s)}</span>36              </button>37            ))}38          </div>39          <div className="stagger mt-5 space-y-3">40            {shown.length ? shown.map((s) => (41              <ShareItem key={s.id} share={s} heading={42                <p className="mb-1.5 flex min-w-0 items-center gap-2 text-sm">43                  <Glyph name={s.type} size={18} className={cx('shrink-0', s.type === 'folder' ? 'text-accent' : 'text-ink-3')} />44                  {s.status === 'target_missing' || !s.targetName ? <span className="truncate text-ink-3">{s.targetName || t('sharing.shares.targetGone')}</span>45                    : s.type === 'folder' ? <Link href={`/folder/${s.targetId}`} className="truncate font-medium text-accent underline-offset-2 hover:underline">{s.targetName}</Link>46                      : <button type="button" onClick={() => setManage(s)} className="truncate text-left font-medium text-accent underline-offset-2 hover:underline" title={t('sharing.shares.manageFile')}>{s.targetName}</button>}47                </p>} />48            )) : <p className="py-10 text-center text-sm text-ink-3">{t('sharing.shares.noneForFilter')}</p>}49          </div>50        </>51      )}52      {manage ? <ShareDialog target={{ type: manage.type, id: manage.targetId, name: manage.targetName }} onClose={() => setManage(null)} /> : null}53    </div>54  );55}56
Files main
Menu

Theme