startx 1.0.2 → 1.0.3
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/.dockerignore +4 -0
- package/apps/cli/src/commands/index.ts +1 -1
- package/apps/cli/src/commands/{common → test}/test.ts +4 -2
- package/apps/cli/tsconfig.json +0 -1
- package/apps/core-server/Dockerfile +5 -4
- package/apps/core-server/package.json +1 -1
- package/apps/core-server/tsconfig.json +1 -1
- package/apps/queue-worker/package.json +1 -1
- package/apps/queue-worker/tsconfig.json +1 -1
- package/apps/startx-cli/dist/index.mjs +68 -53
- package/apps/startx-cli/src/commands/package.ts +453 -0
- package/apps/startx-cli/src/configs/scripts.ts +18 -2
- package/apps/startx-cli/src/index.ts +2 -4
- package/apps/startx-cli/src/types.ts +2 -4
- package/apps/startx-cli/src/utils/inquirer.ts +8 -1
- package/apps/web-client/.dockerignore +4 -0
- package/apps/web-client/app/app.css +1 -0
- package/apps/web-client/app/components.json +23 -0
- package/apps/web-client/app/config/auth/auth-state.ts +59 -0
- package/apps/web-client/app/config/axios-client.ts +87 -0
- package/apps/web-client/app/config/env.ts +5 -0
- package/apps/web-client/app/entry.client.tsx +7 -0
- package/apps/web-client/app/eslint.config.ts +4 -0
- package/apps/web-client/app/root.tsx +77 -0
- package/apps/web-client/app/routes/home.tsx +12 -0
- package/apps/web-client/app/routes.ts +3 -0
- package/apps/web-client/eslint.config.ts +4 -0
- package/apps/web-client/package.json +55 -0
- package/apps/web-client/react-router.config.ts +7 -0
- package/apps/web-client/tsconfig.json +22 -0
- package/apps/web-client/vite-env.d.ts +8 -0
- package/apps/web-client/vite.config.ts +30 -0
- package/biome.json +5 -0
- package/configs/eslint-config/eslint.config.ts +1 -0
- package/configs/eslint-config/src/configs/base.ts +0 -1
- package/configs/eslint-config/src/configs/frontend.ts +1 -1
- package/configs/eslint-config/tsconfig.json +1 -1
- package/configs/typescript-config/tsconfig.frontend.json +1 -1
- package/configs/vitest-config/tsconfig.json +1 -1
- package/package.json +1 -1
- package/packages/@db/drizzle/tsconfig.json +1 -1
- package/packages/@db/sqlite/tsconfig.json +1 -1
- package/packages/@repo/env/package.json +1 -2
- package/packages/@repo/env/src/utils.ts +17 -11
- package/packages/@repo/lib/package.json +3 -1
- package/packages/@repo/lib/src/session-module/i-session.ts +108 -0
- package/packages/@repo/lib/src/session-module/index.ts +8 -111
- package/packages/@repo/lib/src/session-module/redis-session.ts +44 -0
- package/packages/@repo/lib/tsconfig.json +0 -1
- package/packages/@repo/logger/package.json +0 -1
- package/packages/@repo/logger/tsconfig.json +1 -1
- package/packages/@repo/mail/tsconfig.json +1 -1
- package/packages/@repo/redis/tsconfig.json +1 -1
- package/packages/aix/package.json +2 -0
- package/packages/aix/src/providers/ai-interface.ts +4 -4
- package/packages/aix/src/providers/bedrock/bedrock.ts +261 -0
- package/packages/aix/src/providers/default-models.ts +65 -0
- package/packages/aix/src/providers/openai/openai.ts +2 -2
- package/packages/aix/src/providers/providers.ts +11 -0
- package/packages/aix/src/providers/types.ts +1 -1
- package/packages/{constants → common}/package.json +4 -2
- package/packages/{constants/src/index.ts → common/src/constants.ts} +0 -5
- package/packages/common/src/types/users.ts +10 -0
- package/packages/{constants → common}/tsconfig.json +0 -3
- package/packages/ui/components.json +15 -8
- package/packages/ui/package.json +23 -36
- package/packages/ui/src/api/axios/i-client.ts +40 -0
- package/packages/ui/src/api/index.ts +6 -0
- package/packages/ui/src/api/query-provider.tsx +34 -0
- package/packages/ui/src/api/use-api/api-builder.ts +139 -0
- package/packages/ui/src/api/use-api/api-helpers.ts +165 -0
- package/packages/ui/src/api/use-api/api-types.ts +138 -0
- package/packages/ui/src/api/use-api/query-factory.ts +66 -0
- package/packages/ui/src/api/use-api/react-query/types.ts +64 -0
- package/packages/ui/src/api/use-api/react-query/use-api-client.ts +56 -0
- package/packages/ui/src/api/use-api/react-query/use-api.ts +297 -0
- package/packages/ui/src/components/custom/form-wrapper.tsx +113 -160
- package/packages/ui/src/components/custom/grid-component.tsx +4 -4
- package/packages/ui/src/components/custom/hover-tool.tsx +1 -1
- package/packages/ui/src/components/custom/image-picker.tsx +18 -20
- package/packages/ui/src/components/custom/no-content.tsx +6 -16
- package/packages/ui/src/components/custom/page-section.tsx +14 -17
- package/packages/ui/src/components/custom/simple-popover.tsx +5 -9
- package/packages/ui/src/components/custom/theme-provider.tsx +117 -42
- package/packages/ui/src/components/custom/typography.tsx +20 -22
- package/packages/ui/src/components/extensions/timeline.tsx +100 -0
- package/packages/ui/src/components/ui/alert-dialog.tsx +46 -108
- package/packages/ui/src/components/ui/avatar.tsx +79 -42
- package/packages/ui/src/components/ui/badge.tsx +29 -34
- package/packages/ui/src/components/ui/breadcrumb.tsx +65 -81
- package/packages/ui/src/components/ui/button.tsx +80 -80
- package/packages/ui/src/components/ui/card.tsx +48 -69
- package/packages/ui/src/components/ui/carousel.tsx +184 -211
- package/packages/ui/src/components/ui/checkbox.tsx +21 -24
- package/packages/ui/src/components/ui/command.tsx +121 -102
- package/packages/ui/src/components/ui/dialog.tsx +45 -32
- package/packages/ui/src/components/ui/dropdown-menu.tsx +45 -33
- package/packages/ui/src/components/ui/field.tsx +218 -0
- package/packages/ui/src/components/ui/form.tsx +63 -76
- package/packages/ui/src/components/ui/input-group.tsx +137 -0
- package/packages/ui/src/components/ui/input-otp.tsx +60 -50
- package/packages/ui/src/components/ui/input.tsx +16 -15
- package/packages/ui/src/components/ui/label.tsx +14 -17
- package/packages/ui/src/components/ui/multiple-select.tsx +22 -33
- package/packages/ui/src/components/ui/popover.tsx +20 -8
- package/packages/ui/src/components/ui/select.tsx +33 -34
- package/packages/ui/src/components/ui/separator.tsx +8 -8
- package/packages/ui/src/components/ui/sheet.tsx +32 -59
- package/packages/ui/src/components/ui/sidebar.tsx +654 -0
- package/packages/ui/src/components/ui/skeleton.tsx +2 -8
- package/packages/ui/src/components/ui/sonner.tsx +39 -0
- package/packages/ui/src/components/ui/spinner.tsx +6 -13
- package/packages/ui/src/components/ui/switch.tsx +15 -10
- package/packages/ui/src/components/ui/table.tsx +48 -89
- package/packages/ui/src/components/ui/tabs.tsx +37 -15
- package/packages/ui/src/components/ui/textarea.tsx +13 -13
- package/packages/ui/src/components/ui/tooltip.tsx +37 -23
- package/packages/ui/src/{components/hooks → hooks}/event/use-click.tsx +6 -10
- package/packages/ui/src/hooks/time/use-timer.tsx +51 -0
- package/packages/ui/src/hooks/use-media-query.tsx +19 -0
- package/packages/ui/src/hooks/use-mobile.tsx +17 -0
- package/packages/ui/src/{components/hooks → hooks}/use-update-effect.tsx +2 -2
- package/packages/ui/src/lib/utils.ts +113 -0
- package/packages/ui/src/styles/globals.css +311 -0
- package/packages/ui/src/styles/tailwind.css +89 -0
- package/packages/ui/tsconfig.json +7 -9
- package/pnpm-workspace.yaml +74 -64
- package/packages/ui/postcss.config.mjs +0 -9
- package/packages/ui/src/components/extensions/carousel.tsx +0 -392
- package/packages/ui/src/components/hooks/time/useTimer.tsx +0 -51
- package/packages/ui/src/components/hooks/use-media-query.tsx +0 -19
- package/packages/ui/src/components/lib/utils.ts +0 -242
- package/packages/ui/src/components/ui/timeline.tsx +0 -118
- package/packages/ui/src/components/util/n-formattor.ts +0 -22
- package/packages/ui/src/components/util/storage.ts +0 -37
- package/packages/ui/src/globals.css +0 -87
- package/packages/ui/tailwind.config.ts +0 -94
- /package/packages/{constants → common}/eslint.config.ts +0 -0
- /package/packages/{constants → common}/src/api.ts +0 -0
- /package/packages/{constants → common}/src/time.ts +0 -0
- /package/packages/{constants → common}/vitest.config.ts +0 -0
- /package/packages/ui/src/{components/hooks/time/useDebounce.tsx → hooks/time/use-debounce.tsx} +0 -0
- /package/packages/ui/src/{components/hooks/time/useInterval.tsx → hooks/time/use-interval.tsx} +0 -0
- /package/packages/ui/src/{components/hooks/time/useTimeout.tsx → hooks/time/use-timeout.tsx} +0 -0
- /package/packages/ui/src/{components/hooks → hooks}/use-persistent-storage.tsx +0 -0
- /package/packages/ui/src/{components/hooks → hooks}/use-window-dimension.tsx +0 -0
- /package/packages/ui/src/{components/sonner.tsx → sonner.ts} +0 -0
|
@@ -1,392 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import type { EmblaOptionsType } from 'embla-carousel';
|
|
4
|
-
import useEmblaCarousel from 'embla-carousel-react';
|
|
5
|
-
import { ChevronRightIcon, ChevronLeftIcon } from 'lucide-react';
|
|
6
|
-
import type React from 'react';
|
|
7
|
-
import { forwardRef, useCallback, useContext, useEffect, useState, createContext } from 'react';
|
|
8
|
-
|
|
9
|
-
import { Button } from '@/components/ui/button';
|
|
10
|
-
|
|
11
|
-
import { cn } from '../lib/utils';
|
|
12
|
-
|
|
13
|
-
type CarouselContextProps = {
|
|
14
|
-
carouselOptions?: EmblaOptionsType;
|
|
15
|
-
orientation?: 'vertical' | 'horizontal';
|
|
16
|
-
plugins?: Parameters<typeof useEmblaCarousel>[1];
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
type DirectionOption = 'ltr' | 'rtl' | undefined;
|
|
20
|
-
|
|
21
|
-
type CarouselContextType = {
|
|
22
|
-
emblaMainApi: ReturnType<typeof useEmblaCarousel>[1];
|
|
23
|
-
mainRef: ReturnType<typeof useEmblaCarousel>[0];
|
|
24
|
-
thumbsRef: ReturnType<typeof useEmblaCarousel>[0];
|
|
25
|
-
scrollNext: () => void;
|
|
26
|
-
scrollPrev: () => void;
|
|
27
|
-
canScrollNext: boolean;
|
|
28
|
-
canScrollPrev: boolean;
|
|
29
|
-
activeIndex: number;
|
|
30
|
-
onThumbClick: (index: number) => void;
|
|
31
|
-
handleKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => void;
|
|
32
|
-
orientation: 'vertical' | 'horizontal';
|
|
33
|
-
direction: DirectionOption;
|
|
34
|
-
} & CarouselContextProps;
|
|
35
|
-
|
|
36
|
-
const useCarousel = () => {
|
|
37
|
-
const context = useContext(CarouselContext);
|
|
38
|
-
if (!context) {
|
|
39
|
-
throw new Error('useCarousel must be used within a CarouselProvider');
|
|
40
|
-
}
|
|
41
|
-
return context;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const CarouselContext = createContext<CarouselContextType | null>(null);
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Carousel Docs: {@link: https://shadcn-extension.vercel.app/docs/carousel}
|
|
48
|
-
*/
|
|
49
|
-
|
|
50
|
-
const Carousel = forwardRef<
|
|
51
|
-
HTMLDivElement,
|
|
52
|
-
CarouselContextProps & React.HTMLAttributes<HTMLDivElement> & {dir: DirectionOption}
|
|
53
|
-
>(
|
|
54
|
-
(
|
|
55
|
-
{ carouselOptions, orientation = 'horizontal', dir, plugins, children, className, ...props },
|
|
56
|
-
ref,
|
|
57
|
-
) => {
|
|
58
|
-
const [emblaMainRef, emblaMainApi] = useEmblaCarousel(
|
|
59
|
-
{
|
|
60
|
-
...carouselOptions,
|
|
61
|
-
axis: orientation === 'vertical' ? 'y' : 'x',
|
|
62
|
-
direction: carouselOptions?.direction ?? (dir as DirectionOption),
|
|
63
|
-
},
|
|
64
|
-
plugins,
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
const [emblaThumbsRef, emblaThumbsApi] = useEmblaCarousel(
|
|
68
|
-
{
|
|
69
|
-
...carouselOptions,
|
|
70
|
-
axis: orientation === 'vertical' ? 'y' : 'x',
|
|
71
|
-
direction: carouselOptions?.direction ?? (dir as DirectionOption),
|
|
72
|
-
containScroll: 'keepSnaps',
|
|
73
|
-
dragFree: true,
|
|
74
|
-
},
|
|
75
|
-
plugins,
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
const [canScrollPrev, setCanScrollPrev] = useState<boolean>(false);
|
|
79
|
-
const [canScrollNext, setCanScrollNext] = useState<boolean>(false);
|
|
80
|
-
const [activeIndex, setActiveIndex] = useState<number>(0);
|
|
81
|
-
|
|
82
|
-
const ScrollNext = useCallback(() => {
|
|
83
|
-
if (!emblaMainApi) return;
|
|
84
|
-
emblaMainApi.scrollNext();
|
|
85
|
-
}, [emblaMainApi]);
|
|
86
|
-
|
|
87
|
-
const ScrollPrev = useCallback(() => {
|
|
88
|
-
if (!emblaMainApi) return;
|
|
89
|
-
emblaMainApi.scrollPrev();
|
|
90
|
-
}, [emblaMainApi]);
|
|
91
|
-
|
|
92
|
-
const direction = carouselOptions?.direction ?? (dir as DirectionOption);
|
|
93
|
-
|
|
94
|
-
const handleKeyDown = useCallback(
|
|
95
|
-
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
96
|
-
if (!emblaMainApi) return;
|
|
97
|
-
switch (event.key) {
|
|
98
|
-
case 'ArrowLeft':
|
|
99
|
-
event.preventDefault();
|
|
100
|
-
if (orientation === 'horizontal') {
|
|
101
|
-
if (direction === 'rtl') {
|
|
102
|
-
ScrollNext();
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
ScrollPrev();
|
|
106
|
-
}
|
|
107
|
-
break;
|
|
108
|
-
case 'ArrowRight':
|
|
109
|
-
event.preventDefault();
|
|
110
|
-
if (orientation === 'horizontal') {
|
|
111
|
-
if (direction === 'rtl') {
|
|
112
|
-
ScrollPrev();
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
ScrollNext();
|
|
116
|
-
}
|
|
117
|
-
break;
|
|
118
|
-
case 'ArrowUp':
|
|
119
|
-
event.preventDefault();
|
|
120
|
-
if (orientation === 'vertical') {
|
|
121
|
-
ScrollPrev();
|
|
122
|
-
}
|
|
123
|
-
break;
|
|
124
|
-
case 'ArrowDown':
|
|
125
|
-
event.preventDefault();
|
|
126
|
-
if (orientation === 'vertical') {
|
|
127
|
-
ScrollNext();
|
|
128
|
-
}
|
|
129
|
-
break;
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
[emblaMainApi, orientation, direction],
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
const onThumbClick = useCallback(
|
|
136
|
-
(index: number) => {
|
|
137
|
-
if (!emblaMainApi || !emblaThumbsApi) return;
|
|
138
|
-
emblaMainApi.scrollTo(index);
|
|
139
|
-
},
|
|
140
|
-
[emblaMainApi, emblaThumbsApi],
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
const onSelect = useCallback(() => {
|
|
144
|
-
if (!emblaMainApi || !emblaThumbsApi) return;
|
|
145
|
-
const selected = emblaMainApi.selectedScrollSnap();
|
|
146
|
-
setActiveIndex(selected);
|
|
147
|
-
emblaThumbsApi.scrollTo(selected);
|
|
148
|
-
setCanScrollPrev(emblaMainApi.canScrollPrev());
|
|
149
|
-
setCanScrollNext(emblaMainApi.canScrollNext());
|
|
150
|
-
}, [emblaMainApi, emblaThumbsApi]);
|
|
151
|
-
|
|
152
|
-
useEffect(() => {
|
|
153
|
-
if (!emblaMainApi) return;
|
|
154
|
-
onSelect();
|
|
155
|
-
emblaMainApi.on('select', onSelect);
|
|
156
|
-
emblaMainApi.on('reInit', onSelect);
|
|
157
|
-
|
|
158
|
-
return () => {
|
|
159
|
-
emblaMainApi.off('select', onSelect);
|
|
160
|
-
};
|
|
161
|
-
}, [emblaMainApi, onSelect]);
|
|
162
|
-
|
|
163
|
-
return (
|
|
164
|
-
<CarouselContext.Provider
|
|
165
|
-
value={{
|
|
166
|
-
emblaMainApi,
|
|
167
|
-
mainRef: emblaMainRef,
|
|
168
|
-
thumbsRef: emblaThumbsRef,
|
|
169
|
-
scrollNext: ScrollNext,
|
|
170
|
-
scrollPrev: ScrollPrev,
|
|
171
|
-
canScrollNext,
|
|
172
|
-
canScrollPrev,
|
|
173
|
-
activeIndex,
|
|
174
|
-
onThumbClick,
|
|
175
|
-
handleKeyDown,
|
|
176
|
-
carouselOptions,
|
|
177
|
-
direction,
|
|
178
|
-
orientation: orientation || (carouselOptions?.axis === 'y' ? 'vertical' : 'horizontal'),
|
|
179
|
-
}}
|
|
180
|
-
>
|
|
181
|
-
<div
|
|
182
|
-
{...props}
|
|
183
|
-
// tabIndex={0}
|
|
184
|
-
ref={ref}
|
|
185
|
-
onKeyDownCapture={handleKeyDown}
|
|
186
|
-
className={cn('grid gap-2 w-full relative focus:outline-none', className)}
|
|
187
|
-
dir={direction}
|
|
188
|
-
>
|
|
189
|
-
{children}
|
|
190
|
-
</div>
|
|
191
|
-
</CarouselContext.Provider>
|
|
192
|
-
);
|
|
193
|
-
},
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
Carousel.displayName = 'Carousel';
|
|
197
|
-
|
|
198
|
-
const CarouselMainContainer = forwardRef<HTMLDivElement, {
|
|
199
|
-
} & React.HTMLAttributes<HTMLDivElement>>(
|
|
200
|
-
({ className, children, ...props }, ref) => {
|
|
201
|
-
const { mainRef, orientation, direction } = useCarousel();
|
|
202
|
-
|
|
203
|
-
return (
|
|
204
|
-
<div {...props} ref={mainRef} className="overflow-hidden" dir={direction}>
|
|
205
|
-
<div
|
|
206
|
-
ref={ref}
|
|
207
|
-
className={cn('flex', `${orientation === 'vertical' ? 'flex-col' : ''}`, className)}
|
|
208
|
-
>
|
|
209
|
-
{children}
|
|
210
|
-
</div>
|
|
211
|
-
</div>
|
|
212
|
-
);
|
|
213
|
-
},
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
CarouselMainContainer.displayName = 'CarouselMainContainer';
|
|
217
|
-
|
|
218
|
-
const CarouselThumbsContainer = forwardRef<
|
|
219
|
-
HTMLDivElement,
|
|
220
|
-
{} & React.HTMLAttributes<HTMLDivElement>
|
|
221
|
-
>(({ className, children, ...props }, ref) => {
|
|
222
|
-
const { thumbsRef, orientation, direction } = useCarousel();
|
|
223
|
-
|
|
224
|
-
return (
|
|
225
|
-
<div {...props} ref={thumbsRef} className="overflow-hidden" dir={direction}>
|
|
226
|
-
<div
|
|
227
|
-
ref={ref}
|
|
228
|
-
className={cn('flex', `${orientation === 'vertical' ? 'flex-col' : ''}`, className)}
|
|
229
|
-
>
|
|
230
|
-
{children}
|
|
231
|
-
</div>
|
|
232
|
-
</div>
|
|
233
|
-
);
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
CarouselThumbsContainer.displayName = 'CarouselThumbsContainer';
|
|
237
|
-
|
|
238
|
-
const SliderMainItem = forwardRef<HTMLDivElement, {} & React.HTMLAttributes<HTMLDivElement>>(
|
|
239
|
-
({ className, children, ...props }, ref) => {
|
|
240
|
-
const { orientation } = useCarousel();
|
|
241
|
-
return (
|
|
242
|
-
<div
|
|
243
|
-
{...props}
|
|
244
|
-
ref={ref}
|
|
245
|
-
className={cn(
|
|
246
|
-
`min-w-0 shrink-0 grow-0 basis-full bg-background p-1 ${
|
|
247
|
-
orientation === 'vertical' ? 'pb-1' : 'pr-1'
|
|
248
|
-
}`,
|
|
249
|
-
className,
|
|
250
|
-
)}
|
|
251
|
-
>
|
|
252
|
-
{children}
|
|
253
|
-
</div>
|
|
254
|
-
);
|
|
255
|
-
},
|
|
256
|
-
);
|
|
257
|
-
|
|
258
|
-
SliderMainItem.displayName = 'SliderMainItem';
|
|
259
|
-
|
|
260
|
-
const SliderThumbItem = forwardRef<
|
|
261
|
-
HTMLDivElement,
|
|
262
|
-
{
|
|
263
|
-
index: number;
|
|
264
|
-
} & React.HTMLAttributes<HTMLDivElement>
|
|
265
|
-
>(({ className, index, children, ...props }, ref) => {
|
|
266
|
-
const { activeIndex, onThumbClick, orientation } = useCarousel();
|
|
267
|
-
const isSlideActive = activeIndex === index;
|
|
268
|
-
return (
|
|
269
|
-
<div
|
|
270
|
-
{...props}
|
|
271
|
-
ref={ref}
|
|
272
|
-
onClick={() => onThumbClick(index)}
|
|
273
|
-
className={cn(
|
|
274
|
-
'flex min-w-0 shrink-0 grow-0 basis-1/3 bg-background p-1',
|
|
275
|
-
`${orientation === 'vertical' ? 'pb-1' : 'pr-1'}`,
|
|
276
|
-
className,
|
|
277
|
-
)}
|
|
278
|
-
>
|
|
279
|
-
<div
|
|
280
|
-
className={`relative aspect-square h-20 w-full opacity-50 rounded-md transition-opacity ${
|
|
281
|
-
isSlideActive ? '!opacity-100' : ''
|
|
282
|
-
}`}
|
|
283
|
-
>
|
|
284
|
-
{children}
|
|
285
|
-
</div>
|
|
286
|
-
</div>
|
|
287
|
-
);
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
SliderThumbItem.displayName = 'SliderThumbItem';
|
|
291
|
-
|
|
292
|
-
const CarouselIndicator = forwardRef<
|
|
293
|
-
HTMLButtonElement,
|
|
294
|
-
{ index: number } & React.ComponentProps<typeof Button>
|
|
295
|
-
>(({ className, index, children, ...props }, ref) => {
|
|
296
|
-
const { activeIndex, onThumbClick } = useCarousel();
|
|
297
|
-
const isSlideActive = activeIndex === index;
|
|
298
|
-
return (
|
|
299
|
-
<Button
|
|
300
|
-
ref={ref}
|
|
301
|
-
size="icon"
|
|
302
|
-
className={cn(
|
|
303
|
-
'h-1 w-6 rounded-full',
|
|
304
|
-
"data-[active='false']:bg-primary/50 data-[active='true']:bg-primary",
|
|
305
|
-
className,
|
|
306
|
-
)}
|
|
307
|
-
data-active={isSlideActive}
|
|
308
|
-
onClick={() => onThumbClick(index)}
|
|
309
|
-
{...props}
|
|
310
|
-
>
|
|
311
|
-
<span className="sr-only">slide {index + 1} </span>
|
|
312
|
-
</Button>
|
|
313
|
-
);
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
CarouselIndicator.displayName = 'CarouselIndicator';
|
|
317
|
-
|
|
318
|
-
const CarouselPrevious = forwardRef<HTMLButtonElement, React.ComponentProps<typeof Button>>(
|
|
319
|
-
({ className, dir, variant = 'outline', size = 'icon', ...props }, ref) => {
|
|
320
|
-
const { canScrollNext, canScrollPrev, scrollNext, scrollPrev, orientation, direction } =
|
|
321
|
-
useCarousel();
|
|
322
|
-
|
|
323
|
-
const scroll = direction === 'rtl' ? scrollNext : scrollPrev;
|
|
324
|
-
const canScroll = direction === 'rtl' ? canScrollNext : canScrollPrev;
|
|
325
|
-
return (
|
|
326
|
-
<Button
|
|
327
|
-
type="button"
|
|
328
|
-
ref={ref}
|
|
329
|
-
variant={variant}
|
|
330
|
-
size={size}
|
|
331
|
-
className={cn(
|
|
332
|
-
'absolute h-6 w-6 rounded-full z-10',
|
|
333
|
-
orientation === 'vertical'
|
|
334
|
-
? '-top-2 left-1/2 -translate-x-1/2 rotate-90'
|
|
335
|
-
: '-left-2 top-1/2 -translate-y-1/2',
|
|
336
|
-
className,
|
|
337
|
-
)}
|
|
338
|
-
onClick={scroll}
|
|
339
|
-
disabled={!canScroll}
|
|
340
|
-
{...props}
|
|
341
|
-
>
|
|
342
|
-
<ChevronLeftIcon className="h-4 w-4" />
|
|
343
|
-
<span className="sr-only">Previous slide</span>
|
|
344
|
-
</Button>
|
|
345
|
-
);
|
|
346
|
-
},
|
|
347
|
-
);
|
|
348
|
-
CarouselPrevious.displayName = 'CarouselPrevious';
|
|
349
|
-
|
|
350
|
-
const CarouselNext = forwardRef<HTMLButtonElement, React.ComponentProps<typeof Button>>(
|
|
351
|
-
({ className, dir, variant = 'outline', size = 'icon', ...props }, ref) => {
|
|
352
|
-
const { canScrollNext, canScrollPrev, scrollNext, scrollPrev, orientation, direction } =
|
|
353
|
-
useCarousel();
|
|
354
|
-
const scroll = direction === 'rtl' ? scrollPrev : scrollNext;
|
|
355
|
-
const canScroll = direction === 'rtl' ? canScrollPrev : canScrollNext;
|
|
356
|
-
return (
|
|
357
|
-
<Button
|
|
358
|
-
type="button"
|
|
359
|
-
ref={ref}
|
|
360
|
-
variant={variant}
|
|
361
|
-
size={size}
|
|
362
|
-
className={cn(
|
|
363
|
-
'absolute h-6 w-6 rounded-full z-10',
|
|
364
|
-
orientation === 'vertical'
|
|
365
|
-
? '-bottom-2 left-1/2 -translate-x-1/2 rotate-90'
|
|
366
|
-
: '-right-2 top-1/2 -translate-y-1/2',
|
|
367
|
-
className,
|
|
368
|
-
)}
|
|
369
|
-
onClick={scroll}
|
|
370
|
-
disabled={!canScroll}
|
|
371
|
-
{...props}
|
|
372
|
-
>
|
|
373
|
-
<ChevronRightIcon className="h-4 w-4" />
|
|
374
|
-
<span className="sr-only">Next slide</span>
|
|
375
|
-
</Button>
|
|
376
|
-
);
|
|
377
|
-
},
|
|
378
|
-
);
|
|
379
|
-
|
|
380
|
-
CarouselNext.displayName = 'CarouselNext';
|
|
381
|
-
|
|
382
|
-
export {
|
|
383
|
-
Carousel,
|
|
384
|
-
CarouselMainContainer,
|
|
385
|
-
CarouselThumbsContainer,
|
|
386
|
-
SliderMainItem,
|
|
387
|
-
SliderThumbItem,
|
|
388
|
-
CarouselIndicator,
|
|
389
|
-
CarouselPrevious,
|
|
390
|
-
CarouselNext,
|
|
391
|
-
useCarousel,
|
|
392
|
-
};
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { useState, useRef, useEffect } from "react";
|
|
2
|
-
|
|
3
|
-
type TimerHook = {
|
|
4
|
-
counter: number;
|
|
5
|
-
start: () => void;
|
|
6
|
-
reset: () => void;
|
|
7
|
-
clear: () => void;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export function useTimer(initialSeconds: number): TimerHook {
|
|
11
|
-
const [counter, setCounter] = useState(initialSeconds);
|
|
12
|
-
const timerRef = useRef<NodeJS.Timeout | null>(null);
|
|
13
|
-
|
|
14
|
-
useEffect(() => {
|
|
15
|
-
// Cleanup on component unmount or when counter changes
|
|
16
|
-
return () => {
|
|
17
|
-
if (timerRef.current) {
|
|
18
|
-
clearTimeout(timerRef.current);
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
}, []);
|
|
22
|
-
|
|
23
|
-
const start = () => {
|
|
24
|
-
if (timerRef.current) {
|
|
25
|
-
clearTimeout(timerRef.current);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
timerRef.current = setTimeout(() => {
|
|
29
|
-
setCounter((prev) => (prev > 0 ? prev - 1 : 0));
|
|
30
|
-
}, 1000);
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const reset = () => {
|
|
34
|
-
setCounter(initialSeconds);
|
|
35
|
-
clear();
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const clear = () => {
|
|
39
|
-
if (timerRef.current) {
|
|
40
|
-
clearTimeout(timerRef.current);
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
useEffect(() => {
|
|
45
|
-
if (counter > 0) {
|
|
46
|
-
start();
|
|
47
|
-
}
|
|
48
|
-
}, [counter]);
|
|
49
|
-
|
|
50
|
-
return { counter, start, reset, clear };
|
|
51
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
|
|
3
|
-
export function useMediaQuery(query: string) {
|
|
4
|
-
const [value, setValue] = React.useState(false);
|
|
5
|
-
|
|
6
|
-
React.useEffect(() => {
|
|
7
|
-
function onChange(event: MediaQueryListEvent) {
|
|
8
|
-
setValue(event.matches);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const result = matchMedia(query);
|
|
12
|
-
result.addEventListener("change", onChange);
|
|
13
|
-
setValue(result.matches);
|
|
14
|
-
|
|
15
|
-
return () => result.removeEventListener("change", onChange);
|
|
16
|
-
}, [query]);
|
|
17
|
-
|
|
18
|
-
return value;
|
|
19
|
-
}
|
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import { type ClassValue, clsx } from "clsx";
|
|
3
|
-
import {
|
|
4
|
-
differenceInDays,
|
|
5
|
-
differenceInYears,
|
|
6
|
-
format,
|
|
7
|
-
formatDistance,
|
|
8
|
-
isThisWeek,
|
|
9
|
-
isToday,
|
|
10
|
-
isYesterday,
|
|
11
|
-
parseISO,
|
|
12
|
-
} from "date-fns";
|
|
13
|
-
import { twMerge } from "tailwind-merge";
|
|
14
|
-
|
|
15
|
-
import type { Store } from "../util/storage";
|
|
16
|
-
|
|
17
|
-
export function cn(...inputs: ClassValue[]) {
|
|
18
|
-
return twMerge(clsx(inputs));
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// function isFileArray(value: any): value is File[] {
|
|
22
|
-
// return Array.isArray(value) && value.every(item => item instanceof File);
|
|
23
|
-
// }
|
|
24
|
-
|
|
25
|
-
export function getFormData(object: {
|
|
26
|
-
[key: string]: undefined | string | number | boolean | Blob | File | object | File[];
|
|
27
|
-
}): FormData {
|
|
28
|
-
const formData = new FormData();
|
|
29
|
-
Object.entries(object).forEach(([key, value]) => {
|
|
30
|
-
if (value instanceof Blob || value instanceof File) {
|
|
31
|
-
formData.append(key, value);
|
|
32
|
-
} else if (value instanceof FileList || (Array.isArray(value) && value[0] instanceof File)) {
|
|
33
|
-
Array.from(value).forEach((file: File) => {
|
|
34
|
-
formData.append(key, file);
|
|
35
|
-
});
|
|
36
|
-
} else {
|
|
37
|
-
if (!value) return;
|
|
38
|
-
formData.append(key, JSON.stringify(value));
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
return formData;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function switchPath(pathname: string, tab: string) {
|
|
45
|
-
const fragments = pathname.split("/").filter(str => str);
|
|
46
|
-
fragments[2] = tab;
|
|
47
|
-
const path = `/${fragments.join("/")}`;
|
|
48
|
-
return path;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function getRelativeDate(date?: string | Date) {
|
|
52
|
-
const now = new Date();
|
|
53
|
-
if (!date) return "";
|
|
54
|
-
if (isToday(date)) {
|
|
55
|
-
return format(date, "h:mm a");
|
|
56
|
-
} else if (isYesterday(date)) {
|
|
57
|
-
return "yesterday";
|
|
58
|
-
} else if (isThisWeek(date) && differenceInDays(now, date) <= 7) {
|
|
59
|
-
return format(date, "EEEE"); // EEEE gives the full name of the day like Sunday, Monday
|
|
60
|
-
} else {
|
|
61
|
-
return format(date, "dd/MM/yy");
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function getRelativeDateWithTime(date?: string | Date) {
|
|
66
|
-
const now = new Date();
|
|
67
|
-
if (!date) return "";
|
|
68
|
-
return formatDistance(new Date(date), now, { addSuffix: true });
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function getRelativeProDate(dateString: string) {
|
|
72
|
-
try {
|
|
73
|
-
const date = parseISO(dateString);
|
|
74
|
-
const now = new Date();
|
|
75
|
-
const yearsDifference = differenceInYears(now, date);
|
|
76
|
-
const dateFormat = yearsDifference >= 1 ? "dd MMM yyyy, h:mm a" : "dd MMM, h:mm a";
|
|
77
|
-
const formattedDate = format(date, dateFormat);
|
|
78
|
-
return formattedDate;
|
|
79
|
-
} catch (error) {
|
|
80
|
-
console.error("Error formatting date:", error);
|
|
81
|
-
return "";
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
export function isValidId(str: string) {
|
|
85
|
-
const regex = /^[a-f0-9]{24}$/i;
|
|
86
|
-
return regex.test(str);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function getRelativeDateOnly(date?: string | Date) {
|
|
90
|
-
const now = new Date();
|
|
91
|
-
if (!date) return "";
|
|
92
|
-
if (isToday(date)) {
|
|
93
|
-
return "today";
|
|
94
|
-
} else if (isYesterday(date)) {
|
|
95
|
-
return "yesterday";
|
|
96
|
-
} else if (isThisWeek(date) && differenceInDays(now, date) <= 7) {
|
|
97
|
-
return format(date, "EEEE"); // EEEE gives the full name of the day like Sunday, Monday
|
|
98
|
-
} else {
|
|
99
|
-
return format(date, "dd/MM/yy");
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
export type SocialMediaUrlMetaData = {
|
|
103
|
-
platform: "youtube" | "x" | "instagram";
|
|
104
|
-
url: string;
|
|
105
|
-
contentId: string;
|
|
106
|
-
};
|
|
107
|
-
export function getUrlMetaData(urlString: string): SocialMediaUrlMetaData | null {
|
|
108
|
-
const url = new URL(urlString);
|
|
109
|
-
let contentId: string;
|
|
110
|
-
let platform: "youtube" | "x" | "instagram";
|
|
111
|
-
switch (url.origin) {
|
|
112
|
-
case "https://youtu.be":
|
|
113
|
-
platform = "youtube";
|
|
114
|
-
contentId = url.pathname.replace("/", "");
|
|
115
|
-
break;
|
|
116
|
-
case "https://www.youtube.com":
|
|
117
|
-
platform = "youtube";
|
|
118
|
-
contentId = url.searchParams.get("v") as string;
|
|
119
|
-
break;
|
|
120
|
-
case "https://x.com":
|
|
121
|
-
platform = "x";
|
|
122
|
-
contentId = url.pathname
|
|
123
|
-
.split("/")
|
|
124
|
-
.filter(str => str)
|
|
125
|
-
.at(-1) as string;
|
|
126
|
-
break;
|
|
127
|
-
case "https://www.instagram.com":
|
|
128
|
-
platform = "instagram";
|
|
129
|
-
contentId = url.pathname
|
|
130
|
-
.split("/")
|
|
131
|
-
.filter(str => str)
|
|
132
|
-
.at(-1) as string;
|
|
133
|
-
break;
|
|
134
|
-
case "https://instagram.com":
|
|
135
|
-
platform = "instagram";
|
|
136
|
-
contentId = url.pathname
|
|
137
|
-
.split("/")
|
|
138
|
-
.filter(str => str)
|
|
139
|
-
.at(-1) as string;
|
|
140
|
-
break;
|
|
141
|
-
default:
|
|
142
|
-
return null;
|
|
143
|
-
}
|
|
144
|
-
return {
|
|
145
|
-
platform,
|
|
146
|
-
url: urlString,
|
|
147
|
-
contentId,
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export function isValidUrl(url: string) {
|
|
152
|
-
try {
|
|
153
|
-
const origin = new URL(url).origin;
|
|
154
|
-
return true;
|
|
155
|
-
} catch (error) {
|
|
156
|
-
return false;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
export function memoize<T extends (...args: any[]) => any>(fn: T): T {
|
|
161
|
-
const cache: { [key: string]: ReturnType<T> } = {};
|
|
162
|
-
|
|
163
|
-
return ((...args: Parameters<T>): ReturnType<T> => {
|
|
164
|
-
const key = JSON.stringify(args);
|
|
165
|
-
if (cache[key] !== undefined) {
|
|
166
|
-
return cache[key];
|
|
167
|
-
} else {
|
|
168
|
-
const result = fn(...args);
|
|
169
|
-
cache[key] = result;
|
|
170
|
-
return result;
|
|
171
|
-
}
|
|
172
|
-
}) as T;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
export function memoizeAsync<T extends (...args: any[]) => Promise<any>>(fn: T): T {
|
|
176
|
-
const cache: { [key: string]: ReturnType<T> } = {};
|
|
177
|
-
|
|
178
|
-
return (async (...args: Parameters<T>): Promise<ReturnType<T>> => {
|
|
179
|
-
const key = JSON.stringify(args);
|
|
180
|
-
if (cache[key] !== undefined) {
|
|
181
|
-
return await cache[key];
|
|
182
|
-
} else {
|
|
183
|
-
const result = await fn(...args);
|
|
184
|
-
cache[key] = result;
|
|
185
|
-
return result;
|
|
186
|
-
}
|
|
187
|
-
}) as T;
|
|
188
|
-
}
|
|
189
|
-
export function memoizePersistentAsync<T extends (...args: any[]) => Promise<any>>(
|
|
190
|
-
fn: T,
|
|
191
|
-
store: Store
|
|
192
|
-
): T {
|
|
193
|
-
return (async (...args: Parameters<T>): Promise<ReturnType<T>> => {
|
|
194
|
-
const key = JSON.stringify(args);
|
|
195
|
-
const cachedValue = store.get(key);
|
|
196
|
-
if (cachedValue !== null) {
|
|
197
|
-
try {
|
|
198
|
-
return JSON.parse(cachedValue);
|
|
199
|
-
} catch (error) {
|
|
200
|
-
console.error("Error parsing cached value:", error);
|
|
201
|
-
return await fn(...args);
|
|
202
|
-
}
|
|
203
|
-
} else {
|
|
204
|
-
const result = await fn(...args);
|
|
205
|
-
store.set(key, JSON.stringify(result));
|
|
206
|
-
return result;
|
|
207
|
-
}
|
|
208
|
-
}) as T;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
export function formatNumberWithKMB(num: number): string {
|
|
212
|
-
const formatter = new Intl.NumberFormat("en-US", {
|
|
213
|
-
notation: "compact",
|
|
214
|
-
minimumSignificantDigits: 1,
|
|
215
|
-
maximumSignificantDigits: 3,
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
return formatter.format(num);
|
|
219
|
-
}
|
|
220
|
-
export function currencyToNumber(formattedCurrency: string): number {
|
|
221
|
-
// Remove everything except digits, minus sign, and decimal point
|
|
222
|
-
const numericString = formattedCurrency.replace(/[^0-9.-]+/g, "");
|
|
223
|
-
|
|
224
|
-
// Convert the string back to a number
|
|
225
|
-
const parsedNumber = Number.parseFloat(numericString);
|
|
226
|
-
|
|
227
|
-
// Handle potential NaN if parsing fails
|
|
228
|
-
if (isNaN(parsedNumber)) {
|
|
229
|
-
throw new Error("Invalid currency format");
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
return parsedNumber;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
export function createLabels(e: string) {
|
|
236
|
-
return e
|
|
237
|
-
.replaceAll("_", " ")
|
|
238
|
-
.replace(/\b\w/g, char => char.toUpperCase())
|
|
239
|
-
.trim();
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
export { cva, type VariantProps } from "class-variance-authority";
|