All checks were successful
CI / Lint, typecheck, test (push) Successful in 3m35s
CI / Build container images (push) Has been skipped
CD / Build and push images (push) Successful in 3m44s
CD / Deploy to Test (push) Successful in 15s
CD / Smoke tests against Test (push) Successful in 1m17s
CD / Promote to Int (push) Successful in 11s
CI / Auth e2e pack (push) Successful in 5m36s
CI / Import/export fidelity gate (push) Successful in 46s
- content footer: the collab status is an icon (wifi/off/refresh, localized tooltip + visually-hidden text, class/data-status hooks kept for e2e) on the left, the legal links right-aligned; read mode drops the editor frame and its inner padding, edit mode keeps it - menus (page overflow, user, notifications bell, pond switcher) close on outside click and Escape via a shared useDismissable hook; the bell got its missing tooltip - side panels (labels, history) stack vertically in one column - edit mode gains a Save-version icon (prompt for the name, POST /pages/:id/versions); the history panel lists contributors by display name — more than three collapse to two plus an expandable ellipsis (PageVersionView.contributors resolved server-side, deleted users drop out) - search finds partial words via a LIKE fallback next to the tsquery (FTS matches still rank first; regression-pinned in the db pack), and the recent-searches list has a clear button - pond owners create labels directly in the label picker (plus a permanent link to the full manager); add/remove/delete buttons across the pond settings (members, access rules, labels, files) and the watch/unwatch toggles in pond/user settings are icon buttons now — class hooks and accessible names unchanged for the e2e packs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
61 lines
2.1 KiB
TypeScript
61 lines
2.1 KiB
TypeScript
import type { WatchListView } from '@dorfteich/shared';
|
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
|
import { EyeOff } from 'lucide-react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { Link } from 'react-router-dom';
|
|
|
|
import { apiDelete, apiGet } from '../lib/api';
|
|
|
|
/**
|
|
* The account's watch list (issue #93): everything the user follows, with
|
|
* links to the targets and an unwatch action per entry.
|
|
*/
|
|
export function WatchesSection(): React.JSX.Element {
|
|
const { t } = useTranslation('watches');
|
|
const queryClient = useQueryClient();
|
|
const list = useQuery({
|
|
queryKey: ['watches'],
|
|
queryFn: () => apiGet<WatchListView>('/users/me/watches'),
|
|
});
|
|
|
|
const unwatch = async (targetType: string, targetId: string): Promise<void> => {
|
|
await apiDelete(`/watches/${targetType}/${targetId}`);
|
|
await queryClient.invalidateQueries({ queryKey: ['watches'] });
|
|
await queryClient.invalidateQueries({ queryKey: ['watch', targetType, targetId] });
|
|
};
|
|
|
|
return (
|
|
<section className="settings-section">
|
|
<h2>{t('settings.title')}</h2>
|
|
{list.data && list.data.watches.length === 0 && <p>{t('settings.empty')}</p>}
|
|
{list.data && list.data.watches.length > 0 && (
|
|
<ul className="watches-list">
|
|
{list.data.watches.map((watch) => (
|
|
<li key={watch.id}>
|
|
<Link
|
|
to={
|
|
watch.targetType === 'pond'
|
|
? `/p/${watch.pondSlug}`
|
|
: `/p/${watch.pondSlug}/${watch.slug}`
|
|
}
|
|
>
|
|
{watch.name}
|
|
</Link>
|
|
<span className="watches-list__type">{t(`settings.types.${watch.targetType}`)}</span>
|
|
<button
|
|
type="button"
|
|
className="icon-button watches-list__unwatch"
|
|
aria-label={t('settings.unwatch')}
|
|
title={t('settings.unwatch')}
|
|
onClick={() => void unwatch(watch.targetType, watch.targetId)}
|
|
>
|
|
<EyeOff aria-hidden />
|
|
</button>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
</section>
|
|
);
|
|
}
|