create-steve-rogers 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/apps/SFMS/.env +9 -0
  2. package/apps/SFMS/README.md +0 -0
  3. package/apps/SFMS/backend/.env +9 -0
  4. package/apps/SFMS/backend/.env.example +9 -0
  5. package/apps/SFMS/backend/package-lock.json +1580 -0
  6. package/apps/SFMS/backend/package.json +23 -0
  7. package/apps/SFMS/backend/src/config/database.js +7 -0
  8. package/apps/SFMS/backend/src/config/env.js +35 -0
  9. package/apps/SFMS/backend/src/middleware/authMiddleware.js +32 -0
  10. package/apps/SFMS/backend/src/models/Payment.js +12 -0
  11. package/apps/SFMS/backend/src/models/Student.js +12 -0
  12. package/apps/SFMS/backend/src/models/User.js +13 -0
  13. package/apps/SFMS/backend/src/routes/authRoutes.js +93 -0
  14. package/apps/SFMS/backend/src/routes/paymentRoutes.js +117 -0
  15. package/apps/SFMS/backend/src/routes/reportRoutes.js +59 -0
  16. package/apps/SFMS/backend/src/routes/studentRoutes.js +79 -0
  17. package/apps/SFMS/backend/src/server.js +34 -0
  18. package/apps/SFMS/frontend/.env.example +8 -0
  19. package/apps/SFMS/frontend/dist/assets/index-B08X8imN.css +1 -0
  20. package/apps/SFMS/frontend/dist/assets/index-DVO0_wcb.js +67 -0
  21. package/apps/SFMS/frontend/dist/favicon.svg +4 -0
  22. package/apps/SFMS/frontend/dist/index.html +20 -0
  23. package/apps/SFMS/frontend/index.html +19 -0
  24. package/apps/SFMS/frontend/package-lock.json +2667 -0
  25. package/apps/SFMS/frontend/package.json +23 -0
  26. package/apps/SFMS/frontend/postcss.config.js +6 -0
  27. package/apps/SFMS/frontend/public/favicon.svg +4 -0
  28. package/apps/SFMS/frontend/src/App.jsx +41 -0
  29. package/apps/SFMS/frontend/src/api/apiClient.js +41 -0
  30. package/apps/SFMS/frontend/src/components/AppLayout.jsx +60 -0
  31. package/apps/SFMS/frontend/src/context/AuthContext.jsx +79 -0
  32. package/apps/SFMS/frontend/src/index.css +229 -0
  33. package/apps/SFMS/frontend/src/main.jsx +16 -0
  34. package/apps/SFMS/frontend/src/pages/DashboardPage.jsx +82 -0
  35. package/apps/SFMS/frontend/src/pages/LoginPage.jsx +142 -0
  36. package/apps/SFMS/frontend/src/pages/PaymentsPage.jsx +269 -0
  37. package/apps/SFMS/frontend/src/pages/ReportsPage.jsx +114 -0
  38. package/apps/SFMS/frontend/src/pages/StudentsPage.jsx +257 -0
  39. package/apps/SFMS/frontend/tailwind.config.js +21 -0
  40. package/apps/SFMS/frontend/vite.config.js +35 -0
  41. package/apps/config.js +7 -0
  42. package/package.json +1 -1
@@ -0,0 +1,257 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { apiRequest } from '../api/apiClient.js';
3
+
4
+ const inputClass = 'input-field';
5
+
6
+ export function StudentsPage() {
7
+ const [list, setList] = useState([]);
8
+ const [submitting, setSubmitting] = useState(false);
9
+ const [error, setError] = useState('');
10
+ const [formTarget, setFormTarget] = useState(null);
11
+
12
+ const [fullName, setFullName] = useState('');
13
+ const [className, setClassName] = useState('');
14
+ const [parentPhone, setParentPhone] = useState('');
15
+ const [fieldErrors, setFieldErrors] = useState({});
16
+
17
+ async function load() {
18
+ setError('');
19
+ try {
20
+ const data = await apiRequest('/api/students');
21
+ setList(data);
22
+ } catch (e) {
23
+ setError(e.message);
24
+ }
25
+ }
26
+
27
+ useEffect(() => {
28
+ load();
29
+ }, []);
30
+
31
+ function validate() {
32
+ const err = {};
33
+ if (!fullName.trim()) err.fullName = 'Required';
34
+ if (!className.trim()) err.className = 'Required';
35
+ if (!parentPhone.trim()) {
36
+ err.parentPhone = 'Required';
37
+ } else if (!/^[0-9]{1,10}$/.test(parentPhone)) {
38
+ err.parentPhone = 'Phone must be digits and no more than 10 characters';
39
+ }
40
+ setFieldErrors(err);
41
+ return Object.keys(err).length === 0;
42
+ }
43
+
44
+ function openNew() {
45
+ setFormTarget('new');
46
+ setFullName('');
47
+ setClassName('');
48
+ setParentPhone('');
49
+ setFieldErrors({});
50
+ }
51
+
52
+ function openEdit(row) {
53
+ setFormTarget(row);
54
+ setFullName(row.full_name || '');
55
+ setClassName(row.class || '');
56
+ setParentPhone(row.parent_phone || '');
57
+ setFieldErrors({});
58
+ }
59
+
60
+ function closeForm() {
61
+ setFormTarget(null);
62
+ setFieldErrors({});
63
+ }
64
+
65
+ async function save(e) {
66
+ e.preventDefault();
67
+ if (!validate()) return;
68
+ setSubmitting(true);
69
+ setError('');
70
+ try {
71
+ const payload = {
72
+ full_name: fullName.trim(),
73
+ class: className.trim(),
74
+ parent_phone: parentPhone.trim(),
75
+ };
76
+ if (formTarget !== 'new' && formTarget?.id) {
77
+ await apiRequest(`/api/students/${formTarget.id}`, {
78
+ method: 'PUT',
79
+ body: JSON.stringify(payload),
80
+ });
81
+ } else {
82
+ await apiRequest('/api/students', { method: 'POST', body: JSON.stringify(payload) });
83
+ }
84
+ closeForm();
85
+ await load();
86
+ } catch (err) {
87
+ setError(err.message);
88
+ } finally {
89
+ setSubmitting(false);
90
+ }
91
+ }
92
+
93
+ async function remove(id) {
94
+ if (!window.confirm('Delete this student?')) return;
95
+ setError('');
96
+ try {
97
+ await apiRequest(`/api/students/${id}`, { method: 'DELETE' });
98
+ await load();
99
+ } catch (e) {
100
+ setError(e.message);
101
+ }
102
+ }
103
+
104
+ return (
105
+ <div>
106
+ <div className="app-header">
107
+ <p className="text-sm uppercase tracking-[0.3em] text-slate-500">Students</p>
108
+ <h1 className="page-title">Student roster</h1>
109
+ <p className="page-subtitle">Add, edit, and keep track of each student record from a sleek panel.</p>
110
+ </div>
111
+
112
+ <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between mb-6">
113
+ <div className="space-y-1">
114
+ <p className="text-base font-semibold text-slate-900">Manage student details</p>
115
+ <p className="text-sm text-slate-500">Everything is organized in one clean table.</p>
116
+ </div>
117
+ <button type="button" onClick={openNew} className="btn-primary w-full sm:w-auto">
118
+ + New student
119
+ </button>
120
+ </div>
121
+
122
+ {error && (
123
+ <div className="mb-6 rounded-[28px] border border-red-100 bg-red-50 px-4 py-3 text-sm text-red-700">
124
+ {error}
125
+ </div>
126
+ )}
127
+
128
+ {formTarget === 'new' && (
129
+ <form onSubmit={save} className="form-card mb-8 max-w-2xl space-y-5">
130
+ <div className="flex items-center justify-between gap-4">
131
+ <h2 className="text-xl font-semibold text-slate-900">New student</h2>
132
+ <button type="button" onClick={closeForm} className="btn-secondary">
133
+ Close
134
+ </button>
135
+ </div>
136
+
137
+ <div className="grid gap-4 md:grid-cols-3">
138
+ <div className="input-group">
139
+ <label className="text-sm font-medium text-slate-700">Full name</label>
140
+ <input className={inputClass} value={fullName} onChange={(e) => setFullName(e.target.value)} />
141
+ {fieldErrors.fullName && <p className="text-xs text-red-600">{fieldErrors.fullName}</p>}
142
+ </div>
143
+ <div className="input-group">
144
+ <label className="text-sm font-medium text-slate-700">Class</label>
145
+ <input className={inputClass} value={className} onChange={(e) => setClassName(e.target.value)} />
146
+ {fieldErrors.className && <p className="text-xs text-red-600">{fieldErrors.className}</p>}
147
+ </div>
148
+ <div className="input-group">
149
+ <label className="text-sm font-medium text-slate-700">Parent phone</label>
150
+ <input
151
+ type="tel"
152
+ inputMode="numeric"
153
+ maxLength="10"
154
+ className={inputClass}
155
+ value={parentPhone}
156
+ onChange={(e) => setParentPhone(e.target.value.replace(/\D/g, '').slice(0, 10))}
157
+ placeholder="Digits only"
158
+ />
159
+ {fieldErrors.parentPhone && <p className="text-xs text-red-600">{fieldErrors.parentPhone}</p>}
160
+ </div>
161
+ </div>
162
+
163
+ <div className="form-actions">
164
+ <button type="submit" disabled={submitting} className="btn-primary">
165
+ {submitting ? 'Saving…' : 'Save'}
166
+ </button>
167
+ <button type="button" onClick={closeForm} className="btn-secondary">
168
+ Cancel
169
+ </button>
170
+ </div>
171
+ </form>
172
+ )}
173
+
174
+ <div className="table-shell">
175
+ <table className="min-w-full text-sm">
176
+ <thead className="table-head text-left">
177
+ <tr>
178
+ <th className="table-cell font-medium">Name</th>
179
+ <th className="table-cell font-medium">Class</th>
180
+ <th className="table-cell font-medium">Parent phone</th>
181
+ <th className="table-cell font-medium">Actions</th>
182
+ </tr>
183
+ </thead>
184
+ <tbody>
185
+ {list.map((u) => (
186
+ <tr key={u.id} className="table-row">
187
+ <td className="table-cell font-semibold text-slate-900">{u.full_name}</td>
188
+ <td className="table-cell">{u.class}</td>
189
+ <td className="table-cell">{u.parent_phone}</td>
190
+ <td className="table-cell space-x-2 whitespace-nowrap">
191
+ <button type="button" className="btn-secondary" onClick={() => openEdit(u)}>
192
+ Edit
193
+ </button>
194
+ <button type="button" className="btn-destructive" onClick={() => remove(u.id)}>
195
+ Delete
196
+ </button>
197
+ </td>
198
+ </tr>
199
+ ))}
200
+ </tbody>
201
+ </table>
202
+ {list.length === 0 && <p className="placeholder-card">No students yet.</p>}
203
+ </div>
204
+
205
+ {formTarget && formTarget !== 'new' && (
206
+ <div
207
+ className="fixed inset-0 bg-black/40 flex items-center justify-center p-4 z-50"
208
+ onClick={(e) => e.target === e.currentTarget && closeForm()}
209
+ role="presentation"
210
+ >
211
+ <div
212
+ className="form-card max-w-md w-full"
213
+ onClick={(e) => e.stopPropagation()}
214
+ role="dialog"
215
+ aria-modal="true"
216
+ >
217
+ <div className="flex items-center justify-between gap-4 mb-4">
218
+ <h2 className="text-xl font-semibold text-slate-900">Edit student</h2>
219
+ <button type="button" onClick={closeForm} className="btn-secondary">
220
+ Close
221
+ </button>
222
+ </div>
223
+ <form onSubmit={save} className="space-y-4">
224
+ <div className="input-group">
225
+ <label className="text-sm font-medium text-slate-700">Name</label>
226
+ <input className={inputClass} value={fullName} onChange={(e) => setFullName(e.target.value)} />
227
+ {fieldErrors.fullName && <p className="text-xs text-red-600">{fieldErrors.fullName}</p>}
228
+ </div>
229
+ <div className="input-group">
230
+ <label className="text-sm font-medium text-slate-700">Class</label>
231
+ <input className={inputClass} value={className} onChange={(e) => setClassName(e.target.value)} />
232
+ </div>
233
+ <div className="input-group">
234
+ <label className="text-sm font-medium text-slate-700">Parent phone</label>
235
+ <input
236
+ type="tel"
237
+ inputMode="numeric"
238
+ maxLength="10"
239
+ className={inputClass}
240
+ value={parentPhone}
241
+ onChange={(e) => setParentPhone(e.target.value.replace(/\D/g, '').slice(0, 10))}
242
+ placeholder="Digits only"
243
+ />
244
+ {fieldErrors.parentPhone && <p className="text-xs text-red-600">{fieldErrors.parentPhone}</p>}
245
+ </div>
246
+ <div className="form-actions">
247
+ <button type="submit" disabled={submitting} className="btn-primary w-full">
248
+ Save
249
+ </button>
250
+ </div>
251
+ </form>
252
+ </div>
253
+ </div>
254
+ )}
255
+ </div>
256
+ );
257
+ }
@@ -0,0 +1,21 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ content: ['./index.html', './src/**/*.{js,jsx}'],
4
+ theme: {
5
+ extend: {
6
+ fontFamily: {
7
+ sans: ['DM Sans', 'system-ui', 'sans-serif'],
8
+ display: ['Outfit', 'system-ui', 'sans-serif'],
9
+ },
10
+ colors: {
11
+ sfms: {
12
+ ink: '#0f172a',
13
+ mist: '#f1f5f9',
14
+ accent: '#0d9488',
15
+ accentDark: '#0f766e',
16
+ },
17
+ },
18
+ },
19
+ },
20
+ plugins: [],
21
+ };
@@ -0,0 +1,35 @@
1
+ import { defineConfig, loadEnv } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+
4
+ const DEFAULT_DEV_PORT = 5173;
5
+ const DEFAULT_API_PROXY = 'http://localhost:5000';
6
+
7
+ export default defineConfig(({ mode }) => {
8
+ const rootEnv = loadEnv(mode, process.cwd(), '');
9
+
10
+ const port =
11
+ Number(
12
+ rootEnv.VITE_DEV_SERVER_PORT ||
13
+ rootEnv.SFMS_FRONTEND_PORT ||
14
+ `${DEFAULT_DEV_PORT}`
15
+ ) || DEFAULT_DEV_PORT;
16
+
17
+ const proxyTarget =
18
+ rootEnv.VITE_API_PROXY_TARGET ||
19
+ rootEnv.VITE_BACKEND_URL ||
20
+ DEFAULT_API_PROXY;
21
+
22
+ return {
23
+ plugins: [react()],
24
+ server: {
25
+ port,
26
+ strictPort: true,
27
+ proxy: {
28
+ '/api': {
29
+ target: proxyTarget,
30
+ changeOrigin: true,
31
+ },
32
+ },
33
+ },
34
+ };
35
+ });
package/apps/config.js CHANGED
@@ -11,4 +11,11 @@ export const apps = [
11
11
  description: "Stock inventory management by Steve Rogers",
12
12
  folder: "SIMS",
13
13
  },
14
+ {
15
+ title: "SFMS - Student Fee Management System",
16
+ value: "sfms",
17
+ description: "Student Fee Management System by steve rogers",
18
+ folder: "SFMS"
19
+
20
+ }
14
21
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-steve-rogers",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Steve Rogers personal app installer",
5
5
  "bin": {
6
6
  "create-steve-rogers": "index.js"