startx 0.0.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 (181) hide show
  1. package/.editorconfig +20 -0
  2. package/.prettierignore +24 -0
  3. package/.prettierrc.js +52 -0
  4. package/.vscode/settings.json +3 -0
  5. package/LICENSE +21 -0
  6. package/apps/core-server/.env.example +24 -0
  7. package/apps/core-server/Dockerfile +61 -0
  8. package/apps/core-server/eslint.config.mjs +47 -0
  9. package/apps/core-server/package.json +73 -0
  10. package/apps/core-server/src/config/custom-type.ts +54 -0
  11. package/apps/core-server/src/events/index.ts +37 -0
  12. package/apps/core-server/src/index.ts +19 -0
  13. package/apps/core-server/src/middlewares/auth-middleware.ts +50 -0
  14. package/apps/core-server/src/middlewares/cors-middleware.ts +6 -0
  15. package/apps/core-server/src/middlewares/error-middleware.ts +23 -0
  16. package/apps/core-server/src/middlewares/logger-middleware.ts +21 -0
  17. package/apps/core-server/src/middlewares/notfound-middleware.ts +14 -0
  18. package/apps/core-server/src/middlewares/serve-static.ts +24 -0
  19. package/apps/core-server/src/routes/files/router.ts +7 -0
  20. package/apps/core-server/src/routes/server.ts +36 -0
  21. package/apps/core-server/tsconfig.json +10 -0
  22. package/apps/core-server/tsdown.config.ts +14 -0
  23. package/biome.json +62 -0
  24. package/configs/eslint-config/package.json +60 -0
  25. package/configs/eslint-config/plugins.d.ts +1 -0
  26. package/configs/eslint-config/src/configs/base.ts +237 -0
  27. package/configs/eslint-config/src/configs/frontend.ts +62 -0
  28. package/configs/eslint-config/src/configs/node.ts +10 -0
  29. package/configs/eslint-config/src/plugin.ts +25 -0
  30. package/configs/eslint-config/src/rules/index.ts +30 -0
  31. package/configs/eslint-config/src/rules/no-argument-spread.test.ts +47 -0
  32. package/configs/eslint-config/src/rules/no-argument-spread.ts +96 -0
  33. package/configs/eslint-config/src/rules/no-dynamic-import-template.ts +32 -0
  34. package/configs/eslint-config/src/rules/no-internal-package-import.ts +40 -0
  35. package/configs/eslint-config/src/rules/no-interpolation-in-regular-string.ts +32 -0
  36. package/configs/eslint-config/src/rules/no-json-parse-json-stringify.test.ts +34 -0
  37. package/configs/eslint-config/src/rules/no-json-parse-json-stringify.ts +49 -0
  38. package/configs/eslint-config/src/rules/no-plain-errors.ts +50 -0
  39. package/configs/eslint-config/src/rules/no-skipped-tests.ts +61 -0
  40. package/configs/eslint-config/src/rules/no-top-level-relative-imports-in-backend-module.ts +27 -0
  41. package/configs/eslint-config/src/rules/no-type-unsafe-event-emitter.ts +33 -0
  42. package/configs/eslint-config/src/rules/no-uncaught-json-parse.test.ts +21 -0
  43. package/configs/eslint-config/src/rules/no-uncaught-json-parse.ts +45 -0
  44. package/configs/eslint-config/src/rules/no-untyped-config-class-field.ts +26 -0
  45. package/configs/eslint-config/src/rules/no-unused-param-catch-clause.ts +33 -0
  46. package/configs/eslint-config/src/rules/no-useless-catch-throw.test.ts +34 -0
  47. package/configs/eslint-config/src/rules/no-useless-catch-throw.ts +47 -0
  48. package/configs/eslint-config/src/utils/json.ts +21 -0
  49. package/configs/eslint-config/tsconfig.json +8 -0
  50. package/configs/eslint-config/tsdown.config.ts +11 -0
  51. package/configs/eslint-config/vitest.config.ts +3 -0
  52. package/configs/tsdown-config/package.json +14 -0
  53. package/configs/tsdown-config/src/config/tsdown.base.ts +13 -0
  54. package/configs/typescript-config/package.json +10 -0
  55. package/configs/typescript-config/tsconfig.common.json +32 -0
  56. package/configs/typescript-config/tsconfig.frontend.json +14 -0
  57. package/configs/typescript-config/tsconfig.node.json +9 -0
  58. package/configs/vitest-config/package.json +25 -0
  59. package/configs/vitest-config/src/base.ts +34 -0
  60. package/configs/vitest-config/src/frontend.ts +15 -0
  61. package/configs/vitest-config/src/node.ts +5 -0
  62. package/configs/vitest-config/tsconfig.json +7 -0
  63. package/package.json +47 -0
  64. package/packages/@repo/constants/eslint.config.mjs +21 -0
  65. package/packages/@repo/constants/package.json +19 -0
  66. package/packages/@repo/constants/src/api.ts +1 -0
  67. package/packages/@repo/constants/src/index.ts +8 -0
  68. package/packages/@repo/constants/src/time.ts +23 -0
  69. package/packages/@repo/constants/tsconfig.json +7 -0
  70. package/packages/@repo/db/eslint.config.mjs +21 -0
  71. package/packages/@repo/db/package.json +30 -0
  72. package/packages/@repo/db/src/functions.ts +122 -0
  73. package/packages/@repo/db/src/index.ts +20 -0
  74. package/packages/@repo/db/src/schema/common.ts +49 -0
  75. package/packages/@repo/db/src/schema/index.ts +1 -0
  76. package/packages/@repo/db/tsconfig.json +13 -0
  77. package/packages/@repo/lib/eslint.config.mjs +49 -0
  78. package/packages/@repo/lib/package.json +57 -0
  79. package/packages/@repo/lib/src/bucket-module/file-storage.ts +49 -0
  80. package/packages/@repo/lib/src/bucket-module/s3-storage.ts +114 -0
  81. package/packages/@repo/lib/src/bucket-module/utils.ts +11 -0
  82. package/packages/@repo/lib/src/command-module.ts +77 -0
  83. package/packages/@repo/lib/src/constants.ts +3 -0
  84. package/packages/@repo/lib/src/cookie-module.ts +42 -0
  85. package/packages/@repo/lib/src/custom-type.ts +54 -0
  86. package/packages/@repo/lib/src/env.ts +13 -0
  87. package/packages/@repo/lib/src/error-handlers-module/index.ts +11 -0
  88. package/packages/@repo/lib/src/file-system/index.ts +90 -0
  89. package/packages/@repo/lib/src/hashing-module.ts +9 -0
  90. package/packages/@repo/lib/src/index.ts +27 -0
  91. package/packages/@repo/lib/src/logger-module/log-config.ts +16 -0
  92. package/packages/@repo/lib/src/logger-module/logger.ts +78 -0
  93. package/packages/@repo/lib/src/logger-module/memory-profiler.ts +65 -0
  94. package/packages/@repo/lib/src/mail-module/api.ts +0 -0
  95. package/packages/@repo/lib/src/mail-module/mock.ts +8 -0
  96. package/packages/@repo/lib/src/mail-module/nodemailer.ts +45 -0
  97. package/packages/@repo/lib/src/notification-module/index.ts +172 -0
  98. package/packages/@repo/lib/src/notification-module/push-notification.ts +90 -0
  99. package/packages/@repo/lib/src/oauth2-client.ts +109 -0
  100. package/packages/@repo/lib/src/otp-module.ts +98 -0
  101. package/packages/@repo/lib/src/pagination-module.ts +49 -0
  102. package/packages/@repo/lib/src/token-module.ts +35 -0
  103. package/packages/@repo/lib/src/user-session.ts +117 -0
  104. package/packages/@repo/lib/src/utils.ts +42 -0
  105. package/packages/@repo/lib/src/validation-module.ts +187 -0
  106. package/packages/@repo/lib/tsconfig.json +7 -0
  107. package/packages/@repo/mail/package.json +29 -0
  108. package/packages/@repo/mail/src/emails/admin/OtpEmail.tsx +168 -0
  109. package/packages/@repo/mail/src/index.ts +13 -0
  110. package/packages/@repo/mail/tsconfig.build.json +14 -0
  111. package/packages/@repo/mail/tsconfig.json +13 -0
  112. package/packages/@repo/mail/tsdown.config.ts +9 -0
  113. package/packages/@repo/redis/eslint.config.mjs +8 -0
  114. package/packages/@repo/redis/package.json +31 -0
  115. package/packages/@repo/redis/src/index.ts +2 -0
  116. package/packages/@repo/redis/src/lib/redis-client.ts +23 -0
  117. package/packages/@repo/redis/src/lib/redis-module.ts +3 -0
  118. package/packages/@repo/redis/tsconfig.json +12 -0
  119. package/packages/ui/components.json +17 -0
  120. package/packages/ui/eslint.config.mjs +18 -0
  121. package/packages/ui/package.json +67 -0
  122. package/packages/ui/postcss.config.mjs +9 -0
  123. package/packages/ui/src/components/custom/form-wrapper.tsx +551 -0
  124. package/packages/ui/src/components/custom/grid-component.tsx +23 -0
  125. package/packages/ui/src/components/custom/hover-tool.tsx +38 -0
  126. package/packages/ui/src/components/custom/image-picker.tsx +109 -0
  127. package/packages/ui/src/components/custom/no-content.tsx +37 -0
  128. package/packages/ui/src/components/custom/page-container.tsx +24 -0
  129. package/packages/ui/src/components/custom/page-section.tsx +59 -0
  130. package/packages/ui/src/components/custom/simple-popover.tsx +29 -0
  131. package/packages/ui/src/components/custom/switch-component.tsx +20 -0
  132. package/packages/ui/src/components/custom/theme-provider.tsx +74 -0
  133. package/packages/ui/src/components/custom/typography.tsx +111 -0
  134. package/packages/ui/src/components/extensions/carousel.tsx +392 -0
  135. package/packages/ui/src/components/hooks/event/use-click.tsx +39 -0
  136. package/packages/ui/src/components/hooks/time/useDebounce.tsx +21 -0
  137. package/packages/ui/src/components/hooks/time/useInterval.tsx +35 -0
  138. package/packages/ui/src/components/hooks/time/useTimeout.tsx +19 -0
  139. package/packages/ui/src/components/hooks/time/useTimer.tsx +51 -0
  140. package/packages/ui/src/components/hooks/use-media-query.tsx +19 -0
  141. package/packages/ui/src/components/hooks/use-persistent-storage.tsx +52 -0
  142. package/packages/ui/src/components/hooks/use-update-effect.tsx +13 -0
  143. package/packages/ui/src/components/hooks/use-window-dimension.tsx +30 -0
  144. package/packages/ui/src/components/lib/utils.ts +242 -0
  145. package/packages/ui/src/components/lucide.tsx +3 -0
  146. package/packages/ui/src/components/sonner.tsx +1 -0
  147. package/packages/ui/src/components/ui/alert-dialog.tsx +116 -0
  148. package/packages/ui/src/components/ui/avatar.tsx +53 -0
  149. package/packages/ui/src/components/ui/badge.tsx +46 -0
  150. package/packages/ui/src/components/ui/breadcrumb.tsx +109 -0
  151. package/packages/ui/src/components/ui/button.tsx +96 -0
  152. package/packages/ui/src/components/ui/card.tsx +92 -0
  153. package/packages/ui/src/components/ui/carousel.tsx +243 -0
  154. package/packages/ui/src/components/ui/checkbox.tsx +32 -0
  155. package/packages/ui/src/components/ui/command.tsx +155 -0
  156. package/packages/ui/src/components/ui/dialog.tsx +127 -0
  157. package/packages/ui/src/components/ui/dropdown-menu.tsx +226 -0
  158. package/packages/ui/src/components/ui/form.tsx +165 -0
  159. package/packages/ui/src/components/ui/input-otp.tsx +76 -0
  160. package/packages/ui/src/components/ui/input.tsx +21 -0
  161. package/packages/ui/src/components/ui/label.tsx +24 -0
  162. package/packages/ui/src/components/ui/multiple-select.tsx +510 -0
  163. package/packages/ui/src/components/ui/popover.tsx +42 -0
  164. package/packages/ui/src/components/ui/select.tsx +170 -0
  165. package/packages/ui/src/components/ui/separator.tsx +28 -0
  166. package/packages/ui/src/components/ui/sheet.tsx +130 -0
  167. package/packages/ui/src/components/ui/skeleton.tsx +13 -0
  168. package/packages/ui/src/components/ui/spinner.tsx +16 -0
  169. package/packages/ui/src/components/ui/switch.tsx +28 -0
  170. package/packages/ui/src/components/ui/table.tsx +116 -0
  171. package/packages/ui/src/components/ui/tabs.tsx +54 -0
  172. package/packages/ui/src/components/ui/textarea.tsx +18 -0
  173. package/packages/ui/src/components/ui/timeline.tsx +118 -0
  174. package/packages/ui/src/components/ui/tooltip.tsx +30 -0
  175. package/packages/ui/src/components/util/n-formattor.ts +22 -0
  176. package/packages/ui/src/components/util/storage.ts +37 -0
  177. package/packages/ui/src/globals.css +87 -0
  178. package/packages/ui/tailwind.config.ts +94 -0
  179. package/packages/ui/tsconfig.json +12 -0
  180. package/pnpm-workspace.yaml +43 -0
  181. package/turbo.json +77 -0
@@ -0,0 +1,9 @@
1
+ /** @type {import('postcss-load-config').Config} */
2
+ const config = {
3
+ plugins: {
4
+ "@tailwindcss/postcss": {},
5
+ autoprefixer: {},
6
+ },
7
+ };
8
+
9
+ export default config;
@@ -0,0 +1,551 @@
1
+ import { format } from "date-fns";
2
+ import { Check, ChevronsUpDown, Plus, X } from "lucide-react";
3
+ import { type ReactNode, useState } from "react";
4
+ import {
5
+ type ArrayPath,
6
+ type Control,
7
+ type ControllerProps,
8
+ type FieldPath,
9
+ type FieldValues,
10
+ type UseFormReturn,
11
+ useFieldArray,
12
+ } from "react-hook-form";
13
+ import type { ClassNameValue } from "tailwind-merge";
14
+ import type { z } from "zod";
15
+
16
+ import { cn } from "../lib/utils";
17
+ import { Badge } from "../ui/badge";
18
+ import { Button } from "../ui/button";
19
+ import {
20
+ Command,
21
+ CommandEmpty,
22
+ CommandGroup,
23
+ CommandInput,
24
+ CommandItem,
25
+ CommandList,
26
+ } from "../ui/command";
27
+ import {
28
+ Form,
29
+ FormControl,
30
+ FormDescription,
31
+ FormField,
32
+ FormItem,
33
+ FormLabel,
34
+ FormMessage,
35
+ } from "../ui/form";
36
+ import { Input, type InputProps } from "../ui/input";
37
+ import { Label } from "../ui/label";
38
+ import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
39
+ import {
40
+ Select,
41
+ SelectContent,
42
+ SelectGroup,
43
+ SelectItem,
44
+ SelectLabel,
45
+ SelectTrigger,
46
+ SelectValue,
47
+ } from "../ui/select";
48
+ import { Textarea } from "../ui/textarea";
49
+
50
+ type SimpleFormFieldProps = {
51
+ label?: ReactNode;
52
+ description?: ReactNode;
53
+ };
54
+
55
+ type SelectOption = { label: string; value: string };
56
+ type RelationSelectOption = {
57
+ label: string;
58
+ options: Array<{
59
+ label: string;
60
+ value: string;
61
+ }>;
62
+ };
63
+
64
+ type FormSelectFieldProps<
65
+ TFieldValues extends FieldValues,
66
+ TName extends FieldPath<TFieldValues>,
67
+ > = Omit<ControllerProps<TFieldValues, TName>, "render"> & {
68
+ label?: ReactNode; // The label for the field
69
+ options: Array<SelectOption | RelationSelectOption>; // List of options to display in the select
70
+ className?: ClassNameValue;
71
+ placeholder?: ReactNode;
72
+ onChange?: (value: string) => void;
73
+ inputClassName?: ClassNameValue;
74
+ };
75
+
76
+ export const FormSelectField = <
77
+ TFieldValues extends FieldValues = FieldValues,
78
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
79
+ >({
80
+ control,
81
+ name,
82
+ label,
83
+ disabled,
84
+ className,
85
+ placeholder,
86
+ onChange,
87
+ defaultValue: defaultValueProp,
88
+ inputClassName,
89
+ options,
90
+ }: FormSelectFieldProps<TFieldValues, TName>) => {
91
+ const transformOptions = ({
92
+ options,
93
+ defaultValue,
94
+ }: {
95
+ options: Array<SelectOption | RelationSelectOption>;
96
+ defaultValue?: string;
97
+ }): Array<SelectOption | RelationSelectOption> => {
98
+ if (!defaultValue) return [...options];
99
+
100
+ // Check if defaultValue exists in either flat options or nested group options
101
+ const valueExists = options.some(opt => {
102
+ if ("options" in opt) {
103
+ // Check nested options in RelationSelectOption group
104
+ return opt.options.some(nestedOpt => nestedOpt.value === defaultValue);
105
+ }
106
+ // Check regular SelectOption
107
+ return opt.value === defaultValue;
108
+ });
109
+
110
+ if (!valueExists) {
111
+ // Prepend the default value as a new option
112
+ return [{ label: defaultValue, value: defaultValue }, ...options];
113
+ }
114
+
115
+ return [...options];
116
+ };
117
+ return (
118
+ <FormField
119
+ control={control}
120
+ name={name}
121
+ render={({ field }) => (
122
+ <FormItem className={cn("flex flex-col", className)}>
123
+ <FormLabel className="self-start">{label}</FormLabel>
124
+ <FormControl>
125
+ <Select
126
+ disabled={disabled}
127
+ onValueChange={value => {
128
+ field.onChange(value);
129
+ onChange?.(value);
130
+ }}
131
+ value={field.value ?? defaultValueProp ?? ""}
132
+ >
133
+ <SelectTrigger className={cn("bg-background w-full", inputClassName)}>
134
+ <SelectValue placeholder={placeholder ?? "Select"} />
135
+ </SelectTrigger>
136
+ <SelectContent>
137
+ {transformOptions({ options, defaultValue: field.value }).map(option => {
138
+ if ("options" in option) {
139
+ return (
140
+ <SelectGroup key={option.label}>
141
+ <SelectLabel>{option.label}</SelectLabel>
142
+ {option.options.map(option => (
143
+ <SelectItem key={option.value} value={option.value}>
144
+ {option.label}
145
+ </SelectItem>
146
+ ))}
147
+ </SelectGroup>
148
+ );
149
+ }
150
+ return (
151
+ <SelectItem key={option.value} value={option.value}>
152
+ {option.label}
153
+ </SelectItem>
154
+ );
155
+ })}
156
+ </SelectContent>
157
+ </Select>
158
+ </FormControl>
159
+ <FormMessage />
160
+ </FormItem>
161
+ )}
162
+ />
163
+ );
164
+ };
165
+ export const FormTextField = <
166
+ TFieldValues extends FieldValues = FieldValues,
167
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
168
+ >(
169
+ props: Omit<
170
+ SimpleFormFieldProps &
171
+ ControllerProps<TFieldValues, TName> &
172
+ InputProps &
173
+ React.RefAttributes<HTMLInputElement>,
174
+ "render"
175
+ >
176
+ ) => {
177
+ return (
178
+ <FormField
179
+ control={props.control}
180
+ name={props.name}
181
+ render={({ field }) => (
182
+ <FormItem className="flex flex-col">
183
+ <Label className="self-start">{props.label}</Label>
184
+ <FormControl>
185
+ <Input {...field} {...props} />
186
+ </FormControl>
187
+ {props.description ? <FormDescription>{props.description}</FormDescription> : null}
188
+ <FormMessage />
189
+ </FormItem>
190
+ )}
191
+ />
192
+ );
193
+ };
194
+ export const FormTextAreaField = <
195
+ TFieldValues extends FieldValues = FieldValues,
196
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
197
+ >(
198
+ props: Omit<
199
+ SimpleFormFieldProps &
200
+ ControllerProps<TFieldValues, TName> &
201
+ React.TextareaHTMLAttributes<HTMLTextAreaElement> &
202
+ React.RefAttributes<HTMLTextAreaElement>,
203
+ "render"
204
+ >
205
+ ) => {
206
+ return (
207
+ <FormField
208
+ control={props.control}
209
+ name={props.name}
210
+ render={({ field }) => (
211
+ <FormItem className="flex flex-col ">
212
+ {props.label ? <Label className="self-start">{props.label}</Label> : null}
213
+ <FormControl>
214
+ <Textarea {...field} {...props} ref={field.ref} />
215
+ </FormControl>
216
+ {props.description ? <FormDescription>{props.description}</FormDescription> : null}
217
+ <FormMessage />
218
+ </FormItem>
219
+ )}
220
+ />
221
+ );
222
+ };
223
+ export const FormNumberField = <
224
+ TFieldValues extends FieldValues = FieldValues,
225
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
226
+ >(
227
+ props: Omit<
228
+ SimpleFormFieldProps &
229
+ ControllerProps<TFieldValues, TName> &
230
+ InputProps &
231
+ React.RefAttributes<HTMLInputElement>,
232
+ "render"
233
+ >
234
+ ) => {
235
+ return (
236
+ <FormField
237
+ control={props.control}
238
+ name={props.name}
239
+ render={({ field }) => (
240
+ <FormItem className="flex flex-col">
241
+ <FormLabel className="self-start">{props.label}</FormLabel>
242
+ <FormControl>
243
+ <Input
244
+ inputMode="numeric"
245
+ {...field}
246
+ onChange={e => {
247
+ if (!isNaN(Number(e.currentTarget.value)))
248
+ field.onChange(Number(e.currentTarget.value));
249
+ }}
250
+ {...props}
251
+ />
252
+ </FormControl>
253
+ {props.description ? <FormDescription>{props.description}</FormDescription> : null}
254
+ <FormMessage />
255
+ </FormItem>
256
+ )}
257
+ />
258
+ );
259
+ };
260
+
261
+ export type FormWrapperProps<T extends FieldValues> = {
262
+ onSubmit: (data: T) => void;
263
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
264
+ formData: UseFormReturn<T, unknown, any>;
265
+ children: React.ReactNode;
266
+ className?: string;
267
+ };
268
+
269
+ type MultipleFormItemProps<T extends z.ZodType, K extends ArrayPath<z.infer<T>>> = {
270
+ control: Control<z.infer<T>>;
271
+ name: K;
272
+ label?: string;
273
+ addMoreBtn?: React.ReactNode;
274
+ className?: ClassNameValue;
275
+ wrapperClassName?: ClassNameValue;
276
+ children: (field: { id: string; index: number; remove: () => void }) => React.ReactNode;
277
+ defaultValue: z.infer<T>[K][number];
278
+ };
279
+
280
+ export const MultipleFormItem = <T extends z.ZodType, K extends ArrayPath<z.infer<T>>>({
281
+ control,
282
+ name,
283
+ addMoreBtn,
284
+ children,
285
+ label,
286
+ wrapperClassName,
287
+ className,
288
+ defaultValue,
289
+ }: MultipleFormItemProps<T, K>) => {
290
+ const { fields, append, remove } = useFieldArray({
291
+ control,
292
+ name,
293
+ });
294
+
295
+ return (
296
+ <div className="multiple-form-item">
297
+ {label ? <p className="mt-6 text-sm ">{label}</p> : null}
298
+ <div className={cn(wrapperClassName)}>
299
+ {fields.map((field, index) => (
300
+ <div key={field.id} className={cn("flex flex-col gap-2 max-w-xl", className)}>
301
+ {children({ id: field.id, index, remove: () => remove(index) })}
302
+ </div>
303
+ ))}
304
+ {addMoreBtn ? (
305
+ <div onClick={() => append(defaultValue)}>{addMoreBtn}</div>
306
+ ) : (
307
+ <Button
308
+ className="w-1/3 mt-4 flex gap-1"
309
+ variant="outline"
310
+ type="button"
311
+ size={"sm"}
312
+ onClick={() => append(defaultValue)}
313
+ >
314
+ <Plus />
315
+ <span>Add {label?.toLowerCase() ?? "item"}</span>
316
+ </Button>
317
+ )}
318
+ </div>
319
+ </div>
320
+ );
321
+ };
322
+
323
+ export function FormWrapper<T extends FieldValues>(props: FormWrapperProps<T>) {
324
+ return (
325
+ <Form {...props.formData}>
326
+ <form
327
+ className={cn("flex flex-col gap-4", props.className)}
328
+ onSubmit={async e => {
329
+ e.preventDefault();
330
+ e.stopPropagation();
331
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
332
+ await props.formData.handleSubmit(data => props.onSubmit(data))();
333
+ }}
334
+ >
335
+ {props.children}
336
+ </form>
337
+ </Form>
338
+ );
339
+ }
340
+
341
+ export const FormDefaultDateField = <
342
+ TFieldValues extends FieldValues = FieldValues,
343
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
344
+ >(
345
+ props: Omit<
346
+ SimpleFormFieldProps &
347
+ ControllerProps<TFieldValues, TName> &
348
+ InputProps &
349
+ React.RefAttributes<HTMLInputElement>,
350
+ "render"
351
+ >
352
+ ) => {
353
+ const safeFormat = (date: Date | string | undefined | null) => {
354
+ try {
355
+ if (!date) return undefined;
356
+
357
+ const parsedDate = typeof date === "string" ? new Date(date) : date;
358
+
359
+ // Extract UTC date components
360
+ const year = parsedDate.getUTCFullYear();
361
+ const month = parsedDate.getUTCMonth();
362
+ const day = parsedDate.getUTCDate();
363
+
364
+ // Create a Date object at midnight UTC
365
+ const utcMidnight = new Date(Date.UTC(year, month, day));
366
+ return format(utcMidnight, "yyyy-MM-dd");
367
+ } catch (error) {
368
+ return undefined;
369
+ }
370
+ };
371
+
372
+ const safeParse = (dateString: string | undefined | null) => {
373
+ try {
374
+ if (!dateString) return undefined;
375
+
376
+ // Parse input date as local time
377
+ const localDate = new Date(dateString);
378
+
379
+ // Get local date components
380
+ const year = localDate.getFullYear();
381
+ const month = localDate.getMonth();
382
+ const day = localDate.getDate();
383
+
384
+ // Create Date object at midnight UTC of the local date
385
+ return new Date(Date.UTC(year, month, day));
386
+ } catch (error) {
387
+ console.error(error);
388
+ return undefined;
389
+ }
390
+ };
391
+
392
+ return (
393
+ <FormField
394
+ control={props.control}
395
+ name={props.name}
396
+ render={({ field }) => (
397
+ <FormItem className="flex flex-col">
398
+ <FormLabel className="self-start">{props.label}</FormLabel>
399
+ <FormControl>
400
+ <Input
401
+ {...field}
402
+ {...props}
403
+ value={field.value || ""}
404
+ onChange={e => field.onChange(e.target.value)}
405
+ type="date"
406
+ max="9999-12-31"
407
+ />
408
+ </FormControl>
409
+ {props.description ? <FormDescription>{props.description}</FormDescription> : null}
410
+ <FormMessage />
411
+ </FormItem>
412
+ )}
413
+ />
414
+ );
415
+ };
416
+ type FormMultiSelectFieldProps<
417
+ TFieldValues extends FieldValues,
418
+ TName extends FieldPath<TFieldValues>,
419
+ > = Omit<ControllerProps<TFieldValues, TName>, "render"> & {
420
+ label?: React.ReactNode;
421
+ options: Array<SelectOption | RelationSelectOption>;
422
+ className?: string;
423
+ placeholder?: React.ReactNode;
424
+ onChange?: (value: string[]) => void;
425
+ inputClassName?: string;
426
+ showSelected?: boolean;
427
+ };
428
+
429
+ export function FormMultiSelectField<
430
+ TFieldValues extends FieldValues = FieldValues,
431
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
432
+ >({
433
+ control,
434
+ name,
435
+ label,
436
+ className,
437
+ placeholder,
438
+ showSelected = true,
439
+ onChange,
440
+ defaultValue,
441
+ inputClassName,
442
+ disabled,
443
+ options,
444
+ }: FormMultiSelectFieldProps<TFieldValues, TName>) {
445
+ const [open, setOpen] = useState(false);
446
+
447
+ return (
448
+ <FormField
449
+ control={control}
450
+ name={name}
451
+ defaultValue={defaultValue}
452
+ render={({ field }) => {
453
+ const selected: string[] = field.value ?? [];
454
+
455
+ const toggleOption = (value: string) => {
456
+ const newValue = selected.includes(value)
457
+ ? selected.filter(v => v !== value)
458
+ : [...selected, value];
459
+ field.onChange(newValue);
460
+ onChange?.(newValue);
461
+ };
462
+
463
+ return (
464
+ <FormItem className={cn("flex flex-col", className)}>
465
+ {label ? <FormLabel>{label}</FormLabel> : null}
466
+ <FormControl className="w-full">
467
+ <Popover open={open} onOpenChange={setOpen}>
468
+ <PopoverTrigger asChild disabled={disabled}>
469
+ <Button
470
+ variant="outline"
471
+ // role="combobox"
472
+ size={"sm"}
473
+ // aria-expanded={open}
474
+ className={cn("w-full justify-between", inputClassName)}
475
+ >
476
+ {selected.length > 0
477
+ ? `${selected.length} selected`
478
+ : (placeholder ?? "Select")}
479
+ <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
480
+ </Button>
481
+ </PopoverTrigger>
482
+ <div className="w-full">
483
+ <PopoverContent className="max-w-full min-w-full p-0">
484
+ <Command className="w-full">
485
+ <CommandInput placeholder="Search..." />
486
+ <CommandList>
487
+ <CommandEmpty>No options found.</CommandEmpty>
488
+ {options.map(opt =>
489
+ "options" in opt ? (
490
+ <CommandGroup key={opt.label} heading={opt.label}>
491
+ {opt.options.map(nested => (
492
+ <CommandItem
493
+ key={nested.value}
494
+ onSelect={() => toggleOption(nested.value)}
495
+ >
496
+ <Check
497
+ className={cn(
498
+ "mr-2 h-4 w-4",
499
+ selected.includes(nested.value) ? "opacity-100" : "opacity-0"
500
+ )}
501
+ />
502
+ {nested.label}
503
+ </CommandItem>
504
+ ))}
505
+ </CommandGroup>
506
+ ) : (
507
+ <CommandItem key={opt.value} onSelect={() => toggleOption(opt.value)}>
508
+ <Check
509
+ className={cn(
510
+ "mr-2 h-4 w-4",
511
+ selected.includes(opt.value) ? "opacity-100" : "opacity-0"
512
+ )}
513
+ />
514
+ {opt.label}
515
+ </CommandItem>
516
+ )
517
+ )}
518
+ </CommandList>
519
+ </Command>
520
+ </PopoverContent>
521
+ </div>
522
+ </Popover>
523
+ </FormControl>
524
+ <FormMessage />
525
+
526
+ {showSelected ? <div className=" flex flex-wrap gap-2">
527
+ {selected.map(value => {
528
+ const label =
529
+ options
530
+ .flatMap(o => ("options" in o ? o.options : o))
531
+ .find(o => o.value === value)?.label ?? value;
532
+ return (
533
+ <Badge
534
+ onClick={() => !disabled && toggleOption(value)}
535
+ key={value}
536
+ aria-disabled
537
+ variant="secondary"
538
+ className={cn("px-2 py-1", !disabled && "cursor-pointer")}
539
+ >
540
+ {label}
541
+ {!disabled && <X className="ml-1 h-3 w-3 cursor-pointer" />}
542
+ </Badge>
543
+ );
544
+ })}
545
+ </div> : null}
546
+ </FormItem>
547
+ );
548
+ }}
549
+ />
550
+ );
551
+ }
@@ -0,0 +1,23 @@
1
+ import { cn } from '../lib/utils';
2
+
3
+ export type GridProps = {
4
+ children: React.ReactNode;
5
+ grow?: boolean;
6
+ className?: string;
7
+ };
8
+
9
+ const Grid = (props: GridProps) => {
10
+ return (
11
+ <div
12
+ className={cn(
13
+ 'grid sm:grid-cols-12 gap-2 grid-cols-1',
14
+ props.grow ? 'min-h-[calc(100vh-60px)] py-2' : '',
15
+ props.className,
16
+ )}
17
+ >
18
+ {props.children}
19
+ </div>
20
+ );
21
+ };
22
+
23
+ export { Grid };
@@ -0,0 +1,38 @@
1
+ import type { ReactNode } from "react";
2
+ import type { ClassNameValue } from "tailwind-merge";
3
+
4
+ import { cn } from "../lib/utils";
5
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip";
6
+
7
+ const HoverTool = ({
8
+ children,
9
+ side,
10
+ hideHover,
11
+ content,
12
+ className,
13
+ skipDelay,
14
+ delay,
15
+ }: {
16
+ delay?: number;
17
+ hideHover?: boolean;
18
+ side?: "top" | "bottom" | "left" | "right" | undefined;
19
+ skipDelay?: number;
20
+ children: ReactNode;
21
+ content: ReactNode;
22
+ className?: ClassNameValue;
23
+ }) => {
24
+ return (
25
+ <TooltipProvider delayDuration={delay} skipDelayDuration={skipDelay}>
26
+ <Tooltip>
27
+ <TooltipTrigger className={cn(className)}>{children}</TooltipTrigger>
28
+ {!hideHover && (
29
+ <TooltipContent className="z-50" style={{ zIndex: 999 }} side={side}>
30
+ {content}
31
+ </TooltipContent>
32
+ )}
33
+ </Tooltip>
34
+ </TooltipProvider>
35
+ );
36
+ };
37
+
38
+ export { HoverTool };