-
Notifications
You must be signed in to change notification settings - Fork 0
/
x
222 lines (203 loc) · 6.01 KB
/
x
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
// First, create a new Vite project with React
// Run these commands in your terminal:
/*
npm create vite@latest givehub-frontend -- --template react
cd givehub-frontend
npm install
*/
// package.json - Add these dependencies
{
"dependencies": {
"@tanstack/react-query": "^5.0.0",
"axios": "^1.6.0",
"react-router-dom": "^6.18.0",
"zustand": "^4.4.6",
"@tailwindcss/forms": "^0.5.7",
"clsx": "^2.0.0",
"react-hot-toast": "^2.4.1"
},
"devDependencies": {
"@types/react": "^18.2.15",
"@vitejs/plugin-react": "^4.0.3",
"autoprefixer": "^10.4.16",
"postcss": "^8.4.31",
"tailwindcss": "^3.3.5",
"vite": "^4.4.5"
}
}
// src/main.jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { Toaster } from 'react-hot-toast'
import App from './App'
import './index.css'
const queryClient = new QueryClient()
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<App />
<Toaster position="top-right" />
</BrowserRouter>
</QueryClientProvider>
</React.StrictMode>
)
// src/store/auth.js - Global auth state management
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
export const useAuthStore = create(
persist(
(set) => ({
token: null,
user: null,
setAuth: (token, user) => set({ token, user }),
logout: () => set({ token: null, user: null }),
}),
{
name: 'auth-storage',
}
)
)
// src/api/client.js - API client setup
import axios from 'axios'
import toast from 'react-hot-toast'
import { useAuthStore } from '../store/auth'
const baseURL = import.meta.env.VITE_API_URL || 'http://localhost:3000'
export const client = axios.create({
baseURL,
headers: {
'Content-Type': 'application/json',
},
})
client.interceptors.request.use((config) => {
const token = useAuthStore.getState().token
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
client.interceptors.response.use(
(response) => response,
(error) => {
const message = error.response?.data?.error || 'An error occurred'
toast.error(message)
if (error.response?.status === 401) {
useAuthStore.getState().logout()
}
return Promise.reject(error)
}
)
// src/components/Layout.jsx
import { Link, Outlet } from 'react-router-dom'
import { useAuthStore } from '../store/auth'
export default function Layout() {
const { user, logout } = useAuthStore()
return (
<div className="min-h-screen bg-gray-50">
<nav className="bg-white shadow-sm">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-16">
<div className="flex">
<Link to="/" className="flex items-center">
<span className="font-bold text-xl">The Give Hub</span>
</Link>
<div className="ml-6 flex space-x-8">
<Link
to="/campaigns"
className="inline-flex items-center px-1 pt-1 text-gray-900"
>
Campaigns
</Link>
{user?.role === 'campaigner' && (
<Link
to="/campaigns/create"
className="inline-flex items-center px-1 pt-1 text-gray-900"
>
Create Campaign
</Link>
)}
</div>
</div>
<div className="flex items-center">
{user ? (
<div className="flex items-center space-x-4">
<span>{user.fullName}</span>
<button
onClick={logout}
className="text-gray-700 hover:text-gray-900"
>
Logout
</button>
</div>
) : (
<div className="space-x-4">
<Link
to="/login"
className="text-gray-700 hover:text-gray-900"
>
Login
</Link>
<Link
to="/register"
className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700"
>
Register
</Link>
</div>
)}
</div>
</div>
</div>
</nav>
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<Outlet />
</main>
</div>
)
}
// src/App.jsx
import { Routes, Route, Navigate } from 'react-router-dom'
import { useAuthStore } from './store/auth'
import Layout from './components/Layout'
import Login from './pages/Login'
import Register from './pages/Register'
import Campaigns from './pages/Campaigns'
import CampaignCreate from './pages/CampaignCreate'
import CampaignDetails from './pages/CampaignDetails'
function PrivateRoute({ children }) {
const { token } = useAuthStore()
return token ? children : <Navigate to="/login" />
}
export default function App() {
return (
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Navigate to="/campaigns" replace />} />
<Route path="campaigns" element={<Campaigns />} />
<Route path="campaigns/:id" element={<CampaignDetails />} />
<Route
path="campaigns/create"
element={
<PrivateRoute>
<CampaignCreate />
</PrivateRoute>
}
/>
<Route path="login" element={<Login />} />
<Route path="register" element={<Register />} />
</Route>
</Routes>
)
}
// tailwind.config.js
export default {
content: ['./src/**/*.{js,jsx}'],
theme: {
extend: {},
},
plugins: [require('@tailwindcss/forms')],
}
// .env.local
VITE_API_URL=http://localhost:3000