From cad6c50bff0d03975783224495c493f8d5fdc237 Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Wed, 29 Apr 2026 00:40:04 +0200 Subject: [PATCH] feat(web): RegistrationPanel scaffold with both registration toggles --- .../RegistrationPanel.tsx | 149 ++++++++++++++++++ .../modals/settingsPanels/InstancePanel.tsx | 5 +- 2 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 packages/web/src/components/modals/instanceSettingsPanels/RegistrationPanel.tsx diff --git a/packages/web/src/components/modals/instanceSettingsPanels/RegistrationPanel.tsx b/packages/web/src/components/modals/instanceSettingsPanels/RegistrationPanel.tsx new file mode 100644 index 00000000..36f999d9 --- /dev/null +++ b/packages/web/src/components/modals/instanceSettingsPanels/RegistrationPanel.tsx @@ -0,0 +1,149 @@ +import { useEffect, useState } from 'react'; +import { useSettingsStore } from '../../../stores/settingsStore'; +import { useUIStore } from '../../../stores/uiStore'; +import { Toggle } from '../../ui/Toggle'; + +interface RegistrationDraft { + registrationOpen: boolean; + federatedRegistrationOpen: boolean; +} + +export function RegistrationPanel() { + const instanceSettings = useSettingsStore((s) => s.instanceSettings); + const updateInstanceSettings = useSettingsStore((s) => s.updateInstanceSettings); + const addToast = useUIStore((s) => s.addToast); + + const [draft, setDraft] = useState(null); + const [saving, setSaving] = useState(false); + const [saveError, setSaveError] = useState(''); + + useEffect(() => { + if (instanceSettings) { + setDraft({ + registrationOpen: instanceSettings.registrationOpen, + federatedRegistrationOpen: instanceSettings.federatedRegistrationOpen, + }); + setSaveError(''); + } + }, [instanceSettings]); + + if (!draft) return
Loading settings...
; + + const hasChanges = !!instanceSettings && ( + draft.registrationOpen !== instanceSettings.registrationOpen || + draft.federatedRegistrationOpen !== instanceSettings.federatedRegistrationOpen + ); + + const handleSave = async () => { + setSaving(true); + setSaveError(''); + try { + await updateInstanceSettings({ + registrationOpen: draft.registrationOpen, + federatedRegistrationOpen: draft.federatedRegistrationOpen, + }); + addToast('Registration settings saved', 'success', 2000); + } catch (err) { + const message = err instanceof Error ? err.message : 'Failed to save'; + setSaveError(message); + addToast('Failed to update registration settings', 'warning'); + } finally { + setSaving(false); + } + }; + + const handleReset = () => { + if (instanceSettings) { + setDraft({ + registrationOpen: instanceSettings.registrationOpen, + federatedRegistrationOpen: instanceSettings.federatedRegistrationOpen, + }); + } + setSaveError(''); + }; + + return ( +
e.preventDefault()}> +

Registration

+
+ Control who can create accounts on this instance. Public registration covers local + sign-ups; federated registration covers users from peered instances creating an account here. +
+ + {/* Public registration */} +
+
Public Registration
+
+ +
+
+ + {/* Federated registration */} +
+
Federated Registration
+
+ +
+
+ + {/* Invite Links — populated in subsequent tasks */} +
+
Invite Links
+
+ Invite-link UI will appear here. +
+
+ + {/* Status messages */} + {saveError && ( +
{saveError}
+ )} + {/* Save / Reset bar */} + {hasChanges && ( +
+
+
+ + +
+
+
+ )} +
+ ); +} diff --git a/packages/web/src/components/modals/settingsPanels/InstancePanel.tsx b/packages/web/src/components/modals/settingsPanels/InstancePanel.tsx index 16e5ffc2..d9d4f23b 100644 --- a/packages/web/src/components/modals/settingsPanels/InstancePanel.tsx +++ b/packages/web/src/components/modals/settingsPanels/InstancePanel.tsx @@ -4,12 +4,13 @@ import { useSettingsSections } from '../../../hooks/useSettingsSections'; import type { SettingsSection } from '../SettingsSectionsContext'; import { SettingsTabBar } from '../SettingsTabBar'; import { GeneralPanel } from '../instanceSettingsPanels/GeneralPanel'; +import { RegistrationPanel } from '../instanceSettingsPanels/RegistrationPanel'; import { FederationPanel } from '../instanceSettingsPanels/FederationPanel'; import { StreamingPanel } from '../instanceSettingsPanels/StreamingPanel'; import { StoragePanel } from '../instanceSettingsPanels/StoragePanel'; import { UsersPanel } from '../instanceSettingsPanels/UsersPanel'; -type SubTab = 'general' | 'federation' | 'streaming' | 'storage' | 'users'; +type SubTab = 'general' | 'registration' | 'federation' | 'streaming' | 'storage' | 'users'; export function InstancePanel() { const fetchInstanceSettings = useSettingsStore((s) => s.fetchInstanceSettings); @@ -20,6 +21,7 @@ export function InstancePanel() { const sections = useMemo(() => [ { id: 'general', label: 'General' }, + { id: 'registration', label: 'Registration' }, { id: 'federation', label: 'Federation', badgeCount: approvalCount }, { id: 'streaming', label: 'Streaming' }, { id: 'storage', label: 'Storage' }, @@ -43,6 +45,7 @@ export function InstancePanel() { {subTab === 'general' && } + {subTab === 'registration' && } {subTab === 'federation' && } {subTab === 'streaming' && } {subTab === 'storage' && }