This commit is contained in:
2026-03-06 19:42:15 +00:00
parent abcbe3e1e5
commit 04cbb846d1
99 changed files with 11724 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
import * as React from "react"
const MOBILE_BREAKPOINT = 768
export function useIsMobile() {
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
React.useEffect(() => {
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
}
mql.addEventListener("change", onChange)
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
return () => mql.removeEventListener("change", onChange)
}, [])
return !!isMobile
}

View File

@@ -0,0 +1,48 @@
import { useState, useEffect, useCallback } from 'react'
import { auth, type User } from '@/lib/api'
import i18n from '@/i18n'
export function useAuth() {
const [user, setUser] = useState<User | null>(null)
const [loading, setLoading] = useState(true)
const fetchUser = useCallback(async () => {
try {
const u = await auth.me()
setUser(u)
if (u.preferred_locale) {
i18n.changeLanguage(u.preferred_locale)
}
} catch {
setUser(null)
} finally {
setLoading(false)
}
}, [])
useEffect(() => {
fetchUser()
}, [fetchUser])
const login = async (email: string, password: string) => {
const u = await auth.login({ email, password })
setUser(u)
if (u.preferred_locale) {
i18n.changeLanguage(u.preferred_locale)
}
}
const register = async (email: string, password: string, displayName: string) => {
const u = await auth.register({ email, password, display_name: displayName })
setUser(u)
}
const logout = async () => {
await auth.logout()
setUser(null)
}
return { user, loading, login, register, logout, refetch: fetchUser }
}
export type AuthContext = ReturnType<typeof useAuth>

View File

@@ -0,0 +1,35 @@
import { useState, useEffect, useCallback } from 'react'
import { budgets, type Budget, type BudgetDetail } from '@/lib/api'
export function useBudgets() {
const [list, setList] = useState<Budget[]>([])
const [current, setCurrent] = useState<BudgetDetail | null>(null)
const [loading, setLoading] = useState(true)
const fetchList = useCallback(async () => {
try {
const data = await budgets.list()
setList(data)
} catch {
setList([])
} finally {
setLoading(false)
}
}, [])
const selectBudget = useCallback(async (id: string) => {
setLoading(true)
try {
const detail = await budgets.get(id)
setCurrent(detail)
} finally {
setLoading(false)
}
}, [])
useEffect(() => {
fetchList()
}, [fetchList])
return { list, current, loading, fetchList, selectBudget, setCurrent }
}