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

74 lines
1.8 KiB
TypeScript

import { create } from 'zustand';
import { apiFetch } from '../lib/api';
import { getDeviceId, getPlatformName } from '../lib/deviceId';
export interface UserDevice {
id: string;
deviceId: string;
platform: string;
model: string | null;
name: string | null;
lastSeenAt: string;
createdAt: string;
isCurrent?: boolean;
}
type DevicesState = {
devices: UserDevice[];
maxDevices: number;
plan: string;
loading: boolean;
registered: boolean;
ensureRegistered: () => Promise<void>;
loadDevices: () => Promise<void>;
removeDevice: (id: string) => Promise<void>;
};
export const useDevicesStore = create<DevicesState>((set, get) => ({
devices: [],
maxDevices: 1,
plan: 'free',
loading: false,
registered: false,
ensureRegistered: async () => {
if (get().registered) return;
const deviceId = await getDeviceId().catch(() => null);
if (!deviceId) return;
const platform = getPlatformName();
await apiFetch('/api/devices/register', {
method: 'POST',
skipDeviceHeader: true,
body: { deviceId, platform },
}).then((res: any) => {
set({ registered: true, maxDevices: res.max ?? 1 });
}).catch(() => {
// Limit reached or transient — App continues; limit UI is handled at auth level
});
},
loadDevices: async () => {
set({ loading: true });
try {
if (!get().registered) {
await get().ensureRegistered();
}
const res = await apiFetch<{ devices: UserDevice[]; max: number; plan: string }>(
'/api/devices'
);
set({ devices: res.devices, maxDevices: res.max, plan: res.plan });
} finally {
set({ loading: false });
}
},
removeDevice: async (id: string) => {
await apiFetch(`/api/devices/${id}`, { method: 'DELETE' });
set((s) => ({ devices: s.devices.filter((d) => d.id !== id) }));
},
}));