Skip to content

Commit db38071

Browse files
committed
xyz
1 parent d6d52ad commit db38071

5 files changed

Lines changed: 205 additions & 102 deletions

File tree

src/App.tsx

Lines changed: 40 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { useEffect } from 'react';
22
import { Routes, Route, useLocation } from 'react-router-dom';
33
import Layout from './components/layout/Layout';
44
import Home from './pages/Home';
@@ -15,9 +15,8 @@ import { SplashScreen } from '@capacitor/splash-screen';
1515
import { StatusBar } from "@capacitor/status-bar";
1616
import { App as CapApp } from '@capacitor/app';
1717
import { Browser } from '@capacitor/browser';
18-
import { checkForUpdate } from './utils/updateChecker';
1918
import { UpdateModal } from './components/modals/UpdateModal';
20-
import { useEffect, useState } from 'react';
19+
import { UpdateProvider, useUpdate } from './contexts/UpdateContext';
2120

2221
const FontsCatalog = React.lazy(() => import('./pages/FontsCatalog'));
2322
const FontDetails = React.lazy(() => import('./pages/FontDetails'));
@@ -31,21 +30,9 @@ const FontPairing = React.lazy(() => import('./pages/FontPairing'));
3130
const Cli = React.lazy(() => import('./pages/Cli'));
3231
const DesignerFonts = React.lazy(() => import('./pages/DesignerFonts'));
3332

34-
function App() {
33+
function AppContent() {
3534
const location = useLocation();
36-
const [updateInfo, setUpdateInfo] = useState<{
37-
isOpen: boolean;
38-
latestBuild: string;
39-
currentBuild: string;
40-
releaseNotes: string;
41-
apkUrl: string;
42-
}>({
43-
isOpen: false,
44-
latestBuild: '',
45-
currentBuild: '',
46-
releaseNotes: '',
47-
apkUrl: ''
48-
});
35+
const { isModalOpen, updateInfo, closeModal } = useUpdate();
4936

5037
useEffect(() => {
5138
const init = async () => {
@@ -89,56 +76,48 @@ function App() {
8976
};
9077
}, []);
9178

92-
useEffect(() => {
93-
const runUpdateCheck = async () => {
94-
const result = await checkForUpdate();
95-
96-
if (result.hasUpdate && result.apkUrl) {
97-
setUpdateInfo({
98-
isOpen: true,
99-
latestBuild: String(result.latestBuild),
100-
currentBuild: String(result.currentBuild),
101-
releaseNotes: result.releaseNotes || 'New version available.',
102-
apkUrl: result.apkUrl
103-
});
104-
}
105-
};
106-
107-
runUpdateCheck();
108-
}, []);
79+
return (
80+
<>
81+
<ScrollRestoration />
82+
<UploadProgressPopup />
83+
<BackHandler />
84+
<Routes location={location} key={location.pathname}>
85+
<Route path="/" element={<Layout />}>
86+
<Route index element={<Home />} />
87+
<Route path="fonts" element={<FontsCatalog />} />
88+
<Route path="fonts/:id" element={<FontDetails />} />
89+
<Route path="pairing" element={<FontPairing />} />
90+
<Route path="auth" element={<Auth />} />
91+
<Route path="upload" element={<Upload />} />
92+
<Route path="profile" element={<Profile />} />
93+
<Route path="members" element={<Members />} />
94+
<Route path="members/:id" element={<MemberDetails />} />
95+
<Route path="designers/:designerName" element={<DesignerFonts />} />
96+
<Route path="admin" element={<AdminDashboard />} />
97+
<Route path="cli" element={<Cli />} />
98+
</Route>
99+
</Routes>
100+
<UpdateModal
101+
isOpen={isModalOpen}
102+
latestVersion={updateInfo?.latestBuild || ''}
103+
currentVersion={updateInfo?.currentBuild || ''}
104+
releaseNotes={updateInfo?.releaseNotes || ''}
105+
apkUrl={updateInfo?.apkUrl || ''}
106+
onClose={closeModal}
107+
/>
108+
</>
109+
);
110+
}
109111

112+
function App() {
110113
return (
111114
<HelmetProvider>
112115
<ThemeProvider>
113116
<AuthProvider>
114117
<UploadProvider>
115-
<ScrollRestoration />
116-
<UploadProgressPopup />
117-
<BackHandler />
118-
<Routes location={location} key={location.pathname}>
119-
<Route path="/" element={<Layout />}>
120-
<Route index element={<Home />} />
121-
<Route path="fonts" element={<FontsCatalog />} />
122-
<Route path="fonts/:id" element={<FontDetails />} />
123-
<Route path="pairing" element={<FontPairing />} />
124-
<Route path="auth" element={<Auth />} />
125-
<Route path="upload" element={<Upload />} />
126-
<Route path="profile" element={<Profile />} />
127-
<Route path="members" element={<Members />} />
128-
<Route path="members/:id" element={<MemberDetails />} />
129-
<Route path="designers/:designerName" element={<DesignerFonts />} />
130-
<Route path="admin" element={<AdminDashboard />} />
131-
<Route path="cli" element={<Cli />} />
132-
</Route>
133-
</Routes>
134-
<UpdateModal
135-
isOpen={updateInfo.isOpen}
136-
latestVersion={updateInfo.latestBuild}
137-
currentVersion={updateInfo.currentBuild}
138-
releaseNotes={updateInfo.releaseNotes}
139-
apkUrl={updateInfo.apkUrl}
140-
onClose={() => setUpdateInfo(prev => ({ ...prev, isOpen: false }))}
141-
/>
118+
<UpdateProvider>
119+
<AppContent />
120+
</UpdateProvider>
142121
</UploadProvider>
143122
</AuthProvider>
144123
</ThemeProvider>

src/components/layout/Navbar.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@ import { Type, Combine, Terminal, Users, Upload, User, Shield, Plus, Sun, Moon,
88
import { motion } from 'framer-motion';
99
import { useTheme } from '../../contexts/ThemeContext';
1010
import { Haptics, ImpactStyle } from '@capacitor/haptics';
11+
import { useUpdate } from '../../contexts/UpdateContext';
12+
import { Download } from 'lucide-react';
1113
import Logo from '/logo/logo.png'; // Assuming white version exists or we filter it
1214

1315
export default function Navbar() {
1416
const { user, profile } = useAuth();
1517
const { theme, toggleTheme } = useTheme();
18+
const { hasUpdate, isDownloading, progress, openModal, closeModal, isModalOpen } = useUpdate();
1619
const [isScrolled, setIsScrolled] = useState(false);
1720
const location = useLocation();
1821

@@ -102,6 +105,51 @@ export default function Navbar() {
102105

103106
{/* Right Side Actions */}
104107
<div className="flex items-center gap-3">
108+
{/* Update Download Icon */}
109+
{hasUpdate && (
110+
<motion.button
111+
whileTap={{ scale: 0.95 }}
112+
onClick={async () => {
113+
await Haptics.impact({ style: ImpactStyle.Light });
114+
isModalOpen ? closeModal() : openModal();
115+
}}
116+
className="relative h-10 w-10 flex items-center justify-center rounded-full bg-[rgb(var(--color-foreground)/0.05)] border border-[rgb(var(--color-border)/0.2)] text-[rgb(var(--color-foreground))] hover:bg-[rgb(var(--color-foreground)/0.1)] transition-all cursor-pointer group"
117+
title="Software Update Available"
118+
>
119+
{isDownloading && (
120+
<svg className="absolute inset-0 w-full h-full -rotate-90" viewBox="0 0 40 40">
121+
<circle
122+
cx="20"
123+
cy="20"
124+
r="18"
125+
fill="none"
126+
stroke="rgb(var(--color-foreground) / 0.1)"
127+
strokeWidth="2"
128+
/>
129+
<motion.circle
130+
cx="20"
131+
cy="20"
132+
r="18"
133+
fill="none"
134+
stroke="currentColor"
135+
strokeWidth="2"
136+
strokeDasharray="113.1"
137+
initial={{ strokeDashoffset: 113.1 }}
138+
animate={{ strokeDashoffset: 113.1 - (113.1 * progress) / 100 }}
139+
transition={{ type: "spring", bounce: 0, duration: 0.5 }}
140+
/>
141+
</svg>
142+
)}
143+
<Download size={18} className={cn("transition-transform duration-300 group-hover:-translate-y-0.5", isDownloading && "animate-pulse")} />
144+
{!isDownloading && (
145+
<span className="absolute top-0 right-0 flex h-3 w-3">
146+
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-400 opacity-75"></span>
147+
<span className="relative inline-flex rounded-full h-3 w-3 bg-blue-500"></span>
148+
</span>
149+
)}
150+
</motion.button>
151+
)}
152+
105153
{/* Theme Switcher */}
106154
<motion.button
107155
whileTap={{ scale: 0.95 }}

src/components/modals/UpdateModal.tsx

Lines changed: 14 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,28 @@
11
import { motion, AnimatePresence } from 'framer-motion';
22
import { Download, X, ArrowRight } from 'lucide-react';
3-
import { useState } from 'react';
4-
import { installApk } from '../../utils/apkInstaller';
5-
import { Toast } from '@capacitor/toast';
3+
import { useUpdate } from '../../contexts/UpdateContext';
64

7-
interface UpdateModalProps {
8-
isOpen: boolean;
9-
latestVersion: string;
10-
currentVersion: string;
11-
releaseNotes: string;
12-
apkUrl: string;
13-
onClose: () => void;
14-
}
15-
16-
export const UpdateModal = ({
17-
isOpen,
18-
latestVersion,
19-
currentVersion,
20-
releaseNotes,
21-
apkUrl,
22-
onClose
23-
}: UpdateModalProps) => {
24-
const [isDownloading, setIsDownloading] = useState(false);
25-
const [progress, setProgress] = useState(0);
5+
export const UpdateModal = () => {
6+
const { isModalOpen, updateInfo, closeModal, isDownloading, progress, startDownload } = useUpdate();
7+
8+
if (!updateInfo) return null;
9+
10+
const { latestBuild, currentBuild, releaseNotes } = updateInfo;
2611

2712
const handleUpdate = async () => {
28-
try {
29-
setIsDownloading(true);
30-
await installApk(apkUrl, (p) => setProgress(p));
31-
} catch (err) {
32-
console.error('Update failed:', err);
33-
Toast.show({
34-
text: 'Failed to download update. Please try again.',
35-
duration: 'long'
36-
});
37-
setIsDownloading(false);
38-
setProgress(0);
39-
}
13+
await startDownload();
4014
};
4115

4216
return (
4317
<AnimatePresence>
44-
{isOpen && (
18+
{isModalOpen && (
4519
<>
4620
{/* Backdrop */}
4721
<motion.div
4822
initial={{ opacity: 0 }}
4923
animate={{ opacity: 1 }}
5024
exit={{ opacity: 0 }}
51-
onClick={onClose}
25+
onClick={closeModal}
5226
className="fixed inset-0 bg-[rgb(var(--color-background)/0.3)] backdrop-blur-sm z-999"
5327
/>
5428

@@ -70,7 +44,7 @@ export const UpdateModal = ({
7044
<h2 className="text-xl font-bold text-[rgb(var(--color-background))]">Update Ready</h2>
7145
</div>
7246
<button
73-
onClick={onClose}
47+
onClick={closeModal}
7448
className="p-2"
7549
>
7650
<X className="w-5 h-5 text-[rgb(var(--color-background))]" />
@@ -81,12 +55,12 @@ export const UpdateModal = ({
8155
<div className="flex items-center gap-4 mb-6 p-4 bg-[rgb(var(--color-background)/0.3)] rounded-2xl border border-[rgb(var(--color-muted))]">
8256
<div className="flex-1">
8357
<p className="text-xs text-[rgb(var(--color-background))] uppercase tracking-wider font-semibold mb-1">Current</p>
84-
<p className="text-[rgb(var(--color-background))] font-medium">Beta v0.{currentVersion}</p>
58+
<p className="text-[rgb(var(--color-background))] font-medium">Beta v0.{currentBuild}</p>
8559
</div>
8660
<ArrowRight className="w-4 h-4 text-[rgb(var(--color-background))]" />
8761
<div className="flex-1 text-right">
8862
<p className="text-xs text-[rgb(var(--color-background))] uppercase tracking-wider font-semibold mb-1">Latest</p>
89-
<p className="text-[rgb(var(--color-background))] font-bold">Beta v0.{latestVersion}</p>
63+
<p className="text-[rgb(var(--color-background))] font-bold">Beta v0.{latestBuild}</p>
9064
</div>
9165
</div>
9266

@@ -131,7 +105,7 @@ export const UpdateModal = ({
131105
Update Now
132106
</motion.button>
133107
<button
134-
onClick={onClose}
108+
onClick={closeModal}
135109
className="w-full py-3 text-[rgb(var(--color-background)/0.8)] hover:[rgb(var(--color-background))] text-sm font-medium transition-colors"
136110
>
137111
Maybe Later

0 commit comments

Comments
 (0)