Compare commits
1 Commits
feature/us
...
7bca854058
| Author | SHA1 | Date | |
|---|---|---|---|
| 7bca854058 |
@@ -1,5 +1,5 @@
|
|||||||
import { createFileRoute, Outlet, Link, redirect, useRouter } from '@tanstack/react-router'
|
import { createFileRoute, Outlet, Link, redirect, useRouter } from '@tanstack/react-router'
|
||||||
import { useQuery, useQueryClient, useMutation } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { queryOptions } from '@tanstack/react-query'
|
import { queryOptions } from '@tanstack/react-query'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { api } from '@/lib/api-client'
|
import { api } from '@/lib/api-client'
|
||||||
@@ -9,9 +9,6 @@ import { moduleListOptions } from '@/api/modules'
|
|||||||
import { Avatar } from '@/components/shared/avatar-upload'
|
import { Avatar } from '@/components/shared/avatar-upload'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Users, UserRound, HelpCircle, Shield, UserCog, LogOut, User, Wrench, Package, ClipboardList, FolderOpen, KeyRound, Settings, PanelLeftClose, PanelLeft, CalendarDays, GraduationCap, CalendarRange, BookOpen, BookMarked, Package2, Tag, Truck, ShoppingCart } from 'lucide-react'
|
import { Users, UserRound, HelpCircle, Shield, UserCog, LogOut, User, Wrench, Package, ClipboardList, FolderOpen, KeyRound, Settings, PanelLeftClose, PanelLeft, CalendarDays, GraduationCap, CalendarRange, BookOpen, BookMarked, Package2, Tag, Truck, ShoppingCart } from 'lucide-react'
|
||||||
import { Input } from '@/components/ui/input'
|
|
||||||
import { Label } from '@/components/ui/label'
|
|
||||||
import { toast } from 'sonner'
|
|
||||||
|
|
||||||
export const Route = createFileRoute('/_authenticated')({
|
export const Route = createFileRoute('/_authenticated')({
|
||||||
beforeLoad: () => {
|
beforeLoad: () => {
|
||||||
@@ -107,58 +104,6 @@ function NavGroup({ label, children, collapsed }: { label: string; children: Rea
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function SetPinModal() {
|
|
||||||
const queryClient = useQueryClient()
|
|
||||||
const [pin, setPin] = useState('')
|
|
||||||
const [confirmPin, setConfirmPin] = useState('')
|
|
||||||
const [error, setError] = useState('')
|
|
||||||
|
|
||||||
const setPinMutation = useMutation({
|
|
||||||
mutationFn: () => api.post('/v1/auth/set-pin', { pin }),
|
|
||||||
onSuccess: () => {
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['auth', 'me'] })
|
|
||||||
toast.success('PIN set successfully')
|
|
||||||
},
|
|
||||||
onError: (err) => setError(err.message),
|
|
||||||
})
|
|
||||||
|
|
||||||
function handleSubmit(e: React.FormEvent) {
|
|
||||||
e.preventDefault()
|
|
||||||
setError('')
|
|
||||||
if (pin.length < 4 || pin.length > 6) { setError('PIN must be 4-6 digits'); return }
|
|
||||||
if (!/^\d+$/.test(pin)) { setError('PIN must be digits only'); return }
|
|
||||||
if (pin !== confirmPin) { setError('PINs do not match'); return }
|
|
||||||
setPinMutation.mutate()
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60">
|
|
||||||
<div className="w-full max-w-sm rounded-xl border bg-card p-6 shadow-xl">
|
|
||||||
<h2 className="text-lg font-semibold mb-1">Set your POS PIN</h2>
|
|
||||||
<p className="text-sm text-muted-foreground mb-4">
|
|
||||||
A PIN is required to use the Point of Sale. Choose a 4-6 digit PIN you'll use to unlock the terminal.
|
|
||||||
</p>
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-4">
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>PIN</Label>
|
|
||||||
<Input type="password" inputMode="numeric" maxLength={6} value={pin} onChange={(e) => setPin(e.target.value)} placeholder="****" autoFocus />
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>Confirm PIN</Label>
|
|
||||||
<Input type="password" inputMode="numeric" maxLength={6} value={confirmPin} onChange={(e) => setConfirmPin(e.target.value)} placeholder="****" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{error && <p className="text-sm text-destructive">{error}</p>}
|
|
||||||
<Button type="submit" className="w-full" disabled={setPinMutation.isPending}>
|
|
||||||
{setPinMutation.isPending ? 'Setting...' : 'Set PIN'}
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function AuthenticatedLayout() {
|
function AuthenticatedLayout() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const user = useAuthStore((s) => s.user)
|
const user = useAuthStore((s) => s.user)
|
||||||
@@ -167,13 +112,6 @@ function AuthenticatedLayout() {
|
|||||||
const setPermissions = useAuthStore((s) => s.setPermissions)
|
const setPermissions = useAuthStore((s) => s.setPermissions)
|
||||||
const permissionsLoaded = useAuthStore((s) => s.permissionsLoaded)
|
const permissionsLoaded = useAuthStore((s) => s.permissionsLoaded)
|
||||||
|
|
||||||
// Fetch profile for PIN warning
|
|
||||||
const { data: profile } = useQuery(queryOptions({
|
|
||||||
queryKey: ['auth', 'me'],
|
|
||||||
queryFn: () => api.get<{ hasPin: boolean }>('/v1/auth/me'),
|
|
||||||
enabled: !!useAuthStore.getState().token,
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Fetch permissions on mount
|
// Fetch permissions on mount
|
||||||
const { data: permData } = useQuery({
|
const { data: permData } = useQuery({
|
||||||
...myPermissionsOptions(),
|
...myPermissionsOptions(),
|
||||||
@@ -325,7 +263,6 @@ function AuthenticatedLayout() {
|
|||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<main className="flex-1 p-6 min-h-screen">
|
<main className="flex-1 p-6 min-h-screen">
|
||||||
{profile && !profile.hasPin && <SetPinModal />}
|
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute, Link } from '@tanstack/react-router'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||||
import { queryOptions } from '@tanstack/react-query'
|
import { queryOptions } from '@tanstack/react-query'
|
||||||
@@ -12,7 +12,8 @@ import { Label } from '@/components/ui/label'
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
import { Separator } from '@/components/ui/separator'
|
import { Separator } from '@/components/ui/separator'
|
||||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'
|
||||||
import { Sun, Moon, Monitor } from 'lucide-react'
|
import { Alert, AlertTitle, AlertDescription } from '@/components/ui/alert'
|
||||||
|
import { Sun, Moon, Monitor, AlertTriangle } from 'lucide-react'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
import { AvatarUpload } from '@/components/shared/avatar-upload'
|
import { AvatarUpload } from '@/components/shared/avatar-upload'
|
||||||
|
|
||||||
@@ -52,6 +53,23 @@ function ProfilePage() {
|
|||||||
<div className="space-y-6 max-w-2xl">
|
<div className="space-y-6 max-w-2xl">
|
||||||
<h1 className="text-2xl font-bold">Profile</h1>
|
<h1 className="text-2xl font-bold">Profile</h1>
|
||||||
|
|
||||||
|
{profile && !profile.hasPin && (
|
||||||
|
<Alert>
|
||||||
|
<AlertTriangle className="h-4 w-4" />
|
||||||
|
<AlertTitle>POS PIN not set</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
You need a PIN to use the Point of Sale.{' '}
|
||||||
|
<Link
|
||||||
|
to="/profile"
|
||||||
|
search={{ tab: 'security' }}
|
||||||
|
className="underline font-medium text-foreground"
|
||||||
|
>
|
||||||
|
Set your PIN
|
||||||
|
</Link>
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
<Tabs defaultValue={tab === 'security' || tab === 'appearance' ? tab : 'account'}>
|
<Tabs defaultValue={tab === 'security' || tab === 'appearance' ? tab : 'account'}>
|
||||||
<TabsList>
|
<TabsList>
|
||||||
<TabsTrigger value="account">Account</TabsTrigger>
|
<TabsTrigger value="account">Account</TabsTrigger>
|
||||||
|
|||||||
Reference in New Issue
Block a user