create-bdpa-react-scaffold 1.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.
Files changed (2) hide show
  1. package/create-ui-lib.js +282 -9
  2. package/package.json +1 -1
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
- "lucide-react": "^0.344.0"
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
- <ToastProvider>
184
- <App />
185
- </ToastProvider>
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
- export default function App() {
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
- <a href={l.href} className="block px-2 py-2 rounded hover:bg-gray-100">
557
+ <Link to={l.href} className="block px-2 py-2 rounded hover:bg-gray-100">
512
558
  {l.label}
513
- </a>
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");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-bdpa-react-scaffold",
3
- "version": "1.1.1",
3
+ "version": "1.2.1",
4
4
  "private": false,
5
5
  "description": "Scaffold a React + Tailwind UI library demo via Vite.",
6
6
  "bin": {