Skip to content

Commit 818b175

Browse files
committed
feat(mock): polish dashboard data, images, and deploy
- Fix MSW mock handlers for dashboard charts (realistic dates/series) - Add local product images for Trending Products - Add /admin/orders/stats page + route - Reduce console noise (router flags, antd message context, MSW quiet) - Add GitHub Pages deploy workflow for mock build
1 parent 819de49 commit 818b175

File tree

18 files changed

+369
-64
lines changed

18 files changed

+369
-64
lines changed

.github/workflows/deploy-pages.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: Deploy (GitHub Pages)
2+
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
pages: write
11+
id-token: write
12+
13+
concurrency:
14+
group: pages
15+
cancel-in-progress: true
16+
17+
jobs:
18+
build:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- uses: actions/checkout@v4
22+
- uses: actions/setup-node@v4
23+
with:
24+
node-version: 20
25+
cache: npm
26+
- run: npm ci
27+
- run: npm run build
28+
env:
29+
VITE_USE_MOCKS: "true"
30+
VITE_ROUTER_MODE: "hash"
31+
VITE_BASE_PATH: "/${{ github.event.repository.name }}/"
32+
- uses: actions/upload-pages-artifact@v3
33+
with:
34+
path: dist
35+
36+
deploy:
37+
runs-on: ubuntu-latest
38+
needs: build
39+
environment:
40+
name: github-pages
41+
url: ${{ steps.deployment.outputs.page_url }}
42+
steps:
43+
- id: deployment
44+
uses: actions/deploy-pages@v4
45+

public/mock/products/backpack.jpg

46.3 KB
Loading
632 KB
Loading

public/mock/products/sneakers.jpg

32.7 KB
Loading

public/mock/products/watch.jpg

716 KB
Loading

src/App.tsx

Lines changed: 83 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Toaster } from "@/components/ui/toaster";
22
import { Toaster as Sonner } from "@/components/ui/sonner";
33
import { TooltipProvider } from "@/components/ui/tooltip";
44
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
5-
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
5+
import { BrowserRouter, HashRouter, Routes, Route, Navigate } from "react-router-dom";
66
import { AuthProvider, useAuth } from "@/contexts/AuthContext";
77
import Index from "./pages/Index";
88
import NotFound from "./pages/NotFound";
@@ -12,6 +12,7 @@ import Dashboard from "./pages/admin/Dashboard";
1212
import Products from "./pages/admin/Products";
1313
import ProductEditor from "./pages/admin/ProductEditor";
1414
import Orders from "./pages/admin/Orders";
15+
import OrderStats from "./pages/admin/OrderStats";
1516
import Customers from "./pages/admin/Customers";
1617
import MediaPage from "./pages/admin/Media";
1718
import Posts from "./pages/admin/Posts";
@@ -29,6 +30,7 @@ import Comments from "./pages/admin/Comments";
2930
import Coupons from "./pages/admin/Coupons";
3031

3132
const queryClient = new QueryClient();
33+
const useHashRouter = import.meta.env.VITE_ROUTER_MODE === "hash";
3234

3335
const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
3436
const { admin, loading } = useAuth();
@@ -55,45 +57,87 @@ const App = () => (
5557
<TooltipProvider>
5658
<Toaster />
5759
<Sonner />
58-
<BrowserRouter
59-
future={{ v7_startTransition: true, v7_relativeSplatPath: true }}
60-
>
61-
<Routes>
62-
<Route path="/" element={<Index />} />
63-
<Route path="/login" element={<Login />} />
64-
<Route
65-
path="/admin"
66-
element={
67-
<ProtectedRoute>
68-
<AdminLayout />
69-
</ProtectedRoute>
70-
}
60+
{useHashRouter ? (
61+
<HashRouter>
62+
<Routes>
63+
<Route path="/" element={<Index />} />
64+
<Route path="/login" element={<Login />} />
65+
<Route
66+
path="/admin"
67+
element={
68+
<ProtectedRoute>
69+
<AdminLayout />
70+
</ProtectedRoute>
71+
}
72+
>
73+
<Route index element={<Dashboard />} />
74+
<Route path="products" element={<Products />} />
75+
<Route path="products/new" element={<ProductEditor />} />
76+
<Route path="products/:id/edit" element={<ProductEditor />} />
77+
<Route path="categories" element={<Categories />} />
78+
<Route path="tags" element={<Tags />} />
79+
<Route path="orders" element={<Orders />} />
80+
<Route path="orders/stats" element={<OrderStats />} />
81+
<Route path="shipments" element={<Shipments />} />
82+
<Route path="customers" element={<Customers />} />
83+
<Route path="media" element={<MediaPage />} />
84+
<Route path="posts" element={<Posts />} />
85+
<Route path="pages" element={<Pages />} />
86+
<Route path="roles" element={<Roles />} />
87+
<Route path="permissions" element={<Permissions />} />
88+
<Route path="attributes" element={<Attributes />} />
89+
<Route path="payment-gateways" element={<PaymentGateways />} />
90+
<Route path="shipping-methods" element={<ShippingMethods />} />
91+
<Route path="comments" element={<Comments />} />
92+
<Route path="coupons" element={<Coupons />} />
93+
<Route path="settings" element={<Settings />} />
94+
</Route>
95+
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
96+
<Route path="*" element={<NotFound />} />
97+
</Routes>
98+
</HashRouter>
99+
) : (
100+
<BrowserRouter
101+
future={{ v7_startTransition: true, v7_relativeSplatPath: true }}
71102
>
72-
<Route index element={<Dashboard />} />
73-
<Route path="products" element={<Products />} />
74-
<Route path="products/new" element={<ProductEditor />} />
75-
<Route path="products/:id/edit" element={<ProductEditor />} />
76-
<Route path="categories" element={<Categories />} />
77-
<Route path="tags" element={<Tags />} />
78-
<Route path="orders" element={<Orders />} />
79-
<Route path="shipments" element={<Shipments />} />
80-
<Route path="customers" element={<Customers />} />
81-
<Route path="media" element={<MediaPage />} />
82-
<Route path="posts" element={<Posts />} />
83-
<Route path="pages" element={<Pages />} />
84-
<Route path="roles" element={<Roles />} />
85-
<Route path="permissions" element={<Permissions />} />
86-
<Route path="attributes" element={<Attributes />} />
87-
<Route path="payment-gateways" element={<PaymentGateways />} />
88-
<Route path="shipping-methods" element={<ShippingMethods />} />
89-
<Route path="comments" element={<Comments />} />
90-
<Route path="coupons" element={<Coupons />} />
91-
<Route path="settings" element={<Settings />} />
92-
</Route>
93-
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
94-
<Route path="*" element={<NotFound />} />
95-
</Routes>
96-
</BrowserRouter>
103+
<Routes>
104+
<Route path="/" element={<Index />} />
105+
<Route path="/login" element={<Login />} />
106+
<Route
107+
path="/admin"
108+
element={
109+
<ProtectedRoute>
110+
<AdminLayout />
111+
</ProtectedRoute>
112+
}
113+
>
114+
<Route index element={<Dashboard />} />
115+
<Route path="products" element={<Products />} />
116+
<Route path="products/new" element={<ProductEditor />} />
117+
<Route path="products/:id/edit" element={<ProductEditor />} />
118+
<Route path="categories" element={<Categories />} />
119+
<Route path="tags" element={<Tags />} />
120+
<Route path="orders" element={<Orders />} />
121+
<Route path="orders/stats" element={<OrderStats />} />
122+
<Route path="shipments" element={<Shipments />} />
123+
<Route path="customers" element={<Customers />} />
124+
<Route path="media" element={<MediaPage />} />
125+
<Route path="posts" element={<Posts />} />
126+
<Route path="pages" element={<Pages />} />
127+
<Route path="roles" element={<Roles />} />
128+
<Route path="permissions" element={<Permissions />} />
129+
<Route path="attributes" element={<Attributes />} />
130+
<Route path="payment-gateways" element={<PaymentGateways />} />
131+
<Route path="shipping-methods" element={<ShippingMethods />} />
132+
<Route path="comments" element={<Comments />} />
133+
<Route path="coupons" element={<Coupons />} />
134+
<Route path="settings" element={<Settings />} />
135+
</Route>
136+
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
137+
<Route path="*" element={<NotFound />} />
138+
</Routes>
139+
</BrowserRouter>
140+
)}
97141
</TooltipProvider>
98142
</AuthProvider>
99143
</QueryClientProvider>

src/components/LanguageSelector.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,8 @@ export const LanguageProvider: React.FC<{ children: React.ReactNode }> = ({ chil
8686
if (selectedLanguage?.dayjsLocale) {
8787
dayjs.locale(selectedLanguage.dayjsLocale);
8888
}
89-
90-
console.log('Language changed to:', languageCode);
91-
} catch (error) {
92-
console.error('Error changing language:', error);
89+
} catch {
90+
// no-op
9391
}
9492
};
9593

src/mocks/browser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,6 @@ export async function startMockWorker() {
2626
onUnhandledRequest: "bypass",
2727
serviceWorker: { url: `${import.meta.env.BASE_URL}mockServiceWorker.js` },
2828
// Ensure the worker is ready before continuing
29-
quiet: false,
29+
quiet: true,
3030
});
3131
}

src/mocks/config.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ function parseNumber(value: string | undefined): number | undefined {
2424

2525
export function shouldEnableMocks(): boolean {
2626
const buildFlag = parseBoolean(import.meta.env.VITE_USE_MOCKS);
27-
console.log("Build flag:", buildFlag);
2827
if (buildFlag !== undefined) return buildFlag;
2928

3029
if (typeof window === "undefined") return false;

src/mocks/createHandlers.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,96 @@ function createResolver(
150150
return HttpResponse.json({ success: true }, { status: 200 });
151151
}
152152

153+
if (args.clientPath === "/dashboard/revenue" && method === "GET") {
154+
const url = new URL(request.url);
155+
const period = url.searchParams.get("period") ?? "daily";
156+
const isDaily = period === "daily";
157+
const points = isDaily ? 7 : 4;
158+
const stepMs = isDaily ? 86400000 : 7 * 86400000;
159+
const start = Date.now() - (points - 1) * stepMs;
160+
161+
const items = Array.from({ length: points }, (_, i) => {
162+
const date = new Date(start + i * stepMs).toISOString().slice(0, 10);
163+
const orders = args.rng.int(120, 980);
164+
const averageOrderValue = args.rng.float(35, 240);
165+
const revenue = Number((orders * averageOrderValue).toFixed(2));
166+
return {
167+
date,
168+
period: date,
169+
orders,
170+
revenue,
171+
};
172+
});
173+
174+
return HttpResponse.json(items, { status: 200 });
175+
}
176+
177+
if (args.clientPath === "/dashboard/sales" && method === "GET") {
178+
const url = new URL(request.url);
179+
const period = url.searchParams.get("period") ?? "daily";
180+
const isDaily = period === "daily";
181+
const points = isDaily ? 7 : 4;
182+
const stepMs = isDaily ? 86400000 : 7 * 86400000;
183+
const start = Date.now() - (points - 1) * stepMs;
184+
185+
const items = Array.from({ length: points }, (_, i) => {
186+
const date = new Date(start + i * stepMs).toISOString().slice(0, 10);
187+
const total_orders = args.rng.int(80, 980);
188+
const average_order_value = Number(args.rng.float(24, 220).toFixed(2));
189+
const total_revenue = Number((total_orders * average_order_value).toFixed(2));
190+
const conversion_rate = Number(args.rng.float(0.8, 4.8).toFixed(2));
191+
return {
192+
period: date,
193+
total_orders,
194+
total_revenue,
195+
average_order_value,
196+
conversion_rate,
197+
};
198+
});
199+
200+
return HttpResponse.json(items, { status: 200 });
201+
}
202+
203+
if (args.clientPath === "/dashboard/top-products" && method === "GET") {
204+
const url = new URL(request.url);
205+
const limit = Math.max(1, Math.min(12, Number(url.searchParams.get("limit") ?? "4") || 4));
206+
const baseUrl = import.meta.env.BASE_URL ?? "/";
207+
const imageUrls = [
208+
`${baseUrl}mock/products/sneakers.jpg`,
209+
`${baseUrl}mock/products/headphones.jpg`,
210+
`${baseUrl}mock/products/watch.jpg`,
211+
`${baseUrl}mock/products/backpack.jpg`,
212+
];
213+
214+
const presets = [
215+
{ name: "Nike Sneakers", sku: "SNK-001" },
216+
{ name: "Beats Headphones", sku: "HDP-002" },
217+
{ name: "Wood Watch", sku: "WCH-003" },
218+
{ name: "Yellow Backpack", sku: "BAG-004" },
219+
{ name: "Minimal Desk Lamp", sku: "LMP-005" },
220+
{ name: "Ceramic Mug", sku: "MUG-006" },
221+
{ name: "Wireless Mouse", sku: "MSE-007" },
222+
{ name: "Scented Candle", sku: "CND-008" },
223+
];
224+
225+
const items = Array.from({ length: limit }, (_, i) => {
226+
const preset = presets[i % presets.length];
227+
const total_sold = args.rng.int(18, 240);
228+
const price = Number(args.rng.float(18, 220).toFixed(2));
229+
const total_revenue = Number((total_sold * price).toFixed(2));
230+
return {
231+
product_id: i + 1,
232+
product_name: preset.name,
233+
sku: preset.sku,
234+
image_url: imageUrls[i % imageUrls.length],
235+
total_sold,
236+
total_revenue,
237+
};
238+
});
239+
240+
return HttpResponse.json(items, { status: 200 });
241+
}
242+
153243
if (
154244
isPaginatedResponse(args, schemaInfo.schema) &&
155245
method === "GET" &&

0 commit comments

Comments
 (0)