feat(profile): useDemographics hook + page-reload re-hydration
User-Bug: Demographics werden korrekt gespeichert (DB verified), aber nach
Page-Reload sah User leere Felder → dachte save kaputt. Root: kein GET-endpoint
+ kein server-state-rehydrate nach PATCH.
- hooks/useProfileData.ts: useDemographics() wraps useFetchOnce<DemographicsResponse>
('/api/profile/me/demographics'), splittet in fields + meta (consentAt/withdrawnAt)
- app/profile/index.tsx: serverDemographics ?? EMPTY_DEMOGRAPHICS const statt local
state. Nach PATCH/DELETE: reloadDemographics() pulled fresh server data.
Edge-cases:
- 404 (endpoint nicht live) → fallback EMPTY, kein crash
- loading → EMPTY initial bis fetch resolved, konsistent mit other hooks
- withdrawnAt set → demoComplete=false (Demographics-Hint sichtbar trotz potentiell
noch befüllter felder durch race-condition)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
53d6e69512
commit
c4cfd351c4
@ -18,6 +18,7 @@ import {
|
||||
useApprovedDomains,
|
||||
useCooldownHistory,
|
||||
useSosInsights,
|
||||
useDemographics,
|
||||
} from '../../hooks/useProfileData';
|
||||
import { apiFetch } from '../../lib/api';
|
||||
|
||||
@ -84,7 +85,6 @@ function mapHelpedBy(helpedBy: {
|
||||
export default function ProfileScreen() {
|
||||
const insets = useSafeAreaInsets();
|
||||
const [bannerDismissed, setBannerDismissed] = useState(false);
|
||||
const [demographics, setDemographics] = useState<Demographics>(EMPTY_DEMOGRAPHICS);
|
||||
const [demographicsExpanded, setDemographicsExpanded] = useState(false);
|
||||
const { me } = useMe();
|
||||
const { user } = useAuthStore();
|
||||
@ -93,6 +93,13 @@ export default function ProfileScreen() {
|
||||
const { domains: approvedDomainsData } = useApprovedDomains();
|
||||
const { cooldownHistory } = useCooldownHistory();
|
||||
const { sosInsights } = useSosInsights();
|
||||
const {
|
||||
demographics: serverDemographics,
|
||||
withdrawnAt,
|
||||
reload: reloadDemographics,
|
||||
} = useDemographics();
|
||||
|
||||
const demographics: Demographics = serverDemographics ?? EMPTY_DEMOGRAPHICS;
|
||||
|
||||
const scrollViewRef = useRef<ScrollView | null>(null);
|
||||
const demographicsAnchorRef = useRef<View | null>(null);
|
||||
@ -117,7 +124,7 @@ export default function ProfileScreen() {
|
||||
const streakStartDate = formatStreakStartDate(me?.created_at);
|
||||
|
||||
const showDigaBanner = currentStreak >= 30 && !bannerDismissed;
|
||||
const demoComplete = isDemographicsComplete(demographics);
|
||||
const demoComplete = !withdrawnAt && isDemographicsComplete(demographics);
|
||||
|
||||
function scrollToDemographics() {
|
||||
const node = demographicsAnchorRef.current;
|
||||
@ -230,12 +237,12 @@ export default function ProfileScreen() {
|
||||
plan={profile.plan}
|
||||
expanded={demographicsExpanded}
|
||||
onChange={async (next) => {
|
||||
setDemographics(next);
|
||||
try {
|
||||
const result = await apiFetch<{ trialAwarded: boolean; expiresAt: string | null }>(
|
||||
'/api/profile/me/demographics',
|
||||
{ method: 'PATCH', body: next },
|
||||
);
|
||||
reloadDemographics();
|
||||
if (result.trialAwarded) {
|
||||
Alert.alert(
|
||||
'Pro-Woche freigeschaltet',
|
||||
@ -243,7 +250,7 @@ export default function ProfileScreen() {
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
// write failed — local state still updated optimistically
|
||||
// write failed — optimistic update not applied, server state preserved
|
||||
}
|
||||
}}
|
||||
onRevokeConsent={() => {
|
||||
@ -256,8 +263,9 @@ export default function ProfileScreen() {
|
||||
text: 'Loschen',
|
||||
style: 'destructive',
|
||||
onPress: () => {
|
||||
apiFetch('/api/profile/me/demographics', { method: 'DELETE' }).catch(() => {});
|
||||
setDemographics(EMPTY_DEMOGRAPHICS);
|
||||
apiFetch('/api/profile/me/demographics', { method: 'DELETE' })
|
||||
.then(() => reloadDemographics())
|
||||
.catch(() => {});
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@ -141,3 +141,49 @@ export function useSosInsights() {
|
||||
return { sosInsights: data, loading, error, reload };
|
||||
}
|
||||
|
||||
export type Demographics = {
|
||||
birthYear: number | null;
|
||||
gender: string | null;
|
||||
maritalStatus: string | null;
|
||||
employmentStatus: string | null;
|
||||
shiftWork: boolean | null;
|
||||
industry: string | null;
|
||||
jobTenure: string | null;
|
||||
bundesland: string | null;
|
||||
city: string | null;
|
||||
};
|
||||
|
||||
type DemographicsResponse = Demographics & {
|
||||
consentAt: string | null;
|
||||
withdrawnAt: string | null;
|
||||
};
|
||||
|
||||
export function useDemographics() {
|
||||
const { data, loading, error, reload } = useFetchOnce<DemographicsResponse>(
|
||||
'/api/profile/me/demographics',
|
||||
);
|
||||
|
||||
const demographics: Demographics | null = data
|
||||
? {
|
||||
birthYear: data.birthYear,
|
||||
gender: data.gender,
|
||||
maritalStatus: data.maritalStatus,
|
||||
employmentStatus: data.employmentStatus,
|
||||
shiftWork: data.shiftWork,
|
||||
industry: data.industry,
|
||||
jobTenure: data.jobTenure,
|
||||
bundesland: data.bundesland,
|
||||
city: data.city,
|
||||
}
|
||||
: null;
|
||||
|
||||
return {
|
||||
demographics,
|
||||
consentAt: data?.consentAt ?? null,
|
||||
withdrawnAt: data?.withdrawnAt ?? null,
|
||||
loading,
|
||||
error,
|
||||
reload,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user