Skip to content

Commit 38b40e0

Browse files
RakshitKashyap1ektashreeharshraj280Learnermeet
committed
did frontend cleanup
elevated the user interface to production - quality standards Co-Authored-By: ektashree <268168531+ektashree@users.noreply.github.com> Co-Authored-By: harshraj280 <191345293+harshraj280@users.noreply.github.com> Co-Authored-By: meetthevibe <128215257+Learnermeet@users.noreply.github.com>
1 parent 3276a87 commit 38b40e0

9 files changed

Lines changed: 271 additions & 46 deletions

File tree

node_modules/.package-lock.json

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package-lock.json

Lines changed: 33 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"lucide-react": "^0.292.0",
1515
"react": "^18.2.0",
1616
"react-dom": "^18.2.0",
17+
"react-hot-toast": "^2.6.0",
1718
"react-router-dom": "^6.20.0"
1819
},
1920
"devDependencies": {

src/App.jsx

Lines changed: 110 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ import { AuthProvider } from './context/AuthContext';
1212
import PublicLayout from './layouts/PublicLayout';
1313
import DashboardLayout from './layouts/DashboardLayout';
1414
import ProtectedRoute from './components/common/ProtectedRoute';
15+
import { Toaster } from 'react-hot-toast';
16+
import Spinner from './components/common/Spinner';
17+
import api from './services/api';
18+
import { User, Layers, Calendar, CreditCard, PieChart } from 'lucide-react';
1519

1620
// Pages to import - Actual page content components
1721
import Home from './pages/public/Home';
@@ -46,6 +50,58 @@ const MockPage = ({ title }) => (
4650
</div>
4751
);
4852

53+
// Generic Data Manager for formerly MockPages
54+
const DataDashboard = ({ title, icon: Icon, endpoint, columns }) => {
55+
const [data, setData] = React.useState([]);
56+
const [loading, setLoading] = React.useState(true);
57+
const [error, setError] = React.useState(null);
58+
59+
React.useEffect(() => {
60+
const fetch = async () => {
61+
try {
62+
const res = await api.get(endpoint);
63+
setData(res.data.results || res.data);
64+
} catch (err) {
65+
setError(`Failed to load ${title.toLowerCase()}`);
66+
} finally {
67+
setLoading(false);
68+
}
69+
};
70+
fetch();
71+
}, [endpoint, title]);
72+
73+
if (loading) return <div className="h-full flex items-center justify-center"><Spinner size="lg" /></div>;
74+
if (error) return <div className="card border-danger text-danger p-8 text-center"><h2>Error</h2><p>{error}</p></div>;
75+
76+
return (
77+
<div className="animate-fade-in">
78+
<div className="flex items-center gap-4 mb-8">
79+
<div className="stat-icon"><Icon size={24} /></div>
80+
<h1 className="m-0">{title}</h1>
81+
</div>
82+
83+
<div className="table-container">
84+
<table>
85+
<thead>
86+
<tr>{columns.map(c => <th key={c.key}>{c.label}</th>)}</tr>
87+
</thead>
88+
<tbody>
89+
{data.length === 0 ? (
90+
<tr><td colSpan={columns.length} className="text-center py-8">No records found</td></tr>
91+
) : (
92+
data.map((row, i) => (
93+
<tr key={row.id || i}>
94+
{columns.map(c => <td key={c.key}>{c.render ? c.render(row) : row[c.key]}</td>)}
95+
</tr>
96+
))
97+
)}
98+
</tbody>
99+
</table>
100+
</div>
101+
</div>
102+
);
103+
};
104+
49105
/**
50106
* App: The root React component.
51107
* It manages:
@@ -56,6 +112,7 @@ const MockPage = ({ title }) => (
56112
export default function App() {
57113
return (
58114
<AuthProvider>
115+
<Toaster position="top-right" />
59116
<BrowserRouter>
60117
<Routes>
61118
{/*
@@ -111,10 +168,59 @@ export default function App() {
111168
<Route element={<ProtectedRoute allowedRoles={['admin']} />}>
112169
<Route element={<DashboardLayout />}>
113170
<Route path="/admin/dashboard" element={<AdminDashboard />} />
114-
<Route path="/admin/users" element={<MockPage title="Manage Users" />} />
115-
<Route path="/admin/adspaces" element={<MockPage title="Manage All Ad Spaces" />} />
116-
<Route path="/admin/bookings" element={<MockPage title="All Bookings" />} />
117-
<Route path="/admin/payments" element={<MockPage title="Payments Control" />} />
171+
<Route path="/admin/users" element={
172+
<DataDashboard
173+
title="Platform Users"
174+
icon={User}
175+
endpoint="/users/"
176+
columns={[
177+
{ key: 'id', label: 'ID' },
178+
{ key: 'name', label: 'Name' },
179+
{ key: 'email', label: 'Email' },
180+
{ key: 'role', label: 'Role', render: (u) => <span className={`badge ${u.role === 'admin' ? 'badge-primary' : u.role === 'owner' ? 'badge-warning' : 'badge-info'}`}>{u.role}</span> }
181+
]}
182+
/>
183+
} />
184+
<Route path="/admin/adspaces" element={
185+
<DataDashboard
186+
title="Global Ad Spaces"
187+
icon={Layers}
188+
endpoint="/adspaces/"
189+
columns={[
190+
{ key: 'title', label: 'Billboard' },
191+
{ key: 'city', label: 'City' },
192+
{ key: 'basePricePerDay', label: 'Price/Day', render: (a) => `$${a.basePricePerDay}` },
193+
{ key: 'availabilityStatus', label: 'Status', render: (a) => <span className={`badge ${a.availabilityStatus === 'available' ? 'badge-success' : 'badge-warning'}`}>{a.availabilityStatus}</span> }
194+
]}
195+
/>
196+
} />
197+
<Route path="/admin/bookings" element={
198+
<DataDashboard
199+
title="System Bookings"
200+
icon={Calendar}
201+
endpoint="/bookings/"
202+
columns={[
203+
{ key: 'id', label: 'Ref' },
204+
{ key: 'startDate', label: 'Starts' },
205+
{ key: 'endDate', label: 'Ends' },
206+
{ key: 'totalPrice', label: 'Total', render: (b) => `$${b.totalPrice}` },
207+
{ key: 'status', label: 'Status', render: (b) => <span className={`badge ${b.status === 'active' ? 'badge-success' : 'badge-warning'}`}>{b.status}</span> }
208+
]}
209+
/>
210+
} />
211+
<Route path="/admin/payments" element={
212+
<DataDashboard
213+
title="Payment Transactions"
214+
icon={CreditCard}
215+
endpoint="/payments/"
216+
columns={[
217+
{ key: 'id', label: 'ID' },
218+
{ key: 'amount', label: 'Amount', render: (p) => `$${p.amount}` },
219+
{ key: 'payment_method', label: 'Method' },
220+
{ key: 'status', label: 'Status', render: (p) => <span className={`badge ${p.status === 'successful' ? 'badge-success' : 'badge-warning'}`}>{p.status}</span> }
221+
]}
222+
/>
223+
} />
118224
<Route path="/admin/reports" element={<MockPage title="System Reports" />} />
119225
</Route>
120226
</Route>

src/components/common/Spinner.jsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React from 'react';
2+
3+
const Spinner = ({ size = 'md', color = 'primary' }) => {
4+
const sizeClasses = {
5+
sm: 'w-5 h-5',
6+
md: 'w-8 h-8',
7+
lg: 'w-12 h-12'
8+
};
9+
10+
const colorClasses = {
11+
primary: 'text-primary',
12+
secondary: 'text-secondary',
13+
white: 'text-white'
14+
};
15+
16+
return (
17+
<div className="flex items-center justify-center p-4">
18+
<svg
19+
className={`animate-spin ${sizeClasses[size] || sizeClasses.md} ${colorClasses[color] || colorClasses.primary}`}
20+
xmlns="http://www.w3.org/2000/svg"
21+
fill="none"
22+
viewBox="0 0 24 24"
23+
>
24+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
25+
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
26+
</svg>
27+
</div>
28+
);
29+
};
30+
31+
export default Spinner;

src/pages/owner/AddAdSpace.jsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import React, { useState } from 'react';
22
import api from '../../services/api';
33
import { useNavigate } from 'react-router-dom';
44
import { Layout, MapPin, DollarSign, ArrowLeft, Loader2 } from 'lucide-react';
5+
import toast from 'react-hot-toast';
6+
import Spinner from '../../components/common/Spinner';
57

68
export default function AddAdSpace() {
79
const navigate = useNavigate();
@@ -26,10 +28,12 @@ export default function AddAdSpace() {
2628
setIsLoading(true);
2729
try {
2830
await api.post('/adspaces/', formData);
31+
toast.success("Ad space listed successfully!");
2932
navigate('/owner/adspaces');
3033
} catch (error) {
3134
console.error("Failed to add ad space:", error);
32-
alert("Error adding ad space. Please check your inputs.");
35+
const errorMsg = error.response?.data?.error || "Error adding ad space. Please check your inputs.";
36+
toast.error(errorMsg);
3337
} finally {
3438
setIsLoading(false);
3539
}
@@ -153,7 +157,7 @@ export default function AddAdSpace() {
153157
<div className="mt-8 flex justify-end gap-4 border-t pt-8" style={{ borderTop: '1px solid var(--border)' }}>
154158
<button type="button" onClick={() => navigate(-1)} className="btn btn-secondary">Cancel</button>
155159
<button type="submit" className="btn btn-primary px-8 flex items-center gap-2" disabled={isLoading}>
156-
{isLoading ? <Loader2 className="animate-spin" size={18} /> : 'List Space'}
160+
{isLoading ? <Spinner size="sm" color="white" /> : 'List Space'}
157161
</button>
158162
</div>
159163
</form>

0 commit comments

Comments
 (0)