All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 4m44s
CI / Build container images (pull_request) Successful in 4m2s
CI / Auth e2e pack (pull_request) Successful in 10m50s
CI / Import/export fidelity gate (pull_request) Successful in 55s
CD / Build and push images (push) Successful in 19s
CD / Deploy to Test (push) Successful in 17s
CD / Smoke tests against Test (push) Successful in 4m2s
CI / Lint, typecheck, test (push) Successful in 4m47s
CI / Build container images (push) Has been skipped
CD / Promote to Int (push) Successful in 14s
CI / Auth e2e pack (push) Successful in 10m7s
CI / Import/export fidelity gate (push) Successful in 56s
Release / Build release images and notes (push) Successful in 1m11s
Release / Release-candidate operations QA (push) Successful in 1m0s
Prod deploy / Deploy the released images to Prod (push) Successful in 17s
pondSettingsSchema gains theme = { accent: '#rrggbb' | null } (null =
inherit the viewer's theme), exposed as a top-level key of the flat
updatePondInputSchema and included in the PondsService settings merge
(the known silent-no-op pitfall). The server validates only the hex;
conformance arises at render time: PondThemeScope (mounted around the
page content next to PondFontScope) derives the accent pair for the
EFFECTIVE mode via useEffectiveTheme and sets it as inline custom
properties — inline beats both tokens.css and the user-theme <style>,
which IS the cascade precedence pond > user > default.
Pond settings get a PondThemeSection (inherit | presets | custom color
with per-mode preview swatches, explicit save like the font manager);
AccentSwatches extracted for reuse; i18n de+en. The no-JS public shell
stays deliberately un-themed (ADR 0018 amendment).
Tests: pond DB test (theme merge keeps fonts, invalid hex 400), e2e
pond-theme.spec (scope boundary content vs. chrome, per-mode
re-derivation, axe on the pond settings page; resets the fixture pond).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRtCnB3uLdQtFmvp9HXcRX
167 lines
6.2 KiB
TypeScript
167 lines
6.2 KiB
TypeScript
import type { PondView } from '@dorfteich/shared';
|
|
import { useQuery } from '@tanstack/react-query';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { useParams } from 'react-router-dom';
|
|
|
|
import { useAuth } from '../auth/auth-context';
|
|
import { SettingsLayout } from '../components/SettingsLayout';
|
|
import { ApiOptInSetting } from '../api-tokens/ApiOptInSetting';
|
|
import { CommentPolicySetting } from '../comments/CommentPolicySetting';
|
|
import { WatchToggle } from '../watches/WatchToggle';
|
|
import { FormError } from '../components/forms';
|
|
import { AppearanceManager } from '../fonts/AppearanceManager';
|
|
import { LabelManager } from '../labels/LabelManager';
|
|
import { PhantomPagesView } from '../links/PhantomPagesView';
|
|
import { AccessRulesManager } from '../access/AccessRulesManager';
|
|
import { EffectivePermissionsInspector } from '../access/EffectivePermissionsInspector';
|
|
import { PondFileManager } from '../files/PondFileManager';
|
|
import { apiGet } from '../lib/api';
|
|
import { VaultImportSection } from '../import/VaultImportSection';
|
|
import { SidebarViewSetting } from '../layout/SidebarViewSetting';
|
|
import { MemberManager } from '../members/MemberManager';
|
|
import { DeletePondSection } from '../ponds/DeletePondSection';
|
|
import { PondPluginSettings } from '../plugins/PondPluginSettings';
|
|
import { PondThemeSection } from '../theme/PondThemeSection';
|
|
|
|
import { useDocumentTitle } from '../lib/use-document-title';
|
|
/**
|
|
* Pond settings (issues #44/#54). Hosts the 'Members' and 'Labels' sections;
|
|
* future pond-level configuration (fonts, etc.) joins it here. Member
|
|
* management is Pond-Admin-gated in the api (the MemberManager shows the list
|
|
* to any member and hides the controls otherwise); label management needs
|
|
* modify rights. The sidebar links here for the pond owner (Site Admins and
|
|
* other members can still navigate directly).
|
|
*/
|
|
export function PondSettingsPage(): React.JSX.Element {
|
|
const { t } = useTranslation('labels');
|
|
const { t: tLinks } = useTranslation('links');
|
|
const { t: tMembers } = useTranslation('members');
|
|
const { t: tErrors } = useTranslation('errors');
|
|
const { t: tFiles } = useTranslation('files');
|
|
const { t: tExport } = useTranslation('export');
|
|
const { t: tComments } = useTranslation('comments');
|
|
const { t: tApiTokens } = useTranslation('apiTokens');
|
|
const { t: tFont } = useTranslation('font');
|
|
const { t: tCommon } = useTranslation();
|
|
const { t: tImport } = useTranslation('import');
|
|
const { pondSlug = '' } = useParams<{ pondSlug: string }>();
|
|
const { user } = useAuth();
|
|
|
|
const pond = useQuery({
|
|
queryKey: ['pond', pondSlug],
|
|
queryFn: () => apiGet<PondView>(`/ponds/${pondSlug}`),
|
|
});
|
|
useDocumentTitle(tCommon('settings:title'), pond.data?.name);
|
|
|
|
if (pond.error) return <FormError error={pond.error} />;
|
|
if (!pond.data) return <></>;
|
|
|
|
const canModify = Boolean(user && (user.isSiteAdmin || user.id === pond.data.ownerId));
|
|
|
|
return (
|
|
<div className="pond-settings-page">
|
|
<div className="pond-settings-page__header">
|
|
<h1>{pond.data.name}</h1>
|
|
<WatchToggle targetType="pond" targetId={pond.data.id} variant="icon" />
|
|
</div>
|
|
<SettingsLayout>
|
|
<section>
|
|
<h2>{tMembers('title')}</h2>
|
|
<MemberManager pondId={pond.data.id} />
|
|
</section>
|
|
<AccessRulesManager pondId={pond.data.id} />
|
|
<EffectivePermissionsInspector pondId={pond.data.id} />
|
|
<section>
|
|
<h2>{t('settings.title')}</h2>
|
|
{canModify ? (
|
|
<LabelManager pondId={pond.data.id} />
|
|
) : (
|
|
<p className="form-banner form-banner--error" role="alert">
|
|
{tErrors('forbidden')}
|
|
</p>
|
|
)}
|
|
</section>
|
|
{canModify && (
|
|
<section>
|
|
<h2>{tLinks('missing.title')}</h2>
|
|
<PhantomPagesView pondId={pond.data.id} pondSlug={pondSlug} />
|
|
</section>
|
|
)}
|
|
{canModify && (
|
|
<section>
|
|
<h2>{tImport('vault.title')}</h2>
|
|
<VaultImportSection pondId={pond.data.id} pondSlug={pondSlug} />
|
|
</section>
|
|
)}
|
|
{canModify && (
|
|
<section>
|
|
<h2>{tFiles('manager.title')}</h2>
|
|
<PondFileManager pondId={pond.data.id} />
|
|
</section>
|
|
)}
|
|
{canModify && (
|
|
<section className="appearance-section">
|
|
<h2>{tFont('heading')}</h2>
|
|
<AppearanceManager
|
|
pondId={pond.data.id}
|
|
pondSlug={pondSlug}
|
|
fonts={pond.data.settings.fonts}
|
|
/>
|
|
<PondThemeSection
|
|
pondId={pond.data.id}
|
|
pondSlug={pondSlug}
|
|
theme={pond.data.settings.theme}
|
|
/>
|
|
</section>
|
|
)}
|
|
{canModify && <PondPluginSettings pondId={pond.data.id} />}
|
|
{canModify && (
|
|
<section>
|
|
<h2>{tCommon('layout.sidebar.view.defaultTitle')}</h2>
|
|
<SidebarViewSetting
|
|
pondId={pond.data.id}
|
|
pondSlug={pondSlug}
|
|
value={pond.data.settings.sidebarView}
|
|
/>
|
|
</section>
|
|
)}
|
|
{canModify && (
|
|
<section>
|
|
<h2>{tComments('policy.title')}</h2>
|
|
<CommentPolicySetting
|
|
pondId={pond.data.id}
|
|
pondSlug={pondSlug}
|
|
value={pond.data.settings.commentPolicy}
|
|
/>
|
|
</section>
|
|
)}
|
|
{canModify && (
|
|
<section>
|
|
<h2>{tApiTokens('pond.title')}</h2>
|
|
<ApiOptInSetting
|
|
pondId={pond.data.id}
|
|
pondSlug={pondSlug}
|
|
value={pond.data.settings.apiEnabled}
|
|
mcpValue={pond.data.settings.mcpEnabled}
|
|
/>
|
|
</section>
|
|
)}
|
|
<section className="pond-export">
|
|
<h2>{tExport('pond.heading')}</h2>
|
|
<p className="pond-export__hint">{tExport('pond.hint')}</p>
|
|
<a
|
|
className="button"
|
|
href={`/api/v1/ponds/${pond.data.id}/export/markdown`}
|
|
download={`${pond.data.slug}.zip`}
|
|
>
|
|
{tExport('pond.zip')}
|
|
</a>
|
|
</section>
|
|
{canModify && pond.data.type === 'shared' && (
|
|
<DeletePondSection pondId={pond.data.id} pondName={pond.data.name} />
|
|
)}
|
|
</SettingsLayout>
|
|
</div>
|
|
);
|
|
}
|