Online-Status (Phase 1+):
- UserAvatar mit 4 Size-Variants (sm/md/lg/xl) + integrierter Online-Dot
- OnlinePresenceProvider: Supabase-Channel + Following-Filter
- ChatHeaderStatus: "Online" neutral / "vor X min" offline
- useLastSeen + Heartbeat (60s interval + AppState-background ping)
- Privatsphäre-Toggle in profile/index
Sheets:
- FormSheet Android-keyboard-fix (Dimensions.get('screen'), kein
useWindowDimensions-Kollaps), useKeyboardHandler statt manual
Keyboard.addListener, state-reset on re-open
- PostCommentsSheet same Pattern + close-after-submit + drag bis under
app-header
- ConnectMailSheet form-view refactor: scrollable, AES-Banner als
footnote, field-order email→pw→label, fixed 0.85 über alle Steps
Chat:
- DmChatBackground iOS klecks fix (G transform statt nested Svg)
- ChatInput Lyra-1:1 (keyboardWillShow, surfaceElevated bubble,
arrow-up send, attachment links)
- dm/room/chat headers + conversation-list nutzen UserAvatar
- Foreign-Profile "Nachricht"-Button öffnet richtige DM
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
103 lines
2.8 KiB
TypeScript
103 lines
2.8 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { apiFetch } from '../lib/api';
|
|
|
|
export type Plan = 'free' | 'pro' | 'legend';
|
|
|
|
/**
|
|
* Single source of truth für den eingeloggten User. /api/auth/me joint
|
|
* `auth.users` mit `rebreak.profiles` server-side — wir bekommen alles in
|
|
* einem Request: plan, avatar, nickname, streak.
|
|
*
|
|
* Live-Update-Pattern (siehe RECOVERY_LOG): nach Profile-Edit (PATCH /api/auth/me)
|
|
* MUSS `invalidateMe()` aufgerufen werden — alle useMe-Konsumenten (AppHeader,
|
|
* PostCard, ComposeCard, etc.) re-fetchen automatisch via Listener-Subscribe.
|
|
*
|
|
* WICHTIG: nicht aus `supabase.auth.getUser().user_metadata` lesen — das
|
|
* sind nur die JWT-Claims vom Signup-Zeitpunkt, NICHT der aktuelle Profile-
|
|
* Stand (Avatar/Nickname/Plan werden via Profile-Edit-API geupdated, landen
|
|
* in der DB, NICHT zurück ins JWT-Claim).
|
|
*/
|
|
export type OnboardingStep =
|
|
| 'welcome'
|
|
| 'account'
|
|
| 'plan'
|
|
| 'pre_protection'
|
|
| 'done'
|
|
// legacy (alte Builds könnten das im Profile haben — wird im neuen Flow nicht gesetzt)
|
|
| 'nickname'
|
|
| 'block';
|
|
|
|
export type Me = {
|
|
id: string;
|
|
email: string;
|
|
username: string;
|
|
nickname: string | null;
|
|
avatar: string | null;
|
|
plan: Plan;
|
|
streak: number;
|
|
lyraVoiceId: string | null;
|
|
onboardingStep: OnboardingStep;
|
|
created_at?: string;
|
|
presenceVisible?: boolean;
|
|
};
|
|
|
|
let cachedMe: Me | null = null;
|
|
const listeners = new Set<() => void>();
|
|
|
|
/**
|
|
* Lädt /api/auth/me neu und benachrichtigt ALLE useMe-Konsumenten in der App.
|
|
* Nach jedem PATCH /api/auth/me aufrufen — sonst sehen Konsumenten alten Cache.
|
|
*/
|
|
export function invalidateMe(): void {
|
|
cachedMe = null;
|
|
for (const cb of listeners) cb();
|
|
}
|
|
|
|
export function useMe(): { me: Me | null; loading: boolean; reload: () => void } {
|
|
const [me, setMe] = useState<Me | null>(cachedMe);
|
|
const [loading, setLoading] = useState(cachedMe === null);
|
|
const [version, setVersion] = useState(0);
|
|
|
|
// Auf globale Invalidierung lauschen (Avatar-/Nickname-Update aus Profile-Edit)
|
|
useEffect(() => {
|
|
const cb = () => setVersion((v) => v + 1);
|
|
listeners.add(cb);
|
|
return () => {
|
|
listeners.delete(cb);
|
|
};
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
(async () => {
|
|
// Falls cache schon frisch ist (von anderem Konsumenten gerade geladen): nutzen
|
|
if (cachedMe !== null) {
|
|
setMe(cachedMe);
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
try {
|
|
const res = await apiFetch<Me>('/api/auth/me');
|
|
if (cancelled) return;
|
|
cachedMe = res;
|
|
setMe(res);
|
|
} catch (e) {
|
|
console.warn('[useMe] fetch failed:', e);
|
|
} finally {
|
|
if (!cancelled) setLoading(false);
|
|
}
|
|
})();
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [version]);
|
|
|
|
return {
|
|
me,
|
|
loading,
|
|
reload: () => {
|
|
invalidateMe();
|
|
},
|
|
};
|
|
}
|