Phase 1 scaffold: auth for all apps

- Backend: Fastify with two listeners (public + internal), routes, services, DB migration + seed
- client_app: Flutter with BLoC, all auth screens (welcome, display name, register, OTP, force-register)
- mitra_app: Flutter with BLoC, OTP-only login
- control_center: React + Vite, email/password login, mitra/user management, anonymity settings
- Docs: phase1 plan, API contract, client app mockup
- CLAUDE.md and shared memory for all subprojects

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-05 10:08:42 +08:00
commit a7a2a32d27
85 changed files with 3953 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { apiClient } from '../../core/api/api-client'
const fetchAnonymityConfig = async () => {
const res = await apiClient.get('/internal/config/anonymity')
return res.data.data
}
const updateAnonymityConfig = async (anonymity_enabled) => {
const res = await apiClient.patch('/internal/config/anonymity', { anonymity_enabled })
return res.data.data
}
export default function SettingsPage() {
const queryClient = useQueryClient()
const { data, isLoading } = useQuery({ queryKey: ['config-anonymity'], queryFn: fetchAnonymityConfig })
const mutation = useMutation({
mutationFn: updateAnonymityConfig,
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['config-anonymity'] }),
})
if (isLoading) return <div>Loading...</div>
return (
<div>
<h1>Settings</h1>
<section style={{ marginBottom: 24 }}>
<h2>Anonymity</h2>
<p>Ketika dinonaktifkan, pengguna anonim akan diminta mendaftar setelah sesi selesai.</p>
<label style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<input
type="checkbox"
checked={data?.anonymity_enabled ?? true}
onChange={e => mutation.mutate(e.target.checked)}
disabled={mutation.isPending}
/>
Izinkan pengguna anonim
</label>
{mutation.isError && <p style={{ color: 'red' }}>Gagal menyimpan.</p>}
</section>
</div>
)
}