@wealthx/shadcn 1.3.1 → 1.3.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/.turbo/turbo-build.log +259 -223
- package/CHANGELOG.md +12 -0
- package/dist/{chunk-2UM72RJ7.mjs → chunk-2D3HQPFN.mjs} +12 -10
- package/dist/chunk-2EM2FRU6.mjs +613 -0
- package/dist/{chunk-FH6QVUVZ.mjs → chunk-2GIYVERS.mjs} +2 -2
- package/dist/chunk-2P7HP7LR.mjs +68 -0
- package/dist/{chunk-HISNT2MG.mjs → chunk-37AE3OM5.mjs} +5 -5
- package/dist/{chunk-HBZLGDIN.mjs → chunk-3ERBUVHC.mjs} +169 -110
- package/dist/{chunk-C7CQJNMR.mjs → chunk-3VDET466.mjs} +2 -2
- package/dist/{chunk-462HMNO4.mjs → chunk-4MM7LHM5.mjs} +2 -2
- package/dist/{chunk-QMY3AZJH.mjs → chunk-4Z66LMIQ.mjs} +2 -2
- package/dist/{chunk-U5X52X37.mjs → chunk-57ZXILTS.mjs} +6 -6
- package/dist/{chunk-3OYFOX3X.mjs → chunk-5VOTTIXF.mjs} +2 -2
- package/dist/{chunk-LBMRIB3G.mjs → chunk-6AJUS7VX.mjs} +1 -1
- package/dist/{chunk-OODBHKG7.mjs → chunk-6HIOM2HL.mjs} +7 -4
- package/dist/{chunk-BDYZCBRT.mjs → chunk-6QAFGZC2.mjs} +2 -2
- package/dist/{chunk-U4NDAF2P.mjs → chunk-6TX73WG7.mjs} +1 -1
- package/dist/{chunk-GD4BJDJR.mjs → chunk-7BTFGCFC.mjs} +4 -4
- package/dist/{chunk-FAKPBKLT.mjs → chunk-7GWRPXHD.mjs} +4 -4
- package/dist/{chunk-NMOI6CQD.mjs → chunk-7YI3HEBH.mjs} +5 -5
- package/dist/{chunk-T4BJLT57.mjs → chunk-AE7MASLF.mjs} +5 -5
- package/dist/{chunk-VLQZANBF.mjs → chunk-AFML43VJ.mjs} +6 -1
- package/dist/chunk-BBXSNDS3.mjs +260 -0
- package/dist/chunk-BOW7U26Y.mjs +203 -0
- package/dist/{chunk-34NWQURD.mjs → chunk-BS75ICOO.mjs} +2 -2
- package/dist/chunk-D2NSIIXG.mjs +394 -0
- package/dist/{chunk-3GF7OVTP.mjs → chunk-DGNHGNYH.mjs} +2 -2
- package/dist/{chunk-VLARHE5V.mjs → chunk-DMXYRCHM.mjs} +6 -6
- package/dist/{chunk-OGOYQ7BG.mjs → chunk-DQB4EPIS.mjs} +1 -1
- package/dist/{chunk-MIZQHHUO.mjs → chunk-FL6DZFJK.mjs} +106 -38
- package/dist/{chunk-I3RZS7V2.mjs → chunk-FLL633WS.mjs} +19 -33
- package/dist/{chunk-PBL4OQV2.mjs → chunk-FTPBQVQ6.mjs} +4 -4
- package/dist/chunk-FYPSTTEJ.mjs +169 -0
- package/dist/{chunk-6O6KD7CE.mjs → chunk-G27TSQLQ.mjs} +6 -6
- package/dist/{chunk-66MI7Q4B.mjs → chunk-GT3RU6GA.mjs} +2 -2
- package/dist/{chunk-D6ID6M4V.mjs → chunk-GTAVSBDO.mjs} +2 -2
- package/dist/{chunk-24FUO7TD.mjs → chunk-H6NQTIF4.mjs} +2 -2
- package/dist/{chunk-7DHU4VGG.mjs → chunk-HK4HUQTV.mjs} +2 -2
- package/dist/chunk-I4KVSZCH.mjs +101 -0
- package/dist/{chunk-RGVKLTLH.mjs → chunk-IKXYTCSB.mjs} +2 -2
- package/dist/{chunk-Y6DWJSKZ.mjs → chunk-ISUA7DSB.mjs} +1 -1
- package/dist/{chunk-2A5RRQGG.mjs → chunk-JD3YWRNP.mjs} +10 -14
- package/dist/{chunk-J5UICVJS.mjs → chunk-JPGL36WQ.mjs} +2 -2
- package/dist/{chunk-7XJHLGUV.mjs → chunk-JTK6VJXY.mjs} +2 -2
- package/dist/{chunk-7YAU5CY6.mjs → chunk-JVMXMFBB.mjs} +2 -2
- package/dist/{chunk-IAE3F7DR.mjs → chunk-JZY6TNIS.mjs} +21 -21
- package/dist/{chunk-K5A5L6T2.mjs → chunk-K4KOD3KR.mjs} +12 -12
- package/dist/{chunk-MBON7YRJ.mjs → chunk-K5QV4TT6.mjs} +3 -3
- package/dist/{chunk-IHMFS7NZ.mjs → chunk-K5VHK7CM.mjs} +21 -21
- package/dist/{chunk-RJI6GKVF.mjs → chunk-KCWNDYPZ.mjs} +5 -5
- package/dist/{chunk-UFYSFDER.mjs → chunk-KFH36NKF.mjs} +1 -1
- package/dist/{chunk-EBXQWIYG.mjs → chunk-KLTACJ2G.mjs} +5 -5
- package/dist/{chunk-3TTACBDP.mjs → chunk-KWD6GANL.mjs} +4 -4
- package/dist/{chunk-IOJRDS6V.mjs → chunk-L4NSRQ3T.mjs} +218 -147
- package/dist/{chunk-GYMYRIZP.mjs → chunk-LBTHZSBT.mjs} +2 -2
- package/dist/{chunk-AMQZRHEZ.mjs → chunk-LQULK2E3.mjs} +5 -5
- package/dist/{chunk-YJG55G2H.mjs → chunk-LR6LHDP3.mjs} +5 -5
- package/dist/{chunk-7PV3IWCN.mjs → chunk-M4VYX2PV.mjs} +19 -1
- package/dist/{chunk-P76HMUI6.mjs → chunk-MDUKXXIL.mjs} +2 -2
- package/dist/{chunk-LV35NGVG.mjs → chunk-N6Q5IPKT.mjs} +9 -9
- package/dist/{chunk-DOEO3CDL.mjs → chunk-NB3ZL36B.mjs} +1 -1
- package/dist/{chunk-XREGSKX3.mjs → chunk-NOOEKOWY.mjs} +5 -5
- package/dist/{chunk-NL3ZO62D.mjs → chunk-NT4FX27K.mjs} +1 -1
- package/dist/{chunk-QZ4RE6NA.mjs → chunk-NTYQWVLI.mjs} +6 -6
- package/dist/{chunk-ERGGHC2V.mjs → chunk-OEOOYMC2.mjs} +2 -2
- package/dist/{chunk-4GAWMKMI.mjs → chunk-OIKBW2QD.mjs} +291 -54
- package/dist/{chunk-DUJTAXMH.mjs → chunk-OKTJFDPN.mjs} +6 -6
- package/dist/chunk-OLKMCXAR.mjs +1219 -0
- package/dist/{chunk-EI5F6FMT.mjs → chunk-OWFQSXVD.mjs} +3 -3
- package/dist/{chunk-6DZEXFNB.mjs → chunk-P2N2PEFY.mjs} +3 -3
- package/dist/{chunk-NSLMILBT.mjs → chunk-P7CEBZM6.mjs} +2 -2
- package/dist/{chunk-7S5AESZO.mjs → chunk-PNRUH7JY.mjs} +6 -6
- package/dist/{chunk-ZU4NV6RG.mjs → chunk-PNSYFE3K.mjs} +2 -2
- package/dist/{chunk-JKGDCQTZ.mjs → chunk-QTRSCVQ3.mjs} +5 -5
- package/dist/{chunk-ABFDMHOR.mjs → chunk-QX7IFQSF.mjs} +5 -5
- package/dist/{chunk-CFMQP5QS.mjs → chunk-QXKGOMUX.mjs} +6 -6
- package/dist/{chunk-NQPOYKAQ.mjs → chunk-R2ON6CAN.mjs} +2 -2
- package/dist/{chunk-DBHJ5KC3.mjs → chunk-R4HCRDU5.mjs} +1 -1
- package/dist/{chunk-EWRB4PAD.mjs → chunk-RCAOCHWA.mjs} +14 -14
- package/dist/{chunk-EFRENWEJ.mjs → chunk-RSUIPKGX.mjs} +2 -2
- package/dist/{chunk-DGHAXJBN.mjs → chunk-S2FKV4M5.mjs} +5 -5
- package/dist/{chunk-RGU7HOEC.mjs → chunk-SET2ANTY.mjs} +5 -7
- package/dist/chunk-SFH2NJEJ.mjs +47 -0
- package/dist/{chunk-6AW4KJHE.mjs → chunk-SIVYAI3M.mjs} +12 -12
- package/dist/{chunk-5FQIKDKP.mjs → chunk-THVO2N47.mjs} +8 -8
- package/dist/{chunk-JMHR3YGZ.mjs → chunk-TLAWKTSA.mjs} +3 -3
- package/dist/{chunk-HVY6KCCF.mjs → chunk-TOWTPLRC.mjs} +68 -72
- package/dist/{chunk-6JQFUE5I.mjs → chunk-UALR6JGV.mjs} +2 -2
- package/dist/{chunk-MLNEWRWV.mjs → chunk-UJZ4UHWI.mjs} +10 -15
- package/dist/{chunk-MARPPFOJ.mjs → chunk-UNACI2YK.mjs} +2 -2
- package/dist/{chunk-3NCUZIFP.mjs → chunk-V6XGXYCJ.mjs} +7 -7
- package/dist/chunk-VB5M6OZQ.mjs +57 -0
- package/dist/{chunk-5IS7G74I.mjs → chunk-VY5NEUP7.mjs} +6 -6
- package/dist/{chunk-JHJHG4GO.mjs → chunk-WE4YKBDE.mjs} +2 -2
- package/dist/{chunk-BKNFWEH2.mjs → chunk-WL6WVV47.mjs} +3 -3
- package/dist/{chunk-FWCSY2DS.mjs → chunk-WNQUEZJF.mjs} +22 -1
- package/dist/{chunk-2Y7YJKPE.mjs → chunk-WZ6UJCBL.mjs} +1 -1
- package/dist/{chunk-UMTOX62O.mjs → chunk-XYPW2XA5.mjs} +13 -10
- package/dist/chunk-Y2MTAVAK.mjs +34 -0
- package/dist/{chunk-6CR5N2JW.mjs → chunk-YCWLFG27.mjs} +6 -6
- package/dist/{chunk-PU4YZQXV.mjs → chunk-YE67AALL.mjs} +12 -12
- package/dist/{chunk-M3FV7LOK.mjs → chunk-YEWNFK5S.mjs} +6 -1
- package/dist/{chunk-R3VSPKNP.mjs → chunk-YIZHS72Z.mjs} +11 -12
- package/dist/{chunk-7PYJD5JI.mjs → chunk-ZEDMKQK2.mjs} +2 -2
- package/dist/{chunk-N2PT566P.mjs → chunk-ZFCDYW6N.mjs} +4 -4
- package/dist/chunk-ZGQIVGIN.mjs +57 -0
- package/dist/{chunk-Q2BGOAMG.mjs → chunk-ZKWXDQDG.mjs} +4 -4
- package/dist/{chunk-GHC7LLUX.mjs → chunk-ZOWL2L5J.mjs} +5 -5
- package/dist/components/ui/accordion.mjs +3 -3
- package/dist/components/ui/add-column-modal.js +2 -2
- package/dist/components/ui/add-column-modal.mjs +10 -10
- package/dist/components/ui/add-lead-modal.js +424 -82
- package/dist/components/ui/add-lead-modal.mjs +12 -9
- package/dist/components/ui/advisor-card.js +2 -2
- package/dist/components/ui/advisor-card.mjs +8 -8
- package/dist/components/ui/ai-assistant-drawer.js +2 -2
- package/dist/components/ui/ai-assistant-drawer.mjs +9 -9
- package/dist/components/ui/ai-builder.js +958 -0
- package/dist/components/ui/ai-builder.mjs +25 -0
- package/dist/components/ui/ai-conversations.js +2045 -0
- package/dist/components/ui/ai-conversations.mjs +41 -0
- package/dist/components/ui/alert-dialog.js +2 -2
- package/dist/components/ui/alert-dialog.mjs +5 -5
- package/dist/components/ui/alert.mjs +3 -3
- package/dist/components/ui/appointment-action-dialogs.js +19 -3
- package/dist/components/ui/appointment-action-dialogs.mjs +15 -14
- package/dist/components/ui/appointment-availability-settings.js +181 -111
- package/dist/components/ui/appointment-availability-settings.mjs +20 -18
- package/dist/components/ui/appointment-book-dialog.js +113 -24
- package/dist/components/ui/appointment-book-dialog.mjs +21 -20
- package/dist/components/ui/appointment-calendar-view.js +19 -3
- package/dist/components/ui/appointment-calendar-view.mjs +10 -9
- package/dist/components/ui/appointment-detail-sheet.js +19 -3
- package/dist/components/ui/appointment-detail-sheet.mjs +18 -17
- package/dist/components/ui/appointment-gmail-connect.js +49 -89
- package/dist/components/ui/appointment-gmail-connect.mjs +8 -9
- package/dist/components/ui/appointment-mini-card.js +2 -2
- package/dist/components/ui/appointment-mini-card.mjs +6 -6
- package/dist/components/ui/appointment-time-slot-picker.mjs +6 -6
- package/dist/components/ui/appointment-upcoming-card.js +19 -3
- package/dist/components/ui/appointment-upcoming-card.mjs +15 -14
- package/dist/components/ui/auth-logo.js +95 -0
- package/dist/components/ui/auth-logo.mjs +8 -0
- package/dist/components/ui/auth-page-layout.js +108 -0
- package/dist/components/ui/auth-page-layout.mjs +8 -0
- package/dist/components/ui/avatar.mjs +3 -3
- package/dist/components/ui/backoffice-alert-history-chart.js +2 -2
- package/dist/components/ui/backoffice-alert-history-chart.mjs +9 -9
- package/dist/components/ui/backoffice-alerts-chart.js +2 -2
- package/dist/components/ui/backoffice-alerts-chart.mjs +11 -11
- package/dist/components/ui/backoffice-connections-chart.js +2 -2
- package/dist/components/ui/backoffice-connections-chart.mjs +11 -11
- package/dist/components/ui/backoffice-contact-history-chart.js +2 -2
- package/dist/components/ui/backoffice-contact-history-chart.mjs +9 -9
- package/dist/components/ui/badge.mjs +4 -4
- package/dist/components/ui/borrowing-capacity-line-chart.js +145 -132
- package/dist/components/ui/borrowing-capacity-line-chart.mjs +9 -9
- package/dist/components/ui/button.js +2 -2
- package/dist/components/ui/button.mjs +4 -4
- package/dist/components/ui/calendar.js +17 -3
- package/dist/components/ui/calendar.mjs +6 -5
- package/dist/components/ui/card.mjs +3 -3
- package/dist/components/ui/cash-balance-line-chart.js +157 -152
- package/dist/components/ui/cash-balance-line-chart.mjs +9 -9
- package/dist/components/ui/cashflow-bar-chart.js +2 -2
- package/dist/components/ui/cashflow-bar-chart.mjs +9 -9
- package/dist/components/ui/chat-widget-primitives.js +573 -0
- package/dist/components/ui/chat-widget-primitives.mjs +21 -0
- package/dist/components/ui/chat-widget.js +1268 -0
- package/dist/components/ui/chat-widget.mjs +29 -0
- package/dist/components/ui/checkbox.mjs +3 -3
- package/dist/components/ui/chip.js +2 -2
- package/dist/components/ui/chip.mjs +6 -6
- package/dist/components/ui/color-picker.js +2 -2
- package/dist/components/ui/color-picker.mjs +7 -7
- package/dist/components/ui/combobox.mjs +3 -3
- package/dist/components/ui/data-table.js +2 -2
- package/dist/components/ui/data-table.mjs +12 -12
- package/dist/components/ui/date-picker.js +22 -6
- package/dist/components/ui/date-picker.mjs +9 -8
- package/dist/components/ui/dialog.js +2 -2
- package/dist/components/ui/dialog.mjs +5 -5
- package/dist/components/ui/document-checklist-template.js +630 -0
- package/dist/components/ui/document-checklist-template.mjs +15 -0
- package/dist/components/ui/drawer.js +2 -2
- package/dist/components/ui/drawer.mjs +3 -3
- package/dist/components/ui/dropdown-menu.mjs +3 -3
- package/dist/components/ui/empty.mjs +3 -3
- package/dist/components/ui/expense-bar-chart.js +2 -2
- package/dist/components/ui/expense-bar-chart.mjs +9 -9
- package/dist/components/ui/field.mjs +5 -5
- package/dist/components/ui/financial-cards.js +431 -291
- package/dist/components/ui/financial-cards.mjs +10 -9
- package/dist/components/ui/financial-drawers.js +4 -4
- package/dist/components/ui/financial-drawers.mjs +8 -8
- package/dist/components/ui/financial-primitives.mjs +3 -3
- package/dist/components/ui/financial-sections.js +8 -9
- package/dist/components/ui/financial-sections.mjs +12 -12
- package/dist/components/ui/form-primitives.mjs +8 -8
- package/dist/components/ui/income-bar-chart.js +2 -2
- package/dist/components/ui/income-bar-chart.mjs +9 -9
- package/dist/components/ui/input-group.js +2 -2
- package/dist/components/ui/input-group.mjs +7 -7
- package/dist/components/ui/input-otp.mjs +3 -3
- package/dist/components/ui/input.mjs +3 -3
- package/dist/components/ui/kanban-column.js +19 -23
- package/dist/components/ui/kanban-column.mjs +14 -14
- package/dist/components/ui/label.mjs +3 -3
- package/dist/components/ui/onboarding-layout.js +476 -0
- package/dist/components/ui/onboarding-layout.mjs +11 -0
- package/dist/components/ui/opportunity-card.js +2 -2
- package/dist/components/ui/opportunity-card.mjs +12 -12
- package/dist/components/ui/opportunity-edit-modals.js +22 -6
- package/dist/components/ui/opportunity-edit-modals.mjs +21 -20
- package/dist/components/ui/opportunity-summary-tab.js +991 -674
- package/dist/components/ui/opportunity-summary-tab.mjs +26 -26
- package/dist/components/ui/page-header.mjs +3 -3
- package/dist/components/ui/page-top-bar.mjs +3 -3
- package/dist/components/ui/pagination.js +2 -2
- package/dist/components/ui/pagination.mjs +6 -6
- package/dist/components/ui/password-strength-tooltip.js +197 -0
- package/dist/components/ui/password-strength-tooltip.mjs +11 -0
- package/dist/components/ui/pipeline-alerts.mjs +3 -3
- package/dist/components/ui/pipeline-board.js +19 -23
- package/dist/components/ui/pipeline-board.mjs +18 -18
- package/dist/components/ui/pipeline-chart.js +12 -6
- package/dist/components/ui/pipeline-chart.mjs +4 -3
- package/dist/components/ui/pipeline-dialogs.js +28 -12
- package/dist/components/ui/pipeline-dialogs.mjs +14 -13
- package/dist/components/ui/pipeline-primitives.mjs +6 -6
- package/dist/components/ui/popover.mjs +3 -3
- package/dist/components/ui/progress.mjs +3 -3
- package/dist/components/ui/property-cashflow-doughnut-chart.js +2 -2
- package/dist/components/ui/property-cashflow-doughnut-chart.mjs +9 -9
- package/dist/components/ui/property-debt-equity-doughnut-chart.js +2 -2
- package/dist/components/ui/property-debt-equity-doughnut-chart.mjs +9 -9
- package/dist/components/ui/property-mobile-estimate-line-chart.js +2 -2
- package/dist/components/ui/property-mobile-estimate-line-chart.mjs +9 -9
- package/dist/components/ui/radio-group.mjs +3 -3
- package/dist/components/ui/select.mjs +3 -3
- package/dist/components/ui/separator.mjs +3 -3
- package/dist/components/ui/sheet.mjs +3 -3
- package/dist/components/ui/sidebar-nav.js +7 -9
- package/dist/components/ui/sidebar-nav.mjs +7 -7
- package/dist/components/ui/skeleton.mjs +3 -3
- package/dist/components/ui/slider.mjs +3 -3
- package/dist/components/ui/sonner.mjs +2 -2
- package/dist/components/ui/spinner.mjs +3 -3
- package/dist/components/ui/stage-timeline.mjs +10 -10
- package/dist/components/ui/stepper.mjs +3 -3
- package/dist/components/ui/switch.mjs +3 -3
- package/dist/components/ui/table.mjs +3 -3
- package/dist/components/ui/tabs.mjs +3 -3
- package/dist/components/ui/textarea.mjs +3 -3
- package/dist/components/ui/toggle-group.mjs +4 -4
- package/dist/components/ui/toggle.mjs +3 -3
- package/dist/components/ui/tooltip.mjs +3 -3
- package/dist/components/ui/transactions-expense-categories-doughnut-chart.js +2 -2
- package/dist/components/ui/transactions-expense-categories-doughnut-chart.mjs +9 -9
- package/dist/components/ui/transactions-income-expense-bar-chart.js +2 -2
- package/dist/components/ui/transactions-income-expense-bar-chart.mjs +9 -9
- package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.js +2 -2
- package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.mjs +9 -9
- package/dist/components/ui/two-fa-setup-form.js +612 -0
- package/dist/components/ui/two-fa-setup-form.mjs +16 -0
- package/dist/components/ui/upload-card.js +187 -0
- package/dist/components/ui/upload-card.mjs +10 -0
- package/dist/components/ui/video-background.js +118 -0
- package/dist/components/ui/video-background.mjs +8 -0
- package/dist/index.js +12765 -9400
- package/dist/index.mjs +341 -245
- package/dist/lib/colors.mjs +1 -1
- package/dist/lib/theme-provider.mjs +1 -1
- package/dist/lib/typography.mjs +2 -2
- package/dist/lib/utils.js +8 -2
- package/dist/lib/utils.mjs +6 -4
- package/dist/styles.css +1 -1
- package/package.json +61 -1
- package/src/components/index.tsx +126 -1
- package/src/components/ui/add-lead-modal.tsx +101 -142
- package/src/components/ui/ai-builder.tsx +560 -0
- package/src/components/ui/ai-conversations.tsx +1690 -0
- package/src/components/ui/appointment-availability-settings.tsx +152 -101
- package/src/components/ui/appointment-book-dialog.tsx +138 -24
- package/src/components/ui/appointment-calendar-view.tsx +2 -3
- package/src/components/ui/appointment-gmail-connect.tsx +23 -42
- package/src/components/ui/auth-logo.tsx +50 -0
- package/src/components/ui/auth-page-layout.tsx +59 -0
- package/src/components/ui/borrowing-capacity-line-chart.tsx +10 -8
- package/src/components/ui/button.tsx +2 -2
- package/src/components/ui/calendar.tsx +2 -1
- package/src/components/ui/cash-balance-line-chart.tsx +10 -14
- package/src/components/ui/chart-shared.tsx +10 -0
- package/src/components/ui/chat-widget-primitives.tsx +336 -0
- package/src/components/ui/chat-widget.tsx +822 -0
- package/src/components/ui/document-checklist-template.tsx +264 -0
- package/src/components/ui/drawer.tsx +2 -2
- package/src/components/ui/financial-cards.tsx +176 -78
- package/src/components/ui/financial-drawers.tsx +2 -2
- package/src/components/ui/financial-sections.tsx +1 -1
- package/src/components/ui/kanban-column.tsx +2 -5
- package/src/components/ui/onboarding-layout.tsx +109 -0
- package/src/components/ui/opportunity-summary-tab.tsx +469 -142
- package/src/components/ui/password-strength-tooltip.tsx +70 -0
- package/src/components/ui/pipeline-chart.tsx +2 -6
- package/src/components/ui/sidebar-nav.tsx +2 -15
- package/src/components/ui/two-fa-setup-form.tsx +229 -0
- package/src/components/ui/upload-card.tsx +98 -0
- package/src/components/ui/video-background.tsx +55 -0
- package/src/lib/format-date.ts +26 -0
- package/src/lib/utils.ts +11 -0
- package/src/styles/styles-css.ts +1 -1
- package/tsup.config.ts +13 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Check, X } from "lucide-react";
|
|
3
|
+
import {
|
|
4
|
+
Popover,
|
|
5
|
+
PopoverContent,
|
|
6
|
+
PopoverTrigger,
|
|
7
|
+
} from "@/components/ui/popover";
|
|
8
|
+
|
|
9
|
+
type ValidationRule = { label: string; test: (p: string) => boolean };
|
|
10
|
+
|
|
11
|
+
const RULES: ValidationRule[] = [
|
|
12
|
+
{ label: "Minimum 8 characters", test: (p) => p.length >= 8 },
|
|
13
|
+
{ label: "At least one uppercase letter", test: (p) => /[A-Z]/.test(p) },
|
|
14
|
+
{ label: "At least one lowercase letter", test: (p) => /[a-z]/.test(p) },
|
|
15
|
+
{ label: "At least one number", test: (p) => /\d/.test(p) },
|
|
16
|
+
{
|
|
17
|
+
label: "At least one special character",
|
|
18
|
+
test: (p) => /[^A-Za-z0-9]/.test(p),
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
export type PasswordStrengthTooltipProps = {
|
|
23
|
+
open?: boolean;
|
|
24
|
+
password: string;
|
|
25
|
+
children: React.ReactNode;
|
|
26
|
+
side?: "top" | "right" | "bottom" | "left";
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export function PasswordStrengthTooltip({
|
|
30
|
+
open = false,
|
|
31
|
+
password,
|
|
32
|
+
children,
|
|
33
|
+
side = "right",
|
|
34
|
+
}: PasswordStrengthTooltipProps) {
|
|
35
|
+
return (
|
|
36
|
+
<Popover open={open}>
|
|
37
|
+
<PopoverTrigger asChild>
|
|
38
|
+
<div>{children}</div>
|
|
39
|
+
</PopoverTrigger>
|
|
40
|
+
<PopoverContent
|
|
41
|
+
side={side}
|
|
42
|
+
align="start"
|
|
43
|
+
sideOffset={8}
|
|
44
|
+
onOpenAutoFocus={(e) => e.preventDefault()}
|
|
45
|
+
className="w-auto max-w-[280px] font-sans"
|
|
46
|
+
>
|
|
47
|
+
<div className="flex flex-col gap-1.5">
|
|
48
|
+
{RULES.map((rule) => {
|
|
49
|
+
const valid = password ? rule.test(password) : false;
|
|
50
|
+
return (
|
|
51
|
+
<div
|
|
52
|
+
key={rule.label}
|
|
53
|
+
className={`flex items-center gap-1.5 text-[13px] leading-[18px] ${
|
|
54
|
+
valid ? "text-success" : "text-destructive"
|
|
55
|
+
}`}
|
|
56
|
+
>
|
|
57
|
+
{valid ? (
|
|
58
|
+
<Check size={14} className="shrink-0" />
|
|
59
|
+
) : (
|
|
60
|
+
<X size={14} className="shrink-0" />
|
|
61
|
+
)}
|
|
62
|
+
<span>{rule.label}</span>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
})}
|
|
66
|
+
</div>
|
|
67
|
+
</PopoverContent>
|
|
68
|
+
</Popover>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { cn } from "@/lib/utils";
|
|
3
3
|
import { useThemeVars } from "@/lib/theme-provider";
|
|
4
|
+
import { formatCurrencyAbbrev } from "@/lib/format-currency";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* PipelineChart — WealthX DS (L4 Section)
|
|
@@ -40,12 +41,7 @@ export interface PipelineChartProps {
|
|
|
40
41
|
// ---------------------------------------------------------------------------
|
|
41
42
|
|
|
42
43
|
function formatValue(v: number): string {
|
|
43
|
-
return
|
|
44
|
-
style: "currency",
|
|
45
|
-
currency: "AUD",
|
|
46
|
-
notation: "compact",
|
|
47
|
-
maximumFractionDigits: 1,
|
|
48
|
-
}).format(v);
|
|
44
|
+
return formatCurrencyAbbrev(v);
|
|
49
45
|
}
|
|
50
46
|
|
|
51
47
|
// Fallback palette (used when stage.color is not set) — uses stage vars so
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
} from "lucide-react";
|
|
25
25
|
import type { LucideIcon } from "lucide-react";
|
|
26
26
|
import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion";
|
|
27
|
-
import { cn } from "@/lib/utils";
|
|
27
|
+
import { cn, getInitials } from "@/lib/utils";
|
|
28
28
|
import { formatCurrency } from "@/lib/format-currency";
|
|
29
29
|
import { Accordion, AccordionContent, AccordionItem } from "./accordion";
|
|
30
30
|
import { Button } from "./button";
|
|
@@ -100,16 +100,6 @@ export interface SidebarNavProps {
|
|
|
100
100
|
|
|
101
101
|
// ─── Helpers ───────────────────────────────────────────────────────────────────
|
|
102
102
|
|
|
103
|
-
function getInitials(name: string): string {
|
|
104
|
-
return name
|
|
105
|
-
.split(" ")
|
|
106
|
-
.filter(Boolean)
|
|
107
|
-
.map((word) => word[0])
|
|
108
|
-
.join("")
|
|
109
|
-
.toUpperCase()
|
|
110
|
-
.slice(0, 2);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
103
|
function navIconCn(isActive: boolean): string {
|
|
114
104
|
return cn(
|
|
115
105
|
"shrink-0 transition-colors",
|
|
@@ -385,10 +375,7 @@ export function SidebarNav({
|
|
|
385
375
|
data-slot="sidebar-nav"
|
|
386
376
|
data-collapsed={collapsed}
|
|
387
377
|
className={cn(
|
|
388
|
-
|
|
389
|
-
// regardless of system theme, so semantic tokens (destructive, success, etc.)
|
|
390
|
-
// must use their dark-mode values to maintain WCAG contrast.
|
|
391
|
-
"dark flex h-full flex-col bg-brand-secondary text-brand-secondary-foreground",
|
|
378
|
+
"flex h-full flex-col bg-brand-secondary text-brand-secondary-foreground",
|
|
392
379
|
"transition-all duration-200 ease-in-out",
|
|
393
380
|
collapsed ? "w-14" : "w-[279px]",
|
|
394
381
|
className,
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { Smartphone } from "lucide-react";
|
|
4
|
+
import { Button } from "@/components/ui/button";
|
|
5
|
+
import { Field, FieldError } from "@/components/ui/field";
|
|
6
|
+
import { InputGroup, InputGroupInput } from "@/components/ui/input-group";
|
|
7
|
+
import { Label } from "@/components/ui/label";
|
|
8
|
+
import { cn } from "@/lib/utils";
|
|
9
|
+
|
|
10
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
export type TwoFAApp = {
|
|
13
|
+
name: string;
|
|
14
|
+
icon?: React.ReactNode;
|
|
15
|
+
qrNode?: React.ReactNode;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type TwoFASetupFormProps = {
|
|
19
|
+
title?: string;
|
|
20
|
+
apps?: TwoFAApp[];
|
|
21
|
+
qrCodeNode?: React.ReactNode;
|
|
22
|
+
onVerify: (token: string) => Promise<void>;
|
|
23
|
+
onSetupLater?: () => void;
|
|
24
|
+
required?: boolean;
|
|
25
|
+
className?: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const DEFAULT_APPS: TwoFAApp[] = [
|
|
29
|
+
{ name: "Google Authenticator" },
|
|
30
|
+
{ name: "Microsoft Authenticator" },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
// ─── AppDownloadStep ─────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
function AppDownloadStep({
|
|
36
|
+
apps,
|
|
37
|
+
onNext,
|
|
38
|
+
onSetupLater,
|
|
39
|
+
required,
|
|
40
|
+
}: {
|
|
41
|
+
apps: TwoFAApp[];
|
|
42
|
+
onNext: () => void;
|
|
43
|
+
onSetupLater?: () => void;
|
|
44
|
+
required?: boolean;
|
|
45
|
+
}) {
|
|
46
|
+
return (
|
|
47
|
+
<div className="flex flex-col gap-6">
|
|
48
|
+
<div className="flex gap-4">
|
|
49
|
+
{apps.map((app) => (
|
|
50
|
+
<div
|
|
51
|
+
key={app.name}
|
|
52
|
+
className="flex flex-1 flex-col items-center gap-3 border border-border bg-muted/30 p-4 text-center"
|
|
53
|
+
>
|
|
54
|
+
<div className="flex items-center gap-2 text-sm font-medium text-foreground">
|
|
55
|
+
{app.icon ?? (
|
|
56
|
+
<Smartphone size={18} className="text-muted-foreground" />
|
|
57
|
+
)}
|
|
58
|
+
{app.name}
|
|
59
|
+
</div>
|
|
60
|
+
{app.qrNode ? (
|
|
61
|
+
<div className="flex items-center justify-center">
|
|
62
|
+
{app.qrNode}
|
|
63
|
+
</div>
|
|
64
|
+
) : (
|
|
65
|
+
<div className="flex h-[100px] w-[100px] items-center justify-center border border-dashed border-border bg-muted/50 text-[11px] text-muted-foreground">
|
|
66
|
+
Scan to download
|
|
67
|
+
</div>
|
|
68
|
+
)}
|
|
69
|
+
</div>
|
|
70
|
+
))}
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<p className="text-center text-sm text-muted-foreground">
|
|
74
|
+
Once downloaded, or if you already have the app installed, click Next.
|
|
75
|
+
</p>
|
|
76
|
+
|
|
77
|
+
<div className="flex flex-col gap-2">
|
|
78
|
+
<Button className="w-full" onClick={onNext}>
|
|
79
|
+
Next
|
|
80
|
+
</Button>
|
|
81
|
+
{!required && onSetupLater && (
|
|
82
|
+
<Button variant="ghost" className="w-full" onClick={onSetupLater}>
|
|
83
|
+
Setup Later
|
|
84
|
+
</Button>
|
|
85
|
+
)}
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ─── VerificationStep ────────────────────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
function VerificationStep({
|
|
94
|
+
qrCodeNode,
|
|
95
|
+
onVerify,
|
|
96
|
+
onBack,
|
|
97
|
+
onSetupLater,
|
|
98
|
+
required,
|
|
99
|
+
}: {
|
|
100
|
+
qrCodeNode?: React.ReactNode;
|
|
101
|
+
onVerify: (token: string) => Promise<void>;
|
|
102
|
+
onBack: () => void;
|
|
103
|
+
onSetupLater?: () => void;
|
|
104
|
+
required?: boolean;
|
|
105
|
+
}) {
|
|
106
|
+
const [token, setToken] = useState("");
|
|
107
|
+
const [error, setError] = useState("");
|
|
108
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
109
|
+
|
|
110
|
+
const handleVerify = async () => {
|
|
111
|
+
if (!token) {
|
|
112
|
+
setError("Required");
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
setError("");
|
|
116
|
+
setIsLoading(true);
|
|
117
|
+
try {
|
|
118
|
+
await onVerify(token);
|
|
119
|
+
} catch {
|
|
120
|
+
setError("Invalid code. Please try again.");
|
|
121
|
+
} finally {
|
|
122
|
+
setIsLoading(false);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<div className="flex flex-col gap-6">
|
|
128
|
+
<div className="flex flex-col items-center gap-3">
|
|
129
|
+
{qrCodeNode ? (
|
|
130
|
+
<div className="flex items-center justify-center">{qrCodeNode}</div>
|
|
131
|
+
) : (
|
|
132
|
+
<div className="flex h-[140px] w-[140px] items-center justify-center border border-dashed border-border bg-muted/50 text-xs text-muted-foreground">
|
|
133
|
+
QR Code
|
|
134
|
+
</div>
|
|
135
|
+
)}
|
|
136
|
+
<p className="text-center text-sm text-muted-foreground">
|
|
137
|
+
The temporary code will show for authentication. Insert below.
|
|
138
|
+
</p>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<Field>
|
|
142
|
+
<Label htmlFor="2fa-token">Verification Code</Label>
|
|
143
|
+
<InputGroup>
|
|
144
|
+
<InputGroupInput
|
|
145
|
+
id="2fa-token"
|
|
146
|
+
type="text"
|
|
147
|
+
name="token"
|
|
148
|
+
placeholder="Enter Your Code"
|
|
149
|
+
value={token}
|
|
150
|
+
aria-invalid={!!error}
|
|
151
|
+
onChange={(e) => {
|
|
152
|
+
setToken(e.target.value);
|
|
153
|
+
if (error) setError("");
|
|
154
|
+
}}
|
|
155
|
+
onKeyDown={(e) => {
|
|
156
|
+
if (e.key === "Enter") handleVerify();
|
|
157
|
+
}}
|
|
158
|
+
/>
|
|
159
|
+
</InputGroup>
|
|
160
|
+
{error && <FieldError>{error}</FieldError>}
|
|
161
|
+
</Field>
|
|
162
|
+
|
|
163
|
+
<div className="flex flex-col gap-2">
|
|
164
|
+
<Button
|
|
165
|
+
loading={isLoading}
|
|
166
|
+
disabled={!token}
|
|
167
|
+
className="w-full"
|
|
168
|
+
onClick={handleVerify}
|
|
169
|
+
>
|
|
170
|
+
Verify Now
|
|
171
|
+
</Button>
|
|
172
|
+
<Button variant="outline" className="w-full" onClick={onBack}>
|
|
173
|
+
Back
|
|
174
|
+
</Button>
|
|
175
|
+
{!required && onSetupLater && (
|
|
176
|
+
<Button variant="ghost" className="w-full" onClick={onSetupLater}>
|
|
177
|
+
Setup Later
|
|
178
|
+
</Button>
|
|
179
|
+
)}
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ─── TwoFASetupForm ───────────────────────────────────────────────────────────
|
|
186
|
+
|
|
187
|
+
export function TwoFASetupForm({
|
|
188
|
+
title = "2FA Setup",
|
|
189
|
+
apps = DEFAULT_APPS,
|
|
190
|
+
qrCodeNode,
|
|
191
|
+
onVerify,
|
|
192
|
+
onSetupLater,
|
|
193
|
+
required = true,
|
|
194
|
+
className,
|
|
195
|
+
}: TwoFASetupFormProps) {
|
|
196
|
+
const [step, setStep] = useState<1 | 2>(1);
|
|
197
|
+
|
|
198
|
+
const stepTitle = step === 1 ? title : "Open Auth App and Scan Code";
|
|
199
|
+
const stepSubtitle =
|
|
200
|
+
step === 1
|
|
201
|
+
? "We recommend Google Authenticator or Microsoft Authenticator"
|
|
202
|
+
: "Open your authenticator app and scan the QR code";
|
|
203
|
+
|
|
204
|
+
return (
|
|
205
|
+
<div className={cn("flex flex-col gap-6", className)}>
|
|
206
|
+
<div className="flex flex-col gap-1">
|
|
207
|
+
<h2 className="text-xl font-semibold text-foreground">{stepTitle}</h2>
|
|
208
|
+
<p className="text-sm text-muted-foreground">{stepSubtitle}</p>
|
|
209
|
+
</div>
|
|
210
|
+
|
|
211
|
+
{step === 1 ? (
|
|
212
|
+
<AppDownloadStep
|
|
213
|
+
apps={apps}
|
|
214
|
+
onNext={() => setStep(2)}
|
|
215
|
+
onSetupLater={onSetupLater}
|
|
216
|
+
required={required}
|
|
217
|
+
/>
|
|
218
|
+
) : (
|
|
219
|
+
<VerificationStep
|
|
220
|
+
qrCodeNode={qrCodeNode}
|
|
221
|
+
onVerify={onVerify}
|
|
222
|
+
onBack={() => setStep(1)}
|
|
223
|
+
onSetupLater={onSetupLater}
|
|
224
|
+
required={required}
|
|
225
|
+
/>
|
|
226
|
+
)}
|
|
227
|
+
</div>
|
|
228
|
+
);
|
|
229
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { UploadIcon } from "lucide-react";
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* UploadCard — WealthX DS
|
|
8
|
+
*
|
|
9
|
+
* A dashed-border upload zone used in forms that accept file input.
|
|
10
|
+
* Comes in two sizes:
|
|
11
|
+
* - `sm` — compact, fixed max-width (e.g. logo / photo uploads)
|
|
12
|
+
* - `lg` — full-width, taller drop zone (e.g. bulk CSV upload)
|
|
13
|
+
*
|
|
14
|
+
* WealthX overrides:
|
|
15
|
+
* - No border-radius — sharp corners per WealthX DS
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const uploadCardVariants = cva(
|
|
19
|
+
[
|
|
20
|
+
"flex flex-col items-center justify-center gap-2",
|
|
21
|
+
"border border-dashed border-border",
|
|
22
|
+
"cursor-pointer text-muted-foreground",
|
|
23
|
+
"hover:bg-muted/50 bg-muted/20 transition-colors",
|
|
24
|
+
],
|
|
25
|
+
{
|
|
26
|
+
variants: {
|
|
27
|
+
size: {
|
|
28
|
+
sm: "w-[200px] py-4 px-3",
|
|
29
|
+
lg: "w-full py-8 px-6",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
defaultVariants: {
|
|
33
|
+
size: "sm",
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
export type UploadCardProps = React.ComponentProps<"label"> &
|
|
39
|
+
VariantProps<typeof uploadCardVariants> & {
|
|
40
|
+
/** Label text shown below the upload icon */
|
|
41
|
+
label?: string;
|
|
42
|
+
/** Helper text shown below the label */
|
|
43
|
+
description?: string;
|
|
44
|
+
/** File types to accept, passed to the hidden <input> */
|
|
45
|
+
accept?: string;
|
|
46
|
+
/** Called when the user selects a file */
|
|
47
|
+
onFileChange?: (file: File | null) => void;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
function UploadCard({
|
|
51
|
+
label = "Upload image",
|
|
52
|
+
description,
|
|
53
|
+
accept = ".png,.jpg,.jpeg",
|
|
54
|
+
size,
|
|
55
|
+
onFileChange,
|
|
56
|
+
className,
|
|
57
|
+
...props
|
|
58
|
+
}: UploadCardProps) {
|
|
59
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
60
|
+
onFileChange?.(e.target.files?.[0] ?? null);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<label
|
|
65
|
+
className={cn(uploadCardVariants({ size }), className)}
|
|
66
|
+
data-slot="upload-card"
|
|
67
|
+
data-size={size ?? "sm"}
|
|
68
|
+
{...props}
|
|
69
|
+
>
|
|
70
|
+
<UploadIcon
|
|
71
|
+
className={cn("text-primary", size === "lg" ? "size-6" : "size-4")}
|
|
72
|
+
/>
|
|
73
|
+
<span
|
|
74
|
+
className={cn("font-medium", size === "lg" ? "text-sm" : "text-xs")}
|
|
75
|
+
>
|
|
76
|
+
{label}
|
|
77
|
+
</span>
|
|
78
|
+
{description && (
|
|
79
|
+
<p
|
|
80
|
+
className={cn(
|
|
81
|
+
"text-center leading-relaxed",
|
|
82
|
+
size === "lg" ? "text-sm" : "text-xs",
|
|
83
|
+
)}
|
|
84
|
+
>
|
|
85
|
+
{description}
|
|
86
|
+
</p>
|
|
87
|
+
)}
|
|
88
|
+
<input
|
|
89
|
+
type="file"
|
|
90
|
+
accept={accept}
|
|
91
|
+
className="sr-only"
|
|
92
|
+
onChange={handleChange}
|
|
93
|
+
/>
|
|
94
|
+
</label>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export { UploadCard, uploadCardVariants };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
|
|
4
|
+
export type VideoBackgroundProps = {
|
|
5
|
+
src?: string;
|
|
6
|
+
poster?: string;
|
|
7
|
+
overlay?: "light" | "medium" | "dark";
|
|
8
|
+
children?: React.ReactNode;
|
|
9
|
+
className?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function VideoBackground({
|
|
13
|
+
src,
|
|
14
|
+
poster,
|
|
15
|
+
overlay = "medium",
|
|
16
|
+
children,
|
|
17
|
+
className,
|
|
18
|
+
}: VideoBackgroundProps) {
|
|
19
|
+
const overlayClass = {
|
|
20
|
+
light: "bg-black/25",
|
|
21
|
+
medium: "bg-black/50",
|
|
22
|
+
dark: "bg-black/70",
|
|
23
|
+
}[overlay];
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div
|
|
27
|
+
className={cn(
|
|
28
|
+
"relative min-h-screen w-full overflow-hidden bg-neutral-900",
|
|
29
|
+
className,
|
|
30
|
+
)}
|
|
31
|
+
>
|
|
32
|
+
{src && (
|
|
33
|
+
<video
|
|
34
|
+
className="absolute inset-0 h-full w-full object-cover"
|
|
35
|
+
autoPlay
|
|
36
|
+
muted
|
|
37
|
+
loop
|
|
38
|
+
playsInline
|
|
39
|
+
poster={poster}
|
|
40
|
+
src={src}
|
|
41
|
+
/>
|
|
42
|
+
)}
|
|
43
|
+
{!src && poster && (
|
|
44
|
+
<img
|
|
45
|
+
className="absolute inset-0 h-full w-full object-cover"
|
|
46
|
+
src={poster}
|
|
47
|
+
alt=""
|
|
48
|
+
aria-hidden="true"
|
|
49
|
+
/>
|
|
50
|
+
)}
|
|
51
|
+
<div className={cn("absolute inset-0", overlayClass)} />
|
|
52
|
+
<div className="relative z-10">{children}</div>
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
package/src/lib/format-date.ts
CHANGED
|
@@ -48,3 +48,29 @@ export function formatDateWithWeekday(iso: string): string {
|
|
|
48
48
|
return iso;
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Format a Date or ISO string as "Monday, 15 January" (long weekday + day + long month).
|
|
54
|
+
* Returns raw string on parse failure.
|
|
55
|
+
*/
|
|
56
|
+
export function formatDateLong(date: Date | string): string {
|
|
57
|
+
try {
|
|
58
|
+
const d = typeof date === "string" ? safeParse(date) : date;
|
|
59
|
+
return format(d, "EEEE, d MMMM");
|
|
60
|
+
} catch {
|
|
61
|
+
return String(date);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Format a Date or ISO string as an uppercase short weekday: "MON", "TUE", etc.
|
|
67
|
+
* Returns raw string on parse failure.
|
|
68
|
+
*/
|
|
69
|
+
export function formatWeekdayShort(date: Date | string): string {
|
|
70
|
+
try {
|
|
71
|
+
const d = typeof date === "string" ? safeParse(date) : date;
|
|
72
|
+
return format(d, "EEE").toUpperCase();
|
|
73
|
+
} catch {
|
|
74
|
+
return String(date);
|
|
75
|
+
}
|
|
76
|
+
}
|
package/src/lib/utils.ts
CHANGED
|
@@ -42,3 +42,14 @@ const twMerge = extendTailwindMerge({
|
|
|
42
42
|
export function cn(...inputs: ClassValue[]): string {
|
|
43
43
|
return twMerge(clsx(inputs));
|
|
44
44
|
}
|
|
45
|
+
|
|
46
|
+
export function getInitials(name: string): string {
|
|
47
|
+
if (!name.trim()) return "?";
|
|
48
|
+
return name
|
|
49
|
+
.split(" ")
|
|
50
|
+
.filter(Boolean)
|
|
51
|
+
.map((word) => word[0])
|
|
52
|
+
.join("")
|
|
53
|
+
.toUpperCase()
|
|
54
|
+
.slice(0, 2);
|
|
55
|
+
}
|