diff --git a/.gitignore b/.gitignore
index 47c83f0..6b083ca 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
node_modules/
.next/
dist/
+out/
.env
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index fafce0f..e35fa07 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -4,7 +4,7 @@ FROM node:20-alpine AS builder
WORKDIR /app
-# Install dependencies
+# Install dependencies (including sharp for icon generation)
COPY package.json package-lock.json ./
RUN npm ci
@@ -15,8 +15,11 @@ COPY . .
ARG NEXT_PUBLIC_POCKETBASE_URL
ENV NEXT_PUBLIC_POCKETBASE_URL=${NEXT_PUBLIC_POCKETBASE_URL}
-# Build the static export
-RUN npm run build
+# Generate PWA icons
+RUN node scripts/generate-icons.js
+
+# Build the static export (sets NEXT_EXPORT=true)
+RUN npm run export
# Stage 2: Serve with nginx
FROM nginx:alpine
@@ -24,14 +27,12 @@ FROM nginx:alpine
# Remove default nginx static assets
RUN rm -rf /usr/share/nginx/html/*
-# Copy built static files from builder
-COPY --from=builder /app/dist /usr/share/nginx/html
+# Copy built static files from builder (Next.js export outputs to /out)
+COPY --from=builder /app/out /usr/share/nginx/html
# Copy custom nginx config
COPY nginx.conf /etc/nginx/conf.d/default.conf
-
-
EXPOSE 80
-CMD ["nginx", "-g", "daemon off;"]
+CMD ["nginx", "-g", "daemon off;"]
\ No newline at end of file
diff --git a/app/globals.css b/app/globals.css
index 33c8fb8..9805b7d 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -4,12 +4,14 @@
:root {
--foreground-rgb: 0, 0, 0;
- --background-rgb: 245, 245, 245;
+ --background-rgb: 249, 250, 251;
--primary: 234, 88, 12;
+ --primary-light: 249, 115, 22;
--success: 34, 197, 94;
--warning: 234, 179, 8;
--danger: 239, 68, 68;
--info: 6, 182, 212;
+ --nav-height: 4rem;
}
html,
@@ -18,18 +20,22 @@ body {
overflow-x: hidden;
height: 100%;
-webkit-tap-highlight-color: transparent;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
}
body {
color: rgb(var(--foreground-rgb));
background: rgb(var(--background-rgb));
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
+ font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
overscroll-behavior-y: none;
-webkit-user-select: none;
user-select: none;
+ /* Smooth page transitions */
+ scroll-behavior: smooth;
}
-/* Mobile viewport height fix */
+/* Mobile viewport height fix (100vh is wrong on mobile) */
@supports (-webkit-touch-callout: none) {
.h-screen {
height: -webkit-fill-available;
@@ -48,7 +54,12 @@ body {
padding-bottom: env(safe-area-inset-bottom);
}
-/* Hide scrollbar but keep functionality */
+/* Content must account for bottom nav */
+.pb-nav {
+ padding-bottom: calc(var(--nav-height) + env(safe-area-inset-bottom, 0px) + 1rem);
+}
+
+/* Hide scrollbar but keep scroll functionality */
.no-scrollbar::-webkit-scrollbar {
display: none;
}
@@ -62,67 +73,237 @@ body {
touch-action: manipulation;
}
-/* Prevent text selection on buttons */
-button {
+/* Prevent text selection on interactive elements */
+button, a, nav {
-webkit-user-select: none;
user-select: none;
}
+/* Allow text selection in content areas */
+.selectable {
+ -webkit-user-select: text;
+ user-select: text;
+}
+
/* Leaflet container fix */
.leaflet-container {
width: 100%;
height: 100%;
z-index: 1;
+ font-family: inherit;
}
-/* Animation utilities */
+/* ========================================
+ Animations
+ ======================================== */
+
@keyframes fadeIn {
- from { opacity: 0; transform: translateY(10px); }
- to { opacity: 1; transform: translateY(0); }
+ from {
+ opacity: 0;
+ transform: translateY(8px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
}
-@keyframes slideInRight {
- from { transform: translateX(100%); }
- to { transform: translateX(0); }
+@keyframes slideUp {
+ from {
+ transform: translateY(100%);
+ opacity: 0;
+ }
+ to {
+ transform: translateY(0);
+ opacity: 1;
+ }
}
-@keyframes slideOutRight {
- from { transform: translateX(0); }
- to { transform: translateX(100%); }
+@keyframes slideDown {
+ from {
+ transform: translateY(-100%);
+ opacity: 0;
+ }
+ to {
+ transform: translateY(0);
+ opacity: 1;
+ }
+}
+
+@keyframes scaleIn {
+ from {
+ transform: scale(0.95);
+ opacity: 0;
+ }
+ to {
+ transform: scale(1);
+ opacity: 1;
+ }
+}
+
+@keyframes shimmer {
+ 0% { background-position: -200% 0; }
+ 100% { background-position: 200% 0; }
}
.animate-fade-in {
animation: fadeIn 0.3s ease-out forwards;
}
-/* Status badges */
+.animate-slide-up {
+ animation: slideUp 0.35s cubic-bezier(0.32, 0.72, 0, 1) forwards;
+}
+
+.animate-slide-down {
+ animation: slideDown 0.3s ease-out forwards;
+}
+
+.animate-scale-in {
+ animation: scaleIn 0.2s ease-out forwards;
+}
+
+/* Stagger animation delay helper */
+.stagger-1 { animation-delay: 0.05s; }
+.stagger-2 { animation-delay: 0.1s; }
+.stagger-3 { animation-delay: 0.15s; }
+.stagger-4 { animation-delay: 0.2s; }
+.stagger-5 { animation-delay: 0.25s; }
+
+/* ========================================
+ Card Styles
+ ======================================== */
+
+.card-shadow {
+ box-shadow:
+ 0 1px 2px rgba(0, 0, 0, 0.06),
+ 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.card-shadow-md {
+ box-shadow:
+ 0 4px 6px -1px rgba(0, 0, 0, 0.1),
+ 0 2px 4px -2px rgba(0, 0, 0, 0.1);
+}
+
+.card-shadow-lg {
+ box-shadow:
+ 0 10px 15px -3px rgba(0, 0, 0, 0.1),
+ 0 4px 6px -4px rgba(0, 0, 0, 0.1);
+}
+
+/* Pressed/active state for cards */
+.card-press {
+ transition: transform 0.15s ease, box-shadow 0.15s ease;
+}
+
+.card-press:active {
+ transform: scale(0.985);
+ box-shadow:
+ 0 1px 1px rgba(0, 0, 0, 0.06),
+ 0 1px 2px rgba(0, 0, 0, 0.08);
+}
+
+/* ========================================
+ Status Badges
+ ======================================== */
+
.status-pending { @apply bg-gray-100 text-gray-700 border-gray-300; }
.status-assigned { @apply bg-blue-100 text-blue-700 border-blue-300; }
.status-collected { @apply bg-green-100 text-green-700 border-green-300; }
.status-problem { @apply bg-red-100 text-red-700 border-red-300; }
-/* Card shadows */
-.card-shadow {
- box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
+/* ========================================
+ Loading Skeleton
+ ======================================== */
+
+.skeleton {
+ background: linear-gradient(90deg, #f0f0f0 25%, #e8e8e8 50%, #f0f0f0 75%);
+ background-size: 200% 100%;
+ animation: shimmer 1.5s infinite;
}
-.card-shadow-lg {
- box-shadow: 0 4px 6px rgba(0,0,0,0.1), 0 2px 4px rgba(0,0,0,0.06);
+/* ========================================
+ Modal Overlay
+ ======================================== */
+
+.modal-overlay {
+ backdrop-filter: blur(4px);
+ -webkit-backdrop-filter: blur(4px);
}
-/* Pull to refresh indicator */
+/* ========================================
+ Input Focus Rings
+ ======================================== */
+
+input:focus,
+textarea:focus,
+select:focus {
+ outline: none;
+ box-shadow: 0 0 0 2px rgba(234, 88, 12, 0.3);
+}
+
+/* ========================================
+ Bottom Sheet (iOS-style modal)
+ ======================================== */
+
+.bottom-sheet {
+ border-radius: 1rem 1rem 0 0;
+ box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.15);
+}
+
+@media (min-width: 640px) {
+ .bottom-sheet {
+ border-radius: 1rem;
+ }
+}
+
+/* ========================================
+ Custom scrollbar (for when needed)
+ ======================================== */
+
+.custom-scrollbar::-webkit-scrollbar {
+ width: 4px;
+}
+
+.custom-scrollbar::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+.custom-scrollbar::-webkit-scrollbar-thumb {
+ background: rgba(0, 0, 0, 0.15);
+ border-radius: 2px;
+}
+
+.custom-scrollbar::-webkit-scrollbar-thumb:hover {
+ background: rgba(0, 0, 0, 0.25);
+}
+
+/* ========================================
+ Pull-to-refresh visual feedback
+ ======================================== */
+
.pull-indicator {
transition: transform 0.2s ease-out;
}
-/* Loading skeleton */
-.skeleton {
- background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
- background-size: 200% 100%;
- animation: skeleton-loading 1.5s infinite;
+/* ========================================
+ Haptic-like press feedback
+ ======================================== */
+
+.press-feedback {
+ transition: transform 0.1s ease, opacity 0.1s ease;
}
-@keyframes skeleton-loading {
- 0% { background-position: 200% 0; }
- 100% { background-position: -200% 0; }
+.press-feedback:active {
+ transform: scale(0.96);
+ opacity: 0.9;
}
+
+.press-feedback-sm {
+ transition: transform 0.1s ease, opacity 0.1s ease;
+}
+
+.press-feedback-sm:active {
+ transform: scale(0.98);
+ opacity: 0.9;
+}
\ No newline at end of file
diff --git a/app/layout.tsx b/app/layout.tsx
index 5652308..99ff85d 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,16 +1,22 @@
import type { Metadata, Viewport } from "next";
import "./globals.css";
import { AuthProvider } from "@/components/AuthProvider";
+import OfflineIndicator from "@/components/OfflineIndicator";
+import InstallPrompt from "@/components/InstallPrompt";
+import { PWABridge } from "@/components/PWABridge";
export const metadata: Metadata = {
title: "1Driver - Collections",
- description: "Mobile driver collection app",
+ description: "Mobile driver collection management app",
manifest: "/manifest.json",
appleWebApp: {
capable: true,
statusBarStyle: "black-translucent",
title: "1Driver",
},
+ formatDetection: {
+ telephone: false,
+ },
};
export const viewport: Viewport = {
@@ -19,7 +25,7 @@ export const viewport: Viewport = {
maximumScale: 1,
userScalable: false,
viewportFit: "cover",
- themeColor: "#ea580c",
+ themeColor: "#ea580c",
};
export default function RootLayout({
@@ -30,17 +36,25 @@ export default function RootLayout({
return (
-
+
+
+
+
+
+
-
+
+
+
+
{children}
);
-}
+}
\ No newline at end of file
diff --git a/app/login/page.tsx b/app/login/page.tsx
index 15ef9b7..ea253fc 100644
--- a/app/login/page.tsx
+++ b/app/login/page.tsx
@@ -19,7 +19,6 @@ export default function LoginPage() {
try {
await login(email, password);
- // Auth provider will detect the change and redirect
window.location.href = '/';
} catch (err) {
setError('Invalid email or password');
@@ -29,85 +28,92 @@ export default function LoginPage() {
};
return (
-
-
- {/* Logo */}
+
+
+ {/* Logo area */}
-
-
+
+
-
1Driver
-
Collections App
+
1Driver
+
Collection Management
-
);
-}
+}
\ No newline at end of file
diff --git a/app/map/page.tsx b/app/map/page.tsx
index 84b91a8..76629a6 100644
--- a/app/map/page.tsx
+++ b/app/map/page.tsx
@@ -8,14 +8,17 @@ import { useAuth } from '@/components/AuthProvider';
import BottomNav from '@/components/BottomNav';
import LocationTracker from '@/components/LocationTracker';
import { getAssignedCollections, isAuthenticated } from '@/lib/pocketbase';
-import { cn, parseCoordinates } from '@/lib/utils';
+import { cn } from '@/lib/utils';
import { RefreshCw, MapPin, AlertCircle } from 'lucide-react';
const CollectionMap = dynamic(() => import('@/components/CollectionMap'), {
ssr: false,
loading: () => (
),
});
@@ -54,14 +57,17 @@ export default function MapPage() {
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
const url = isIOS
? `maps://maps.apple.com/?q=${encodeURIComponent(name)}&ll=${lat},${lng}`
- : `https://www.google.com/maps?q=${lat},${lng}`;
+ : `https://www.google.com/maps/dir/?api=1&destination=${lat},${lng}`;
window.open(url, '_blank');
};
if (authLoading) {
return (
);
}
@@ -72,46 +78,57 @@ export default function MapPage() {
-
+
-
-
-
Collection Map
-
-
-
{items.length} stops
-
+
+
+
+
+
+
Map
+
{items.length} stop{items.length !== 1 ? 's' : ''}
+
+
{isLoading ? (
) : error ? (
-
+
{error}
-
+
) : items.length === 0 ? (
-
-
No collections to show
+
+
+
+
No collections to show
All collections completed!
) : (
-
+
}>
@@ -122,4 +139,4 @@ export default function MapPage() {
);
-}
+}
\ No newline at end of file
diff --git a/app/page.tsx b/app/page.tsx
index 1686a2f..f4e0d4e 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -23,10 +23,10 @@ import {
MapPin,
AlertCircle,
CheckCircle2,
- Clock,
- Package,
Search,
X,
+ Package,
+ ListFilter,
} from 'lucide-react';
export default function CollectionsPage() {
@@ -42,12 +42,9 @@ export default function CollectionsPage() {
const [showCollectModal, setShowCollectModal] = useState(false);
const [showProblemModal, setShowProblemModal] = useState(false);
const [toast, setToast] = useState<{ message: string; type: 'success' | 'error' | 'warning' } | null>(null);
- const [filter, setFilter] = useState<'all' | 'today' | 'all_date'>('all');
useEffect(() => {
- if (!authLoading && !isLoggedIn) {
- router.push('/login/');
- }
+ if (!authLoading && !isLoggedIn) router.push('/login/');
}, [isLoggedIn, authLoading, router]);
const loadItems = useCallback(async (showLoading = true) => {
@@ -61,16 +58,14 @@ export default function CollectionsPage() {
setFilteredItems(data);
} catch (err) {
console.error('Failed to load collections:', err);
- setError('Failed to load collections. Please try again.');
+ setError('Failed to load collections. Pull down to retry.');
} finally {
setIsLoading(false);
setIsRefreshing(false);
}
}, []);
- useEffect(() => {
- loadItems();
- }, [loadItems]);
+ useEffect(() => { loadItems(); }, [loadItems]);
useEffect(() => {
let filtered = items;
@@ -109,7 +104,6 @@ export default function CollectionsPage() {
notes,
collected_date: new Date().toISOString(),
});
-
setItems((prev) => prev.filter((i) => i.id !== item.id));
setShowCollectModal(false);
showToast('Collection completed successfully');
@@ -119,12 +113,7 @@ export default function CollectionsPage() {
}
};
- const handleProblem = async (
- item: OrderItem,
- problemType: string,
- description: string,
- photo?: File
- ) => {
+ const handleProblem = async (item: OrderItem, problemType: string, description: string) => {
try {
await reportProblem(item.id, problemType, description);
setItems((prev) => prev.filter((i) => i.id !== item.id));
@@ -140,7 +129,7 @@ export default function CollectionsPage() {
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
const url = isIOS
? `maps://maps.apple.com/?q=${encodeURIComponent(name)}&ll=${lat},${lng}`
- : `https://www.google.com/maps?q=${lat},${lng}`;
+ : `https://www.google.com/maps/dir/?api=1&destination=${lat},${lng}`;
window.open(url, '_blank');
};
@@ -163,7 +152,10 @@ export default function CollectionsPage() {
if (authLoading) {
return (
);
}
@@ -171,106 +163,135 @@ export default function CollectionsPage() {
if (!isLoggedIn) return null;
return (
-
+
-
-
+ {/* Header */}
+
+
-
-
Collections
-
{user?.name || user?.email}
+
+
Collections
+
{user?.name || user?.email}
-
-
-
+ {/* Search bar */}
-
- {[
- { key: 'all' as const, label: 'All', icon: Package },
- ].map(({ key, label, icon: Icon }) => (
-
setFilter(key)}
- className={cn(
- 'flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium whitespace-nowrap transition-colors',
- filter === key ? 'bg-white text-orange-600' : 'bg-orange-700/50 text-orange-100'
- )}
- >
-
- {label}
- {items.length}
-
- ))}
+ {/* Stats pill */}
+
+
+
+ {items.length} item{items.length !== 1 ? 's' : ''}
+
+ {groupedArray.length > 0 && (
+
+
+ {groupedArray.length} stop{groupedArray.length !== 1 ? 's' : ''}
+
+ )}
-
+ {/* Content */}
+
{isLoading ? (
- {[...Array(5)].map((_, i) => (
-
-
-
-
+ {[...Array(4)].map((_, i) => (
+
))}
) : error ? (
-
-
-
{error}
-
+
+
+
{error}
+
Check your connection and try again
+
+
Try Again
) : groupedArray.length === 0 ? (
-
-
-
All caught up!
-
- {searchQuery ? 'No collections match your search' : 'No assigned collections'}
+
+
+
+
+
All caught up!
+
+ {searchQuery ? 'No collections match your search' : 'No assigned collections right now'}
) : (
- {groupedArray.map(({ supplier, items }) => (
-
-
-
-
{supplier!.suppliername}
-
{items.length}
- {supplier!.supplieraddress && (
-
{supplier!.supplieraddress}
- )}
+ {groupedArray.map(({ supplier, items: supplierItems }, groupIdx) => (
+
+ {/* Supplier header */}
+
+
+
+
+
+
{supplier!.suppliername}
+ {supplier!.supplieraddress && (
+
{supplier!.supplieraddress}
+ )}
+
+
+ {supplierItems.length}
+
-
- {items.map((item) => (
+
+ {/* Items */}
+
+ {supplierItems.map((item) => (
+ {/* Modals */}
+ {/* Toast */}
{toast && (
-
+
);
-}
+}
\ No newline at end of file
diff --git a/app/settings/page.tsx b/app/settings/page.tsx
index 9992447..b59d308 100644
--- a/app/settings/page.tsx
+++ b/app/settings/page.tsx
@@ -11,29 +11,29 @@ import {
Settings,
Clock,
Crosshair,
- Moon,
- Type,
- WifiOff,
LogOut,
- ChevronRight,
Save,
RotateCcw,
Info,
+ ChevronRight,
+ Signal,
+ MapPin,
+ Shield,
} from 'lucide-react';
const INTERVAL_OPTIONS = [
- { value: 30, label: '30 seconds' },
- { value: 60, label: '1 minute' },
- { value: 120, label: '2 minutes' },
- { value: 300, label: '5 minutes' },
- { value: 600, label: '10 minutes' },
+ { value: 30, label: '30s' },
+ { value: 60, label: '1 min' },
+ { value: 120, label: '2 min' },
+ { value: 300, label: '5 min' },
+ { value: 600, label: '10 min' },
];
const ACCURACY_OPTIONS = [
- { value: 10, label: '10m (High)' },
- { value: 50, label: '50m (Balanced)' },
- { value: 100, label: '100m (Battery)' },
- { value: 500, label: '500m (Low)' },
+ { value: 10, label: '10m', desc: 'High precision' },
+ { value: 50, label: '50m', desc: 'Balanced' },
+ { value: 100, label: '100m', desc: 'Battery saver' },
+ { value: 500, label: '500m', desc: 'Low power' },
];
export default function SettingsPage() {
@@ -43,9 +43,7 @@ export default function SettingsPage() {
const [saved, setSaved] = useState(false);
useEffect(() => {
- if (!authLoading && !isLoggedIn) {
- router.push('/login/');
- }
+ if (!authLoading && !isLoggedIn) router.push('/login/');
}, [isLoggedIn, authLoading, router]);
useEffect(() => {
@@ -72,29 +70,28 @@ export default function SettingsPage() {
if (authLoading) {
return (
);
}
- if (!isLoggedIn) {
- return null;
- }
+ if (!isLoggedIn) return null;
return (
-
+
{/* Header */}
-
-
+
+
-
+
Settings
-
- {user?.name || user?.email}
-
+
{user?.name || user?.email}
@@ -104,190 +101,129 @@ export default function SettingsPage() {
{/* Location Settings */}
-
-
+
+
Location Tracking
-
+
{/* Update Interval */}
-
+
-
- Update Interval
-
+ Ping Interval
-
- {settings.locationInterval}s
+
+ {settings.locationInterval >= 60 ? `${settings.locationInterval / 60} min` : `${settings.locationInterval}s`}
-
+
{INTERVAL_OPTIONS.map((option) => (
updateSetting('locationInterval', option.value)}
className={cn(
- 'py-2 px-2 rounded-lg text-xs font-medium transition-all',
- 'touch-manipulation',
+ 'py-2.5 rounded-xl text-xs font-semibold transition-all',
+ 'touch-manipulation press-feedback-sm',
settings.locationInterval === option.value
- ? 'bg-orange-500 text-white shadow-sm'
- : 'bg-gray-50 text-gray-600 hover:bg-gray-100'
+ ? 'bg-orange-500 text-white shadow-sm shadow-orange-500/25'
+ : 'bg-gray-50 text-gray-600 active:bg-gray-100'
)}
>
{option.label}
))}
-
- How often your location is sent to the server
-
{/* Minimum Accuracy */}
-
+
-
- Minimum Accuracy
-
+ Accuracy Filter
-
+
{settings.minimumAccuracy}m
-
+
{ACCURACY_OPTIONS.map((option) => (
updateSetting('minimumAccuracy', option.value)}
className={cn(
- 'py-2 px-2 rounded-lg text-xs font-medium transition-all',
- 'touch-manipulation',
+ 'py-2.5 rounded-xl text-xs font-semibold transition-all flex flex-col items-center',
+ 'touch-manimotion press-feedback-sm',
settings.minimumAccuracy === option.value
- ? 'bg-orange-500 text-white shadow-sm'
- : 'bg-gray-50 text-gray-600 hover:bg-gray-100'
+ ? 'bg-orange-500 text-white shadow-sm shadow-orange-500/25'
+ : 'bg-gray-50 text-gray-600 active:bg-gray-100'
)}
>
- {option.label}
+ {option.label}
+
+ {option.desc}
+
))}
-
- Skip location updates with worse accuracy
-
{/* High Accuracy Toggle */}
-
-
+
+
+
+
-
- High Accuracy Mode
-
-
- Uses GPS for better precision
-
+ High Accuracy GPS
+ Uses more battery for precision
- updateSetting('enableHighAccuracy', !settings.enableHighAccuracy)
- }
+ onClick={() => updateSetting('enableHighAccuracy', !settings.enableHighAccuracy)}
className={cn(
- 'w-12 h-7 rounded-full transition-colors relative',
- settings.enableHighAccuracy ? 'bg-orange-500' : 'bg-gray-300'
+ 'w-[52px] h-[30px] rounded-full transition-colors relative',
+ settings.enableHighAccuracy ? 'bg-orange-500' : 'bg-gray-200'
)}
>
-
+
-
-
- {/* Display Settings */}
-
-
-
-
- Display
-
-
-
-
- {/* Font Size */}
-
-
-
- Font Size
-
-
- {(['small', 'normal', 'large'] as const).map((size) => (
- updateSetting('fontSize', size)}
- className={cn(
- 'flex-1 py-2.5 rounded-lg text-sm font-medium transition-all',
- 'touch-manipulation capitalize',
- settings.fontSize === size
- ? 'bg-orange-500 text-white shadow-sm'
- : 'bg-gray-50 text-gray-600 hover:bg-gray-100'
- )}
- >
- {size === 'small' && (
- A
- )}
- {size === 'normal' && (
- A
- )}
- {size === 'large' && (
- A
- )}
-
- ))}
-
-
-
- {/* Offline Mode */}
-
+ {/* Screen Wake Lock */}
+
-
-
+
+
+
+
-
- Offline Queue
-
-
- Queue actions when offline
-
+ Keep Screen On
+ Prevents phone from sleeping
- updateSetting('offlineMode', !settings.offlineMode)
- }
+ onClick={() => updateSetting('offlineMode', !settings.offlineMode)}
className={cn(
- 'w-12 h-7 rounded-full transition-colors relative',
- settings.offlineMode ? 'bg-orange-500' : 'bg-gray-300'
+ 'w-[52px] h-[30px] rounded-full transition-colors relative',
+ settings.offlineMode ? 'bg-orange-500' : 'bg-gray-200'
)}
>
-
+
@@ -297,31 +233,30 @@ export default function SettingsPage() {
{/* About */}
-
-
- About
-
+
+ About
-
-
-
1Driver
+
+
+
+ 1D
+
+
1Driver
Version 1.0.0
-
- Collections Management App
-
+
Collection Management App
{/* Actions */}
-
+
@@ -335,9 +270,9 @@ export default function SettingsPage() {
}
}}
className={cn(
- 'w-full py-3.5 px-4 rounded-xl text-white font-semibold text-base',
- 'bg-red-500 active:bg-red-600 transition-colors',
- 'touch-manipulation flex items-center justify-center gap-2'
+ 'w-full py-3.5 px-4 rounded-2xl text-white font-semibold text-[15px]',
+ 'bg-red-500 active:bg-red-600 transition-colors shadow-lg shadow-red-500/20',
+ 'touch-manimotion press-feedback flex items-center justify-center gap-2'
)}
>
@@ -348,8 +283,8 @@ export default function SettingsPage() {
{/* Saved Toast */}
{saved && (
-
-
+
+
Settings saved
@@ -359,4 +294,4 @@ export default function SettingsPage() {
);
-}
+}
\ No newline at end of file
diff --git a/components/BottomNav.tsx b/components/BottomNav.tsx
index 9701003..f66878f 100644
--- a/components/BottomNav.tsx
+++ b/components/BottomNav.tsx
@@ -2,12 +2,12 @@
import Link from 'next/link';
import { usePathname } from 'next/navigation';
-import { List, Map, Settings } from 'lucide-react';
+import { List, MapPin, Settings } from 'lucide-react';
import { cn } from '@/lib/utils';
const navItems = [
- { href: '/', label: 'List', icon: List },
- { href: '/map/', label: 'Map', icon: Map },
+ { href: '/', label: 'Collections', icon: List },
+ { href: '/map/', label: 'Map', icon: MapPin },
{ href: '/settings/', label: 'Settings', icon: Settings },
];
@@ -15,34 +15,37 @@ export default function BottomNav() {
const pathname = usePathname();
return (
-