create-bdpa-react-scaffold 1.1.1 → 1.3.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 (3) hide show
  1. package/README.md +21 -0
  2. package/create-ui-lib.js +230 -15
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -38,6 +38,7 @@ npx create-bdpa-react-scaffold . --force --no-install
38
38
  - Example UI components (Button, Card, Input, FormField, Table, Navbar, Sidebar, Modal, Tabs, Toast)
39
39
  - Simple Auth pages (Login, Register)
40
40
  - Demo app and wiring
41
+ - Axios-based API client with CRUD helpers (`src/utils/api.js`)
41
42
 
42
43
  ## Local Development (for this CLI)
43
44
 
@@ -81,6 +82,26 @@ After publishing, users can run:
81
82
  npx create-bdpa-react-scaffold my-app
82
83
  ```
83
84
 
85
+ ## API (Axios)
86
+
87
+ - Base URL: set `VITE_API_BASE_URL` in a `.env` file or your environment.
88
+ - Default export `api` is a preconfigured instance; `ApiClient` class is also exported.
89
+
90
+ Example:
91
+
92
+ ```js
93
+ import api, { ApiClient } from "./src/utils/api";
94
+
95
+ // Using default instance
96
+ await api.create("/students", { name: "Ada" });
97
+ const { data } = await api.getAll("/students");
98
+
99
+ // Or create your own client
100
+ const client = new ApiClient("https://api.example.com");
101
+ client.setToken("<jwt>");
102
+ await client.update("/students/1", { name: "Grace" });
103
+ ```
104
+
84
105
  ## License
85
106
 
86
107
  UNLICENSED (update as desired).
package/create-ui-lib.js CHANGED
@@ -88,9 +88,12 @@ write("package.json", `
88
88
  "preview": "vite preview"
89
89
  },
90
90
  "dependencies": {
91
+ "axios": "^1.6.8",
91
92
  "react": "^18.2.0",
92
93
  "react-dom": "^18.2.0",
93
- "lucide-react": "^0.344.0"
94
+ "react-router-dom": "^6.20.0",
95
+ "lucide-react": "^0.344.0",
96
+ "bcryptjs": "^2.4.3"
94
97
  },
95
98
  "devDependencies": {
96
99
  "@vitejs/plugin-react-swc": "^3.5.0",
@@ -150,7 +153,7 @@ write("index.html", `
150
153
  <div id="root"></div>
151
154
  <script type="module" src="/src/main.jsx"></script>
152
155
  </body>
153
- </html>
156
+ </html>
154
157
  `);
155
158
 
156
159
  // -------------------------------
@@ -174,15 +177,18 @@ h1, h2, h3, h4 {
174
177
  write("src/main.jsx", `
175
178
  import React from "react";
176
179
  import ReactDOM from "react-dom/client";
180
+ import { BrowserRouter } from "react-router-dom";
177
181
  import App from "./App.jsx";
178
182
  import "./index.css";
179
183
  import { ToastProvider } from "./components/ui/ToastProvider.jsx";
180
184
 
181
185
  ReactDOM.createRoot(document.getElementById("root")).render(
182
186
  <React.StrictMode>
183
- <ToastProvider>
184
- <App />
185
- </ToastProvider>
187
+ <BrowserRouter>
188
+ <ToastProvider>
189
+ <App />
190
+ </ToastProvider>
191
+ </BrowserRouter>
186
192
  </React.StrictMode>
187
193
  );
188
194
  `);
@@ -204,14 +210,14 @@ export { default as Register } from "./components/auth/Register.jsx";
204
210
 
205
211
  export { default as Container } from "./components/layout/Container.jsx";
206
212
  export { default as Section } from "./components/layout/Section.jsx";
207
- `);
208
213
 
209
- // -------------------------------
210
- // App.jsx (FULL DEMO)
211
- // -------------------------------
214
+ export { default as api, ApiClient } from "./utils/api.js";
215
+ export { hashPassword, verifyPassword, getPasswordStrength, getPasswordStrengthLabel } from "./utils/password.js";
216
+ `);
212
217
 
213
218
  write("src/App.jsx", `
214
219
  import { useState } from "react";
220
+ import { Routes, Route, useNavigate } from "react-router-dom";
215
221
  import {
216
222
  Button,
217
223
  Card,
@@ -222,7 +228,10 @@ import {
222
228
  Sidebar,
223
229
  Modal,
224
230
  Tabs,
225
- useToast
231
+ ApiClient,
232
+ useToast,
233
+ Login,
234
+ Register
226
235
  } from "./index.js";
227
236
 
228
237
  const columns = [
@@ -237,10 +246,27 @@ const data = [
237
246
  { name: "Taylor", course: "eSports Strategy", status: "Enrolled" }
238
247
  ];
239
248
 
240
- export default function App() {
249
+ function Dashboard() {
241
250
  const [sidebarOpen, setSidebarOpen] = useState(false);
242
251
  const [modalOpen, setModalOpen] = useState(false);
252
+ const [posts, setPosts] = useState([]);
253
+ const [loadingPosts, setLoadingPosts] = useState(false);
254
+ const [postsError, setPostsError] = useState("");
243
255
  const toast = useToast();
256
+ const navigate = useNavigate();
257
+ const client = new ApiClient("https://jsonplaceholder.typicode.com");
258
+
259
+ const fetchPosts = async () => {
260
+ setLoadingPosts(true);
261
+ setPostsError("");
262
+ const res = await client.getAll("/posts?_limit=5");
263
+ if (res.success) {
264
+ setPosts(res.data);
265
+ } else {
266
+ setPostsError(res.error || "Failed to load posts");
267
+ }
268
+ setLoadingPosts(false);
269
+ };
244
270
 
245
271
  const tabs = [
246
272
  { label: "Overview", content: <p>Welcome to the UI Library demo.</p> },
@@ -256,7 +282,7 @@ export default function App() {
256
282
  open={sidebarOpen}
257
283
  onToggle={() => setSidebarOpen(!sidebarOpen)}
258
284
  links={[
259
- { label: "Home", href: "#" },
285
+ { label: "Home", href: "/" },
260
286
  { label: "Login", href: "/login" },
261
287
  { label: "Register", href: "/register" }
262
288
  ]}
@@ -317,6 +343,28 @@ export default function App() {
317
343
  </div>
318
344
  </Card>
319
345
 
346
+ {/* Live API Demo */}
347
+ <Card>
348
+ <h2 className="text-lg font-semibold mb-4">Live API Demo (JSONPlaceholder)</h2>
349
+ <div className="flex items-center gap-3 mb-3">
350
+ <Button onClick={fetchPosts} disabled={loadingPosts}>
351
+ {loadingPosts ? "Loading..." : "Fetch Posts"}
352
+ </Button>
353
+ {postsError && (
354
+ <span className="text-sm text-red-600">{postsError}</span>
355
+ )}
356
+ </div>
357
+ {posts.length > 0 && (
358
+ <ul className="list-disc pl-6 space-y-1">
359
+ {posts.map((p) => (
360
+ <li key={p.id} className="text-sm">
361
+ <span className="font-medium">#{p.id}</span> {p.title}
362
+ </li>
363
+ ))}
364
+ </ul>
365
+ )}
366
+ </Card>
367
+
320
368
  {/* Modal + Toast */}
321
369
  <div className="flex gap-4">
322
370
  <Button onClick={() => setModalOpen(true)}>Open Modal</Button>
@@ -338,6 +386,38 @@ export default function App() {
338
386
  </div>
339
387
  );
340
388
  }
389
+
390
+ export default function App() {
391
+ const navigate = useNavigate();
392
+
393
+ return (
394
+ <Routes>
395
+ <Route path="/" element={<Dashboard />} />
396
+ <Route
397
+ path="/login"
398
+ element={
399
+ <Login
400
+ onSubmit={() => {
401
+ alert("Login submitted!");
402
+ navigate("/");
403
+ }}
404
+ />
405
+ }
406
+ />
407
+ <Route
408
+ path="/register"
409
+ element={
410
+ <Register
411
+ onSubmit={() => {
412
+ alert("Registration submitted!");
413
+ navigate("/");
414
+ }}
415
+ />
416
+ }
417
+ />
418
+ </Routes>
419
+ );
420
+ }
341
421
  `);
342
422
 
343
423
  // -------------------------------
@@ -489,6 +569,8 @@ export default function Navbar({ onMenuClick }) {
489
569
  `);
490
570
 
491
571
  write("src/components/ui/Sidebar.jsx", `
572
+ import { Link } from "react-router-dom";
573
+
492
574
  export default function Sidebar({ open, onToggle, links }) {
493
575
  return (
494
576
  <div
@@ -508,9 +590,9 @@ export default function Sidebar({ open, onToggle, links }) {
508
590
  <ul className="p-4 space-y-2">
509
591
  {links.map((l) => (
510
592
  <li key={l.label}>
511
- <a href={l.href} className="block px-2 py-2 rounded hover:bg-gray-100">
593
+ <Link to={l.href} className="block px-2 py-2 rounded hover:bg-gray-100">
512
594
  {l.label}
513
- </a>
595
+ </Link>
514
596
  </li>
515
597
  ))}
516
598
  </ul>
@@ -701,14 +783,147 @@ export default function Section({ children, className = "" }) {
701
783
  }
702
784
  `);
703
785
 
786
+ // -------------------------------
787
+ // API Utility
788
+ // -------------------------------
789
+
790
+ write("src/utils/api.js", `
791
+ import axios from "axios";
792
+
793
+ /**
794
+ * Axios-powered API client with CRUD helpers.
795
+ */
796
+
797
+ export class ApiClient {
798
+ constructor(baseURL = "") {
799
+ this.instance = axios.create({
800
+ baseURL,
801
+ headers: { "Content-Type": "application/json" }
802
+ });
803
+
804
+ this.instance.interceptors.response.use(
805
+ (res) => res,
806
+ (err) => {
807
+ const message = err?.response?.data?.message || err?.message || "Request failed";
808
+ return Promise.reject(new Error(message));
809
+ }
810
+ );
811
+ }
812
+
813
+ setToken(token) {
814
+ if (token) {
815
+ this.instance.defaults.headers.common["Authorization"] = "Bearer " + token;
816
+ } else {
817
+ delete this.instance.defaults.headers.common["Authorization"];
818
+ }
819
+ }
820
+
821
+ async request(config) {
822
+ try {
823
+ const res = await this.instance.request(config);
824
+ return { success: true, data: res.data };
825
+ } catch (error) {
826
+ return { success: false, error: error.message };
827
+ }
828
+ }
829
+
830
+ async getAll(url, config = {}) {
831
+ return this.request({ url, method: "GET", ...config });
832
+ }
833
+
834
+ async getOne(url, config = {}) {
835
+ return this.request({ url, method: "GET", ...config });
836
+ }
837
+
838
+ async create(url, data, config = {}) {
839
+ return this.request({ url, method: "POST", data, ...config });
840
+ }
841
+
842
+ async update(url, data, config = {}) {
843
+ return this.request({ url, method: "PUT", data, ...config });
844
+ }
845
+
846
+ async patch(url, data, config = {}) {
847
+ return this.request({ url, method: "PATCH", data, ...config });
848
+ }
849
+
850
+ async delete(url, config = {}) {
851
+ return this.request({ url, method: "DELETE", ...config });
852
+ }
853
+ }
854
+
855
+ export default new ApiClient(import.meta.env.VITE_API_BASE_URL || "");
856
+ `);
857
+
858
+ // -------------------------------
859
+ // Password Utility
860
+ // -------------------------------
861
+
862
+ write("src/utils/password.js", `
863
+ import bcrypt from "bcryptjs";
864
+
865
+ const SALT_ROUNDS = 10;
866
+
867
+ export async function hashPassword(password) {
868
+ if (!password || typeof password !== "string" || password.trim().length === 0) {
869
+ throw new Error("Password must be a non-empty string");
870
+ }
871
+
872
+ try {
873
+ const hash = await bcrypt.hash(password, SALT_ROUNDS);
874
+ return hash;
875
+ } catch (error) {
876
+ throw new Error("Error hashing password: " + error.message);
877
+ }
878
+ }
879
+
880
+ export async function verifyPassword(password, hash) {
881
+ if (!password || typeof password !== "string") {
882
+ throw new Error("Password must be a non-empty string");
883
+ }
884
+
885
+ if (!hash || typeof hash !== "string") {
886
+ throw new Error("Hash must be a valid string");
887
+ }
888
+
889
+ try {
890
+ const isValid = await bcrypt.compare(password, hash);
891
+ return isValid;
892
+ } catch (error) {
893
+ throw new Error("Error verifying password: " + error.message);
894
+ }
895
+ }
896
+
897
+ export function getPasswordStrength(password) {
898
+ if (!password) return 0;
899
+
900
+ let strength = 0;
901
+
902
+ if (password.length >= 8) strength++;
903
+ if (password.length >= 12) strength++;
904
+ if (/[a-z]/.test(password) && /[A-Z]/.test(password)) strength++;
905
+ if (/\d/.test(password)) strength++;
906
+ if (/[!@#\$%^&*(),.?":{}|<>]/.test(password)) strength++;
907
+
908
+ return Math.min(strength, 4);
909
+ }
910
+
911
+ export function getPasswordStrengthLabel(password) {
912
+ const levels = ["Weak", "Fair", "Good", "Strong", "Very Strong"];
913
+ const strength = getPasswordStrength(password);
914
+ return levels[strength];
915
+ }
916
+ `);
917
+
704
918
  console.log("\n✅ UI Library scaffolding complete!");
705
919
  console.log("\nNext steps:");
706
920
  console.log(" 1. npm run dev");
707
921
  console.log(" 3. Open http://localhost:5173 in your browser\n");
708
922
 
709
- // Install dependencies unless disabled
710
923
  if (doInstall) {
711
924
  installDependencies(packageManager, BASE_DIR);
712
925
  } else {
713
926
  console.log("\nℹ️ Skipping install (flag --no-install). Run manually later.");
714
927
  }
928
+
929
+
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.3.1",
4
4
  "private": false,
5
5
  "description": "Scaffold a React + Tailwind UI library demo via Vite.",
6
6
  "bin": {