diff --git a/README.md b/README.md index 98276c9e..b6e29dd7 100644 --- a/README.md +++ b/README.md @@ -108,11 +108,6 @@ message naming which limit was hit — the app is not broken, it is full. Limits (`DEMO_MAX_*`), enforced in the service layer, and scoped per owner so no single visitor can consume everyone else's allowance. -**Not yet built (by design, not oversight):** Facebook sign-in and avatar uploads. The Facebook strategy is -written and dormant — it needs only credentials — but Facebook's HTTPS redirect requirement makes it poor -value for a demo, so only Google is wired up. See the phase table in -`docs/superpowers/specs/2026-07-16-express-react-rebuild-design.md` §13 for what's next. - Both integrations are optional infrastructure. With no `CLOUDINARY_*` variables the API still boots, the upload endpoint reports 503, and every post falls back to its generated cover. With no `GOOGLE_*` variables there is simply no Google button — `GET /api/v1/auth/providers` tells the client which providers diff --git a/apps/client/src/api/auth.ts b/apps/client/src/api/auth.ts index 61e6f442..d34b86c2 100644 --- a/apps/client/src/api/auth.ts +++ b/apps/client/src/api/auth.ts @@ -6,7 +6,7 @@ import { DEBUG } from '../lib/constants.js' export type User = { id: string; username: string; email: string } /** Which federated sign-in options this deployment can actually offer. */ -export type AuthProviders = { google: boolean; facebook: boolean } +export type AuthProviders = { google: boolean } export const authApi = { signup: (input: z.infer) => { diff --git a/apps/client/src/api/users.ts b/apps/client/src/api/users.ts index 8333248e..86f7a9c9 100644 --- a/apps/client/src/api/users.ts +++ b/apps/client/src/api/users.ts @@ -1,8 +1,22 @@ import { request } from './client.js' import type { z } from 'zod' -import { UpdateUserSchema } from '@blog/zod-shared' +import { DeleteUserSchema, UpdateUserSchema } from '@blog/zod-shared' -export type UserProfile = { id: string; username: string; bio?: string; avatar?: string } +/** + * `email`/`hasPassword`/`oauthProvider` are only ever present when the + * requester is viewing their own account — see userService.getPublicProfile's + * viewerId gate. Anyone else's profile arrives without them. + */ +export type UserProfile = { + id: string + username: string + bio?: string + image?: string + createdAt: string + email?: string + hasPassword?: boolean + oauthProvider?: 'google' | null +} export const usersApi = { get: (id: string) => request(`/api/v1/users/${id}`), @@ -10,5 +24,6 @@ export const usersApi = { update: (id: string, input: z.infer) => request(`/api/v1/users/${id}`, { method: 'PATCH', body: JSON.stringify(input) }), - remove: (id: string) => request(`/api/v1/users/${id}`, { method: 'DELETE' }), + remove: (id: string, confirmation: z.infer) => + request(`/api/v1/users/${id}`, { method: 'DELETE', body: JSON.stringify(confirmation) }), } diff --git a/apps/client/src/components/layouts/PageShell.tsx b/apps/client/src/components/layouts/PageShell.tsx index fba99bd2..21087014 100644 --- a/apps/client/src/components/layouts/PageShell.tsx +++ b/apps/client/src/components/layouts/PageShell.tsx @@ -39,7 +39,9 @@ export function PageShell({ children }: { children: React.ReactNode }) { New post