chahinebrini 2f5d0382f0 feat(profile,devices): real DB wiring + Devices-Settings migration
Profile (rebreak-native-ui):
- New hook hooks/useProfileData.ts (143 LOC, 4 hooks):
  useSocialStats, useApprovedDomains, useCooldownHistory, useSosInsights
- app/profile/index.tsx: alle DUMMY_* constants entfernt → live data via hooks
- PATCH /api/profile/me/demographics nun wired in onChange (war TODO-only)
- DELETE /api/profile/me/demographics für revoke-consent
- POST /api/profile/me/diga-banner-dismiss

Devices (rebreak-native-ui):
- New app/devices.tsx push-page: slot-counter, progress-bar, device-list mit
  trash-button (gesperrt für isCurrent)
- New lib/deviceId.ts: persistent device-ID via expo-application
  (getIosIdForVendorAsync / getAndroidId) mit AsyncStorage-UUID-fallback
- New stores/devices.ts: Zustand store (loadDevices, removeDevice, ensureRegistered)
- lib/api.ts: x-device-id + x-platform headers bei jedem Backend-Call
  (skipDeviceHeader option für Bootstrap-register)
- app/settings.tsx: Geräte-Row aktiv (push to /devices) statt soon-flagged
- locales: 14 neue settings.devices_* keys DE+EN

Backend-Status: alle Devices-Endpoints existieren (GET /api/devices, POST /register,
DELETE /:id). Pending: GET /api/profile/me/demographics für reload-state-fetch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 20:47:30 +02:00

63 lines
1.8 KiB
TypeScript

import Constants from 'expo-constants';
import { supabase } from './supabase';
import { getDeviceId, getPlatformName } from './deviceId';
const apiUrl = Constants.expoConfig?.extra?.apiUrl as string;
type FetchOptions = Omit<RequestInit, 'body'> & {
body?: any;
/** Set true on bootstrap calls (device register) to skip x-device-id injection */
skipDeviceHeader?: boolean;
};
/**
* Wrapper für Backend-API-Calls mit automatischem Auth-Token.
* Pendant zum Nuxt-`useSafeFetch` aus apps/rebreak/.
*
* Backend antwortet mit { success, data, status } — wir entpacken `data`.
*/
export async function apiFetch<T = any>(
path: string,
options: FetchOptions = {}
): Promise<T> {
const session = (await supabase.auth.getSession()).data.session;
const { skipDeviceHeader, ...fetchOptions } = options;
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...(fetchOptions.headers as Record<string, string>),
};
if (session?.access_token) {
headers.Authorization = `Bearer ${session.access_token}`;
}
if (!skipDeviceHeader) {
const deviceId = await getDeviceId().catch(() => null);
if (deviceId) {
headers['x-device-id'] = deviceId;
headers['x-platform'] = getPlatformName();
}
}
const res = await fetch(`${apiUrl}${path}`, {
...fetchOptions,
headers,
body: fetchOptions.body ? JSON.stringify(fetchOptions.body) : undefined,
});
if (!res.ok) {
const text = await res.text();
throw new Error(`API ${res.status}: ${text}`);
}
const json = await res.json();
// Unwrap { success, data, status } — siehe useSafeFetch-Pattern in der Vue-App
if (json && typeof json === 'object' && 'success' in json && 'data' in json) {
return json.data as T;
}
return json as T;
}