@spaceinvoices/react-ui 0.1.1
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 +21 -0
- package/README.md +340 -0
- package/cli/dist/index.js +922 -0
- package/package.json +87 -0
- package/registry.json +600 -0
- package/spaceinvoices.schema.json +47 -0
- package/src/app.tsx +25 -0
- package/src/common/autocomplete.tsx +135 -0
- package/src/components/activities/activity-timeline.tsx +160 -0
- package/src/components/activities/index.ts +1 -0
- package/src/components/activities/locales/de.ts +30 -0
- package/src/components/activities/locales/sl.ts +30 -0
- package/src/components/advance-invoices/advance-invoices.hooks.ts +75 -0
- package/src/components/advance-invoices/create/create-advance-invoice-form.tsx +702 -0
- package/src/components/advance-invoices/create/locales/de.ts +29 -0
- package/src/components/advance-invoices/create/locales/sl.ts +25 -0
- package/src/components/advance-invoices/create/prepare-advance-invoice-submission.ts +74 -0
- package/src/components/advance-invoices/index.ts +5 -0
- package/src/components/advance-invoices/list/index.ts +3 -0
- package/src/components/advance-invoices/list/list-row-actions.tsx +119 -0
- package/src/components/advance-invoices/list/list-table.tsx +178 -0
- package/src/components/advance-invoices/list/locales/de.ts +32 -0
- package/src/components/advance-invoices/list/locales/sl.ts +32 -0
- package/src/components/advance-invoices/list/use-advance-invoice-download.ts +63 -0
- package/src/components/button-loader.tsx +11 -0
- package/src/components/combobox.tsx +96 -0
- package/src/components/company-registry/company-registry-autocomplete.tsx +151 -0
- package/src/components/company-registry/company-registry.hooks.ts +67 -0
- package/src/components/company-registry/index.ts +7 -0
- package/src/components/credit-notes/create/create-credit-note-form.tsx +332 -0
- package/src/components/credit-notes/create/index.ts +1 -0
- package/src/components/credit-notes/create/locales/de.ts +69 -0
- package/src/components/credit-notes/create/locales/sl.ts +67 -0
- package/src/components/credit-notes/credit-notes.hooks.ts +22 -0
- package/src/components/credit-notes/index.ts +10 -0
- package/src/components/credit-notes/list/index.ts +3 -0
- package/src/components/credit-notes/list/list-row-actions.tsx +116 -0
- package/src/components/credit-notes/list/list-table.tsx +183 -0
- package/src/components/credit-notes/list/locales/de.ts +33 -0
- package/src/components/credit-notes/list/locales/sl.ts +33 -0
- package/src/components/credit-notes/list/use-credit-note-download.ts +65 -0
- package/src/components/customers/create-customer-form/create-customer-form.tsx +134 -0
- package/src/components/customers/create-customer-form/locales/de.ts +20 -0
- package/src/components/customers/create-customer-form/locales/sl.ts +20 -0
- package/src/components/customers/customer-autocomplete.tsx +173 -0
- package/src/components/customers/customer-combobox.tsx +130 -0
- package/src/components/customers/customer-list-table/customer-list-row-actions.tsx +48 -0
- package/src/components/customers/customer-list-table/customer-list-table.tsx +124 -0
- package/src/components/customers/customer-list-table/index.ts +2 -0
- package/src/components/customers/customer-list-table/locales/de.ts +16 -0
- package/src/components/customers/customer-list-table/locales/sl.ts +16 -0
- package/src/components/customers/customers.hooks.test.ts +348 -0
- package/src/components/customers/customers.hooks.ts +57 -0
- package/src/components/customers/index.ts +5 -0
- package/src/components/dashboard/chart-empty-state.tsx +29 -0
- package/src/components/dashboard/collection-rate-card/collection-rate-card.tsx +80 -0
- package/src/components/dashboard/collection-rate-card/index.ts +4 -0
- package/src/components/dashboard/collection-rate-card/locales/sl.ts +3 -0
- package/src/components/dashboard/collection-rate-card/use-collection-rate.ts +74 -0
- package/src/components/dashboard/index.ts +54 -0
- package/src/components/dashboard/invoice-status-chart/index.ts +4 -0
- package/src/components/dashboard/invoice-status-chart/invoice-status-chart.tsx +130 -0
- package/src/components/dashboard/invoice-status-chart/locales/sl.ts +9 -0
- package/src/components/dashboard/invoice-status-chart/use-invoice-status.ts +105 -0
- package/src/components/dashboard/loading-card.tsx +19 -0
- package/src/components/dashboard/payment-methods-chart/index.ts +4 -0
- package/src/components/dashboard/payment-methods-chart/locales/sl.ts +12 -0
- package/src/components/dashboard/payment-methods-chart/payment-methods-chart.tsx +152 -0
- package/src/components/dashboard/payment-methods-chart/use-payment-methods.ts +50 -0
- package/src/components/dashboard/payment-trend-chart/index.ts +4 -0
- package/src/components/dashboard/payment-trend-chart/locales/sl.ts +5 -0
- package/src/components/dashboard/payment-trend-chart/payment-trend-chart.tsx +137 -0
- package/src/components/dashboard/payment-trend-chart/use-payment-trend.ts +92 -0
- package/src/components/dashboard/revenue-card.tsx +49 -0
- package/src/components/dashboard/revenue-trend-chart/index.ts +4 -0
- package/src/components/dashboard/revenue-trend-chart/locales/sl.ts +5 -0
- package/src/components/dashboard/revenue-trend-chart/revenue-trend-chart.tsx +137 -0
- package/src/components/dashboard/revenue-trend-chart/use-revenue-trend.ts +93 -0
- package/src/components/dashboard/shared/index.ts +5 -0
- package/src/components/dashboard/shared/use-revenue-data.ts +160 -0
- package/src/components/dashboard/shared/use-stats-counts.ts +89 -0
- package/src/components/dashboard/shared/use-stats-query.ts +38 -0
- package/src/components/dashboard/stat-card.tsx +41 -0
- package/src/components/dashboard/tax-collected-card/index.ts +2 -0
- package/src/components/dashboard/tax-collected-card/tax-collected-card.tsx +77 -0
- package/src/components/dashboard/tax-collected-card/use-tax-collected.ts +145 -0
- package/src/components/dashboard/top-customers-chart/index.ts +4 -0
- package/src/components/dashboard/top-customers-chart/locales/sl.ts +5 -0
- package/src/components/dashboard/top-customers-chart/top-customers-chart.tsx +130 -0
- package/src/components/dashboard/top-customers-chart/use-top-customers.ts +72 -0
- package/src/components/documents/create/document-add-item-form.tsx +379 -0
- package/src/components/documents/create/document-add-item-tax-rate-field.tsx +120 -0
- package/src/components/documents/create/document-details-section.tsx +597 -0
- package/src/components/documents/create/document-items-section.tsx +133 -0
- package/src/components/documents/create/document-recipient-section.tsx +101 -0
- package/src/components/documents/create/form-types.ts +36 -0
- package/src/components/documents/create/index.ts +9 -0
- package/src/components/documents/create/live-preview.tsx +235 -0
- package/src/components/documents/create/mark-as-paid-section.tsx +82 -0
- package/src/components/documents/create/prepare-document-submission.test.ts +132 -0
- package/src/components/documents/create/prepare-document-submission.ts +187 -0
- package/src/components/documents/create/prepare-preview-data.test.ts +155 -0
- package/src/components/documents/create/prepare-preview-data.ts +16 -0
- package/src/components/documents/create/smart-code-insert-button.tsx +139 -0
- package/src/components/documents/create/use-document-customer-form.ts +161 -0
- package/src/components/documents/document-preview.tsx +13 -0
- package/src/components/documents/documents.hooks.ts +146 -0
- package/src/components/documents/index.ts +23 -0
- package/src/components/documents/shared/document-preview-display.tsx +172 -0
- package/src/components/documents/shared/index.ts +3 -0
- package/src/components/documents/shared/scaled-document-preview.tsx +70 -0
- package/src/components/documents/shared/use-a4-scaling.ts +62 -0
- package/src/components/documents/types.ts +61 -0
- package/src/components/documents/view/document-actions-bar.tsx +328 -0
- package/src/components/documents/view/document-details-card.tsx +179 -0
- package/src/components/documents/view/document-payments-list.tsx +256 -0
- package/src/components/documents/view/index.ts +4 -0
- package/src/components/documents/view/locales/de.ts +85 -0
- package/src/components/documents/view/locales/sl.ts +84 -0
- package/src/components/documents/view/use-document-download.ts +125 -0
- package/src/components/entities/create-entity-form.tsx +105 -0
- package/src/components/entities/entities.hooks.ts +50 -0
- package/src/components/entities/entity-settings-form/email-template-variables-info.tsx +103 -0
- package/src/components/entities/entity-settings-form/entity-settings-form.tsx +1326 -0
- package/src/components/entities/entity-settings-form/image-upload-with-crop.tsx +222 -0
- package/src/components/entities/entity-settings-form/index.ts +2 -0
- package/src/components/entities/entity-settings-form/input-with-preview.tsx +190 -0
- package/src/components/entities/entity-settings-form/locales/de.ts +192 -0
- package/src/components/entities/entity-settings-form/locales/sl.ts +188 -0
- package/src/components/entities/furs-settings-form/furs-settings-form.tsx +410 -0
- package/src/components/entities/furs-settings-form/furs-settings.hooks.ts +320 -0
- package/src/components/entities/furs-settings-form/index.ts +3 -0
- package/src/components/entities/furs-settings-form/locales/de.ts +233 -0
- package/src/components/entities/furs-settings-form/locales/en.ts +194 -0
- package/src/components/entities/furs-settings-form/locales/sl.ts +196 -0
- package/src/components/entities/furs-settings-form/sections/certificate-settings-section.tsx +242 -0
- package/src/components/entities/furs-settings-form/sections/enable-fiscalization-section.tsx +139 -0
- package/src/components/entities/furs-settings-form/sections/general-settings-section.tsx +252 -0
- package/src/components/entities/furs-settings-form/sections/premises-management-section.tsx +370 -0
- package/src/components/entities/furs-settings-form/sections/register-premise-dialog.tsx +420 -0
- package/src/components/entities/keys.ts +2 -0
- package/src/components/entities/settings/branding-settings-form.tsx +274 -0
- package/src/components/entities/settings/company-settings-form.tsx +256 -0
- package/src/components/entities/settings/defaults-settings-form.tsx +501 -0
- package/src/components/entities/settings/email-settings-form.tsx +288 -0
- package/src/components/entities/settings/eslog-settings-form.tsx +113 -0
- package/src/components/entities/settings/index.ts +8 -0
- package/src/components/entities/settings/number-format-settings-form.tsx +244 -0
- package/src/components/entities/settings/pdf-template-selector/demo-invoice-data.ts +164 -0
- package/src/components/entities/settings/pdf-template-selector/index.ts +2 -0
- package/src/components/entities/settings/pdf-template-selector/locales/de.ts +18 -0
- package/src/components/entities/settings/pdf-template-selector/locales/sl.ts +18 -0
- package/src/components/entities/settings/pdf-template-selector/pdf-template-cards.tsx +49 -0
- package/src/components/entities/settings/settings-footer.tsx +16 -0
- package/src/components/entities/settings/tax-rules-settings-form.tsx +346 -0
- package/src/components/estimates/create/create-estimate-form.tsx +384 -0
- package/src/components/estimates/create/locales/de.ts +64 -0
- package/src/components/estimates/create/locales/sl.ts +63 -0
- package/src/components/estimates/create/prepare-estimate-submission.ts +39 -0
- package/src/components/estimates/create/use-estimate-customer-form.ts +5 -0
- package/src/components/estimates/estimates.hooks.ts +15 -0
- package/src/components/estimates/index.ts +6 -0
- package/src/components/estimates/list/index.ts +3 -0
- package/src/components/estimates/list/list-row-actions.tsx +103 -0
- package/src/components/estimates/list/list-table.tsx +171 -0
- package/src/components/estimates/list/locales/de.ts +26 -0
- package/src/components/estimates/list/locales/sl.ts +26 -0
- package/src/components/estimates/list/use-estimate-download.ts +63 -0
- package/src/components/export/document-export-form.tsx +288 -0
- package/src/components/export/index.ts +2 -0
- package/src/components/form/form-input.tsx +89 -0
- package/src/components/form/index.ts +1 -0
- package/src/components/invoices/create/create-invoice-form.tsx +852 -0
- package/src/components/invoices/create/eslog-validation.test.ts +242 -0
- package/src/components/invoices/create/eslog-validation.ts +208 -0
- package/src/components/invoices/create/locales/de.ts +118 -0
- package/src/components/invoices/create/locales/sl.ts +114 -0
- package/src/components/invoices/create/prepare-invoice-submission.test.ts +777 -0
- package/src/components/invoices/create/prepare-invoice-submission.ts +79 -0
- package/src/components/invoices/create/use-invoice-customer-form.ts +5 -0
- package/src/components/invoices/index.ts +9 -0
- package/src/components/invoices/invoices-furs.hooks.ts +28 -0
- package/src/components/invoices/invoices.hooks.ts +110 -0
- package/src/components/invoices/list/index.ts +3 -0
- package/src/components/invoices/list/list-row-actions.tsx +132 -0
- package/src/components/invoices/list/list-table.tsx +165 -0
- package/src/components/invoices/list/locales/de.ts +33 -0
- package/src/components/invoices/list/locales/sl.ts +33 -0
- package/src/components/invoices/list/use-invoice-download.ts +62 -0
- package/src/components/invoices/send-email-dialog/index.ts +1 -0
- package/src/components/invoices/send-email-dialog/locales/de.ts +18 -0
- package/src/components/invoices/send-email-dialog/locales/sl.ts +17 -0
- package/src/components/invoices/send-email-dialog/send-email-dialog.tsx +289 -0
- package/src/components/invoices/send-email-dialog.tsx +2 -0
- package/src/components/invoices/shared/index.ts +2 -0
- package/src/components/invoices/shared/scaled-document-preview.tsx +32 -0
- package/src/components/invoices/shared/use-a4-scaling.tsx +39 -0
- package/src/components/invoices/view/eslog-info-display.tsx +160 -0
- package/src/components/invoices/view/furs-info-display.tsx +213 -0
- package/src/components/items/create-item-form/create-item-form.tsx +155 -0
- package/src/components/items/create-item-form/locales/de.ts +14 -0
- package/src/components/items/create-item-form/locales/en.ts +9 -0
- package/src/components/items/create-item-form/locales/sl.ts +14 -0
- package/src/components/items/item-combobox.tsx +147 -0
- package/src/components/items/item-list-table/item-list-header.tsx +33 -0
- package/src/components/items/item-list-table/item-list-row-actions.tsx +48 -0
- package/src/components/items/item-list-table/item-list-row.tsx +32 -0
- package/src/components/items/item-list-table/item-list-table.tsx +76 -0
- package/src/components/items/item-list-table/locales/de.ts +10 -0
- package/src/components/items/item-list-table/locales/en.ts +10 -0
- package/src/components/items/item-list-table/locales/sl.ts +10 -0
- package/src/components/items/items.hooks.ts +63 -0
- package/src/components/loading-spinner.tsx +24 -0
- package/src/components/payments/create-payment-form/create-payment-form.tsx +222 -0
- package/src/components/payments/create-payment-form/locales/de.ts +20 -0
- package/src/components/payments/create-payment-form/locales/sl.ts +20 -0
- package/src/components/payments/edit-payment-form/edit-payment-form.tsx +230 -0
- package/src/components/payments/edit-payment-form/index.ts +1 -0
- package/src/components/payments/edit-payment-form/locales/de.ts +20 -0
- package/src/components/payments/edit-payment-form/locales/sl.ts +20 -0
- package/src/components/payments/index.ts +4 -0
- package/src/components/payments/list/index.ts +2 -0
- package/src/components/payments/list/list-row-actions.tsx +98 -0
- package/src/components/payments/list/list-table.tsx +186 -0
- package/src/components/payments/list/locales/de.ts +19 -0
- package/src/components/payments/list/locales/sl.ts +19 -0
- package/src/components/payments/payments.hooks.ts +15 -0
- package/src/components/request-logs/index.ts +3 -0
- package/src/components/request-logs/request-log-detail.tsx +242 -0
- package/src/components/request-logs/request-log-list-table.tsx +266 -0
- package/src/components/request-logs/request-logs-page.tsx +10 -0
- package/src/components/table/README.md +410 -0
- package/src/components/table/data-table.tsx +251 -0
- package/src/components/table/date-cell.tsx +35 -0
- package/src/components/table/filter-bar.tsx +114 -0
- package/src/components/table/filter-panel.tsx +407 -0
- package/src/components/table/hooks/use-table-fetch.ts +17 -0
- package/src/components/table/hooks/use-table-query.ts +36 -0
- package/src/components/table/hooks/use-table-state.ts +293 -0
- package/src/components/table/index.ts +35 -0
- package/src/components/table/search-input.tsx +85 -0
- package/src/components/table/sortable-header.tsx +56 -0
- package/src/components/table/table-empty-state.tsx +40 -0
- package/src/components/table/table-no-results.tsx +41 -0
- package/src/components/table/table-pagination.tsx +42 -0
- package/src/components/table/table-skeleton.tsx +54 -0
- package/src/components/table/types.ts +136 -0
- package/src/components/tax-reports/index.ts +1 -0
- package/src/components/tax-reports/kir-export-form.tsx +172 -0
- package/src/components/taxes/create-tax-form/create-tax-form.tsx +112 -0
- package/src/components/taxes/create-tax-form/locales/de.ts +8 -0
- package/src/components/taxes/create-tax-form/locales/en.ts +7 -0
- package/src/components/taxes/create-tax-form/locales/sl.ts +8 -0
- package/src/components/taxes/tax-list-table/locales/de.ts +11 -0
- package/src/components/taxes/tax-list-table/locales/en.ts +10 -0
- package/src/components/taxes/tax-list-table/locales/sl.ts +11 -0
- package/src/components/taxes/tax-list-table/tax-list-header.tsx +29 -0
- package/src/components/taxes/tax-list-table/tax-list-row-actions.tsx +43 -0
- package/src/components/taxes/tax-list-table/tax-list-row.tsx +46 -0
- package/src/components/taxes/tax-list-table/tax-list-table.tsx +59 -0
- package/src/components/taxes/taxes.hooks.ts +35 -0
- package/src/components/ui/alert-dialog.tsx +61 -0
- package/src/components/ui/alert.tsx +72 -0
- package/src/components/ui/badge.tsx +48 -0
- package/src/components/ui/breadcrumb.tsx +132 -0
- package/src/components/ui/button.tsx +61 -0
- package/src/components/ui/calendar.tsx +213 -0
- package/src/components/ui/card.tsx +94 -0
- package/src/components/ui/chart.tsx +380 -0
- package/src/components/ui/checkbox.tsx +27 -0
- package/src/components/ui/collapsible.tsx +56 -0
- package/src/components/ui/command.tsx +187 -0
- package/src/components/ui/dialog.tsx +187 -0
- package/src/components/ui/drawer.tsx +123 -0
- package/src/components/ui/dropdown-menu.tsx +291 -0
- package/src/components/ui/form.tsx +166 -0
- package/src/components/ui/input-group.tsx +149 -0
- package/src/components/ui/input.tsx +20 -0
- package/src/components/ui/label.tsx +18 -0
- package/src/components/ui/loading-spinner.tsx +16 -0
- package/src/components/ui/popover.tsx +108 -0
- package/src/components/ui/radio-group.tsx +37 -0
- package/src/components/ui/select.tsx +200 -0
- package/src/components/ui/separator.tsx +23 -0
- package/src/components/ui/sheet.tsx +145 -0
- package/src/components/ui/sidebar.tsx +771 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/sonner.tsx +60 -0
- package/src/components/ui/spinner.tsx +10 -0
- package/src/components/ui/sticky-form-footer.tsx +55 -0
- package/src/components/ui/switch.tsx +30 -0
- package/src/components/ui/table.tsx +101 -0
- package/src/components/ui/tabs.tsx +80 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +89 -0
- package/src/components/wl-subscription/index.ts +2 -0
- package/src/components/wl-subscription/locked-feature.tsx +173 -0
- package/src/components/wl-subscription/upgrade-modal.tsx +209 -0
- package/src/frontend.tsx +28 -0
- package/src/generate-schemas.ts +265 -0
- package/src/generated/schemas/advanceinvoice.ts +177 -0
- package/src/generated/schemas/creditnote.ts +187 -0
- package/src/generated/schemas/customer.ts +29 -0
- package/src/generated/schemas/entity.ts +252 -0
- package/src/generated/schemas/estimate.ts +159 -0
- package/src/generated/schemas/furssettings.ts +25 -0
- package/src/generated/schemas/index.ts +24 -0
- package/src/generated/schemas/invoice.ts +167 -0
- package/src/generated/schemas/item.ts +38 -0
- package/src/generated/schemas/payment.ts +44 -0
- package/src/generated/schemas/previewadvanceinvoice_body.ts +354 -0
- package/src/generated/schemas/previewestimate_body.ts +309 -0
- package/src/generated/schemas/registerfursmovablepremise_body.ts +22 -0
- package/src/generated/schemas/registerfursrealestatepremise_body.ts +32 -0
- package/src/generated/schemas/renderdocument_body.ts +594 -0
- package/src/generated/schemas/sendemail_body.ts +26 -0
- package/src/generated/schemas/startpdfexport_body.ts +20 -0
- package/src/generated/schemas/tax.ts +48 -0
- package/src/generated/schemas/uploadfile_body.ts +23 -0
- package/src/generated/schemas/uploadfurscertificate_body.ts +20 -0
- package/src/generated/schemas/userfurssettings.ts +19 -0
- package/src/hooks/create-resource-hooks.test.ts +483 -0
- package/src/hooks/create-resource-hooks.ts +300 -0
- package/src/hooks/use-debounce.ts +12 -0
- package/src/hooks/use-duplicate-document.ts +185 -0
- package/src/hooks/use-media-query.tsx +19 -0
- package/src/hooks/use-mobile.ts +39 -0
- package/src/hooks/use-next-document-number.ts +57 -0
- package/src/hooks/use-resource-mutation.ts +118 -0
- package/src/hooks/use-vies-check.ts +130 -0
- package/src/index.css +11 -0
- package/src/index.html +13 -0
- package/src/index.tsx +12 -0
- package/src/lib/auth.ts +4 -0
- package/src/lib/browser-cookies.ts +70 -0
- package/src/lib/constants.ts +287 -0
- package/src/lib/cookies.ts +36 -0
- package/src/lib/schemas/advance-invoice.ts +43 -0
- package/src/lib/schemas/credit-note.ts +32 -0
- package/src/lib/schemas/estimate.ts +31 -0
- package/src/lib/schemas/index.ts +18 -0
- package/src/lib/schemas/invoice.ts +43 -0
- package/src/lib/schemas/shared.ts +79 -0
- package/src/lib/translation.ts +38 -0
- package/src/lib/utils.ts +6 -0
- package/src/providers/entities-context.tsx +41 -0
- package/src/providers/entities-provider.tsx +201 -0
- package/src/providers/form-footer-context.tsx +72 -0
- package/src/providers/sdk-provider.tsx +164 -0
- package/src/providers/white-label-provider.tsx +91 -0
- package/src/providers/wl-subscription-provider.tsx +277 -0
- package/src/utils/string-helpers.ts +111 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { cn } from "@/ui/lib/utils"
|
|
2
|
+
|
|
3
|
+
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
|
4
|
+
return (
|
|
5
|
+
<div
|
|
6
|
+
data-slot="skeleton"
|
|
7
|
+
className={cn("bg-muted rounded-md animate-pulse", className)}
|
|
8
|
+
{...props}
|
|
9
|
+
/>
|
|
10
|
+
)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { Skeleton }
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { Toaster as Sonner, type ToasterProps } from "sonner"
|
|
4
|
+
import { CircleCheckIcon, InfoIcon, TriangleAlertIcon, OctagonXIcon, Loader2Icon } from "lucide-react"
|
|
5
|
+
|
|
6
|
+
// Try to use next-themes if available, otherwise fall back to system theme
|
|
7
|
+
let useTheme: () => { theme?: string } = () => ({ theme: "system" })
|
|
8
|
+
try {
|
|
9
|
+
// Dynamic import to avoid hard dependency on next-themes
|
|
10
|
+
const nextThemes = require("next-themes")
|
|
11
|
+
if (nextThemes?.useTheme) {
|
|
12
|
+
useTheme = nextThemes.useTheme
|
|
13
|
+
}
|
|
14
|
+
} catch {
|
|
15
|
+
// next-themes not available, use fallback
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const Toaster = ({ ...props }: ToasterProps) => {
|
|
19
|
+
const { theme = "system" } = useTheme()
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Sonner
|
|
23
|
+
theme={theme as ToasterProps["theme"]}
|
|
24
|
+
className="toaster group"
|
|
25
|
+
icons={{
|
|
26
|
+
success: (
|
|
27
|
+
<CircleCheckIcon className="size-4" />
|
|
28
|
+
),
|
|
29
|
+
info: (
|
|
30
|
+
<InfoIcon className="size-4" />
|
|
31
|
+
),
|
|
32
|
+
warning: (
|
|
33
|
+
<TriangleAlertIcon className="size-4" />
|
|
34
|
+
),
|
|
35
|
+
error: (
|
|
36
|
+
<OctagonXIcon className="size-4" />
|
|
37
|
+
),
|
|
38
|
+
loading: (
|
|
39
|
+
<Loader2Icon className="size-4 animate-spin" />
|
|
40
|
+
),
|
|
41
|
+
}}
|
|
42
|
+
style={
|
|
43
|
+
{
|
|
44
|
+
"--normal-bg": "var(--popover)",
|
|
45
|
+
"--normal-text": "var(--popover-foreground)",
|
|
46
|
+
"--normal-border": "var(--border)",
|
|
47
|
+
"--border-radius": "var(--radius)",
|
|
48
|
+
} as React.CSSProperties
|
|
49
|
+
}
|
|
50
|
+
toastOptions={{
|
|
51
|
+
classNames: {
|
|
52
|
+
toast: "cn-toast",
|
|
53
|
+
},
|
|
54
|
+
}}
|
|
55
|
+
{...props}
|
|
56
|
+
/>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export { Toaster }
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { cn } from "@/ui/lib/utils"
|
|
2
|
+
import { Loader2Icon } from "lucide-react"
|
|
3
|
+
|
|
4
|
+
function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
|
|
5
|
+
return (
|
|
6
|
+
<Loader2Icon role="status" aria-label="Loading" className={cn("size-4 animate-spin", className)} {...props} />
|
|
7
|
+
)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export { Spinner }
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Button } from "@/ui/components/ui/button";
|
|
2
|
+
import ButtonLoader from "../button-loader";
|
|
3
|
+
import { cn } from "@/ui/lib/utils";
|
|
4
|
+
|
|
5
|
+
type SecondaryAction = {
|
|
6
|
+
label: string;
|
|
7
|
+
onClick: () => void;
|
|
8
|
+
isPending?: boolean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type StickyFormFooterProps = {
|
|
12
|
+
formId: string;
|
|
13
|
+
isPending: boolean;
|
|
14
|
+
isDirty: boolean;
|
|
15
|
+
label: string;
|
|
16
|
+
onSubmit?: () => void;
|
|
17
|
+
secondaryAction?: SecondaryAction;
|
|
18
|
+
className?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export function StickyFormFooter({
|
|
22
|
+
formId,
|
|
23
|
+
isPending,
|
|
24
|
+
isDirty,
|
|
25
|
+
label,
|
|
26
|
+
onSubmit,
|
|
27
|
+
secondaryAction,
|
|
28
|
+
className,
|
|
29
|
+
}: StickyFormFooterProps) {
|
|
30
|
+
// If onSubmit is provided, use onClick, otherwise use form attribute for native form submission
|
|
31
|
+
const buttonProps = onSubmit
|
|
32
|
+
? { type: "button" as const, onClick: onSubmit }
|
|
33
|
+
: { type: "submit" as const, form: formId };
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className={cn("sticky bottom-0 z-10 border-t bg-sidebar px-4 py-3", className)}>
|
|
37
|
+
<div className="flex gap-2">
|
|
38
|
+
<Button {...buttonProps} className="cursor-pointer px-8" disabled={isPending || !isDirty}>
|
|
39
|
+
{isPending ? <ButtonLoader /> : label}
|
|
40
|
+
</Button>
|
|
41
|
+
{secondaryAction && (
|
|
42
|
+
<Button
|
|
43
|
+
type="button"
|
|
44
|
+
variant="outline"
|
|
45
|
+
className="cursor-pointer px-8"
|
|
46
|
+
disabled={secondaryAction.isPending || !isDirty}
|
|
47
|
+
onClick={secondaryAction.onClick}
|
|
48
|
+
>
|
|
49
|
+
{secondaryAction.isPending ? <ButtonLoader /> : secondaryAction.label}
|
|
50
|
+
</Button>
|
|
51
|
+
)}
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Switch as SwitchPrimitive } from "@base-ui/react/switch"
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/ui/lib/utils"
|
|
4
|
+
|
|
5
|
+
function Switch({
|
|
6
|
+
className,
|
|
7
|
+
size = "default",
|
|
8
|
+
...props
|
|
9
|
+
}: SwitchPrimitive.Root.Props & {
|
|
10
|
+
size?: "sm" | "default"
|
|
11
|
+
}) {
|
|
12
|
+
return (
|
|
13
|
+
<SwitchPrimitive.Root
|
|
14
|
+
data-slot="switch"
|
|
15
|
+
data-size={size}
|
|
16
|
+
className={cn(
|
|
17
|
+
"data-checked:bg-primary data-unchecked:bg-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 dark:data-unchecked:bg-input/80 shrink-0 rounded-full border border-transparent shadow-xs focus-visible:ring-[3px] aria-invalid:ring-[3px] data-[size=default]:h-[18.4px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] peer group/switch relative inline-flex items-center transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 cursor-pointer data-disabled:cursor-not-allowed data-disabled:opacity-50",
|
|
18
|
+
className
|
|
19
|
+
)}
|
|
20
|
+
{...props}
|
|
21
|
+
>
|
|
22
|
+
<SwitchPrimitive.Thumb
|
|
23
|
+
data-slot="switch-thumb"
|
|
24
|
+
className="bg-background dark:data-unchecked:bg-foreground dark:data-checked:bg-primary-foreground rounded-full group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0 pointer-events-none block ring-0 transition-transform"
|
|
25
|
+
/>
|
|
26
|
+
</SwitchPrimitive.Root>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { Switch }
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/ui/lib/utils"
|
|
6
|
+
|
|
7
|
+
function Table({ className, ...props }: React.ComponentProps<"table">) {
|
|
8
|
+
return (
|
|
9
|
+
<div data-slot="table-container" className="relative w-full overflow-x-auto">
|
|
10
|
+
<table
|
|
11
|
+
data-slot="table"
|
|
12
|
+
className={cn("w-full caption-bottom text-sm", className)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
</div>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
|
|
20
|
+
return (
|
|
21
|
+
<thead
|
|
22
|
+
data-slot="table-header"
|
|
23
|
+
className={cn("[&_tr]:border-b", className)}
|
|
24
|
+
{...props}
|
|
25
|
+
/>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
|
|
30
|
+
return (
|
|
31
|
+
<tbody
|
|
32
|
+
data-slot="table-body"
|
|
33
|
+
className={cn("[&_tr:last-child]:border-0", className)}
|
|
34
|
+
{...props}
|
|
35
|
+
/>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
|
|
40
|
+
return (
|
|
41
|
+
<tfoot
|
|
42
|
+
data-slot="table-footer"
|
|
43
|
+
className={cn("bg-muted/50 border-t font-medium [&>tr]:last:border-b-0", className)}
|
|
44
|
+
{...props}
|
|
45
|
+
/>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
|
|
50
|
+
return (
|
|
51
|
+
<tr
|
|
52
|
+
data-slot="table-row"
|
|
53
|
+
className={cn("hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors", className)}
|
|
54
|
+
{...props}
|
|
55
|
+
/>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
|
60
|
+
return (
|
|
61
|
+
<th
|
|
62
|
+
data-slot="table-head"
|
|
63
|
+
className={cn("text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap first:pl-4 last:pr-4 [&:has([role=checkbox])]:pr-0", className)}
|
|
64
|
+
{...props}
|
|
65
|
+
/>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
|
70
|
+
return (
|
|
71
|
+
<td
|
|
72
|
+
data-slot="table-cell"
|
|
73
|
+
className={cn("p-2 align-middle whitespace-nowrap first:pl-4 last:pr-4 [&:has([role=checkbox])]:pr-0", className)}
|
|
74
|
+
{...props}
|
|
75
|
+
/>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function TableCaption({
|
|
80
|
+
className,
|
|
81
|
+
...props
|
|
82
|
+
}: React.ComponentProps<"caption">) {
|
|
83
|
+
return (
|
|
84
|
+
<caption
|
|
85
|
+
data-slot="table-caption"
|
|
86
|
+
className={cn("text-muted-foreground mt-4 text-sm", className)}
|
|
87
|
+
{...props}
|
|
88
|
+
/>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export {
|
|
93
|
+
Table,
|
|
94
|
+
TableHeader,
|
|
95
|
+
TableBody,
|
|
96
|
+
TableFooter,
|
|
97
|
+
TableHead,
|
|
98
|
+
TableRow,
|
|
99
|
+
TableCell,
|
|
100
|
+
TableCaption,
|
|
101
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Tabs as TabsPrimitive } from "@base-ui/react/tabs"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
|
|
4
|
+
import { cn } from "@/ui/lib/utils"
|
|
5
|
+
|
|
6
|
+
function Tabs({
|
|
7
|
+
className,
|
|
8
|
+
orientation = "horizontal",
|
|
9
|
+
...props
|
|
10
|
+
}: TabsPrimitive.Root.Props) {
|
|
11
|
+
return (
|
|
12
|
+
<TabsPrimitive.Root
|
|
13
|
+
data-slot="tabs"
|
|
14
|
+
data-orientation={orientation}
|
|
15
|
+
className={cn(
|
|
16
|
+
"gap-2 group/tabs flex data-[orientation=horizontal]:flex-col",
|
|
17
|
+
className
|
|
18
|
+
)}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const tabsListVariants = cva(
|
|
25
|
+
"rounded-lg p-[3px] w-full group-data-horizontal/tabs:h-9 data-[variant=line]:rounded-none group/tabs-list text-muted-foreground flex items-center justify-center group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col group-data-[orientation=vertical]/tabs:w-fit",
|
|
26
|
+
{
|
|
27
|
+
variants: {
|
|
28
|
+
variant: {
|
|
29
|
+
default: "bg-muted",
|
|
30
|
+
line: "gap-1 bg-transparent",
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
defaultVariants: {
|
|
34
|
+
variant: "default",
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
function TabsList({
|
|
40
|
+
className,
|
|
41
|
+
variant = "default",
|
|
42
|
+
...props
|
|
43
|
+
}: TabsPrimitive.List.Props & VariantProps<typeof tabsListVariants>) {
|
|
44
|
+
return (
|
|
45
|
+
<TabsPrimitive.List
|
|
46
|
+
data-slot="tabs-list"
|
|
47
|
+
data-variant={variant}
|
|
48
|
+
className={cn(tabsListVariants({ variant }), className)}
|
|
49
|
+
{...props}
|
|
50
|
+
/>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
|
|
55
|
+
return (
|
|
56
|
+
<TabsPrimitive.Tab
|
|
57
|
+
data-slot="tabs-trigger"
|
|
58
|
+
className={cn(
|
|
59
|
+
"gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg:not([class*='size-'])]:size-4 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 cursor-pointer disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
60
|
+
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
|
|
61
|
+
"data-active:bg-background dark:data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 data-active:text-foreground",
|
|
62
|
+
"after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
|
|
63
|
+
className
|
|
64
|
+
)}
|
|
65
|
+
{...props}
|
|
66
|
+
/>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {
|
|
71
|
+
return (
|
|
72
|
+
<TabsPrimitive.Panel
|
|
73
|
+
data-slot="tabs-content"
|
|
74
|
+
className={cn("text-sm flex-1 outline-none", className)}
|
|
75
|
+
{...props}
|
|
76
|
+
/>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/ui/lib/utils"
|
|
4
|
+
|
|
5
|
+
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
|
6
|
+
return (
|
|
7
|
+
<textarea
|
|
8
|
+
data-slot="textarea"
|
|
9
|
+
className={cn(
|
|
10
|
+
"border-input dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-md border bg-transparent px-2.5 py-2 text-base shadow-xs transition-[color,box-shadow] focus-visible:ring-[3px] aria-invalid:ring-[3px] md:text-sm placeholder:text-muted-foreground flex field-sizing-content min-h-16 w-full outline-none disabled:cursor-not-allowed disabled:opacity-50",
|
|
11
|
+
className
|
|
12
|
+
)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { Textarea }
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/ui/lib/utils"
|
|
7
|
+
|
|
8
|
+
function TooltipProvider({
|
|
9
|
+
delay = 0,
|
|
10
|
+
...props
|
|
11
|
+
}: TooltipPrimitive.Provider.Props) {
|
|
12
|
+
return (
|
|
13
|
+
<TooltipPrimitive.Provider
|
|
14
|
+
data-slot="tooltip-provider"
|
|
15
|
+
delay={delay}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function Tooltip({ ...props }: TooltipPrimitive.Root.Props) {
|
|
22
|
+
return (
|
|
23
|
+
<TooltipProvider>
|
|
24
|
+
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
|
25
|
+
</TooltipProvider>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function TooltipTrigger({
|
|
30
|
+
asChild,
|
|
31
|
+
children,
|
|
32
|
+
...props
|
|
33
|
+
}: TooltipPrimitive.Trigger.Props & { asChild?: boolean }) {
|
|
34
|
+
// Base UI uses render prop instead of asChild
|
|
35
|
+
if (asChild && React.isValidElement(children)) {
|
|
36
|
+
return (
|
|
37
|
+
<TooltipPrimitive.Trigger
|
|
38
|
+
data-slot="tooltip-trigger"
|
|
39
|
+
render={children}
|
|
40
|
+
{...props}
|
|
41
|
+
/>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
return (
|
|
45
|
+
<TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props}>
|
|
46
|
+
{children}
|
|
47
|
+
</TooltipPrimitive.Trigger>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function TooltipContent({
|
|
52
|
+
className,
|
|
53
|
+
side = "top",
|
|
54
|
+
sideOffset = 4,
|
|
55
|
+
align = "center",
|
|
56
|
+
alignOffset = 0,
|
|
57
|
+
children,
|
|
58
|
+
...props
|
|
59
|
+
}: TooltipPrimitive.Popup.Props &
|
|
60
|
+
Pick<
|
|
61
|
+
TooltipPrimitive.Positioner.Props,
|
|
62
|
+
"align" | "alignOffset" | "side" | "sideOffset"
|
|
63
|
+
>) {
|
|
64
|
+
return (
|
|
65
|
+
<TooltipPrimitive.Portal>
|
|
66
|
+
<TooltipPrimitive.Positioner
|
|
67
|
+
align={align}
|
|
68
|
+
alignOffset={alignOffset}
|
|
69
|
+
side={side}
|
|
70
|
+
sideOffset={sideOffset}
|
|
71
|
+
className="isolate z-50"
|
|
72
|
+
>
|
|
73
|
+
<TooltipPrimitive.Popup
|
|
74
|
+
data-slot="tooltip-content"
|
|
75
|
+
className={cn(
|
|
76
|
+
"data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 rounded-md px-3 py-1.5 text-xs bg-foreground text-background z-50 w-fit max-w-xs origin-(--transform-origin)",
|
|
77
|
+
className
|
|
78
|
+
)}
|
|
79
|
+
{...props}
|
|
80
|
+
>
|
|
81
|
+
{children}
|
|
82
|
+
<TooltipPrimitive.Arrow className="size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground z-50 data-[side=bottom]:top-1 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" />
|
|
83
|
+
</TooltipPrimitive.Popup>
|
|
84
|
+
</TooltipPrimitive.Positioner>
|
|
85
|
+
</TooltipPrimitive.Portal>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { Lock } from "lucide-react";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
|
|
5
|
+
import { type GatedFeature, useWLSubscriptionOptional } from "../../providers/wl-subscription-provider";
|
|
6
|
+
import { UpgradeModal } from "./upgrade-modal";
|
|
7
|
+
|
|
8
|
+
type LockedFeatureProps = {
|
|
9
|
+
/** Feature slug to check access for */
|
|
10
|
+
feature: GatedFeature;
|
|
11
|
+
/** Content to render when feature is unlocked */
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
/** Optional custom locked message */
|
|
14
|
+
lockedMessage?: string;
|
|
15
|
+
/** Whether to show the upgrade modal on click (default: true) */
|
|
16
|
+
showUpgradeModal?: boolean;
|
|
17
|
+
/** Custom render for locked state */
|
|
18
|
+
lockedRender?: () => ReactNode;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* LockedFeature wrapper component
|
|
23
|
+
*
|
|
24
|
+
* Wraps content that requires a specific subscription feature.
|
|
25
|
+
* Shows a locked overlay with upgrade prompt if feature is not available.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* <LockedFeature feature="furs">
|
|
29
|
+
* <FursSettingsPage />
|
|
30
|
+
* </LockedFeature>
|
|
31
|
+
*/
|
|
32
|
+
export function LockedFeature({
|
|
33
|
+
feature,
|
|
34
|
+
children,
|
|
35
|
+
lockedMessage,
|
|
36
|
+
showUpgradeModal = true,
|
|
37
|
+
lockedRender,
|
|
38
|
+
}: LockedFeatureProps) {
|
|
39
|
+
const subscription = useWLSubscriptionOptional();
|
|
40
|
+
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
41
|
+
|
|
42
|
+
// If no subscription context, feature is unlocked (Space Invoices)
|
|
43
|
+
if (!subscription) {
|
|
44
|
+
return <>{children}</>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const hasFeature = subscription.hasFeature(feature);
|
|
48
|
+
|
|
49
|
+
// Feature is available, render children
|
|
50
|
+
if (hasFeature) {
|
|
51
|
+
return <>{children}</>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Feature is locked - show locked state
|
|
55
|
+
const handleClick = () => {
|
|
56
|
+
if (showUpgradeModal) {
|
|
57
|
+
setIsModalOpen(true);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Custom locked render
|
|
62
|
+
if (lockedRender) {
|
|
63
|
+
return (
|
|
64
|
+
<>
|
|
65
|
+
<button type="button" className="w-full text-left" onClick={handleClick}>
|
|
66
|
+
{lockedRender()}
|
|
67
|
+
</button>
|
|
68
|
+
{showUpgradeModal && (
|
|
69
|
+
<UpgradeModal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} feature={feature} />
|
|
70
|
+
)}
|
|
71
|
+
</>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Default locked state
|
|
76
|
+
return (
|
|
77
|
+
<>
|
|
78
|
+
<button type="button" className="relative w-full cursor-pointer" onClick={handleClick}>
|
|
79
|
+
{/* Blurred content */}
|
|
80
|
+
<div className="pointer-events-none select-none opacity-50 blur-sm">{children}</div>
|
|
81
|
+
|
|
82
|
+
{/* Lock overlay */}
|
|
83
|
+
<div className="absolute inset-0 flex items-center justify-center bg-background/60">
|
|
84
|
+
<div className="flex flex-col items-center gap-2 p-4 text-center">
|
|
85
|
+
<div className="rounded-full bg-muted p-3">
|
|
86
|
+
<Lock className="h-6 w-6 text-muted-foreground" />
|
|
87
|
+
</div>
|
|
88
|
+
<p className="font-medium text-muted-foreground text-sm">
|
|
89
|
+
{lockedMessage || getDefaultLockedMessage(feature)}
|
|
90
|
+
</p>
|
|
91
|
+
<p className="text-muted-foreground text-xs">Click to upgrade</p>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</button>
|
|
95
|
+
|
|
96
|
+
{showUpgradeModal && (
|
|
97
|
+
<UpgradeModal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} feature={feature} />
|
|
98
|
+
)}
|
|
99
|
+
</>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* LockedBadge component for sidebar items
|
|
105
|
+
* Shows a small lock icon next to locked features
|
|
106
|
+
*/
|
|
107
|
+
export function LockedBadge({ feature }: { feature: GatedFeature }) {
|
|
108
|
+
const subscription = useWLSubscriptionOptional();
|
|
109
|
+
|
|
110
|
+
// No subscription context or has feature = no badge
|
|
111
|
+
if (!subscription || subscription.hasFeature(feature)) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<span className="ml-auto">
|
|
117
|
+
<Lock className="h-3.5 w-3.5 text-muted-foreground" />
|
|
118
|
+
</span>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* UsageBadge component showing document usage percentage
|
|
124
|
+
* For displaying in settings or dashboard
|
|
125
|
+
*/
|
|
126
|
+
export function UsageBadge() {
|
|
127
|
+
const subscription = useWLSubscriptionOptional();
|
|
128
|
+
|
|
129
|
+
if (!subscription) return null;
|
|
130
|
+
|
|
131
|
+
const percentage = subscription.getUsagePercentage("documents");
|
|
132
|
+
const { usage, plan } = subscription;
|
|
133
|
+
|
|
134
|
+
if (!plan || !usage) return null;
|
|
135
|
+
|
|
136
|
+
if (plan.limits?.documents_per_month === null) {
|
|
137
|
+
return null; // Unlimited - no badge needed
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const limit = plan.limits?.documents_per_month ?? 0;
|
|
141
|
+
const isNearLimit = percentage >= 80;
|
|
142
|
+
const isAtLimit = percentage >= 100;
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<div
|
|
146
|
+
className={`rounded-full px-2 py-1 text-xs ${
|
|
147
|
+
isAtLimit
|
|
148
|
+
? "bg-destructive/10 text-destructive"
|
|
149
|
+
: isNearLimit
|
|
150
|
+
? "bg-warning/10 text-warning"
|
|
151
|
+
: "bg-muted text-muted-foreground"
|
|
152
|
+
}`}
|
|
153
|
+
>
|
|
154
|
+
{usage.documents_count}/{limit} docs
|
|
155
|
+
</div>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Helper to get default locked message for a feature
|
|
160
|
+
function getDefaultLockedMessage(feature: GatedFeature): string {
|
|
161
|
+
const messages: Record<GatedFeature, string> = {
|
|
162
|
+
furs: "FURS fiscalization requires a Starter or Advanced plan",
|
|
163
|
+
eslog: "eSlog export requires a Starter or Advanced plan",
|
|
164
|
+
recurring: "Recurring invoices require a Starter or Advanced plan",
|
|
165
|
+
email_sending: "Email sending requires a Starter or Advanced plan",
|
|
166
|
+
custom_templates: "Custom templates require an Advanced plan",
|
|
167
|
+
api_access: "API access requires an Advanced plan",
|
|
168
|
+
webhooks: "Webhooks require an Advanced plan",
|
|
169
|
+
priority_support: "Priority support requires an Advanced plan",
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
return messages[feature] || "This feature requires a plan upgrade";
|
|
173
|
+
}
|