@m5kdev/web-ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +621 -0
- package/README.md +17 -0
- package/package.json +169 -0
- package/src/animations/card.motion.ts +9 -0
- package/src/components/AvatarUpload.tsx +133 -0
- package/src/components/Button.tsx +14 -0
- package/src/components/Calendar.css +684 -0
- package/src/components/Calendar.tsx +32 -0
- package/src/components/CardsSelect.tsx +155 -0
- package/src/components/CollapsibleSidebarMenuItem.tsx +57 -0
- package/src/components/ColorPicker.tsx +56 -0
- package/src/components/CopyButton.tsx +45 -0
- package/src/components/CropDialog.tsx +154 -0
- package/src/components/DialogProvider.tsx +105 -0
- package/src/components/ErrorFallback.tsx +17 -0
- package/src/components/FileDropzone.tsx +120 -0
- package/src/components/MultiSelectDropdown.tsx +233 -0
- package/src/components/Orb.tsx +288 -0
- package/src/components/PageAlert.tsx +121 -0
- package/src/components/SelectChips.tsx +40 -0
- package/src/components/SidebarItem.tsx +26 -0
- package/src/components/Steps.tsx +340 -0
- package/src/components/TablerIconPicker.tsx +4260 -0
- package/src/components/app-header.tsx +40 -0
- package/src/components/blur-card.tsx +132 -0
- package/src/components/features-section-demo-1.tsx +127 -0
- package/src/components/features-section-demo-2.tsx +102 -0
- package/src/components/features-section-demo-3.tsx +272 -0
- package/src/components/mode-toggle.tsx +31 -0
- package/src/components/nav-main.tsx +69 -0
- package/src/components/pricing-cards.tsx +133 -0
- package/src/components/shared/ButtonCopy.tsx +50 -0
- package/src/components/team-switcher.tsx +83 -0
- package/src/components/theme-provider.tsx +74 -0
- package/src/components/typewriter.tsx +90 -0
- package/src/components/ui/alert-dialog.tsx +133 -0
- package/src/components/ui/alert.tsx +60 -0
- package/src/components/ui/avatar.tsx +47 -0
- package/src/components/ui/badge.tsx +33 -0
- package/src/components/ui/bento-grid.tsx +54 -0
- package/src/components/ui/bento-grid2.tsx +66 -0
- package/src/components/ui/breadcrumb.tsx +101 -0
- package/src/components/ui/button.tsx +50 -0
- package/src/components/ui/card.tsx +55 -0
- package/src/components/ui/checkbox.tsx +26 -0
- package/src/components/ui/collapsible.tsx +9 -0
- package/src/components/ui/dialog.tsx +119 -0
- package/src/components/ui/dropdown-menu.tsx +186 -0
- package/src/components/ui/floating-navbar.tsx +78 -0
- package/src/components/ui/form.tsx +167 -0
- package/src/components/ui/image.tsx +55 -0
- package/src/components/ui/input.tsx +22 -0
- package/src/components/ui/label.tsx +19 -0
- package/src/components/ui/pagination.tsx +105 -0
- package/src/components/ui/progress.tsx +23 -0
- package/src/components/ui/resizable-navbar.tsx +260 -0
- package/src/components/ui/segment-control.tsx +143 -0
- package/src/components/ui/select.tsx +153 -0
- package/src/components/ui/separator.tsx +24 -0
- package/src/components/ui/sheet.tsx +121 -0
- package/src/components/ui/sidebar.tsx +736 -0
- package/src/components/ui/skeleton.tsx +7 -0
- package/src/components/ui/slider.tsx +23 -0
- package/src/components/ui/sonner.tsx +27 -0
- package/src/components/ui/spinner.tsx +45 -0
- package/src/components/ui/switch.tsx +27 -0
- package/src/components/ui/table.tsx +90 -0
- package/src/components/ui/tabs.tsx +52 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/timeline.tsx +95 -0
- package/src/components/ui/toast.tsx +126 -0
- package/src/components/ui/tooltip.tsx +55 -0
- package/src/components/ui/typewriter-effect.tsx +181 -0
- package/src/hooks/use-mobile.ts +19 -0
- package/src/hooks/useDialog.ts +25 -0
- package/src/icons/GoogleIcon.tsx +32 -0
- package/src/icons/LinkedInIcon.tsx +30 -0
- package/src/icons/MicrosoftIcon.tsx +21 -0
- package/src/lib/chatwoot.ts +51 -0
- package/src/lib/utils.ts +6 -0
- package/src/modules/app/components/AppLoader.tsx +9 -0
- package/src/modules/app/components/AppShell.tsx +21 -0
- package/src/modules/app/components/AppSidebar.tsx +26 -0
- package/src/modules/app/components/AppSidebarContent.tsx +73 -0
- package/src/modules/app/components/AppSidebarHeader.tsx +57 -0
- package/src/modules/app/components/AppSidebarInvites.tsx +32 -0
- package/src/modules/app/components/AppSidebarUser.tsx +128 -0
- package/src/modules/auth/components/AdminUserManagement.tsx +1136 -0
- package/src/modules/auth/components/AdminWaitlist.tsx +358 -0
- package/src/modules/auth/components/AuthLayout.tsx +13 -0
- package/src/modules/auth/components/AuthProviders.tsx +105 -0
- package/src/modules/auth/components/AuthRouter.tsx +29 -0
- package/src/modules/auth/components/ClaimAccountRoute.tsx +242 -0
- package/src/modules/auth/components/ErrorAuthRoute.tsx +121 -0
- package/src/modules/auth/components/ForgotPasswordForm.tsx +58 -0
- package/src/modules/auth/components/ForgotPasswordRoute.tsx +27 -0
- package/src/modules/auth/components/InviteFriends.tsx +273 -0
- package/src/modules/auth/components/LastUsedBadge.tsx +22 -0
- package/src/modules/auth/components/LoginForm.tsx +104 -0
- package/src/modules/auth/components/LoginRoute.tsx +31 -0
- package/src/modules/auth/components/LogoutRoute.tsx +21 -0
- package/src/modules/auth/components/OrganizationAcceptInvitationRoute.tsx +161 -0
- package/src/modules/auth/components/OrganizationMembersRoute.tsx +730 -0
- package/src/modules/auth/components/OrganizationSettingsRoute.tsx +280 -0
- package/src/modules/auth/components/OrganizationSwitcher.tsx +148 -0
- package/src/modules/auth/components/ProfileRoute.tsx +104 -0
- package/src/modules/auth/components/RangeNuqsDatePicker.tsx +365 -0
- package/src/modules/auth/components/ResetPasswordForm.tsx +103 -0
- package/src/modules/auth/components/ResetPasswordRoute.tsx +27 -0
- package/src/modules/auth/components/SignupFormRoute.tsx +189 -0
- package/src/modules/auth/components/SignupRoute.tsx +53 -0
- package/src/modules/auth/components/UserPreferences.tsx +144 -0
- package/src/modules/auth/components/WaitlistCard.tsx +78 -0
- package/src/modules/auth/components/WaitlistCodeValidation.tsx +79 -0
- package/src/modules/billing/components/BillingBetaPage.tsx +124 -0
- package/src/modules/billing/components/BillingInvoicePage.tsx +180 -0
- package/src/modules/billing/components/BillingPlanSelect.tsx +14 -0
- package/src/modules/billing/components/BillingRouter.tsx +20 -0
- package/src/modules/billing/components/BillingSinglePlanSelect.tsx +172 -0
- package/src/modules/table/components/ColumnOrderAndVisibility.tsx +127 -0
- package/src/modules/table/components/NuqsTable.tsx +396 -0
- package/src/modules/table/components/TableFiltering.tsx +520 -0
- package/src/modules/table/components/TablePagination.tsx +59 -0
- package/src/modules/table/components/table.types.ts +11 -0
- package/src/modules/table/filterTransformers.ts +323 -0
- package/src/types.ts +4 -0
- package/src/vite-env.d.ts +1 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { motion, stagger, useAnimate, useInView } from "motion/react";
|
|
4
|
+
import { useEffect } from "react";
|
|
5
|
+
import { cn } from "#utils";
|
|
6
|
+
|
|
7
|
+
export const TypewriterEffect = ({
|
|
8
|
+
words,
|
|
9
|
+
className,
|
|
10
|
+
cursorClassName,
|
|
11
|
+
}: {
|
|
12
|
+
words: {
|
|
13
|
+
text: string;
|
|
14
|
+
className?: string;
|
|
15
|
+
}[];
|
|
16
|
+
className?: string;
|
|
17
|
+
cursorClassName?: string;
|
|
18
|
+
}) => {
|
|
19
|
+
// split text inside of words into array of characters
|
|
20
|
+
const wordsArray = words.map((word) => {
|
|
21
|
+
return {
|
|
22
|
+
...word,
|
|
23
|
+
text: word.text.split(""),
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const [scope, animate] = useAnimate();
|
|
28
|
+
const isInView = useInView(scope);
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (isInView) {
|
|
31
|
+
animate(
|
|
32
|
+
"span",
|
|
33
|
+
{
|
|
34
|
+
display: "inline-block",
|
|
35
|
+
opacity: 1,
|
|
36
|
+
width: "fit-content",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
duration: 0.3,
|
|
40
|
+
delay: stagger(0.1),
|
|
41
|
+
ease: "easeInOut",
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}, [isInView]);
|
|
46
|
+
|
|
47
|
+
const renderWords = () => {
|
|
48
|
+
return (
|
|
49
|
+
<motion.div ref={scope} className="inline">
|
|
50
|
+
{wordsArray.map((word, idx) => {
|
|
51
|
+
return (
|
|
52
|
+
<div key={`word-${idx}`} className="inline-block">
|
|
53
|
+
{word.text.map((char, index) => (
|
|
54
|
+
<motion.span
|
|
55
|
+
initial={{}}
|
|
56
|
+
key={`char-${index}`}
|
|
57
|
+
className={cn("dark:text-white text-black opacity-0 hidden", word.className)}
|
|
58
|
+
>
|
|
59
|
+
{char}
|
|
60
|
+
</motion.span>
|
|
61
|
+
))}
|
|
62
|
+
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
})}
|
|
66
|
+
</motion.div>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
return (
|
|
70
|
+
<div
|
|
71
|
+
className={cn(
|
|
72
|
+
"text-base sm:text-xl md:text-3xl lg:text-5xl font-bold text-center",
|
|
73
|
+
className
|
|
74
|
+
)}
|
|
75
|
+
>
|
|
76
|
+
{renderWords()}
|
|
77
|
+
<motion.span
|
|
78
|
+
initial={{
|
|
79
|
+
opacity: 0,
|
|
80
|
+
}}
|
|
81
|
+
animate={{
|
|
82
|
+
opacity: 1,
|
|
83
|
+
}}
|
|
84
|
+
transition={{
|
|
85
|
+
duration: 0.8,
|
|
86
|
+
repeat: Number.POSITIVE_INFINITY,
|
|
87
|
+
repeatType: "reverse",
|
|
88
|
+
}}
|
|
89
|
+
className={cn(
|
|
90
|
+
"inline-block rounded-sm w-[4px] h-4 md:h-6 lg:h-10 bg-green-500",
|
|
91
|
+
cursorClassName
|
|
92
|
+
)}
|
|
93
|
+
/>
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export const TypewriterEffectSmooth = ({
|
|
99
|
+
words,
|
|
100
|
+
className,
|
|
101
|
+
cursorClassName,
|
|
102
|
+
}: {
|
|
103
|
+
words: {
|
|
104
|
+
text: string;
|
|
105
|
+
className?: string;
|
|
106
|
+
}[];
|
|
107
|
+
className?: string;
|
|
108
|
+
cursorClassName?: string;
|
|
109
|
+
}) => {
|
|
110
|
+
// split text inside of words into array of characters
|
|
111
|
+
const wordsArray = words.map((word) => {
|
|
112
|
+
return {
|
|
113
|
+
...word,
|
|
114
|
+
text: word.text.split(""),
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
const renderWords = () => {
|
|
118
|
+
return (
|
|
119
|
+
<div>
|
|
120
|
+
{wordsArray.map((word, idx) => {
|
|
121
|
+
return (
|
|
122
|
+
<div key={`word-${idx}`} className="inline-block">
|
|
123
|
+
{word.text.map((char, index) => (
|
|
124
|
+
<span
|
|
125
|
+
key={`char-${index}`}
|
|
126
|
+
className={cn("dark:text-white text-black ", word.className)}
|
|
127
|
+
>
|
|
128
|
+
{char}
|
|
129
|
+
</span>
|
|
130
|
+
))}
|
|
131
|
+
|
|
132
|
+
</div>
|
|
133
|
+
);
|
|
134
|
+
})}
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<div className={cn("flex space-x-1 my-6", className)}>
|
|
141
|
+
<motion.div
|
|
142
|
+
className="overflow-hidden pb-2"
|
|
143
|
+
initial={{
|
|
144
|
+
width: "0%",
|
|
145
|
+
}}
|
|
146
|
+
whileInView={{
|
|
147
|
+
width: "fit-content",
|
|
148
|
+
}}
|
|
149
|
+
transition={{
|
|
150
|
+
duration: 2,
|
|
151
|
+
ease: "linear",
|
|
152
|
+
delay: 1,
|
|
153
|
+
}}
|
|
154
|
+
>
|
|
155
|
+
<div
|
|
156
|
+
className="text-xs sm:text-base md:text-xl lg:text:3xl xl:text-5xl font-bold"
|
|
157
|
+
style={{
|
|
158
|
+
whiteSpace: "nowrap",
|
|
159
|
+
}}
|
|
160
|
+
>
|
|
161
|
+
{renderWords()}{" "}
|
|
162
|
+
</div>{" "}
|
|
163
|
+
</motion.div>
|
|
164
|
+
<motion.span
|
|
165
|
+
initial={{
|
|
166
|
+
opacity: 0,
|
|
167
|
+
}}
|
|
168
|
+
animate={{
|
|
169
|
+
opacity: 1,
|
|
170
|
+
}}
|
|
171
|
+
transition={{
|
|
172
|
+
duration: 0.8,
|
|
173
|
+
|
|
174
|
+
repeat: Number.POSITIVE_INFINITY,
|
|
175
|
+
repeatType: "reverse",
|
|
176
|
+
}}
|
|
177
|
+
className={cn("block rounded-sm w-[4px] h-4 sm:h-6 xl:h-12 bg-green-500", cursorClassName)}
|
|
178
|
+
/>
|
|
179
|
+
</div>
|
|
180
|
+
);
|
|
181
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
const MOBILE_BREAKPOINT = 768;
|
|
4
|
+
|
|
5
|
+
export function useIsMobile() {
|
|
6
|
+
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined);
|
|
7
|
+
|
|
8
|
+
React.useEffect(() => {
|
|
9
|
+
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
|
|
10
|
+
const onChange = () => {
|
|
11
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
|
12
|
+
};
|
|
13
|
+
mql.addEventListener("change", onChange);
|
|
14
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
|
15
|
+
return () => mql.removeEventListener("change", onChange);
|
|
16
|
+
}, []);
|
|
17
|
+
|
|
18
|
+
return !!isMobile;
|
|
19
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type DialogProps, useDialog } from "#components/DialogProvider";
|
|
2
|
+
|
|
3
|
+
export { useDialog };
|
|
4
|
+
|
|
5
|
+
export const useConfirmDialog = () => {
|
|
6
|
+
const dialog = useDialog();
|
|
7
|
+
return (props: DialogProps) => {
|
|
8
|
+
dialog({
|
|
9
|
+
color: "danger",
|
|
10
|
+
cancelable: true,
|
|
11
|
+
...props,
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const useAlertDialog = () => {
|
|
17
|
+
const dialog = useDialog();
|
|
18
|
+
return (props: DialogProps) => {
|
|
19
|
+
dialog({
|
|
20
|
+
color: "warning",
|
|
21
|
+
cancelable: false,
|
|
22
|
+
...props,
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef } from "react";
|
|
2
|
+
|
|
3
|
+
interface GoogleIconProps extends ComponentPropsWithoutRef<"svg"> {}
|
|
4
|
+
|
|
5
|
+
export function GoogleIcon(props: GoogleIconProps) {
|
|
6
|
+
return (
|
|
7
|
+
<svg
|
|
8
|
+
aria-hidden="true"
|
|
9
|
+
viewBox="10 10 20 20"
|
|
10
|
+
fill="none"
|
|
11
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
12
|
+
{...props}
|
|
13
|
+
>
|
|
14
|
+
<path
|
|
15
|
+
d="M29.6 20.2273C29.6 19.5182 29.5364 18.8364 29.4182 18.1818H20V22.05H25.3818C25.15 23.3 24.4455 24.3591 23.3864 25.0682V27.5773H26.6182C28.5091 25.8364 29.6 23.2727 29.6 20.2273Z"
|
|
16
|
+
fill="#4285F4"
|
|
17
|
+
/>
|
|
18
|
+
<path
|
|
19
|
+
d="M20 30C22.7 30 24.9636 29.1045 26.6181 27.5773L23.3863 25.0682C22.4909 25.6682 21.3454 26.0227 20 26.0227C17.3954 26.0227 15.1909 24.2636 14.4045 21.9H11.0636V24.4909C12.7091 27.7591 16.0909 30 20 30Z"
|
|
20
|
+
fill="#34A853"
|
|
21
|
+
/>
|
|
22
|
+
<path
|
|
23
|
+
d="M14.4045 21.9C14.2045 21.3 14.0909 20.6591 14.0909 20C14.0909 19.3409 14.2045 18.7 14.4045 18.1V15.5091H11.0636C10.3864 16.8591 10 18.3864 10 20C10 21.6136 10.3864 23.1409 11.0636 24.4909L14.4045 21.9Z"
|
|
24
|
+
fill="#FBBC04"
|
|
25
|
+
/>
|
|
26
|
+
<path
|
|
27
|
+
d="M20 13.9773C21.4681 13.9773 22.7863 14.4818 23.8227 15.4727L26.6909 12.6045C24.9591 10.9909 22.6954 10 20 10C16.0909 10 12.7091 12.2409 11.0636 15.5091L14.4045 18.1C15.1909 15.7364 17.3954 13.9773 20 13.9773Z"
|
|
28
|
+
fill="#E94235"
|
|
29
|
+
/>
|
|
30
|
+
</svg>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef } from "react";
|
|
2
|
+
|
|
3
|
+
interface LinkedInIconProps extends ComponentPropsWithoutRef<"svg"> {}
|
|
4
|
+
|
|
5
|
+
export function LinkedInIcon(props: LinkedInIconProps) {
|
|
6
|
+
return (
|
|
7
|
+
<svg
|
|
8
|
+
aria-hidden="true"
|
|
9
|
+
height="72"
|
|
10
|
+
width="72"
|
|
11
|
+
viewBox="0 0 72 72"
|
|
12
|
+
fill="none"
|
|
13
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
14
|
+
{...props}
|
|
15
|
+
>
|
|
16
|
+
<g fill="none" fillRule="evenodd">
|
|
17
|
+
<path
|
|
18
|
+
d="M8 72h56c4.418 0 8-3.582 8-8V8c0-4.418-3.582-8-8-8H8C3.582 0 0 3.582 0 8v56c0 4.418 3.582 8 8 8Z"
|
|
19
|
+
fill="#007EBB"
|
|
20
|
+
/>
|
|
21
|
+
<path
|
|
22
|
+
d="M62 62H51.316V43.802c0-4.99-1.896-7.778-5.845-7.778-4.296 0-6.541 2.901-6.541 7.777V62H28.633V27.333h10.297v4.67S42.026 26.274 49.383 26.274C56.736 26.274 62 30.764 62 40.051zM16.349 22.794c-3.507 0-6.349-2.864-6.349-6.397C10 12.864 12.842 10 16.349 10c3.507 0 6.348 2.864 6.348 6.397 0 3.533-2.84 6.397-6.348 6.397zM11.033 62h10.737V27.333H11.033z"
|
|
23
|
+
fill="#FFF"
|
|
24
|
+
/>
|
|
25
|
+
</g>
|
|
26
|
+
</svg>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef } from "react";
|
|
2
|
+
|
|
3
|
+
interface MicrosoftIconProps extends ComponentPropsWithoutRef<"svg"> {}
|
|
4
|
+
|
|
5
|
+
export function MicrosoftIcon(props: MicrosoftIconProps) {
|
|
6
|
+
return (
|
|
7
|
+
<svg
|
|
8
|
+
aria-hidden="true"
|
|
9
|
+
viewBox="0 0 21 21"
|
|
10
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
11
|
+
{...props}
|
|
12
|
+
>
|
|
13
|
+
<path fill="#F35325" d="M0 0h10v10H0z" />
|
|
14
|
+
<path fill="#81BC06" d="M11 0h10v10H11z" />
|
|
15
|
+
<path fill="#05A6F0" d="M0 11h10v10H0z" />
|
|
16
|
+
<path fill="#FFBA08" d="M11 11h10v10H11z" />
|
|
17
|
+
</svg>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export function startChatwoot(
|
|
2
|
+
{
|
|
3
|
+
baseUrl,
|
|
4
|
+
websiteToken,
|
|
5
|
+
}: {
|
|
6
|
+
baseUrl: string;
|
|
7
|
+
websiteToken: string;
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
user,
|
|
11
|
+
}: {
|
|
12
|
+
user?: {
|
|
13
|
+
id: string;
|
|
14
|
+
email: string;
|
|
15
|
+
name?: string | null;
|
|
16
|
+
image?: string | null;
|
|
17
|
+
};
|
|
18
|
+
},
|
|
19
|
+
allowAnonymous?: boolean
|
|
20
|
+
) {
|
|
21
|
+
// avoid loading twice
|
|
22
|
+
if ((window as any).chatwootSDK) return;
|
|
23
|
+
|
|
24
|
+
if (!allowAnonymous && !user) {
|
|
25
|
+
throw new Error("User is required to start Chatwoot");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const script = document.createElement("script");
|
|
29
|
+
script.src = `${baseUrl}/packs/js/sdk.js`;
|
|
30
|
+
script.async = true;
|
|
31
|
+
|
|
32
|
+
script.onload = () => {
|
|
33
|
+
(window as any).chatwootSDK.run({
|
|
34
|
+
websiteToken,
|
|
35
|
+
baseUrl,
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
document.body.appendChild(script);
|
|
40
|
+
|
|
41
|
+
if (user) {
|
|
42
|
+
window.addEventListener("chatwoot:ready", () => {
|
|
43
|
+
// @ts-expect-error chatwoot is in global window
|
|
44
|
+
window.$chatwoot.setUser(user.id, {
|
|
45
|
+
email: user.email,
|
|
46
|
+
name: user.name,
|
|
47
|
+
avatar_url: user.image,
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
package/src/lib/utils.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { SidebarInset, SidebarProvider } from "@m5kdev/web-ui/components/ui/sidebar";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import { Outlet } from "react-router";
|
|
4
|
+
import { AppSidebar, type AppSidebarProps } from "#modules/app/components/AppSidebar";
|
|
5
|
+
|
|
6
|
+
export type AppShellProps = {
|
|
7
|
+
header?: ReactNode;
|
|
8
|
+
sidebar: AppSidebarProps;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function AppShell({ header, sidebar }: AppShellProps) {
|
|
12
|
+
return (
|
|
13
|
+
<SidebarProvider>
|
|
14
|
+
{header}
|
|
15
|
+
<AppSidebar {...sidebar} />
|
|
16
|
+
<SidebarInset>
|
|
17
|
+
<Outlet />
|
|
18
|
+
</SidebarInset>
|
|
19
|
+
</SidebarProvider>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Sidebar,
|
|
4
|
+
SidebarContent,
|
|
5
|
+
SidebarFooter,
|
|
6
|
+
SidebarHeader,
|
|
7
|
+
SidebarRail,
|
|
8
|
+
} from "#components/ui/sidebar";
|
|
9
|
+
|
|
10
|
+
export type AppSidebarProps = {
|
|
11
|
+
header: ReactNode;
|
|
12
|
+
content?: ReactNode;
|
|
13
|
+
|
|
14
|
+
footer?: ReactNode;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function AppSidebar({ header, content, footer }: AppSidebarProps) {
|
|
18
|
+
return (
|
|
19
|
+
<Sidebar collapsible="icon">
|
|
20
|
+
<SidebarHeader>{header}</SidebarHeader>
|
|
21
|
+
<SidebarContent>{content}</SidebarContent>
|
|
22
|
+
<SidebarFooter>{footer}</SidebarFooter>
|
|
23
|
+
<SidebarRail />
|
|
24
|
+
</Sidebar>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Link } from "react-router";
|
|
2
|
+
import { CollapsibleSidebarMenuItem } from "#components/CollapsibleSidebarMenuItem";
|
|
3
|
+
import {
|
|
4
|
+
SidebarGroup,
|
|
5
|
+
SidebarMenu,
|
|
6
|
+
SidebarMenuButton,
|
|
7
|
+
SidebarMenuItem,
|
|
8
|
+
} from "#components/ui/sidebar";
|
|
9
|
+
|
|
10
|
+
type NavigationItem = {
|
|
11
|
+
label: string;
|
|
12
|
+
icon: React.ReactNode;
|
|
13
|
+
link: string;
|
|
14
|
+
badge?: React.ReactNode;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type AppSidebarContentProps = {
|
|
18
|
+
navigationItems: (NavigationItem & {
|
|
19
|
+
defaultOpen?: boolean;
|
|
20
|
+
subItems?: NavigationItem[];
|
|
21
|
+
})[];
|
|
22
|
+
navigationState: Record<string, boolean>;
|
|
23
|
+
onNavigationStateChange: (state: Record<string, boolean>) => void;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function AppSidebarContent({
|
|
27
|
+
navigationItems,
|
|
28
|
+
navigationState,
|
|
29
|
+
onNavigationStateChange,
|
|
30
|
+
}: AppSidebarContentProps) {
|
|
31
|
+
return (
|
|
32
|
+
<SidebarGroup>
|
|
33
|
+
<SidebarMenu>
|
|
34
|
+
{navigationItems.map((item) =>
|
|
35
|
+
item.subItems ? (
|
|
36
|
+
<CollapsibleSidebarMenuItem
|
|
37
|
+
key={item.label}
|
|
38
|
+
defaultOpen={item.defaultOpen}
|
|
39
|
+
open={navigationState[item.label]}
|
|
40
|
+
label={item.label}
|
|
41
|
+
icon={item.icon}
|
|
42
|
+
link={item.link}
|
|
43
|
+
badge={item.badge}
|
|
44
|
+
onOpenChange={(open) =>
|
|
45
|
+
onNavigationStateChange({ ...navigationState, [item.label]: open })
|
|
46
|
+
}
|
|
47
|
+
>
|
|
48
|
+
{item.subItems.map((subItem) => (
|
|
49
|
+
<SidebarMenuItem key={subItem.label}>
|
|
50
|
+
<SidebarMenuButton asChild>
|
|
51
|
+
<Link to={subItem.link}>
|
|
52
|
+
{subItem.icon}
|
|
53
|
+
<span>{subItem.label}</span>
|
|
54
|
+
</Link>
|
|
55
|
+
</SidebarMenuButton>
|
|
56
|
+
</SidebarMenuItem>
|
|
57
|
+
))}
|
|
58
|
+
</CollapsibleSidebarMenuItem>
|
|
59
|
+
) : (
|
|
60
|
+
<SidebarMenuItem key={item.label}>
|
|
61
|
+
<SidebarMenuButton asChild>
|
|
62
|
+
<Link to={item.link}>
|
|
63
|
+
{item.icon}
|
|
64
|
+
<span>{item.label}</span>
|
|
65
|
+
</Link>
|
|
66
|
+
</SidebarMenuButton>
|
|
67
|
+
</SidebarMenuItem>
|
|
68
|
+
)
|
|
69
|
+
)}
|
|
70
|
+
</SidebarMenu>
|
|
71
|
+
</SidebarGroup>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Button } from "@heroui/react";
|
|
2
|
+
import { useSidebar } from "@m5kdev/web-ui/components/ui/sidebar";
|
|
3
|
+
import { cn } from "@m5kdev/web-ui/utils";
|
|
4
|
+
import { ChevronsLeftIcon, ChevronsRightIcon } from "lucide-react";
|
|
5
|
+
import { Link } from "react-router";
|
|
6
|
+
|
|
7
|
+
export function AppSidebarHeader({
|
|
8
|
+
logo,
|
|
9
|
+
title,
|
|
10
|
+
size = 30,
|
|
11
|
+
}: {
|
|
12
|
+
logo: { src: string; alt: string };
|
|
13
|
+
title: string;
|
|
14
|
+
size?: number;
|
|
15
|
+
}) {
|
|
16
|
+
const { open, toggleSidebar } = useSidebar();
|
|
17
|
+
return (
|
|
18
|
+
<div
|
|
19
|
+
className={cn(
|
|
20
|
+
"flex justify-between items-center overflow-hidden gap-2",
|
|
21
|
+
open ? "w-[239px] px-2 flex-row" : "w-auto flex-col"
|
|
22
|
+
)}
|
|
23
|
+
>
|
|
24
|
+
<Link
|
|
25
|
+
to="/"
|
|
26
|
+
className={cn(
|
|
27
|
+
"flex items-center font-medium overflow-hidden gap-2",
|
|
28
|
+
open ? "w-[239px] px-2" : "w-auto"
|
|
29
|
+
)}
|
|
30
|
+
style={{ height: size }}
|
|
31
|
+
>
|
|
32
|
+
<img
|
|
33
|
+
className="shrink-0"
|
|
34
|
+
src={logo.src}
|
|
35
|
+
alt={logo.alt}
|
|
36
|
+
style={{ width: size, height: size }}
|
|
37
|
+
/>
|
|
38
|
+
<span className="group-data-[collapsible=icon]:hidden font-semibold text-lg text-neutral-900">
|
|
39
|
+
{title}
|
|
40
|
+
</span>
|
|
41
|
+
</Link>
|
|
42
|
+
<Button
|
|
43
|
+
isIconOnly
|
|
44
|
+
variant="faded"
|
|
45
|
+
size="sm"
|
|
46
|
+
className="w-4 h-6"
|
|
47
|
+
onPress={() => toggleSidebar()}
|
|
48
|
+
>
|
|
49
|
+
{open ? (
|
|
50
|
+
<ChevronsLeftIcon className="w-4 h-4" />
|
|
51
|
+
) : (
|
|
52
|
+
<ChevronsRightIcon className="w-4 h-4" />
|
|
53
|
+
)}
|
|
54
|
+
</Button>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Badge, Button, Tooltip } from "@heroui/react";
|
|
2
|
+
import { useSidebar } from "@m5kdev/web-ui/components/ui/sidebar";
|
|
3
|
+
import { cn } from "@m5kdev/web-ui/utils";
|
|
4
|
+
import { GiftIcon } from "lucide-react";
|
|
5
|
+
import { useTranslation } from "react-i18next";
|
|
6
|
+
import { Link } from "react-router";
|
|
7
|
+
|
|
8
|
+
export function AppSidebarInvites({ count }: { count: number }) {
|
|
9
|
+
const { open } = useSidebar();
|
|
10
|
+
const { t } = useTranslation("web-ui");
|
|
11
|
+
return (
|
|
12
|
+
<div className={cn("flex justify-center w-auto")}>
|
|
13
|
+
<Tooltip content={t("sidebar.invites.title")} placement="right" isDisabled={open}>
|
|
14
|
+
<Badge color="primary" variant="faded" content={count} size="sm" isOneChar>
|
|
15
|
+
<Button
|
|
16
|
+
as={Link}
|
|
17
|
+
to="/invites"
|
|
18
|
+
className={cn("flex items-center gap-2")}
|
|
19
|
+
isIconOnly={!open}
|
|
20
|
+
size="sm"
|
|
21
|
+
variant="light"
|
|
22
|
+
>
|
|
23
|
+
<GiftIcon className="w-4 h-4 text-neutral-500" />
|
|
24
|
+
<span className="group-data-[collapsible=icon]:hidden text-sm text-neutral-500">
|
|
25
|
+
{t("sidebar.invites.title")}
|
|
26
|
+
</span>
|
|
27
|
+
</Button>
|
|
28
|
+
</Badge>
|
|
29
|
+
</Tooltip>
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|