create-bdpa-react-scaffold 0.1.1 → 1.2.1
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.
- package/README.md +1 -0
- package/create-ui-lib.js +282 -9
- package/package.json +1 -1
package/README.md
CHANGED
package/create-ui-lib.js
CHANGED
|
@@ -90,7 +90,9 @@ write("package.json", `
|
|
|
90
90
|
"dependencies": {
|
|
91
91
|
"react": "^18.2.0",
|
|
92
92
|
"react-dom": "^18.2.0",
|
|
93
|
-
"
|
|
93
|
+
"react-router-dom": "^6.20.0",
|
|
94
|
+
"lucide-react": "^0.344.0",
|
|
95
|
+
"bcryptjs": "^2.4.3"
|
|
94
96
|
},
|
|
95
97
|
"devDependencies": {
|
|
96
98
|
"@vitejs/plugin-react-swc": "^3.5.0",
|
|
@@ -174,15 +176,18 @@ h1, h2, h3, h4 {
|
|
|
174
176
|
write("src/main.jsx", `
|
|
175
177
|
import React from "react";
|
|
176
178
|
import ReactDOM from "react-dom/client";
|
|
179
|
+
import { BrowserRouter } from "react-router-dom";
|
|
177
180
|
import App from "./App.jsx";
|
|
178
181
|
import "./index.css";
|
|
179
182
|
import { ToastProvider } from "./components/ui/ToastProvider.jsx";
|
|
180
183
|
|
|
181
184
|
ReactDOM.createRoot(document.getElementById("root")).render(
|
|
182
185
|
<React.StrictMode>
|
|
183
|
-
<
|
|
184
|
-
<
|
|
185
|
-
|
|
186
|
+
<BrowserRouter>
|
|
187
|
+
<ToastProvider>
|
|
188
|
+
<App />
|
|
189
|
+
</ToastProvider>
|
|
190
|
+
</BrowserRouter>
|
|
186
191
|
</React.StrictMode>
|
|
187
192
|
);
|
|
188
193
|
`);
|
|
@@ -204,6 +209,9 @@ export { default as Register } from "./components/auth/Register.jsx";
|
|
|
204
209
|
|
|
205
210
|
export { default as Container } from "./components/layout/Container.jsx";
|
|
206
211
|
export { default as Section } from "./components/layout/Section.jsx";
|
|
212
|
+
|
|
213
|
+
export { default as api, ApiClient } from "./utils/api.js";
|
|
214
|
+
export { hashPassword, verifyPassword, getPasswordStrength, getPasswordStrengthLabel } from "./utils/password.js";
|
|
207
215
|
`);
|
|
208
216
|
|
|
209
217
|
// -------------------------------
|
|
@@ -212,6 +220,7 @@ export { default as Section } from "./components/layout/Section.jsx";
|
|
|
212
220
|
|
|
213
221
|
write("src/App.jsx", `
|
|
214
222
|
import { useState } from "react";
|
|
223
|
+
import { Routes, Route, useNavigate } from "react-router-dom";
|
|
215
224
|
import {
|
|
216
225
|
Button,
|
|
217
226
|
Card,
|
|
@@ -222,7 +231,9 @@ import {
|
|
|
222
231
|
Sidebar,
|
|
223
232
|
Modal,
|
|
224
233
|
Tabs,
|
|
225
|
-
useToast
|
|
234
|
+
useToast,
|
|
235
|
+
Login,
|
|
236
|
+
Register
|
|
226
237
|
} from "./index.js";
|
|
227
238
|
|
|
228
239
|
const columns = [
|
|
@@ -237,10 +248,11 @@ const data = [
|
|
|
237
248
|
{ name: "Taylor", course: "eSports Strategy", status: "Enrolled" }
|
|
238
249
|
];
|
|
239
250
|
|
|
240
|
-
|
|
251
|
+
function Dashboard() {
|
|
241
252
|
const [sidebarOpen, setSidebarOpen] = useState(false);
|
|
242
253
|
const [modalOpen, setModalOpen] = useState(false);
|
|
243
254
|
const toast = useToast();
|
|
255
|
+
const navigate = useNavigate();
|
|
244
256
|
|
|
245
257
|
const tabs = [
|
|
246
258
|
{ label: "Overview", content: <p>Welcome to the UI Library demo.</p> },
|
|
@@ -256,7 +268,7 @@ export default function App() {
|
|
|
256
268
|
open={sidebarOpen}
|
|
257
269
|
onToggle={() => setSidebarOpen(!sidebarOpen)}
|
|
258
270
|
links={[
|
|
259
|
-
{ label: "Home", href: "
|
|
271
|
+
{ label: "Home", href: "/" },
|
|
260
272
|
{ label: "Login", href: "/login" },
|
|
261
273
|
{ label: "Register", href: "/register" }
|
|
262
274
|
]}
|
|
@@ -338,6 +350,38 @@ export default function App() {
|
|
|
338
350
|
</div>
|
|
339
351
|
);
|
|
340
352
|
}
|
|
353
|
+
|
|
354
|
+
export default function App() {
|
|
355
|
+
const navigate = useNavigate();
|
|
356
|
+
|
|
357
|
+
return (
|
|
358
|
+
<Routes>
|
|
359
|
+
<Route path="/" element={<Dashboard />} />
|
|
360
|
+
<Route
|
|
361
|
+
path="/login"
|
|
362
|
+
element={
|
|
363
|
+
<Login
|
|
364
|
+
onSubmit={() => {
|
|
365
|
+
alert("Login submitted!");
|
|
366
|
+
navigate("/");
|
|
367
|
+
}}
|
|
368
|
+
/>
|
|
369
|
+
}
|
|
370
|
+
/>
|
|
371
|
+
<Route
|
|
372
|
+
path="/register"
|
|
373
|
+
element={
|
|
374
|
+
<Register
|
|
375
|
+
onSubmit={() => {
|
|
376
|
+
alert("Registration submitted!");
|
|
377
|
+
navigate("/");
|
|
378
|
+
}}
|
|
379
|
+
/>
|
|
380
|
+
}
|
|
381
|
+
/>
|
|
382
|
+
</Routes>
|
|
383
|
+
);
|
|
384
|
+
}
|
|
341
385
|
`);
|
|
342
386
|
|
|
343
387
|
// -------------------------------
|
|
@@ -489,6 +533,8 @@ export default function Navbar({ onMenuClick }) {
|
|
|
489
533
|
`);
|
|
490
534
|
|
|
491
535
|
write("src/components/ui/Sidebar.jsx", `
|
|
536
|
+
import { Link } from "react-router-dom";
|
|
537
|
+
|
|
492
538
|
export default function Sidebar({ open, onToggle, links }) {
|
|
493
539
|
return (
|
|
494
540
|
<div
|
|
@@ -508,9 +554,9 @@ export default function Sidebar({ open, onToggle, links }) {
|
|
|
508
554
|
<ul className="p-4 space-y-2">
|
|
509
555
|
{links.map((l) => (
|
|
510
556
|
<li key={l.label}>
|
|
511
|
-
<
|
|
557
|
+
<Link to={l.href} className="block px-2 py-2 rounded hover:bg-gray-100">
|
|
512
558
|
{l.label}
|
|
513
|
-
</
|
|
559
|
+
</Link>
|
|
514
560
|
</li>
|
|
515
561
|
))}
|
|
516
562
|
</ul>
|
|
@@ -701,6 +747,233 @@ export default function Section({ children, className = "" }) {
|
|
|
701
747
|
}
|
|
702
748
|
`);
|
|
703
749
|
|
|
750
|
+
// -------------------------------
|
|
751
|
+
// API Utility
|
|
752
|
+
// -------------------------------
|
|
753
|
+
|
|
754
|
+
write("src/utils/api.js", `
|
|
755
|
+
/**
|
|
756
|
+
* API Utility for CRUD operations
|
|
757
|
+
*
|
|
758
|
+
* Usage:
|
|
759
|
+
* const api = new ApiClient("https://api.example.com");
|
|
760
|
+
*
|
|
761
|
+
* // Create
|
|
762
|
+
* await api.create("/students", { name: "John", email: "john@example.com" });
|
|
763
|
+
*
|
|
764
|
+
* // Read
|
|
765
|
+
* await api.getAll("/students");
|
|
766
|
+
* await api.getOne("/students/1");
|
|
767
|
+
*
|
|
768
|
+
* // Update
|
|
769
|
+
* await api.update("/students/1", { name: "Jane" });
|
|
770
|
+
*
|
|
771
|
+
* // Delete
|
|
772
|
+
* await api.delete("/students/1");
|
|
773
|
+
*/
|
|
774
|
+
|
|
775
|
+
export class ApiClient {
|
|
776
|
+
constructor(baseURL = "") {
|
|
777
|
+
this.baseURL = baseURL;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* Make a fetch request with common headers and error handling
|
|
782
|
+
*/
|
|
783
|
+
async request(endpoint, options = {}) {
|
|
784
|
+
const url = \`\${this.baseURL}\${endpoint}\`;
|
|
785
|
+
const defaultHeaders = {
|
|
786
|
+
"Content-Type": "application/json"
|
|
787
|
+
};
|
|
788
|
+
|
|
789
|
+
const config = {
|
|
790
|
+
headers: { ...defaultHeaders, ...options.headers },
|
|
791
|
+
...options
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
try {
|
|
795
|
+
const response = await fetch(url, config);
|
|
796
|
+
|
|
797
|
+
if (!response.ok) {
|
|
798
|
+
throw new Error(\`API Error: \${response.status} \${response.statusText}\`);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
const data = await response.json();
|
|
802
|
+
return { success: true, data };
|
|
803
|
+
} catch (error) {
|
|
804
|
+
console.error("API Request Error:", error);
|
|
805
|
+
return { success: false, error: error.message };
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* GET - Fetch all resources
|
|
811
|
+
*/
|
|
812
|
+
async getAll(endpoint, options = {}) {
|
|
813
|
+
return this.request(endpoint, {
|
|
814
|
+
method: "GET",
|
|
815
|
+
...options
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* GET - Fetch a single resource by ID
|
|
821
|
+
*/
|
|
822
|
+
async getOne(endpoint, options = {}) {
|
|
823
|
+
return this.request(endpoint, {
|
|
824
|
+
method: "GET",
|
|
825
|
+
...options
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* POST - Create a new resource
|
|
831
|
+
*/
|
|
832
|
+
async create(endpoint, data, options = {}) {
|
|
833
|
+
return this.request(endpoint, {
|
|
834
|
+
method: "POST",
|
|
835
|
+
body: JSON.stringify(data),
|
|
836
|
+
...options
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* PUT - Update an entire resource
|
|
842
|
+
*/
|
|
843
|
+
async update(endpoint, data, options = {}) {
|
|
844
|
+
return this.request(endpoint, {
|
|
845
|
+
method: "PUT",
|
|
846
|
+
body: JSON.stringify(data),
|
|
847
|
+
...options
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/**
|
|
852
|
+
* PATCH - Partially update a resource
|
|
853
|
+
*/
|
|
854
|
+
async patch(endpoint, data, options = {}) {
|
|
855
|
+
return this.request(endpoint, {
|
|
856
|
+
method: "PATCH",
|
|
857
|
+
body: JSON.stringify(data),
|
|
858
|
+
...options
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/**
|
|
863
|
+
* DELETE - Remove a resource
|
|
864
|
+
*/
|
|
865
|
+
async delete(endpoint, options = {}) {
|
|
866
|
+
return this.request(endpoint, {
|
|
867
|
+
method: "DELETE",
|
|
868
|
+
...options
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// Export a default instance (optional - users can create their own)
|
|
874
|
+
export default new ApiClient(import.meta.env.VITE_API_BASE_URL || "");
|
|
875
|
+
`);
|
|
876
|
+
|
|
877
|
+
// -------------------------------
|
|
878
|
+
// Password Utility
|
|
879
|
+
// -------------------------------
|
|
880
|
+
|
|
881
|
+
write("src/utils/password.js", `
|
|
882
|
+
import bcrypt from "bcryptjs";
|
|
883
|
+
|
|
884
|
+
/**
|
|
885
|
+
* Password Utility for secure password hashing and verification
|
|
886
|
+
* Uses bcryptjs with salt for secure password storage
|
|
887
|
+
*
|
|
888
|
+
* Usage:
|
|
889
|
+
* import { hashPassword, verifyPassword } from "./utils/password.js";
|
|
890
|
+
*
|
|
891
|
+
* // Hash a password
|
|
892
|
+
* const hash = await hashPassword("myPassword123");
|
|
893
|
+
*
|
|
894
|
+
* // Verify a password
|
|
895
|
+
* const isValid = await verifyPassword("myPassword123", hash);
|
|
896
|
+
*/
|
|
897
|
+
|
|
898
|
+
const SALT_ROUNDS = 10;
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* Hash a password with bcryptjs
|
|
902
|
+
* @param {string} password - The plain text password to hash
|
|
903
|
+
* @returns {Promise<string>} The hashed password
|
|
904
|
+
* @throws {Error} If password is empty or invalid
|
|
905
|
+
*/
|
|
906
|
+
export async function hashPassword(password) {
|
|
907
|
+
if (!password || typeof password !== "string" || password.trim().length === 0) {
|
|
908
|
+
throw new Error("Password must be a non-empty string");
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
try {
|
|
912
|
+
const hash = await bcrypt.hash(password, SALT_ROUNDS);
|
|
913
|
+
return hash;
|
|
914
|
+
} catch (error) {
|
|
915
|
+
throw new Error(\`Error hashing password: \${error.message}\`);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/**
|
|
920
|
+
* Verify a password against a hash
|
|
921
|
+
* @param {string} password - The plain text password to verify
|
|
922
|
+
* @param {string} hash - The hash to compare against
|
|
923
|
+
* @returns {Promise<boolean>} True if password matches the hash, false otherwise
|
|
924
|
+
* @throws {Error} If inputs are invalid
|
|
925
|
+
*/
|
|
926
|
+
export async function verifyPassword(password, hash) {
|
|
927
|
+
if (!password || typeof password !== "string") {
|
|
928
|
+
throw new Error("Password must be a non-empty string");
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
if (!hash || typeof hash !== "string") {
|
|
932
|
+
throw new Error("Hash must be a valid string");
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
try {
|
|
936
|
+
const isValid = await bcrypt.compare(password, hash);
|
|
937
|
+
return isValid;
|
|
938
|
+
} catch (error) {
|
|
939
|
+
throw new Error(\`Error verifying password: \${error.message}\`);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
/**
|
|
944
|
+
* Get password strength indicator (0-4)
|
|
945
|
+
* @param {string} password - The password to evaluate
|
|
946
|
+
* @returns {number} Strength level: 0=weak, 1=fair, 2=good, 3=strong, 4=very strong
|
|
947
|
+
*/
|
|
948
|
+
export function getPasswordStrength(password) {
|
|
949
|
+
if (!password) return 0;
|
|
950
|
+
|
|
951
|
+
let strength = 0;
|
|
952
|
+
|
|
953
|
+
// Length check
|
|
954
|
+
if (password.length >= 8) strength++;
|
|
955
|
+
if (password.length >= 12) strength++;
|
|
956
|
+
|
|
957
|
+
// Character variety checks
|
|
958
|
+
if (/[a-z]/.test(password) && /[A-Z]/.test(password)) strength++;
|
|
959
|
+
if (/\\d/.test(password)) strength++;
|
|
960
|
+
if (/[!@#\$%^&*(),.?":{}|<>]/.test(password)) strength++;
|
|
961
|
+
|
|
962
|
+
return Math.min(strength, 4);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
/**
|
|
966
|
+
* Get password strength label
|
|
967
|
+
* @param {string} password - The password to evaluate
|
|
968
|
+
* @returns {string} Human-readable strength label
|
|
969
|
+
*/
|
|
970
|
+
export function getPasswordStrengthLabel(password) {
|
|
971
|
+
const levels = ["Weak", "Fair", "Good", "Strong", "Very Strong"];
|
|
972
|
+
const strength = getPasswordStrength(password);
|
|
973
|
+
return levels[strength];
|
|
974
|
+
}
|
|
975
|
+
`);
|
|
976
|
+
|
|
704
977
|
console.log("\n✅ UI Library scaffolding complete!");
|
|
705
978
|
console.log("\nNext steps:");
|
|
706
979
|
console.log(" 1. npm run dev");
|