@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.
Files changed (127) hide show
  1. package/LICENSE +621 -0
  2. package/README.md +17 -0
  3. package/package.json +169 -0
  4. package/src/animations/card.motion.ts +9 -0
  5. package/src/components/AvatarUpload.tsx +133 -0
  6. package/src/components/Button.tsx +14 -0
  7. package/src/components/Calendar.css +684 -0
  8. package/src/components/Calendar.tsx +32 -0
  9. package/src/components/CardsSelect.tsx +155 -0
  10. package/src/components/CollapsibleSidebarMenuItem.tsx +57 -0
  11. package/src/components/ColorPicker.tsx +56 -0
  12. package/src/components/CopyButton.tsx +45 -0
  13. package/src/components/CropDialog.tsx +154 -0
  14. package/src/components/DialogProvider.tsx +105 -0
  15. package/src/components/ErrorFallback.tsx +17 -0
  16. package/src/components/FileDropzone.tsx +120 -0
  17. package/src/components/MultiSelectDropdown.tsx +233 -0
  18. package/src/components/Orb.tsx +288 -0
  19. package/src/components/PageAlert.tsx +121 -0
  20. package/src/components/SelectChips.tsx +40 -0
  21. package/src/components/SidebarItem.tsx +26 -0
  22. package/src/components/Steps.tsx +340 -0
  23. package/src/components/TablerIconPicker.tsx +4260 -0
  24. package/src/components/app-header.tsx +40 -0
  25. package/src/components/blur-card.tsx +132 -0
  26. package/src/components/features-section-demo-1.tsx +127 -0
  27. package/src/components/features-section-demo-2.tsx +102 -0
  28. package/src/components/features-section-demo-3.tsx +272 -0
  29. package/src/components/mode-toggle.tsx +31 -0
  30. package/src/components/nav-main.tsx +69 -0
  31. package/src/components/pricing-cards.tsx +133 -0
  32. package/src/components/shared/ButtonCopy.tsx +50 -0
  33. package/src/components/team-switcher.tsx +83 -0
  34. package/src/components/theme-provider.tsx +74 -0
  35. package/src/components/typewriter.tsx +90 -0
  36. package/src/components/ui/alert-dialog.tsx +133 -0
  37. package/src/components/ui/alert.tsx +60 -0
  38. package/src/components/ui/avatar.tsx +47 -0
  39. package/src/components/ui/badge.tsx +33 -0
  40. package/src/components/ui/bento-grid.tsx +54 -0
  41. package/src/components/ui/bento-grid2.tsx +66 -0
  42. package/src/components/ui/breadcrumb.tsx +101 -0
  43. package/src/components/ui/button.tsx +50 -0
  44. package/src/components/ui/card.tsx +55 -0
  45. package/src/components/ui/checkbox.tsx +26 -0
  46. package/src/components/ui/collapsible.tsx +9 -0
  47. package/src/components/ui/dialog.tsx +119 -0
  48. package/src/components/ui/dropdown-menu.tsx +186 -0
  49. package/src/components/ui/floating-navbar.tsx +78 -0
  50. package/src/components/ui/form.tsx +167 -0
  51. package/src/components/ui/image.tsx +55 -0
  52. package/src/components/ui/input.tsx +22 -0
  53. package/src/components/ui/label.tsx +19 -0
  54. package/src/components/ui/pagination.tsx +105 -0
  55. package/src/components/ui/progress.tsx +23 -0
  56. package/src/components/ui/resizable-navbar.tsx +260 -0
  57. package/src/components/ui/segment-control.tsx +143 -0
  58. package/src/components/ui/select.tsx +153 -0
  59. package/src/components/ui/separator.tsx +24 -0
  60. package/src/components/ui/sheet.tsx +121 -0
  61. package/src/components/ui/sidebar.tsx +736 -0
  62. package/src/components/ui/skeleton.tsx +7 -0
  63. package/src/components/ui/slider.tsx +23 -0
  64. package/src/components/ui/sonner.tsx +27 -0
  65. package/src/components/ui/spinner.tsx +45 -0
  66. package/src/components/ui/switch.tsx +27 -0
  67. package/src/components/ui/table.tsx +90 -0
  68. package/src/components/ui/tabs.tsx +52 -0
  69. package/src/components/ui/textarea.tsx +18 -0
  70. package/src/components/ui/timeline.tsx +95 -0
  71. package/src/components/ui/toast.tsx +126 -0
  72. package/src/components/ui/tooltip.tsx +55 -0
  73. package/src/components/ui/typewriter-effect.tsx +181 -0
  74. package/src/hooks/use-mobile.ts +19 -0
  75. package/src/hooks/useDialog.ts +25 -0
  76. package/src/icons/GoogleIcon.tsx +32 -0
  77. package/src/icons/LinkedInIcon.tsx +30 -0
  78. package/src/icons/MicrosoftIcon.tsx +21 -0
  79. package/src/lib/chatwoot.ts +51 -0
  80. package/src/lib/utils.ts +6 -0
  81. package/src/modules/app/components/AppLoader.tsx +9 -0
  82. package/src/modules/app/components/AppShell.tsx +21 -0
  83. package/src/modules/app/components/AppSidebar.tsx +26 -0
  84. package/src/modules/app/components/AppSidebarContent.tsx +73 -0
  85. package/src/modules/app/components/AppSidebarHeader.tsx +57 -0
  86. package/src/modules/app/components/AppSidebarInvites.tsx +32 -0
  87. package/src/modules/app/components/AppSidebarUser.tsx +128 -0
  88. package/src/modules/auth/components/AdminUserManagement.tsx +1136 -0
  89. package/src/modules/auth/components/AdminWaitlist.tsx +358 -0
  90. package/src/modules/auth/components/AuthLayout.tsx +13 -0
  91. package/src/modules/auth/components/AuthProviders.tsx +105 -0
  92. package/src/modules/auth/components/AuthRouter.tsx +29 -0
  93. package/src/modules/auth/components/ClaimAccountRoute.tsx +242 -0
  94. package/src/modules/auth/components/ErrorAuthRoute.tsx +121 -0
  95. package/src/modules/auth/components/ForgotPasswordForm.tsx +58 -0
  96. package/src/modules/auth/components/ForgotPasswordRoute.tsx +27 -0
  97. package/src/modules/auth/components/InviteFriends.tsx +273 -0
  98. package/src/modules/auth/components/LastUsedBadge.tsx +22 -0
  99. package/src/modules/auth/components/LoginForm.tsx +104 -0
  100. package/src/modules/auth/components/LoginRoute.tsx +31 -0
  101. package/src/modules/auth/components/LogoutRoute.tsx +21 -0
  102. package/src/modules/auth/components/OrganizationAcceptInvitationRoute.tsx +161 -0
  103. package/src/modules/auth/components/OrganizationMembersRoute.tsx +730 -0
  104. package/src/modules/auth/components/OrganizationSettingsRoute.tsx +280 -0
  105. package/src/modules/auth/components/OrganizationSwitcher.tsx +148 -0
  106. package/src/modules/auth/components/ProfileRoute.tsx +104 -0
  107. package/src/modules/auth/components/RangeNuqsDatePicker.tsx +365 -0
  108. package/src/modules/auth/components/ResetPasswordForm.tsx +103 -0
  109. package/src/modules/auth/components/ResetPasswordRoute.tsx +27 -0
  110. package/src/modules/auth/components/SignupFormRoute.tsx +189 -0
  111. package/src/modules/auth/components/SignupRoute.tsx +53 -0
  112. package/src/modules/auth/components/UserPreferences.tsx +144 -0
  113. package/src/modules/auth/components/WaitlistCard.tsx +78 -0
  114. package/src/modules/auth/components/WaitlistCodeValidation.tsx +79 -0
  115. package/src/modules/billing/components/BillingBetaPage.tsx +124 -0
  116. package/src/modules/billing/components/BillingInvoicePage.tsx +180 -0
  117. package/src/modules/billing/components/BillingPlanSelect.tsx +14 -0
  118. package/src/modules/billing/components/BillingRouter.tsx +20 -0
  119. package/src/modules/billing/components/BillingSinglePlanSelect.tsx +172 -0
  120. package/src/modules/table/components/ColumnOrderAndVisibility.tsx +127 -0
  121. package/src/modules/table/components/NuqsTable.tsx +396 -0
  122. package/src/modules/table/components/TableFiltering.tsx +520 -0
  123. package/src/modules/table/components/TablePagination.tsx +59 -0
  124. package/src/modules/table/components/table.types.ts +11 -0
  125. package/src/modules/table/filterTransformers.ts +323 -0
  126. package/src/types.ts +4 -0
  127. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,20 @@
1
+ import type { StripePlan } from "@m5kdev/commons/modules/billing/billing.types";
2
+ import { Route } from "react-router";
3
+ import { BillingInvoicePage } from "#modules/billing/components/BillingInvoicePage";
4
+ import type { UseBackendTRPC } from "#types";
5
+
6
+ interface BillingRouterProps {
7
+ useTRPC?: UseBackendTRPC;
8
+ serverUrl: string;
9
+ plans: StripePlan[];
10
+ }
11
+
12
+ export function BillingRouter({ useTRPC, serverUrl }: BillingRouterProps) {
13
+ if (!useTRPC) return null;
14
+
15
+ return (
16
+ <Route path="billing">
17
+ <Route index element={<BillingInvoicePage useTRPC={useTRPC} serverUrl={serverUrl} />} />
18
+ </Route>
19
+ );
20
+ }
@@ -0,0 +1,172 @@
1
+ import { Button } from "@heroui/react";
2
+ import type { StripePlan } from "@m5kdev/commons/modules/billing/billing.types";
3
+ import { authClient } from "@m5kdev/frontend/modules/auth/auth.lib";
4
+ import { Check, LogOut } from "lucide-react";
5
+ import { useState } from "react";
6
+ import { useTranslation } from "react-i18next";
7
+ import { Link, useNavigate } from "react-router";
8
+ import {
9
+ Card,
10
+ CardContent,
11
+ CardDescription,
12
+ CardFooter,
13
+ CardHeader,
14
+ CardTitle,
15
+ } from "#components/ui/card";
16
+ import { Tabs, TabsList, TabsTrigger } from "#components/ui/tabs";
17
+ import { cn } from "#utils";
18
+
19
+ export interface BillingSinglePlanSelectProps {
20
+ plan: StripePlan;
21
+ features?: string[];
22
+ /** URL for the Terms of Service link. Override for your app's legal page. */
23
+ termsOfServiceUrl?: string;
24
+ }
25
+
26
+ export function BillingSinglePlanSelect({
27
+ plan,
28
+ termsOfServiceUrl,
29
+ features = [
30
+ "Unlimited access to all features",
31
+ "Priority support",
32
+ "Early access to new features",
33
+ "Secure data storage",
34
+ "Cancel anytime",
35
+ ],
36
+ }: BillingSinglePlanSelectProps) {
37
+ const { t } = useTranslation("web-ui");
38
+ const navigate = useNavigate();
39
+ const [billingInterval, setBillingInterval] = useState<"monthly" | "annually">("annually");
40
+
41
+ const handleLogout = async () => {
42
+ await authClient.signOut();
43
+ navigate("/login");
44
+ };
45
+
46
+ const isAnnual = billingInterval === "annually";
47
+ const currentPriceId = isAnnual ? plan.annualDiscountPriceId : plan.priceId;
48
+
49
+ // Fallback if no annual price ID exists
50
+ const hasAnnualOption = !!plan.annualDiscountPriceId;
51
+
52
+ const priceUnitAmount = plan.priceUnitAmount ?? Number.NaN;
53
+ const annualPriceUnitAmount = plan.annualPriceUnitAmount ?? Number.NaN;
54
+
55
+ const priceDisplay = {
56
+ monthly: {
57
+ amount: `$${priceUnitAmount / 100}`,
58
+ label: "/ month",
59
+ },
60
+ annually: {
61
+ amount: `$${annualPriceUnitAmount / 100 / 12}`,
62
+ originalAmount: `$${priceUnitAmount / 100}`,
63
+ label: "/ month, billed annually",
64
+ discountLabel: plan.annualPriceUnitAmount
65
+ ? `Save ${Math.floor(((priceUnitAmount - annualPriceUnitAmount / 12) / priceUnitAmount) * 100).toFixed(0)}%`
66
+ : "",
67
+ },
68
+ };
69
+
70
+ return (
71
+ <div className="w-full max-w-3xl mx-auto px-4 py-8">
72
+ <div className="flex flex-col items-center gap-8">
73
+ <div className="text-center space-y-2">
74
+ <h2 className="text-3xl font-bold tracking-tight sm:text-4xl">
75
+ {t("billing.plans.title", "Simple, transparent pricing")}
76
+ </h2>
77
+ <p className="text-muted-foreground text-lg">
78
+ {t("billing.plans.subtitle", "Choose the plan that's right for you")}
79
+ </p>
80
+ </div>
81
+
82
+ {hasAnnualOption && (
83
+ <Tabs
84
+ defaultValue="annually"
85
+ value={billingInterval}
86
+ onValueChange={(v) => setBillingInterval(v as "monthly" | "annually")}
87
+ className="w-full max-w-xs"
88
+ >
89
+ <TabsList className="grid w-full grid-cols-2">
90
+ <TabsTrigger value="monthly">Monthly</TabsTrigger>
91
+ <TabsTrigger value="annually" className="relative">
92
+ Annually
93
+ {priceDisplay.annually.discountLabel && (
94
+ <span className="absolute -top-3 -right-3 px-1.5 py-0.5 rounded-full bg-green-500 text-[10px] text-white font-medium transform rotate-12">
95
+ {priceDisplay.annually.discountLabel}
96
+ </span>
97
+ )}
98
+ </TabsTrigger>
99
+ </TabsList>
100
+ </Tabs>
101
+ )}
102
+
103
+ <Card className={cn("w-full max-w-md border-2 border-primary")}>
104
+ <CardHeader>
105
+ <CardTitle className="flex justify-between items-start">
106
+ <span className="text-xl font-bold">{plan.name}</span>
107
+ </CardTitle>
108
+ <CardDescription>
109
+ {isAnnual ? "Perfect for long-term commitment" : "Flexible monthly billing"}
110
+ </CardDescription>
111
+ </CardHeader>
112
+
113
+ <CardContent className="space-y-6">
114
+ <div className="flex items-baseline gap-1">
115
+ <span className="text-4xl font-bold">
116
+ {isAnnual ? priceDisplay.annually.amount : priceDisplay.monthly.amount}
117
+ </span>
118
+ <span className="text-muted-foreground">
119
+ {isAnnual ? priceDisplay.annually.label : priceDisplay.monthly.label}
120
+ </span>
121
+ </div>
122
+
123
+ {isAnnual && priceDisplay.annually.originalAmount && (
124
+ <p className="text-sm text-green-500 line-through">
125
+ {" "}
126
+ {priceDisplay.annually.originalAmount} / month
127
+ </p>
128
+ )}
129
+
130
+ <div className="space-y-3">
131
+ {features.map((feature) => (
132
+ <div key={feature} className="flex items-center gap-2">
133
+ <div className="h-5 w-5 rounded-full bg-primary/10 flex items-center justify-center shrink-0">
134
+ <Check className="h-3 w-3 text-primary" />
135
+ </div>
136
+ <span className="text-sm text-muted-foreground">{feature}</span>
137
+ </div>
138
+ ))}
139
+ </div>
140
+ </CardContent>
141
+
142
+ <CardFooter>
143
+ <Button
144
+ className="w-full"
145
+ color="primary"
146
+ size="lg"
147
+ as="a"
148
+ href={`${import.meta.env.VITE_SERVER_URL}/stripe/checkout/${currentPriceId}`}
149
+ >
150
+ {isAnnual ? "Subscribe Annually" : "Subscribe Monthly"}
151
+ </Button>
152
+ </CardFooter>
153
+ </Card>
154
+
155
+ <div className="text-balance text-center text-xs text-muted-foreground [&_a]:underline [&_a]:underline-offset-4 [&_a]:hover:text-primary">
156
+ <a href={termsOfServiceUrl} rel="noopener noreferrer" target="_blank">
157
+ {t("common.termsOfService", "Terms of Service")}
158
+ </a>{" "}
159
+ {t("common.and", "and")}{" "}
160
+ <Link to="/privacy">{t("common.privacyPolicy", "Privacy Policy")}</Link>
161
+ </div>
162
+ </div>
163
+
164
+ <div className="fixed bottom-4 left-4">
165
+ <Button variant="light" onPress={handleLogout} className="gap-2">
166
+ <LogOut className="h-4 w-4" />
167
+ {t("sidebar.user.logout", "Log out")}
168
+ </Button>
169
+ </div>
170
+ </div>
171
+ );
172
+ }
@@ -0,0 +1,127 @@
1
+ import { DndContext, KeyboardSensor, PointerSensor, useSensor, useSensors } from "@dnd-kit/core";
2
+ import {
3
+ arrayMove,
4
+ SortableContext,
5
+ sortableKeyboardCoordinates,
6
+ useSortable,
7
+ verticalListSortingStrategy,
8
+ } from "@dnd-kit/sortable";
9
+ import { CSS } from "@dnd-kit/utilities";
10
+ import { EyeIcon, EyeOffIcon, GripVertical } from "lucide-react";
11
+ import { useCallback, useState } from "react";
12
+ import type { ColumnOrderState, VisibilityState } from "@tanstack/react-table";
13
+ import { Button } from "#components/ui/button";
14
+ import type { ColumnItem } from "./table.types";
15
+
16
+ interface ColumnOrderAndVisibilityItemProps {
17
+ column: ColumnItem;
18
+ onChangeVisibilityState: (columnId: string) => void;
19
+ }
20
+
21
+ const ColumnOrderAndVisibilityItem = ({
22
+ column,
23
+ onChangeVisibilityState,
24
+ }: ColumnOrderAndVisibilityItemProps) => {
25
+ const { attributes, listeners, setNodeRef, transform, transition } = useSortable({
26
+ id: column.id,
27
+ });
28
+ return (
29
+ <div
30
+ ref={setNodeRef}
31
+ style={{ transform: CSS.Transform.toString(transform), transition }}
32
+ className="flex justify-between items-center gap-2"
33
+ >
34
+ {column.label}
35
+ <div className="flex items-center gap-2">
36
+ <div
37
+ {...attributes}
38
+ {...listeners}
39
+ className="cursor-grab active:cursor-grabbing touch-none"
40
+ >
41
+ <GripVertical className="h-4 w-4" />
42
+ </div>
43
+ <Button
44
+ variant="ghost"
45
+ size="icon"
46
+ onClick={() => onChangeVisibilityState(String(column.id))}
47
+ >
48
+ {column.visibility ? <EyeIcon className="h-4 w-4" /> : <EyeOffIcon className="h-4 w-4" />}
49
+ </Button>
50
+ </div>
51
+ </div>
52
+ );
53
+ };
54
+
55
+ interface ColumnOrderAndVisibilityProps {
56
+ layout: ColumnItem[];
57
+ onChangeOrder: (order: ColumnOrderState) => void;
58
+ onChangeVisibility: (visibility: VisibilityState) => void;
59
+ onClose: () => void;
60
+ }
61
+
62
+ export const ColumnOrderAndVisibility = ({
63
+ layout,
64
+ onChangeOrder,
65
+ onChangeVisibility,
66
+ onClose,
67
+ }: ColumnOrderAndVisibilityProps) => {
68
+ const [activeLayout, setActiveLayout] = useState<ColumnItem[]>([...layout]);
69
+
70
+ const onChangeVisibilityState = useCallback((columnId: string) => {
71
+ setActiveLayout((prev) => {
72
+ const index = prev.findIndex((c) => c.id === columnId);
73
+ if (index !== -1) {
74
+ const newLayout = [...prev];
75
+ newLayout[index] = { ...prev[index], visibility: !prev[index].visibility };
76
+ return newLayout;
77
+ }
78
+ return prev;
79
+ });
80
+ }, []);
81
+
82
+ const sensors = useSensors(
83
+ useSensor(PointerSensor),
84
+ useSensor(KeyboardSensor, {
85
+ coordinateGetter: sortableKeyboardCoordinates,
86
+ })
87
+ );
88
+
89
+ const onSubmit = useCallback(() => {
90
+ onChangeOrder(activeLayout.map((column) => column.id));
91
+ onChangeVisibility(
92
+ Object.fromEntries(activeLayout.map((column) => [column.id, column.visibility]))
93
+ );
94
+ onClose();
95
+ }, [activeLayout, onChangeOrder, onChangeVisibility, onClose]);
96
+
97
+ return (
98
+ <div className="flex flex-col gap-2 p-1 min-w-[200px]">
99
+ <DndContext
100
+ onDragEnd={({ active, over }) => {
101
+ if (over && active.id !== over?.id) {
102
+ const activeIndex = activeLayout.findIndex(({ id }) => id === active.id);
103
+ const overIndex = activeLayout.findIndex(({ id }) => id === over.id);
104
+
105
+ setActiveLayout(arrayMove(activeLayout, activeIndex, overIndex));
106
+ }
107
+ }}
108
+ sensors={sensors}
109
+ >
110
+ <SortableContext
111
+ items={activeLayout.map((column) => column.id)}
112
+ strategy={verticalListSortingStrategy}
113
+ >
114
+ {activeLayout.map((column) => (
115
+ <ColumnOrderAndVisibilityItem
116
+ key={column.id}
117
+ column={column}
118
+ onChangeVisibilityState={onChangeVisibilityState}
119
+ />
120
+ ))}
121
+ </SortableContext>
122
+ </DndContext>
123
+ <Button onClick={onSubmit}>Apply</Button>
124
+ </div>
125
+ );
126
+ };
127
+