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>
74 lines
1.8 KiB
TypeScript
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) }));
|
|
},
|
|
}));
|