@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,323 @@
1
+ import type { DateValue, RangeValue, SharedSelection } from "@heroui/react";
2
+ import { CalendarDate, getLocalTimeZone, today } from "@internationalized/date";
3
+ import type { QueryFilter, QueryFilters } from "@m5kdev/commons/modules/schemas/query.schema";
4
+ import type { ColumnDataType, FilterMethod } from "@m5kdev/commons/modules/table/filter.types";
5
+ import { DateTime } from "luxon";
6
+
7
+ export type FilterValue =
8
+ | string
9
+ | number
10
+ | string[]
11
+ | DateValue
12
+ | RangeValue<DateValue>
13
+ | boolean
14
+ | SharedSelection
15
+ | null;
16
+
17
+ export interface HeroUIFilter {
18
+ columnId: string;
19
+ type: ColumnDataType | null;
20
+ value: FilterValue;
21
+ method: FilterMethod | null;
22
+ options?: { label: string; value: string }[] | null;
23
+ endColumnId?: string | null;
24
+ periodStartColumnId?: string | null;
25
+ periodEndColumnId?: string | null;
26
+ }
27
+
28
+ /**
29
+ * Convert CalendarDate to UTC ISO string at midnight UTC
30
+ */
31
+ export const calendarDateToUTC = (date: CalendarDate): string => {
32
+ return DateTime.utc(date.year, date.month, date.day).toISO() ?? "";
33
+ };
34
+
35
+ /**
36
+ * Convert CalendarDate to end of day UTC ISO string
37
+ */
38
+ export const calendarDateToEndOfDayUTC = (date: CalendarDate): string => {
39
+ return DateTime.utc(date.year, date.month, date.day).endOf("day").toISO() ?? "";
40
+ };
41
+
42
+ /**
43
+ * Parse UTC ISO string to CalendarDate (preserves UTC date, no timezone shift)
44
+ */
45
+ const parseUTCToCalendarDate = (isoString: string): CalendarDate | null => {
46
+ try {
47
+ const dt = DateTime.fromISO(isoString, { zone: "utc" });
48
+ if (!dt.isValid) return null;
49
+ return new CalendarDate(dt.year, dt.month, dt.day);
50
+ } catch {
51
+ return null;
52
+ }
53
+ };
54
+
55
+ /**
56
+ * Convert any date filter method from URL to a RangeValue for DateRangePicker
57
+ * Handles: on, before, after, between, intersect
58
+ * Parses UTC ISO strings as UTC to avoid timezone shifts
59
+ */
60
+ export const dateFilterToRangeValue = (
61
+ filters: QueryFilters | undefined,
62
+ columnId: string
63
+ ): RangeValue<DateValue> | null => {
64
+ if (!filters) return null;
65
+
66
+ const filter = filters.find((f) => f.columnId === columnId);
67
+ if (!filter || filter.type !== "date") return null;
68
+
69
+ const todayDate = today(getLocalTimeZone());
70
+ const epochStart = new CalendarDate(1970, 1, 1);
71
+
72
+ try {
73
+ switch (filter.method) {
74
+ case "on": {
75
+ // Same day range
76
+ if (typeof filter.value !== "string" || !filter.value) return null;
77
+ const day = parseUTCToCalendarDate(filter.value);
78
+ if (!day) return null;
79
+ return {
80
+ start: day as unknown as DateValue,
81
+ end: day as unknown as DateValue,
82
+ } as RangeValue<DateValue>;
83
+ }
84
+ case "before": {
85
+ // [epochStart, selectedDay]
86
+ if (typeof filter.value !== "string" || !filter.value) return null;
87
+ const day = parseUTCToCalendarDate(filter.value);
88
+ if (!day) return null;
89
+ return {
90
+ start: epochStart as unknown as DateValue,
91
+ end: day as unknown as DateValue,
92
+ } as RangeValue<DateValue>;
93
+ }
94
+ case "after": {
95
+ // [selectedDay, today]
96
+ if (typeof filter.value !== "string" || !filter.value) return null;
97
+ const day = parseUTCToCalendarDate(filter.value);
98
+ if (!day) return null;
99
+ return {
100
+ start: day as unknown as DateValue,
101
+ end: todayDate as unknown as DateValue,
102
+ } as RangeValue<DateValue>;
103
+ }
104
+ case "between":
105
+ case "intersect": {
106
+ // [value, valueTo]
107
+ if (typeof filter.value !== "string" || !filter.value || !filter.valueTo) return null;
108
+ const startDate = parseUTCToCalendarDate(filter.value);
109
+ const endDate = parseUTCToCalendarDate(filter.valueTo);
110
+ if (!startDate || !endDate) return null;
111
+ return {
112
+ start: startDate as unknown as DateValue,
113
+ end: endDate as unknown as DateValue,
114
+ } as RangeValue<DateValue>;
115
+ }
116
+ default:
117
+ return null;
118
+ }
119
+ } catch {
120
+ return null;
121
+ }
122
+ };
123
+
124
+ /**
125
+ * Transform filters from backend format (QueryFilter[]) to HeroUI format (HeroUIFilter[])
126
+ * Used when loading filters from URL/backend to populate HeroUI components
127
+ */
128
+ export const transformFiltersToHeroUI = (
129
+ filtersToTransform: QueryFilters,
130
+ columnsMap: Map<
131
+ string,
132
+ {
133
+ id: string;
134
+ label: string;
135
+ type?: ColumnDataType | null;
136
+ options?: { label: string; value: string }[] | null;
137
+ endColumnId?: string | null;
138
+ periodStartColumnId?: string | null;
139
+ periodEndColumnId?: string | null;
140
+ }
141
+ >,
142
+ filterMethods: Record<ColumnDataType, FilterMethod[]>
143
+ ): HeroUIFilter[] => {
144
+ return filtersToTransform.map((filter) => {
145
+ let value: FilterValue = null;
146
+ let method: FilterMethod | null = null;
147
+ let columnId = filter.columnId;
148
+
149
+ // Check if this intersect filter should map to Period pseudo-column
150
+ if (filter.type === "date" && filter.method === "intersect" && filter.endColumnId) {
151
+ const periodColumnId = `${filter.columnId}__period`;
152
+ const periodColumn = columnsMap.get(periodColumnId);
153
+ if (periodColumn) {
154
+ // Map to Period pseudo-column
155
+ columnId = periodColumnId;
156
+ }
157
+ }
158
+
159
+ // Find the method object from the methods configuration
160
+ if (filter.type) {
161
+ const availableMethods = filterMethods[filter.type as ColumnDataType];
162
+ const methodObj = availableMethods?.find((m: FilterMethod) => m.value === filter.method);
163
+ if (methodObj) {
164
+ method = methodObj;
165
+ } else if (filter.method === "isEmpty" || filter.method === "isNotEmpty") {
166
+ // Handle isEmpty/isNotEmpty even if not in the provided method list
167
+ method = {
168
+ value: filter.method,
169
+ label: filter.method === "isEmpty" ? "Is Empty" : "Is Not Empty",
170
+ component: null,
171
+ };
172
+ }
173
+ }
174
+
175
+ // Transform value based on type
176
+ switch (filter.type) {
177
+ case "string":
178
+ value = filter.value as string;
179
+ break;
180
+ case "number":
181
+ value = filter.value as number;
182
+ break;
183
+ case "date":
184
+ // Use the shared helper for range conversion, or parse single dates as UTC
185
+ if (filter.method === "between" || filter.method === "intersect") {
186
+ // For range methods, use the helper function
187
+ const range = dateFilterToRangeValue([filter], filter.columnId);
188
+ value = range;
189
+ } else {
190
+ // Single date value: parse UTC ISO string and extract UTC calendar date
191
+ if (typeof filter.value === "string" && filter.value) {
192
+ const day = parseUTCToCalendarDate(filter.value);
193
+ value = day ? (day as unknown as DateValue) : null;
194
+ } else {
195
+ value = null;
196
+ }
197
+ }
198
+ break;
199
+ case "boolean":
200
+ value = filter.value as boolean;
201
+ break;
202
+ case "enum":
203
+ if (filter.method === "oneOf" && Array.isArray(filter.value)) {
204
+ // Multi-select: array of strings to SharedSelection (Set)
205
+ value = new Set(filter.value) as SharedSelection;
206
+ } else {
207
+ // Single select: string to SharedSelection with currentKey
208
+ const selection = new Set([filter.value as string]) as SharedSelection;
209
+ // Set currentKey property
210
+ Object.defineProperty(selection, "currentKey", {
211
+ value: filter.value as string,
212
+ writable: true,
213
+ enumerable: false,
214
+ configurable: true,
215
+ });
216
+ value = selection;
217
+ }
218
+ break;
219
+ default:
220
+ value = filter.value;
221
+ }
222
+
223
+ const column = columnsMap.get(columnId);
224
+ return {
225
+ columnId,
226
+ type: filter.type,
227
+ value,
228
+ method,
229
+ options: column?.options ?? null,
230
+ endColumnId: column?.endColumnId ?? null,
231
+ periodStartColumnId: column?.periodStartColumnId ?? null,
232
+ periodEndColumnId: column?.periodEndColumnId ?? null,
233
+ };
234
+ });
235
+ };
236
+
237
+ /**
238
+ * Transform filters from HeroUI format (HeroUIFilter[]) to backend format (QueryFilter[])
239
+ * Used when applying filters to send to backend/URL
240
+ */
241
+ export const transformFiltersFromHeroUI = (filters: HeroUIFilter[]): QueryFilters => {
242
+ const filtersToApply: QueryFilters = [];
243
+
244
+ for (const filter of filters) {
245
+ let value: FilterValue | undefined;
246
+ let valueTo: FilterValue | undefined;
247
+
248
+ // Handle isEmpty/isNotEmpty methods - they don't need a value
249
+ if (filter.method?.value === "isEmpty" || filter.method?.value === "isNotEmpty") {
250
+ if (filter.columnId === "") continue;
251
+
252
+ const result: QueryFilter = {
253
+ columnId: filter.columnId,
254
+ type: filter.type as ColumnDataType,
255
+ method: filter.method.value,
256
+ value: "",
257
+ };
258
+ filtersToApply.push(result);
259
+ continue;
260
+ }
261
+
262
+ // Handle Period pseudo-column: map to intersect on real columns
263
+ const isPeriodColumn = filter.columnId.endsWith("__period");
264
+ const actualColumnId =
265
+ isPeriodColumn && filter.periodStartColumnId ? filter.periodStartColumnId : filter.columnId;
266
+ const actualEndColumnId =
267
+ isPeriodColumn && filter.periodEndColumnId ? filter.periodEndColumnId : filter.endColumnId;
268
+
269
+ switch (filter.type) {
270
+ case "date":
271
+ if (
272
+ filter.method?.value === "between" ||
273
+ filter.method?.value === "intersect" ||
274
+ isPeriodColumn
275
+ ) {
276
+ const range = filter.value as RangeValue<DateValue>;
277
+ if (range?.start && range?.end) {
278
+ value = calendarDateToUTC(range.start as unknown as CalendarDate);
279
+ valueTo = calendarDateToUTC(range.end as unknown as CalendarDate);
280
+ }
281
+ } else {
282
+ const dateValue = filter.value as unknown as CalendarDate;
283
+ if (dateValue?.year) {
284
+ value = calendarDateToUTC(dateValue);
285
+ }
286
+ }
287
+ break;
288
+ case "enum": {
289
+ const selection = filter.value as SharedSelection;
290
+ if (selection) {
291
+ value =
292
+ filter.method?.value === "oneOf"
293
+ ? Array.from(selection).map(String)
294
+ : String(selection.currentKey);
295
+ }
296
+ break;
297
+ }
298
+ default:
299
+ value = filter.value;
300
+ }
301
+
302
+ // Skip filters without valid values
303
+ if (!value || value === "" || filter.columnId === "") continue;
304
+ if (filter.type === "enum" && Array.isArray(value) && value.length === 0) continue;
305
+
306
+ // For Period columns, always use intersect method
307
+ const actualMethod = isPeriodColumn ? "intersect" : (filter.method as FilterMethod).value;
308
+
309
+ const result: QueryFilter = {
310
+ columnId: actualColumnId,
311
+ type: filter.type as ColumnDataType,
312
+ method: actualMethod,
313
+ value: value as string | number | boolean | string[],
314
+ ...(valueTo && { valueTo: valueTo as string }),
315
+ ...(actualMethod === "intersect" && actualEndColumnId
316
+ ? { endColumnId: actualEndColumnId }
317
+ : {}),
318
+ };
319
+ filtersToApply.push(result);
320
+ }
321
+
322
+ return filtersToApply;
323
+ };
package/src/types.ts ADDED
@@ -0,0 +1,4 @@
1
+ import type { BackendTRPCRouter } from "@m5kdev/backend/types";
2
+ import type { createTRPCContext } from "@trpc/tanstack-react-query";
3
+
4
+ export type UseBackendTRPC = ReturnType<typeof createTRPCContext<BackendTRPCRouter>>["useTRPC"];
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />