pjdev2d-cli 1.1.3 → 1.2.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pjdev2d-cli",
3
- "version": "1.1.3",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "description": "CLI to install reusable React components like shadcn/ui",
6
6
  "main": "bin/cli.js",
@@ -30,6 +30,7 @@
30
30
  "fs-extra": "^11.3.5",
31
31
  "inquirer": "^13.4.3",
32
32
  "react": "^19.2.6",
33
+ "react-router-dom": "^7.16.0",
33
34
  "tailwind-merge": "^3.6.0"
34
35
  }
35
36
  }
package/registry.json CHANGED
@@ -8,5 +8,10 @@
8
8
  "name": "Tanstack Query",
9
9
  "category": "tanstack-query",
10
10
  "files": ["templates/tanstack-query"]
11
+ },
12
+ "react-router-template": {
13
+ "name": "React Router",
14
+ "category": "react-router",
15
+ "files": ["templates/react-router"]
11
16
  }
12
17
  }
@@ -0,0 +1,131 @@
1
+ import { useCallback, useMemo, useRef } from "react";
2
+ import { useSearchParams } from "react-router-dom";
3
+
4
+ export function useRouterSearch<T extends Record<string, any>>({
5
+ defaultParams,
6
+ }: {
7
+ defaultParams: T;
8
+ }) {
9
+ const [searchParams, setSearchParams] = useSearchParams();
10
+
11
+ const defaultRef = useRef(defaultParams);
12
+ defaultRef.current = defaultParams;
13
+
14
+ const search = useMemo(() => {
15
+ const result = { ...defaultRef.current };
16
+ const keys = Object.keys(defaultRef.current);
17
+
18
+ for (const key of keys) {
19
+ const fallback = defaultRef.current[key];
20
+ if (Array.isArray(fallback)) {
21
+ const val = searchParams.getAll(key);
22
+ if (val.length > 0) {
23
+ result[key as keyof T] = parseValue(val, fallback) as any;
24
+ }
25
+ } else {
26
+ const val = searchParams.get(key);
27
+ if (val !== null) {
28
+ result[key as keyof T] = parseValue(val, fallback) as any;
29
+ }
30
+ }
31
+ }
32
+ return result;
33
+ }, [searchParams]);
34
+
35
+ const setSearch = useCallback(
36
+ (
37
+ updates: Partial<Record<keyof T, any>>,
38
+ options?: { resetPage?: boolean; replace?: boolean }
39
+ ) => {
40
+ const next = new URLSearchParams(searchParams);
41
+ const keys = Object.keys(defaultRef.current);
42
+
43
+ for (const key of keys) {
44
+ const value = updates[key as keyof T];
45
+ if (value !== undefined) {
46
+ if (value === null || areEqual(value, defaultRef.current[key])) {
47
+ next.delete(key);
48
+ } else if (Array.isArray(value)) {
49
+ next.delete(key);
50
+ value.forEach((val) => {
51
+ if (val !== undefined && val !== null && val !== "") {
52
+ next.append(key, String(val));
53
+ }
54
+ });
55
+ } else {
56
+ next.set(key, String(value));
57
+ }
58
+ }
59
+ }
60
+ /**
61
+ * Reset pagination
62
+ * when filters/search changes
63
+ */
64
+ if (options?.resetPage && "page" in defaultRef.current) {
65
+ next.delete("page");
66
+ }
67
+
68
+ setSearchParams(next, {
69
+ replace: options?.replace,
70
+ });
71
+ },
72
+ [searchParams, setSearchParams]
73
+ );
74
+
75
+ return { search, setSearch };
76
+ }
77
+
78
+ function areEqual(a: any, b: any) {
79
+ if (Array.isArray(a) && Array.isArray(b)) {
80
+ if (a.length !== b.length) return false;
81
+ const sortedA = [...a].sort();
82
+ const sortedB = [...b].sort();
83
+ return sortedA.every((val, index) => val === sortedB[index]);
84
+ }
85
+ return a === b;
86
+ }
87
+
88
+ function parseValue(value: string | string[], fallback: any): any {
89
+ if (Array.isArray(fallback)) {
90
+ const fallbackVal = fallback[0];
91
+ const valArray = Array.isArray(value) ? value : [value];
92
+ return valArray.map((item) => parseSingleValue(item, fallbackVal));
93
+ }
94
+ const singleVal = Array.isArray(value) ? value[0] : value;
95
+ return parseSingleValue(singleVal, fallback);
96
+ }
97
+
98
+ function parseSingleValue(value: string, fallback: any) {
99
+ if (typeof fallback === "number") {
100
+ const parsed = Number(value);
101
+ return Number.isNaN(parsed) ? fallback : parsed;
102
+ }
103
+
104
+ /**
105
+ * Boolean parsing
106
+ */
107
+ if (typeof fallback === "boolean") {
108
+ return value === "true";
109
+ }
110
+
111
+ /**
112
+ * String fallback
113
+ */
114
+ return value;
115
+ }
116
+
117
+ /*
118
+ # NOTE: Array parameter usage example
119
+
120
+ 1. Define the default parameter as an array:
121
+ export const DefaultSearch = {
122
+ color: [] as string[],
123
+ };
124
+
125
+ 2. Set values (e.g., in a component):
126
+ setSearch({ color: ["red", "blue"] });
127
+ // Resulting URL query: ?color=red&color=blue
128
+
129
+ 3. Read values (e.g., in a component):
130
+ const colors = search.color; // Returns ["red", "blue"]
131
+ */
@@ -0,0 +1,21 @@
1
+ import { Outlet } from "react-router-dom";
2
+
3
+ export function PrivateRoute() {
4
+ return <Outlet />;
5
+ }
6
+
7
+ /*
8
+
9
+ # NOTE : use this when you want to redirect user to login page if he is already logged out
10
+
11
+ import { Navigate, Outlet } from "react-router-dom";
12
+
13
+ export default function PrivateRoute() {
14
+ const token = getLocalAuthToken();
15
+ if (!token) {
16
+ return <Navigate to={"/"} replace />;
17
+ }
18
+ return <Outlet />;
19
+ }
20
+
21
+ */
@@ -0,0 +1,21 @@
1
+ import { Outlet } from "react-router-dom";
2
+
3
+ export function PublicRoute() {
4
+ return <Outlet />;
5
+ }
6
+
7
+ /*
8
+
9
+ # NOTE : use this when you want to redirect user to main page if he is already logged in
10
+
11
+ import { Navigate, Outlet } from "react-router-dom";
12
+
13
+ export default function PublicRoute() {
14
+ const token = getLocalAuthToken();
15
+ if (token) {
16
+ return <Navigate to={PATH.desired_path_name} replace />;
17
+ }
18
+ return <Outlet />;
19
+ }
20
+
21
+ */
@@ -0,0 +1,144 @@
1
+ import { createBrowserRouter, RouteObject } from "react-router-dom";
2
+ import { PATH } from "./path";
3
+ import { PublicRoute } from "./guards/public.route";
4
+ import { PrivateRoute } from "./guards/private.route";
5
+ import { AuthLayout } from "./layouts/auth.layout";
6
+ import { ErrorLayout } from "./layouts/error.layout";
7
+ import { MainLayout } from "./layouts/main.layout";
8
+
9
+ const PublicRoutes: RouteObject[] = [
10
+ {
11
+ path: PATH.page_root(),
12
+ Component: PublicRoute,
13
+ children: [
14
+ {
15
+ Component: AuthLayout,
16
+ ErrorBoundary: ErrorLayout,
17
+ children: [
18
+ {
19
+ path: PATH.page_auth(),
20
+ lazy: async () => {
21
+ const module = await import("../routes/page-auth");
22
+ return { Component: module.default };
23
+ },
24
+ },
25
+ ],
26
+ },
27
+ {
28
+ path: PATH.not_found(),
29
+ lazy: async () => {
30
+ const module = await import("../routes/not-found");
31
+ return { Component: module.default };
32
+ },
33
+ },
34
+ ],
35
+ },
36
+ ];
37
+
38
+ const PrivateRoutes: RouteObject[] = [
39
+ {
40
+ Component: PrivateRoute,
41
+ children: [
42
+ {
43
+ Component: MainLayout,
44
+ ErrorBoundary: ErrorLayout,
45
+ children: [
46
+ {
47
+ path: PATH.page_root(),
48
+ ErrorBoundary: ErrorLayout,
49
+ lazy: async () => {
50
+ const module = await import("../routes/page-root");
51
+ return { Component: module.default };
52
+ },
53
+ },
54
+ {
55
+ path: PATH.page_one(),
56
+ ErrorBoundary: ErrorLayout,
57
+ lazy: async () => {
58
+ const module = await import("../routes/page-one");
59
+ return { Component: module.default };
60
+ },
61
+ },
62
+ {
63
+ path: PATH.page_two(),
64
+ ErrorBoundary: ErrorLayout,
65
+ lazy: async () => {
66
+ const module = await import("../routes/page-two");
67
+ return { Component: module.default };
68
+ },
69
+ },
70
+ ],
71
+ },
72
+ ],
73
+ },
74
+ ];
75
+
76
+ export const router = createBrowserRouter([...PublicRoutes, ...PrivateRoutes]);
77
+
78
+ /*
79
+
80
+ currently i am using new way of using react-router version like
81
+ Component: PrivateLayout,
82
+ lazy: async () => ...
83
+
84
+ the old way is
85
+
86
+ {
87
+ path: "/",
88
+ element: (
89
+ <PublicRoute>
90
+ <AuthLayout />
91
+ </PublicRoute>
92
+ ),
93
+ children: [
94
+ {
95
+ path: PATHS.LOGIN,
96
+ element: <Login />,
97
+ },
98
+ ],
99
+ }
100
+
101
+
102
+
103
+ | Feature | `errorElement` | `ErrorBoundary` |
104
+ | -------------------- | --------------------------------- | -------------------------- |
105
+ | What you pass | React Element | Component Function |
106
+ | Syntax | `errorElement: <ErrorPage />` | `ErrorBoundary: ErrorPage` |
107
+ | Requires JSX | Yes | No |
108
+ | Works in `.ts` file | No (unless `React.createElement`) | Yes |
109
+ | React Router version | v6.4+ | Newer preferred API |
110
+
111
+
112
+ ---------------------------------------------------------------------------
113
+
114
+ # ANCHOR : 2 ways to show the error boundry page data
115
+
116
+
117
+ # NOTE : WAY-1 inside layout i.e inplace of outlet
118
+ {
119
+ path: PATH.page_two(),
120
+ ErrorBoundary: ErrorLayout,
121
+ lazy: async () => {
122
+ const module = await import("../routes/page-two");
123
+ return { Component: module.default };
124
+ },
125
+ },
126
+
127
+ ---------------------------------------------------------------------------
128
+
129
+ # NOTE : WAY-2 replace entire layout with error boundary
130
+ {
131
+ Component: AuthLayout,
132
+ ErrorBoundary: ErrorLayout,
133
+ children: [
134
+ {
135
+ path: PATH.page_auth(),
136
+ lazy: async () => {
137
+ const module = await import("../routes/page-auth");
138
+ return { Component: module.default };
139
+ },
140
+ },
141
+ ],
142
+ },
143
+
144
+ */
@@ -0,0 +1,5 @@
1
+ import { Outlet } from "react-router-dom";
2
+
3
+ export function AuthLayout() {
4
+ return <Outlet />;
5
+ }
@@ -0,0 +1,6 @@
1
+ import { Outlet, useRouteError } from "react-router-dom";
2
+
3
+ export function ErrorLayout() {
4
+ const error = useRouteError();
5
+ return <div>Error Page</div>;
6
+ }
@@ -0,0 +1,5 @@
1
+ import { Outlet } from "react-router-dom";
2
+
3
+ export function MainLayout() {
4
+ return <Outlet />;
5
+ }
@@ -0,0 +1,7 @@
1
+ export const PATH = {
2
+ page_root: () => "/" as const,
3
+ page_one: () => "/page_one" as const,
4
+ page_two: () => "/page_two" as const,
5
+ page_auth: () => "/page_auth" as const,
6
+ not_found: () => "*" as const,
7
+ };
@@ -0,0 +1,3 @@
1
+ export default function NotFound() {
2
+ return <div>Not Found</div>;
3
+ }
@@ -0,0 +1,3 @@
1
+ export default function PageAuth() {
2
+ return <div>Page Auth</div>;
3
+ }
@@ -0,0 +1,3 @@
1
+ export default function PageOne() {
2
+ return <div>Page One</div>;
3
+ }
@@ -0,0 +1,5 @@
1
+ export const PageOneDefaultSearchParam = {
2
+ param_one: "",
3
+ param_two: "",
4
+ param_three: "",
5
+ };
@@ -0,0 +1,3 @@
1
+ export default function PageRoot() {
2
+ return <div>Page Root</div>;
3
+ }
@@ -0,0 +1,3 @@
1
+ export default function PageTwo() {
2
+ return <div>Page Two</div>;
3
+ }
@@ -0,0 +1,5 @@
1
+ export const PageTwoDefaultSearchParam = {
2
+ param_one: "",
3
+ param_two: "",
4
+ param_three: "",
5
+ };