import type { MobileIdentity, RoleKey } from '@/types/api'; import { apiRequest, isAuthApiError } from './api'; import { clearOrgPosition, getSelectedPosition, hasOrgPosition, isSupervisorLearnerMode, leaveSupervisorLearnerMode, normalizePosition, roleKey, saveOrgPosition, type UserPosition } from './auth'; export const fetchMobileIdentity = () => apiRequest({ url: '/api/aihr/mobile/me', method: 'GET', timeout: 15000, clearAuthOnUnauthorized: false }); export const userTabPages = new Set([ '/pages/user/today/index', '/pages/user/practice/index', '/pages/user/sop/index', '/pages/user/profile/index' ]); export const isUserRoute = (url: string) => url.startsWith('/pages/user/'); export const isSupervisorRoute = (url: string) => url.startsWith('/pages/supervisor/'); export const routeForMobileRole = (role: RoleKey, requested: string) => { if (role === 'supervisor' && isUserRoute(requested) && !isSupervisorLearnerMode()) { return '/pages/supervisor/index/index'; } if (role === 'user' && isSupervisorRoute(requested)) { return '/pages/user/today/index'; } return requested; }; export const hydrateMobileRole = async (): Promise => { try { const identity = await fetchMobileIdentity(); const role = identity.role === 'supervisor' ? 'supervisor' : 'user'; uni.setStorageSync(roleKey, role); if (role !== 'supervisor') leaveSupervisorLearnerMode(); return role; } catch { // ponytail: role lookup failure fails closed; never reuse a stale supervisor cache. uni.removeStorageSync(roleKey); return 'user'; } }; export const positionSetupUrl = (redirect: string) => `/pages/auth/position/index?redirect=${encodeURIComponent(redirect || '/pages/user/today/index')}`; export const goToMobileTarget = (url: string) => { if (userTabPages.has(url)) { uni.switchTab({ url }); return; } uni.redirectTo({ url }); }; export type OrgPositionResult = | { status: 'matched'; position: UserPosition } | { status: 'unmatched' } | { status: 'unauthorized' } | { status: 'error' }; export const fetchOrgPositionResult = async (): Promise => { try { const identity = await fetchMobileIdentity(); const officialPosition = saveOrgPosition(identity.positionName); if (!officialPosition) { clearOrgPosition(); return { status: 'unmatched' }; } return { status: 'matched', position: officialPosition }; } catch (error) { return { status: isAuthApiError(error) ? 'unauthorized' : 'error' }; } }; export const hydratePositionFromOrg = async (): Promise => { const result = await fetchOrgPositionResult(); if (result.status === 'matched') return result.position; if (result.status === 'unmatched') return ''; return hasOrgPosition() ? getSelectedPosition() : ''; }; export const nextUserRouteAfterLogin = async (redirect: string) => { const role = await hydrateMobileRole(); const roleRoute = routeForMobileRole(role, redirect); if (roleRoute !== redirect) return roleRoute; if (!isUserRoute(redirect)) return redirect; const officialPosition = await hydratePositionFromOrg(); if (normalizePosition(officialPosition) || hasOrgPosition()) return redirect; return positionSetupUrl(redirect); }; export const ensureEmployeePosition = async (redirect: string) => { const nextRoute = await nextUserRouteAfterLogin(redirect); if (nextRoute === redirect) return true; uni.navigateTo({ url: nextRoute }); return false; };