Fix medication system and rename to Synculous.

- Add all 14 missing database tables (medications, med_logs, routines, etc.)
- Rewrite medication scheduling: support specific days, every N days, as-needed (PRN)
- Fix taken_times matching: match by created_at date, not scheduled_time string
- Fix adherence calculation: taken / expected doses, not taken / (taken + skipped)
- Add formatSchedule() helper for readable display
- Update client types and API layer
- Rename brilli-ins-client → synculous-client
- Make client PWA: add manifest, service worker, icons
- Bind dev server to 0.0.0.0 for network access
- Fix SVG icon bugs in Icons.tsx
- Add .dockerignore for client npm caching

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-13 03:23:38 -06:00
parent 3e1134575b
commit 97a166f5aa
47 changed files with 5231 additions and 61 deletions

View File

@@ -0,0 +1,106 @@
'use client';
import React, { createContext, useContext, useState, useEffect, useCallback } from 'react';
import api from '@/lib/api';
import { User } from '@/types';
interface AuthContextType {
user: User | null;
token: string | null;
isLoading: boolean;
isAuthenticated: boolean;
login: (username: string, password: string) => Promise<void>;
register: (username: string, password: string) => Promise<void>;
logout: () => void;
refreshUser: () => Promise<void>;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [token, setToken] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(true);
const refreshUser = useCallback(async () => {
const storedToken = api.auth.getToken();
if (!storedToken) {
setIsLoading(false);
return;
}
setToken(storedToken);
try {
const tokenParts = storedToken.split('.');
if (tokenParts.length === 3) {
const payload = JSON.parse(atob(tokenParts[1]));
const userId = payload.sub;
if (userId) {
const userData = await api.user.get(userId);
setUser(userData);
}
}
} catch (error) {
console.error('Failed to refresh user:', error);
api.auth.logout();
setToken(null);
setUser(null);
} finally {
setIsLoading(false);
}
}, []);
useEffect(() => {
refreshUser();
}, [refreshUser]);
const login = async (username: string, password: string) => {
const result = await api.auth.login(username, password);
const storedToken = api.auth.getToken();
setToken(storedToken);
const tokenParts = storedToken!.split('.');
const payload = JSON.parse(atob(tokenParts[1]));
const userId = payload.sub;
if (userId) {
const userData = await api.user.get(userId);
setUser(userData);
}
};
const register = async (username: string, password: string) => {
await api.auth.register(username, password);
};
const logout = () => {
api.auth.logout();
setToken(null);
setUser(null);
};
return (
<AuthContext.Provider
value={{
user,
token,
isLoading,
isAuthenticated: !!user,
login,
register,
logout,
refreshUser,
}}
>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
}