create-bdpa-react-scaffold 1.2.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 +83 -141
  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,6 +88,7 @@ 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
94
  "react-router-dom": "^6.20.0",
@@ -152,7 +153,7 @@ write("index.html", `
152
153
  <div id="root"></div>
153
154
  <script type="module" src="/src/main.jsx"></script>
154
155
  </body>
155
- </html>
156
+ </html>
156
157
  `);
157
158
 
158
159
  // -------------------------------
@@ -214,10 +215,6 @@ export { default as api, ApiClient } from "./utils/api.js";
214
215
  export { hashPassword, verifyPassword, getPasswordStrength, getPasswordStrengthLabel } from "./utils/password.js";
215
216
  `);
216
217
 
217
- // -------------------------------
218
- // App.jsx (FULL DEMO)
219
- // -------------------------------
220
-
221
218
  write("src/App.jsx", `
222
219
  import { useState } from "react";
223
220
  import { Routes, Route, useNavigate } from "react-router-dom";
@@ -231,6 +228,7 @@ import {
231
228
  Sidebar,
232
229
  Modal,
233
230
  Tabs,
231
+ ApiClient,
234
232
  useToast,
235
233
  Login,
236
234
  Register
@@ -251,8 +249,24 @@ const data = [
251
249
  function Dashboard() {
252
250
  const [sidebarOpen, setSidebarOpen] = useState(false);
253
251
  const [modalOpen, setModalOpen] = useState(false);
252
+ const [posts, setPosts] = useState([]);
253
+ const [loadingPosts, setLoadingPosts] = useState(false);
254
+ const [postsError, setPostsError] = useState("");
254
255
  const toast = useToast();
255
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
+ };
256
270
 
257
271
  const tabs = [
258
272
  { label: "Overview", content: <p>Welcome to the UI Library demo.</p> },
@@ -329,6 +343,28 @@ function Dashboard() {
329
343
  </div>
330
344
  </Card>
331
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
+
332
368
  {/* Modal + Toast */}
333
369
  <div className="flex gap-4">
334
370
  <Button onClick={() => setModalOpen(true)}>Open Modal</Button>
@@ -752,125 +788,70 @@ export default function Section({ children, className = "" }) {
752
788
  // -------------------------------
753
789
 
754
790
  write("src/utils/api.js", `
791
+ import axios from "axios";
792
+
755
793
  /**
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");
794
+ * Axios-powered API client with CRUD helpers.
773
795
  */
774
796
 
775
797
  export class ApiClient {
776
798
  constructor(baseURL = "") {
777
- this.baseURL = baseURL;
778
- }
799
+ this.instance = axios.create({
800
+ baseURL,
801
+ headers: { "Content-Type": "application/json" }
802
+ });
779
803
 
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
- };
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
+ }
788
812
 
789
- const config = {
790
- headers: { ...defaultHeaders, ...options.headers },
791
- ...options
792
- };
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
+ }
793
820
 
821
+ async request(config) {
794
822
  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 };
823
+ const res = await this.instance.request(config);
824
+ return { success: true, data: res.data };
803
825
  } catch (error) {
804
- console.error("API Request Error:", error);
805
826
  return { success: false, error: error.message };
806
827
  }
807
828
  }
808
829
 
809
- /**
810
- * GET - Fetch all resources
811
- */
812
- async getAll(endpoint, options = {}) {
813
- return this.request(endpoint, {
814
- method: "GET",
815
- ...options
816
- });
830
+ async getAll(url, config = {}) {
831
+ return this.request({ url, method: "GET", ...config });
817
832
  }
818
833
 
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
- });
834
+ async getOne(url, config = {}) {
835
+ return this.request({ url, method: "GET", ...config });
827
836
  }
828
837
 
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
+ async create(url, data, config = {}) {
839
+ return this.request({ url, method: "POST", data, ...config });
838
840
  }
839
841
 
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
- });
842
+ async update(url, data, config = {}) {
843
+ return this.request({ url, method: "PUT", data, ...config });
849
844
  }
850
845
 
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
- });
846
+ async patch(url, data, config = {}) {
847
+ return this.request({ url, method: "PATCH", data, ...config });
860
848
  }
861
849
 
862
- /**
863
- * DELETE - Remove a resource
864
- */
865
- async delete(endpoint, options = {}) {
866
- return this.request(endpoint, {
867
- method: "DELETE",
868
- ...options
869
- });
850
+ async delete(url, config = {}) {
851
+ return this.request({ url, method: "DELETE", ...config });
870
852
  }
871
853
  }
872
854
 
873
- // Export a default instance (optional - users can create their own)
874
855
  export default new ApiClient(import.meta.env.VITE_API_BASE_URL || "");
875
856
  `);
876
857
 
@@ -881,28 +862,8 @@ export default new ApiClient(import.meta.env.VITE_API_BASE_URL || "");
881
862
  write("src/utils/password.js", `
882
863
  import bcrypt from "bcryptjs";
883
864
 
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
865
  const SALT_ROUNDS = 10;
899
866
 
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
867
  export async function hashPassword(password) {
907
868
  if (!password || typeof password !== "string" || password.trim().length === 0) {
908
869
  throw new Error("Password must be a non-empty string");
@@ -912,17 +873,10 @@ export async function hashPassword(password) {
912
873
  const hash = await bcrypt.hash(password, SALT_ROUNDS);
913
874
  return hash;
914
875
  } catch (error) {
915
- throw new Error(\`Error hashing password: \${error.message}\`);
876
+ throw new Error("Error hashing password: " + error.message);
916
877
  }
917
878
  }
918
879
 
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
880
  export async function verifyPassword(password, hash) {
927
881
  if (!password || typeof password !== "string") {
928
882
  throw new Error("Password must be a non-empty string");
@@ -936,37 +890,24 @@ export async function verifyPassword(password, hash) {
936
890
  const isValid = await bcrypt.compare(password, hash);
937
891
  return isValid;
938
892
  } catch (error) {
939
- throw new Error(\`Error verifying password: \${error.message}\`);
893
+ throw new Error("Error verifying password: " + error.message);
940
894
  }
941
895
  }
942
896
 
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
897
  export function getPasswordStrength(password) {
949
898
  if (!password) return 0;
950
899
 
951
900
  let strength = 0;
952
901
 
953
- // Length check
954
902
  if (password.length >= 8) strength++;
955
903
  if (password.length >= 12) strength++;
956
-
957
- // Character variety checks
958
904
  if (/[a-z]/.test(password) && /[A-Z]/.test(password)) strength++;
959
- if (/\\d/.test(password)) strength++;
905
+ if (/\d/.test(password)) strength++;
960
906
  if (/[!@#\$%^&*(),.?":{}|<>]/.test(password)) strength++;
961
907
 
962
908
  return Math.min(strength, 4);
963
909
  }
964
910
 
965
- /**
966
- * Get password strength label
967
- * @param {string} password - The password to evaluate
968
- * @returns {string} Human-readable strength label
969
- */
970
911
  export function getPasswordStrengthLabel(password) {
971
912
  const levels = ["Weak", "Fair", "Good", "Strong", "Very Strong"];
972
913
  const strength = getPasswordStrength(password);
@@ -979,9 +920,10 @@ console.log("\nNext steps:");
979
920
  console.log(" 1. npm run dev");
980
921
  console.log(" 3. Open http://localhost:5173 in your browser\n");
981
922
 
982
- // Install dependencies unless disabled
983
923
  if (doInstall) {
984
924
  installDependencies(packageManager, BASE_DIR);
985
925
  } else {
986
926
  console.log("\nℹ️ Skipping install (flag --no-install). Run manually later.");
987
927
  }
928
+
929
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-bdpa-react-scaffold",
3
- "version": "1.2.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": {