stackkit 0.3.5 → 0.3.6

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 (192) hide show
  1. package/README.md +50 -42
  2. package/dist/cli/add.js +122 -56
  3. package/dist/cli/create.d.ts +2 -0
  4. package/dist/cli/create.js +271 -95
  5. package/dist/cli/doctor.js +1 -0
  6. package/dist/cli/list.d.ts +1 -1
  7. package/dist/cli/list.js +6 -4
  8. package/dist/index.js +234 -191
  9. package/dist/lib/constants.d.ts +4 -0
  10. package/dist/lib/constants.js +4 -0
  11. package/dist/lib/discovery/module-discovery.d.ts +4 -0
  12. package/dist/lib/discovery/module-discovery.js +56 -0
  13. package/dist/lib/generation/code-generator.d.ts +11 -2
  14. package/dist/lib/generation/code-generator.js +42 -3
  15. package/dist/lib/generation/generator-utils.js +3 -1
  16. package/dist/lib/pm/package-manager.js +16 -13
  17. package/dist/lib/ui/logger.js +3 -2
  18. package/dist/lib/utils/path-resolver.d.ts +2 -0
  19. package/dist/lib/utils/path-resolver.js +8 -0
  20. package/dist/meta.json +8312 -0
  21. package/modules/auth/better-auth/files/{shared → express}/config/env.ts +48 -50
  22. package/modules/auth/better-auth/files/express/middlewares/authorize.ts +20 -1
  23. package/modules/auth/better-auth/files/express/modules/auth.controller.ts +349 -0
  24. package/modules/auth/better-auth/files/express/modules/{auth/auth.route.ts → auth.route.ts} +9 -4
  25. package/modules/auth/better-auth/files/express/modules/auth.service.ts +664 -0
  26. package/modules/auth/better-auth/files/express/modules/{auth/auth.type.ts → auth.type.ts} +22 -9
  27. package/modules/auth/better-auth/files/{shared/mongoose/auth/helper.ts → express/mongo-modules/auth.helper.ts} +11 -1
  28. package/modules/auth/better-auth/files/express/types/express.d.ts +11 -0
  29. package/modules/auth/better-auth/files/nextjs/api-route.ts +74 -0
  30. package/modules/auth/better-auth/files/nextjs/dashboard/pages/(user)/page.tsx +6 -0
  31. package/modules/auth/better-auth/files/nextjs/dashboard/pages/admin/page.tsx +6 -0
  32. package/modules/auth/better-auth/files/nextjs/dashboard/pages/layout.tsx +48 -0
  33. package/modules/auth/better-auth/files/nextjs/dashboard/pages/my-profile/page.tsx +5 -0
  34. package/modules/auth/better-auth/files/nextjs/features/services/auth.service.ts +102 -0
  35. package/modules/auth/better-auth/files/nextjs/layout/layout.tsx +13 -0
  36. package/modules/auth/better-auth/files/nextjs/lib/axios/http.ts +158 -0
  37. package/modules/auth/better-auth/files/nextjs/lib/env.ts +35 -0
  38. package/modules/auth/better-auth/files/nextjs/lib/utils/auth.ts +75 -0
  39. package/modules/auth/better-auth/files/nextjs/lib/utils/cookie.ts +29 -0
  40. package/modules/auth/better-auth/files/nextjs/lib/utils/jwt.ts +28 -0
  41. package/modules/auth/better-auth/files/nextjs/lib/utils/token.ts +49 -0
  42. package/modules/auth/better-auth/files/nextjs/pages/forgot-password/page.tsx +5 -0
  43. package/modules/auth/better-auth/files/nextjs/pages/layout.tsx +11 -0
  44. package/modules/auth/better-auth/files/nextjs/pages/login/page.tsx +9 -0
  45. package/modules/auth/better-auth/files/nextjs/pages/register/page.tsx +5 -0
  46. package/modules/auth/better-auth/files/nextjs/pages/reset-password/page.tsx +10 -0
  47. package/modules/auth/better-auth/files/nextjs/pages/verify-email/page.tsx +10 -0
  48. package/modules/auth/better-auth/files/nextjs/proxy.ts +154 -42
  49. package/modules/auth/better-auth/files/nextjs/theme/providers/theme-provider.tsx +11 -0
  50. package/modules/auth/better-auth/files/nextjs/types/api.types.ts +18 -0
  51. package/modules/auth/better-auth/files/react/components/protected-route.tsx +39 -0
  52. package/modules/auth/better-auth/files/react/components/route-guards.tsx +13 -0
  53. package/modules/auth/better-auth/files/react/dashboard/admin/pages/overview.tsx +3 -0
  54. package/modules/auth/better-auth/files/react/dashboard/pages/overview.tsx +3 -0
  55. package/modules/auth/better-auth/files/react/features/pages/forgot-password.tsx +5 -0
  56. package/modules/auth/better-auth/files/react/features/pages/login.tsx +5 -0
  57. package/modules/auth/better-auth/files/react/features/pages/my-profile.tsx +5 -0
  58. package/modules/auth/better-auth/files/react/features/pages/oauth-callback.tsx +59 -0
  59. package/modules/auth/better-auth/files/react/features/pages/register.tsx +5 -0
  60. package/modules/auth/better-auth/files/react/features/pages/reset-password.tsx +10 -0
  61. package/modules/auth/better-auth/files/react/features/pages/verify-email.tsx +10 -0
  62. package/modules/auth/better-auth/files/react/layout/dashboard-layout.tsx +54 -0
  63. package/modules/auth/better-auth/files/react/lib/axios/http.ts +68 -0
  64. package/modules/auth/better-auth/files/react/lib/env.ts +25 -0
  65. package/modules/auth/better-auth/files/react/router.tsx +73 -0
  66. package/modules/auth/better-auth/files/react/theme/components/providers/theme-provider-context.ts +13 -0
  67. package/modules/auth/better-auth/files/react/theme/components/providers/theme-provider.tsx +51 -0
  68. package/modules/auth/better-auth/files/react/theme/hooks/use-theme.ts +8 -0
  69. package/modules/auth/better-auth/files/shared/features/components/change-password-dialog.tsx +113 -0
  70. package/modules/auth/better-auth/files/shared/features/components/forgot-password-form.tsx +84 -0
  71. package/modules/auth/better-auth/files/shared/features/components/login-form.tsx +134 -0
  72. package/modules/auth/better-auth/files/shared/features/components/my-profile.tsx +147 -0
  73. package/modules/auth/better-auth/files/shared/features/components/profile-form.tsx +205 -0
  74. package/modules/auth/better-auth/files/shared/features/components/register-form.tsx +100 -0
  75. package/modules/auth/better-auth/files/shared/features/components/reset-password-form.tsx +111 -0
  76. package/modules/auth/better-auth/files/shared/features/components/social-login-buttons.tsx +47 -0
  77. package/modules/auth/better-auth/files/shared/features/components/user-profile-menu.tsx +106 -0
  78. package/modules/auth/better-auth/files/shared/features/components/verify-email-form.tsx +110 -0
  79. package/modules/auth/better-auth/files/shared/features/queries/auth.mutations.tsx +312 -0
  80. package/modules/auth/better-auth/files/shared/features/queries/auth.querie.ts +19 -0
  81. package/modules/auth/better-auth/files/shared/features/services/auth.api.ts +81 -0
  82. package/modules/auth/better-auth/files/shared/features/types/auth.type.ts +47 -0
  83. package/modules/auth/better-auth/files/shared/features/validators/change-password.validator.ts +18 -0
  84. package/modules/auth/better-auth/files/shared/features/validators/forgot.validator.ts +7 -0
  85. package/modules/auth/better-auth/files/shared/features/validators/login.validator.ts +14 -0
  86. package/modules/auth/better-auth/files/shared/features/validators/profile.validator.ts +8 -0
  87. package/modules/auth/better-auth/files/shared/features/validators/register.validator.ts +9 -0
  88. package/modules/auth/better-auth/files/shared/features/validators/reset.validator.ts +9 -0
  89. package/modules/auth/better-auth/files/shared/features/validators/verify.validator.ts +8 -0
  90. package/modules/auth/better-auth/files/shared/lib/auth-client.ts +2 -1
  91. package/modules/auth/better-auth/files/shared/lib/auth.ts +5 -19
  92. package/modules/auth/better-auth/files/shared/lib/constant/dashboard.ts +90 -0
  93. package/modules/auth/better-auth/files/shared/theme/mode-toggle.tsx +30 -0
  94. package/modules/auth/better-auth/files/shared/ui/shadcn/components/dashboard/dashboard-header.tsx +94 -0
  95. package/modules/auth/better-auth/files/shared/ui/shadcn/components/dashboard/dashboard-sidebar.tsx +255 -0
  96. package/modules/auth/better-auth/files/shared/ui/shadcn/components/footer.tsx +35 -0
  97. package/modules/auth/better-auth/files/shared/ui/shadcn/components/navbar.tsx +145 -0
  98. package/modules/auth/better-auth/files/shared/ui/shadcn/form-field/input-field.tsx +440 -0
  99. package/modules/auth/better-auth/files/shared/utils/email.ts +2 -17
  100. package/modules/auth/better-auth/generator.json +172 -51
  101. package/modules/auth/better-auth/module.json +2 -2
  102. package/modules/components/files/shared/hooks/use-file-upload.ts +412 -0
  103. package/modules/components/files/shared/lib/utils/url-helpers.ts +110 -0
  104. package/modules/components/files/shared/shadcn/dashboard/data-table-column-selector.tsx +52 -0
  105. package/modules/components/files/shared/shadcn/dashboard/data-table-footer.tsx +156 -0
  106. package/modules/components/files/shared/shadcn/dashboard/data-table.tsx +405 -0
  107. package/modules/components/files/shared/shadcn/global/form-field/input-field.tsx +440 -0
  108. package/modules/components/files/shared/shadcn/global/form-field/media-uploader-field.tsx +745 -0
  109. package/modules/components/files/shared/shadcn/global/form-field/multi-select-field.tsx +207 -0
  110. package/modules/components/files/shared/shadcn/global/form-field/select-field.tsx +247 -0
  111. package/modules/components/files/shared/shadcn/global/form-field/textarea-field.tsx +277 -0
  112. package/modules/components/files/shared/shadcn/global/form-field/tiptap-editor-field.tsx +35 -0
  113. package/modules/components/files/shared/shadcn/global/no-results.tsx +41 -0
  114. package/modules/components/files/shared/shadcn/tiptap-editor/editor-menu-bar.tsx +217 -0
  115. package/modules/components/files/shared/shadcn/tiptap-editor/tiptap-editor.tsx +104 -0
  116. package/modules/components/files/shared/url/load-more.tsx +93 -0
  117. package/modules/components/files/shared/url/search-bar.tsx +131 -0
  118. package/modules/components/files/shared/url/sort-select.tsx +118 -0
  119. package/modules/components/files/shared/url/url-tabs.tsx +77 -0
  120. package/modules/components/generator.json +109 -0
  121. package/modules/components/module.json +11 -0
  122. package/modules/database/mongoose/generator.json +3 -14
  123. package/modules/database/mongoose/module.json +2 -2
  124. package/modules/database/prisma/generator.json +6 -12
  125. package/modules/database/prisma/module.json +2 -2
  126. package/modules/storage/cloudinary/files/express/config/env.ts +65 -0
  127. package/modules/storage/cloudinary/files/express/config/media.ts +103 -0
  128. package/modules/storage/cloudinary/files/express/modules/media/media.controller.ts +59 -0
  129. package/modules/storage/cloudinary/files/express/modules/media/media.route.ts +29 -0
  130. package/modules/storage/cloudinary/files/express/modules/media/media.service.ts +113 -0
  131. package/modules/storage/cloudinary/files/express/modules/media/media.type.ts +32 -0
  132. package/modules/storage/cloudinary/generator.json +34 -0
  133. package/modules/storage/cloudinary/module.json +11 -0
  134. package/modules/ui/shadcn/generator.json +21 -0
  135. package/modules/ui/shadcn/module.json +11 -0
  136. package/package.json +24 -26
  137. package/templates/express/README.md +11 -16
  138. package/templates/express/src/config/env.ts +7 -5
  139. package/templates/nextjs/README.md +13 -18
  140. package/templates/nextjs/app/favicon.ico +0 -0
  141. package/templates/nextjs/app/layout.tsx +6 -4
  142. package/templates/nextjs/components/providers/query-provider.tsx +3 -0
  143. package/templates/nextjs/env.example +3 -1
  144. package/templates/nextjs/lib/axios/http.ts +23 -0
  145. package/templates/nextjs/lib/env.ts +7 -5
  146. package/templates/nextjs/package.json +2 -1
  147. package/templates/nextjs/template.json +1 -2
  148. package/templates/react/README.md +9 -14
  149. package/templates/react/index.html +1 -1
  150. package/templates/react/package.json +1 -1
  151. package/templates/react/src/assets/favicon.ico +0 -0
  152. package/templates/react/src/components/providers/query-provider.tsx +38 -0
  153. package/templates/react/src/{shared/components → components}/seo.tsx +4 -8
  154. package/templates/react/src/lib/axios/http.ts +24 -0
  155. package/templates/react/src/main.tsx +8 -11
  156. package/templates/react/src/{features/about/pages → pages}/about.tsx +1 -1
  157. package/templates/react/src/{features/home/pages → pages}/home.tsx +1 -1
  158. package/templates/react/src/router.tsx +6 -6
  159. package/templates/react/src/vite-env.d.ts +2 -1
  160. package/templates/react/template.json +0 -1
  161. package/templates/react/tsconfig.app.json +6 -0
  162. package/templates/react/tsconfig.json +7 -1
  163. package/templates/react/vite.config.ts +12 -0
  164. package/modules/auth/authjs/files/nextjs/api/auth/[...nextauth]/route.ts +0 -3
  165. package/modules/auth/authjs/files/nextjs/proxy.ts +0 -1
  166. package/modules/auth/authjs/files/shared/lib/auth.ts +0 -119
  167. package/modules/auth/authjs/files/shared/prisma/schema.prisma +0 -61
  168. package/modules/auth/authjs/generator.json +0 -64
  169. package/modules/auth/authjs/module.json +0 -13
  170. package/modules/auth/better-auth/files/express/modules/auth/auth.controller.ts +0 -264
  171. package/modules/auth/better-auth/files/express/modules/auth/auth.service.ts +0 -549
  172. package/modules/auth/better-auth/files/express/templates/google-redirect.ejs +0 -24
  173. package/modules/auth/better-auth/files/nextjs/api/auth/[...all]/route.ts +0 -4
  174. package/modules/auth/better-auth/files/nextjs/lib/auth/auth-guards.ts +0 -31
  175. package/modules/auth/better-auth/files/nextjs/templates/email-otp.tsx +0 -74
  176. package/templates/nextjs/lib/api/http.ts +0 -40
  177. package/templates/react/public/vite.svg +0 -1
  178. package/templates/react/src/app/layouts/dashboard-layout.tsx +0 -8
  179. package/templates/react/src/app/layouts/public-layout.tsx +0 -5
  180. package/templates/react/src/app/providers.tsx +0 -20
  181. package/templates/react/src/app/router.tsx +0 -21
  182. package/templates/react/src/assets/react.svg +0 -1
  183. package/templates/react/src/shared/api/http.ts +0 -39
  184. package/templates/react/src/shared/components/loading.tsx +0 -8
  185. package/templates/react/src/shared/lib/query-client.ts +0 -12
  186. package/templates/react/src/utils/storage.ts +0 -35
  187. package/templates/react/src/utils/utils.ts +0 -3
  188. /package/modules/auth/better-auth/files/{shared/mongoose/auth/constants.ts → express/mongo-modules/auth.constants.ts} +0 -0
  189. /package/templates/nextjs/app/{page.tsx → (public)/(root)/page.tsx} +0 -0
  190. /package/templates/react/src/{shared/components → components}/error-boundary.tsx +0 -0
  191. /package/templates/react/src/{shared/components → components}/layout.tsx +0 -0
  192. /package/templates/react/src/{shared/pages → pages}/not-found.tsx +0 -0
@@ -0,0 +1,93 @@
1
+ {{#if framework == "nextjs"}}
2
+ "use client";
3
+ {{/if}}
4
+
5
+ import { useEffect, useRef, useState } from "react";
6
+ {{#if framework == "nextjs"}}
7
+ import { useRouter, useSearchParams } from "next/navigation";
8
+ {{else}}
9
+ import { useNavigate, useSearchParams } from "react-router";
10
+ {{/if}}
11
+ import { formUrlQuery } from "@/lib/utils/url-helpers";
12
+
13
+ const LoadMore = ({ hasNextPage }: { hasNextPage: boolean }) => {
14
+ {{#if framework == "nextjs"}}
15
+ const router = useRouter();
16
+ const searchParams = useSearchParams();
17
+ {{else}}
18
+ const navigate = useNavigate();
19
+ const [searchParams] = useSearchParams();
20
+ {{/if}}
21
+
22
+ const loaderRef = useRef<HTMLDivElement | null>(null);
23
+ const isLoadingRef = useRef(false);
24
+ const [isVisible, setIsVisible] = useState(false);
25
+
26
+ const [currentPage, setCurrentPage] = useState(() => {
27
+ const page = searchParams?.get("page");
28
+ return page ? Number(page) : 1;
29
+ });
30
+
31
+ useEffect(() => {
32
+ isLoadingRef.current = false;
33
+ }, []);
34
+
35
+ useEffect(() => {
36
+ if (!hasNextPage) return;
37
+
38
+ const observer = new IntersectionObserver(
39
+ (entries) => {
40
+ const [entry] = entries;
41
+ setIsVisible(entry.isIntersecting);
42
+
43
+ if (entry.isIntersecting && !isLoadingRef.current && hasNextPage) {
44
+ isLoadingRef.current = true;
45
+
46
+ setTimeout(() => {
47
+ const nextPage = currentPage + 1;
48
+ setCurrentPage(nextPage);
49
+
50
+ const url = formUrlQuery({
51
+ params: searchParams?.toString() ?? "",
52
+ key: "page",
53
+ value: nextPage.toString(),
54
+ });
55
+
56
+ {{#if framework == "nextjs"}}
57
+ router.push(url, { scroll: false });
58
+ {{else}}
59
+ navigate(url, { replace: false });
60
+ {{/if}}
61
+ }, 500);
62
+ }
63
+ },
64
+ { threshold: 0.8, rootMargin: "400px" },
65
+ );
66
+
67
+ if (loaderRef.current) observer.observe(loaderRef.current);
68
+
69
+ return () => observer.disconnect();
70
+ }, [
71
+ hasNextPage,
72
+ currentPage,
73
+ {{#if framework == "nextjs"}}
74
+ router,
75
+ {{else}}
76
+ navigate,
77
+ {{/if}}
78
+ searchParams,
79
+ ]);
80
+
81
+ return (
82
+ <div ref={loaderRef} className="my-8 flex h-20 w-full items-center justify-center">
83
+ {isVisible && (
84
+ <div className="flex flex-col items-center gap-2">
85
+ <div className="border-accent-main h-10 w-10 animate-spin rounded-full border-4 border-t-transparent" />
86
+ <span className="text-sm text-gray-500">Loading more ...</span>
87
+ </div>
88
+ )}
89
+ </div>
90
+ );
91
+ };
92
+
93
+ export default LoadMore;
@@ -0,0 +1,131 @@
1
+ {{#if framework == "nextjs"}}
2
+ "use client";
3
+ {{/if}}
4
+
5
+ import * as React from "react";
6
+ {{#if framework == "nextjs"}}
7
+ import { useRouter, useSearchParams } from "next/navigation";
8
+ {{else}}
9
+ import { useNavigate, useSearchParams } from "react-router";
10
+ {{/if}}
11
+ import { Input } from "@/components/ui/input";
12
+ import { cn } from "@/lib/utils";
13
+ import { formUrlQuery, removeKeysFromQuery } from "@/lib/utils/url-helpers";
14
+
15
+ type SearchBarProps = {
16
+ placeholder?: string;
17
+ className?: string;
18
+ inputClassName?: string;
19
+ paramKey?: string;
20
+ defaultValue?: string;
21
+ debounceMs?: number;
22
+ replace?: boolean;
23
+ minLength?: number;
24
+ autoFocus?: boolean;
25
+ showClear?: boolean;
26
+ ariaLabel?: string;
27
+ onDebouncedChange?: (value: string) => void;
28
+ };
29
+
30
+ export default function SearchBar({
31
+ placeholder = "Search by title",
32
+ className,
33
+ inputClassName,
34
+ paramKey = "search",
35
+ defaultValue = "",
36
+ debounceMs = 400,
37
+ replace = true,
38
+ minLength = 0,
39
+ autoFocus = false,
40
+ showClear = true,
41
+ ariaLabel = "Search",
42
+ onDebouncedChange,
43
+ }: SearchBarProps) {
44
+ {{#if framework == "nextjs"}}
45
+ const router = useRouter();
46
+ const searchParams = useSearchParams();
47
+ {{else}}
48
+ const navigate = useNavigate();
49
+ const [searchParams] = useSearchParams();
50
+ {{/if}}
51
+
52
+ const [value, setValue] = React.useState<string>(() => {
53
+ return searchParams?.get(paramKey) ?? defaultValue;
54
+ });
55
+
56
+ React.useEffect(() => {
57
+ const urlVal = searchParams?.get(paramKey) ?? defaultValue;
58
+ setValue((prev) => (prev !== urlVal ? urlVal : prev));
59
+ }, [searchParams, paramKey, defaultValue]);
60
+
61
+ React.useEffect(() => {
62
+ const t = setTimeout(() => {
63
+ const trimmed = value.trim();
64
+ const meetsMin = trimmed.length >= minLength;
65
+ const qs = searchParams?.toString() ?? "";
66
+
67
+ const newUrl =
68
+ meetsMin && trimmed !== defaultValue
69
+ ? formUrlQuery({ params: qs, key: paramKey, value: trimmed })
70
+ : removeKeysFromQuery({ params: qs, keysToRemove: [paramKey] });
71
+
72
+ {{#if framework == "nextjs"}}
73
+ if (replace) router.replace(newUrl, { scroll: false });
74
+ else router.push(newUrl, { scroll: false });
75
+ {{else}}
76
+ navigate(newUrl, { replace });
77
+ {{/if}}
78
+
79
+ onDebouncedChange?.(trimmed);
80
+ }, debounceMs);
81
+
82
+ return () => clearTimeout(t);
83
+ }, [
84
+ value,
85
+ debounceMs,
86
+ replace,
87
+ {{#if framework == "nextjs"}}
88
+ router,
89
+ {{else}}
90
+ navigate,
91
+ {{/if}}
92
+ searchParams,
93
+ paramKey,
94
+ defaultValue,
95
+ minLength,
96
+ onDebouncedChange,
97
+ ]);
98
+
99
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
100
+ if (e.key === "Escape" && value) setValue("");
101
+ };
102
+
103
+ return (
104
+ <div className={cn("relative", className)}>
105
+ <div className="border-input bg-background flex items-center gap-2 overflow-hidden rounded-md">
106
+ <Input
107
+ type="text"
108
+ aria-label={ariaLabel}
109
+ placeholder={placeholder}
110
+ value={value}
111
+ onChange={(e) => setValue(e.target.value)}
112
+ onKeyDown={handleKeyDown}
113
+ autoFocus={autoFocus}
114
+ className={cn("pr-8", inputClassName, "bg-transparent")}
115
+ />
116
+ </div>
117
+
118
+ {showClear && value ? (
119
+ <button
120
+ type="button"
121
+ onClick={() => setValue("")}
122
+ className="text-muted-foreground hover:bg-accent hover:text-accent-foreground absolute inset-y-0 right-2 my-auto flex h-6 w-6 items-center justify-center rounded-md focus:outline-none"
123
+ aria-label="Clear search"
124
+ title="Clear"
125
+ >
126
+ ×
127
+ </button>
128
+ ) : null}
129
+ </div>
130
+ );
131
+ }
@@ -0,0 +1,118 @@
1
+ {{#if framework == "nextjs"}}
2
+ "use client";
3
+ {{/if}}
4
+
5
+ import * as React from "react";
6
+ {{#if framework == "nextjs"}}
7
+ import { useRouter, useSearchParams } from "next/navigation";
8
+ {{else}}
9
+ import { useNavigate, useSearchParams } from "react-router";
10
+ {{/if}}
11
+ import {
12
+ Select,
13
+ SelectContent,
14
+ SelectItem,
15
+ SelectTrigger,
16
+ SelectValue,
17
+ } from "@/components/ui/select";
18
+ import { formUrlQuery } from "@/lib/utils/url-helpers";
19
+
20
+ export type SortOption = {
21
+ value: string;
22
+ label: string;
23
+ };
24
+
25
+ export interface SortSelectProps {
26
+ items: SortOption[];
27
+ paramKey?: string;
28
+ placeholder?: string;
29
+ defaultValue?: string;
30
+ debounceMs?: number;
31
+ replace?: boolean;
32
+ className?: string;
33
+ onValueChange?: (value: string) => void;
34
+ ariaLabel?: string;
35
+ }
36
+
37
+ export default function SortSelect({
38
+ items,
39
+ paramKey = "sort",
40
+ placeholder = "Default sorting",
41
+ defaultValue = "",
42
+ debounceMs = 350,
43
+ replace = true,
44
+ className = "w-full md:w-48",
45
+ onValueChange,
46
+ ariaLabel = "Sort options",
47
+ }: SortSelectProps) {
48
+ {{#if framework == "nextjs"}}
49
+ const router = useRouter();
50
+ const searchParams = useSearchParams();
51
+ {{else}}
52
+ const navigate = useNavigate();
53
+ const [searchParams] = useSearchParams();
54
+ {{/if}}
55
+
56
+ const [value, setValue] = React.useState(() => {
57
+ return searchParams?.get(paramKey) ?? defaultValue;
58
+ });
59
+
60
+ React.useEffect(() => {
61
+ const urlVal = searchParams?.get(paramKey) ?? defaultValue;
62
+ setValue((prev) => (prev !== urlVal ? urlVal : prev));
63
+ }, [searchParams, paramKey, defaultValue]);
64
+
65
+ React.useEffect(() => {
66
+ const t = setTimeout(() => {
67
+ const qs = searchParams?.toString() ?? "";
68
+ const newUrl = formUrlQuery({
69
+ params: qs,
70
+ key: paramKey,
71
+ value: value && value !== defaultValue ? value : null,
72
+ });
73
+
74
+ {{#if framework == "nextjs"}}
75
+ if (replace) router.replace(newUrl, { scroll: false });
76
+ else router.push(newUrl, { scroll: false });
77
+ {{else}}
78
+ navigate(newUrl, { replace });
79
+ {{/if}}
80
+
81
+ onValueChange?.(value);
82
+ }, debounceMs);
83
+
84
+ return () => clearTimeout(t);
85
+ }, [
86
+ value,
87
+ debounceMs,
88
+ replace,
89
+ {{#if framework == "nextjs"}}
90
+ router,
91
+ {{else}}
92
+ navigate,
93
+ {{/if}}
94
+ searchParams,
95
+ paramKey,
96
+ defaultValue,
97
+ onValueChange,
98
+ ]);
99
+
100
+ const handleChange = React.useCallback((v: string | null) => setValue(v ?? ""), []);
101
+
102
+ return (
103
+ <div className="overflow-hidden rounded-md dark:bg-transparent">
104
+ <Select value={value} onValueChange={handleChange}>
105
+ <SelectTrigger className={className} aria-label={ariaLabel}>
106
+ <SelectValue placeholder={placeholder} />
107
+ </SelectTrigger>
108
+ <SelectContent>
109
+ {items.map((it) => (
110
+ <SelectItem key={it.value} value={it.value}>
111
+ {it.label}
112
+ </SelectItem>
113
+ ))}
114
+ </SelectContent>
115
+ </Select>
116
+ </div>
117
+ );
118
+ }
@@ -0,0 +1,77 @@
1
+ {{#if framework == "nextjs"}}
2
+ "use client";
3
+ {{/if}}
4
+
5
+ import * as React from "react";
6
+ {{#if framework == "nextjs"}}
7
+ import { useRouter, useSearchParams } from "next/navigation";
8
+ {{else}}
9
+ import { useNavigate, useSearchParams } from "react-router";
10
+ {{/if}}
11
+ import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
12
+ import { formUrlQuery } from "@/lib/utils/url-helpers";
13
+
14
+ export type TabItem = {
15
+ value: string;
16
+ label: string;
17
+ };
18
+
19
+ interface UrlTabsProps {
20
+ items: TabItem[];
21
+ paramKey?: string;
22
+ defaultValue?: string;
23
+ className?: string;
24
+ replace?: boolean;
25
+ onValueChange?: (value: string) => void;
26
+ children?: React.ReactNode;
27
+ }
28
+
29
+ export default function UrlTabs({
30
+ items,
31
+ paramKey = "tab",
32
+ defaultValue,
33
+ className,
34
+ replace = true,
35
+ onValueChange,
36
+ children,
37
+ }: UrlTabsProps) {
38
+ {{#if framework == "nextjs"}}
39
+ const router = useRouter();
40
+ const searchParams = useSearchParams();
41
+ {{else}}
42
+ const navigate = useNavigate();
43
+ const [searchParams] = useSearchParams();
44
+ {{/if}}
45
+
46
+ const activeTab = searchParams?.get(paramKey) ?? defaultValue ?? items[0]?.value ?? "";
47
+
48
+ const handleChange = (value: string) => {
49
+ const url = formUrlQuery({
50
+ params: searchParams?.toString() ?? "",
51
+ key: paramKey,
52
+ value: value === (defaultValue ?? items[0]?.value) ? null : value,
53
+ });
54
+
55
+ {{#if framework == "nextjs"}}
56
+ if (replace) router.replace(url, { scroll: false });
57
+ else router.push(url, { scroll: false });
58
+ {{else}}
59
+ navigate(url, { replace });
60
+ {{/if}}
61
+
62
+ onValueChange?.(value);
63
+ };
64
+
65
+ return (
66
+ <Tabs value={activeTab} onValueChange={handleChange} className={className}>
67
+ <TabsList>
68
+ {items.map((item) => (
69
+ <TabsTrigger key={item.value} value={item.value}>
70
+ {item.label}
71
+ </TabsTrigger>
72
+ ))}
73
+ </TabsList>
74
+ {children}
75
+ </Tabs>
76
+ );
77
+ }
@@ -0,0 +1,109 @@
1
+ {
2
+ "name": "components",
3
+ "type": "components",
4
+ "priority": 10,
5
+ "operations": [
6
+ {
7
+ "type": "create-file",
8
+ "source": "shared/shadcn/**",
9
+ "destination": "{{framework == 'nextjs' ? 'components/**' : 'src/components/**'}}",
10
+ "condition": { "framework": ["nextjs", "react"], "ui": "shadcn" }
11
+ },
12
+ {
13
+ "type": "create-file",
14
+ "source": "shared/hooks/**",
15
+ "destination": "{{framework == 'nextjs' ? 'hooks/**' : 'src/hooks/**'}}",
16
+ "condition": { "framework": ["nextjs", "react"] }
17
+ },
18
+ {
19
+ "type": "create-file",
20
+ "source": "shared/lib/**",
21
+ "destination": "{{framework == 'nextjs' ? 'lib/**' : 'src/lib/**'}}",
22
+ "condition": { "framework": ["nextjs", "react"] }
23
+ },
24
+ {
25
+ "type": "create-file",
26
+ "source": "shared/url/*",
27
+ "destination": "{{framework == 'nextjs' ? 'components/global/url/*' : 'src/components/global/url/*'}}",
28
+ "condition": { "framework": ["nextjs", "react"] }
29
+ },
30
+ {
31
+ "type": "patch-file",
32
+ "destination": "app/layout.tsx",
33
+ "condition": { "framework": "nextjs" },
34
+ "operations": [
35
+ {
36
+ "type": "add-import",
37
+ "imports": ["import { TooltipProvider } from \"@/components/ui/tooltip\";"]
38
+ },
39
+ {
40
+ "type": "add-code",
41
+ "before": "<QueryProviders>{children}</QueryProviders>",
42
+ "code": ["<TooltipProvider>"]
43
+ },
44
+ {
45
+ "type": "add-code",
46
+ "after": "<QueryProviders>{children}</QueryProviders>",
47
+ "code": ["</TooltipProvider>"]
48
+ }
49
+ ]
50
+ },
51
+ {
52
+ "type": "patch-file",
53
+ "destination": "src/main.tsx",
54
+ "condition": { "framework": "react" },
55
+ "operations": [
56
+ {
57
+ "type": "add-import",
58
+ "imports": ["import { TooltipProvider } from \"./components/ui/tooltip\";"]
59
+ },
60
+ {
61
+ "type": "add-code",
62
+ "before": "<QueryProvider>",
63
+ "code": ["<TooltipProvider>"]
64
+ },
65
+ {
66
+ "type": "add-code",
67
+ "after": "</QueryProvider>",
68
+ "code": ["</TooltipProvider>"]
69
+ }
70
+ ]
71
+ },
72
+ {
73
+ "type": "run-command",
74
+ "command": "{{packageManager=='pnpm' ? 'pnpm dlx shadcn@latest add button checkbox field input input-group textarea select badge command popover scroll-area toggle tabs dialog skeleton table -y' : packageManager=='yarn' ? 'yarn dlx shadcn@latest add button checkbox field input input-group textarea select badge command popover scroll-area toggle tabs dialog skeleton table -y' : packageManager=='bun' ? 'bun dlx shadcn@latest add button checkbox field input input-group textarea select badge command popover scroll-area toggle tabs dialog skeleton table -y' : 'npx shadcn@latest add button checkbox field input input-group textarea select badge command popover scroll-area toggle tabs dialog skeleton table -y'}}",
75
+ "condition": { "framework": ["react", "nextjs"], "ui": "shadcn" }
76
+ },
77
+ {
78
+ "type": "add-dependency",
79
+ "condition": { "framework": ["react", "nextjs"], "ui": "shadcn" },
80
+ "dependencies": {
81
+ "@dnd-kit/core": "^6.3.1",
82
+ "@dnd-kit/sortable": "^10.0.0",
83
+ "@dnd-kit/utilities": "^3.2.2",
84
+ "@tiptap/core": "^3.20.0",
85
+ "@tabler/icons-react": "^3.41.1",
86
+ "@tanstack/react-table": "^8.21.3",
87
+ "@tiptap/extension-blockquote": "^3.20.0",
88
+ "@tiptap/extension-bold": "^3.20.0",
89
+ "@tiptap/extension-bullet-list": "^3.20.0",
90
+ "@tiptap/extension-code": "^3.20.0",
91
+ "@tiptap/extension-hard-break": "^3.20.0",
92
+ "@tiptap/extension-heading": "^3.20.0",
93
+ "@tiptap/extension-highlight": "^3.20.0",
94
+ "@tiptap/extension-history": "^3.20.0",
95
+ "@tiptap/extension-horizontal-rule": "^3.20.0",
96
+ "@tiptap/extension-italic": "^3.20.0",
97
+ "@tiptap/extension-link": "^3.20.0",
98
+ "@tiptap/extension-list-item": "^3.20.0",
99
+ "@tiptap/extension-ordered-list": "^3.20.0",
100
+ "@tiptap/extension-strike": "^3.20.0",
101
+ "@tiptap/extension-table": "^3.20.0",
102
+ "@tiptap/extension-text-align": "^3.20.0",
103
+ "@tiptap/extension-underline": "^3.20.0",
104
+ "@tiptap/react": "^3.20.0",
105
+ "@tiptap/starter-kit": "^3.20.0"
106
+ }
107
+ }
108
+ ]
109
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "components",
3
+ "displayName": "Components",
4
+ "category": "components",
5
+ "description": "Pre-built form fields, data table, and more",
6
+ "supportedFrameworks": ["react", "nextjs"],
7
+ "compatibility": {
8
+ "frameworks": ["react", "nextjs"],
9
+ "languages": ["typescript"]
10
+ }
11
+ }
@@ -7,13 +7,13 @@
7
7
  "type": "create-file",
8
8
  "source": "lib/mongoose.ts",
9
9
  "destination": "src/database/mongoose.ts",
10
- "condition": { "framework": ["express"] }
10
+ "condition": { "framework": "express" }
11
11
  },
12
12
  {
13
13
  "type": "create-file",
14
14
  "source": "models/health.ts",
15
15
  "destination": "src/modules/health/health.model.ts",
16
- "condition": { "framework": ["express"] }
16
+ "condition": { "framework": "express" }
17
17
  },
18
18
  {
19
19
  "type": "patch-file",
@@ -31,18 +31,7 @@
31
31
  }
32
32
  ]
33
33
  },
34
- {
35
- "type": "create-file",
36
- "source": "lib/mongoose.ts",
37
- "destination": "server/database/mongoose.ts",
38
- "condition": { "framework": ["nextjs"] }
39
- },
40
- {
41
- "type": "create-file",
42
- "source": "models/health.ts",
43
- "destination": "server/database/models/health.model.ts",
44
- "condition": { "framework": ["nextjs"] }
45
- },
34
+
46
35
  {
47
36
  "type": "add-dependency",
48
37
  "dependencies": {
@@ -4,9 +4,9 @@
4
4
  "category": "database",
5
5
  "provider": "mongoose",
6
6
  "database": "mongodb",
7
- "supportedFrameworks": ["nextjs", "express"],
7
+ "supportedFrameworks": ["express"],
8
8
  "compatibility": {
9
- "frameworks": ["nextjs", "express"],
9
+ "frameworks": ["express"],
10
10
  "databases": ["mongoose"],
11
11
  "languages": ["typescript", "javascript"],
12
12
  "packageManagers": ["npm", "yarn", "pnpm", "bun"]
@@ -17,13 +17,7 @@
17
17
  "type": "create-file",
18
18
  "source": "lib/prisma.ts",
19
19
  "destination": "src/database/prisma.ts",
20
- "condition": { "framework": ["express"] }
21
- },
22
- {
23
- "type": "create-file",
24
- "source": "lib/prisma.ts",
25
- "destination": "server/database/prisma.ts",
26
- "condition": { "framework": ["nextjs"] }
20
+ "condition": { "framework": "express" }
27
21
  },
28
22
  {
29
23
  "type": "add-dependency",
@@ -34,11 +28,11 @@
34
28
  {
35
29
  "type": "add-script",
36
30
  "scripts": {
37
- "generate": "prisma generate",
38
- "push": "prisma db push",
39
- "seed": "tsx prisma/seed.ts",
40
- "migrate": "prisma migrate dev",
41
- "studio": "prisma studio"
31
+ "prisma:generate": "prisma generate",
32
+ "prisma:push": "prisma db push",
33
+ "prisma:seed": "tsx prisma/seed.ts",
34
+ "prisma:migrate": "prisma migrate dev",
35
+ "prisma:studio": "prisma studio"
42
36
  }
43
37
  },
44
38
  {
@@ -3,9 +3,9 @@
3
3
  "displayName": "Prisma",
4
4
  "category": "database",
5
5
  "provider": "prisma",
6
- "supportedFrameworks": ["nextjs", "express"],
6
+ "supportedFrameworks": ["express"],
7
7
  "compatibility": {
8
- "frameworks": ["nextjs", "express"],
8
+ "frameworks": ["express"],
9
9
  "databases": ["prisma"],
10
10
  "languages": ["typescript", "javascript"],
11
11
  "packageManagers": ["npm", "yarn", "pnpm", "bun"]