@liorian/sdk 0.28.0-alpha.56010ca
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/README.md +483 -0
- package/package.json +94 -0
- package/schemas/module.schema.json +818 -0
- package/src/application/index.ts +3 -0
- package/src/application/module-instance.ts +36 -0
- package/src/application/service/auth-api-service.ts +291 -0
- package/src/application/service/auth-user.service.ts +108 -0
- package/src/application/service/index.ts +12 -0
- package/src/application/service/module-activation-api.service.ts +105 -0
- package/src/application/service/notifications-api-service.ts +38 -0
- package/src/application/service/notifications-push.service.ts +299 -0
- package/src/application/service/notifications-socket-service.ts +76 -0
- package/src/application/service/organizations-api-service.ts +104 -0
- package/src/application/service/otp-api-service.ts +11 -0
- package/src/application/service/storage-api-service.ts +77 -0
- package/src/application/service/storefront-api.service.ts +72 -0
- package/src/application/service/user-preferences-api.service.ts +57 -0
- package/src/domain/config/app.config.ts +35 -0
- package/src/domain/config/auth.config.ts +30 -0
- package/src/domain/config/index.ts +4 -0
- package/src/domain/config/logo.config.ts +17 -0
- package/src/domain/entities/activities.interface.ts +18 -0
- package/src/domain/entities/catalog.interface.ts +96 -0
- package/src/domain/entities/index.ts +13 -0
- package/src/domain/entities/media.ts +22 -0
- package/src/domain/entities/module-activation.interface.ts +173 -0
- package/src/domain/entities/module.interface.ts +101 -0
- package/src/domain/entities/notification.interface.ts +23 -0
- package/src/domain/entities/objectable.type.ts +3 -0
- package/src/domain/entities/organization.interface.ts +81 -0
- package/src/domain/entities/user-auth.interface.ts +17 -0
- package/src/domain/entities/user-phone.interface.ts +11 -0
- package/src/domain/entities/user-profile.interface.ts +23 -0
- package/src/domain/entities/user.interface.ts +79 -0
- package/src/domain/enums/coloring.enum.ts +6 -0
- package/src/domain/enums/domains.enum.ts +282 -0
- package/src/domain/enums/index.ts +8 -0
- package/src/domain/enums/module-category.enum.ts +57 -0
- package/src/domain/enums/module.enum.ts +97 -0
- package/src/domain/enums/routine.enum.ts +12 -0
- package/src/domain/enums/user-profile.enum.ts +25 -0
- package/src/domain/enums/user-status.enum.ts +7 -0
- package/src/domain/index.ts +6 -0
- package/src/domain/typing/auth-caches.ts +12 -0
- package/src/domain/typing/auth-oauth.types.ts +172 -0
- package/src/domain/typing/devise.ts +2 -0
- package/src/domain/typing/index.ts +10 -0
- package/src/domain/typing/response.ts +30 -0
- package/src/domain/typing/routine.types.ts +113 -0
- package/src/domain/typing/session.interface.ts +28 -0
- package/src/domain/typing/statisticals.ts +11 -0
- package/src/domain/typing/user-media-upload.ts +9 -0
- package/src/domain/typing/values.ts +6 -0
- package/src/index.tsx +168 -0
- package/src/infrastructure/capabilities/cache-storage/capability.ts +129 -0
- package/src/infrastructure/capabilities/cache-storage/enum.ts +5 -0
- package/src/infrastructure/capabilities/cache-storage/index.ts +4 -0
- package/src/infrastructure/capabilities/cache-storage/types.ts +13 -0
- package/src/infrastructure/capabilities/index.ts +3 -0
- package/src/infrastructure/capabilities/stackable/capability.ts +55 -0
- package/src/infrastructure/capabilities/stackable/index.ts +3 -0
- package/src/infrastructure/capabilities/stackable/types.ts +24 -0
- package/src/infrastructure/common-classname.constant.ts +4 -0
- package/src/infrastructure/data/countries.ts +208 -0
- package/src/infrastructure/data/gender.ts +5 -0
- package/src/infrastructure/data/index.ts +3 -0
- package/src/infrastructure/dataset/index.ts +3 -0
- package/src/infrastructure/dataset/sign-in.dataset.ts +21 -0
- package/src/infrastructure/dataset/sign-up.dataset.ts +64 -0
- package/src/infrastructure/hooks/index.ts +10 -0
- package/src/infrastructure/hooks/use-activity-lock-draft.ts +69 -0
- package/src/infrastructure/hooks/use-auth.ts +24 -0
- package/src/infrastructure/hooks/use-elastic-interaction.ts +180 -0
- package/src/infrastructure/hooks/use-is-desktop.ts +25 -0
- package/src/infrastructure/hooks/use-is-tauri.ts +17 -0
- package/src/infrastructure/hooks/use-mobile.ts +19 -0
- package/src/infrastructure/hooks/use-notifications.ts +128 -0
- package/src/infrastructure/hooks/use-recent-colors.ts +113 -0
- package/src/infrastructure/hooks/use-recent-modules.ts +76 -0
- package/src/infrastructure/index.tsx +11 -0
- package/src/infrastructure/library/design-system.ts +95 -0
- package/src/infrastructure/library/index.tsx +5 -0
- package/src/infrastructure/library/motion-utils.ts +77 -0
- package/src/infrastructure/library/motion.tsx +31 -0
- package/src/infrastructure/library/scroll-progress.tsx +93 -0
- package/src/infrastructure/providers/auth-guard.provider.tsx +47 -0
- package/src/infrastructure/providers/auth.provider.tsx +168 -0
- package/src/infrastructure/providers/index.tsx +8 -0
- package/src/infrastructure/providers/module-usage.provider.tsx +95 -0
- package/src/infrastructure/providers/modules-guard.provider.tsx +64 -0
- package/src/infrastructure/providers/modules-routines.provider.tsx +33 -0
- package/src/infrastructure/providers/modules-switcher.provider.tsx +25 -0
- package/src/infrastructure/providers/query.provider.tsx +27 -0
- package/src/infrastructure/routines/__tests__/routine.test.ts +368 -0
- package/src/infrastructure/routines/index.tsx +3 -0
- package/src/infrastructure/routines/routine.hook.ts +121 -0
- package/src/infrastructure/routines/routine.ts +283 -0
- package/src/infrastructure/routines/routine.tsx +283 -0
- package/src/infrastructure/stores/activity-lock.store.ts +22 -0
- package/src/infrastructure/stores/auth-user-connected.store.ts +24 -0
- package/src/infrastructure/stores/dataset.store.ts +106 -0
- package/src/infrastructure/stores/index.ts +6 -0
- package/src/infrastructure/stores/module.store.ts +266 -0
- package/src/infrastructure/stores/upload.store.ts +198 -0
- package/src/infrastructure/utilities/access-label.util.ts +158 -0
- package/src/infrastructure/utilities/activities.util.tsx +54 -0
- package/src/infrastructure/utilities/activity-lock.service.ts +263 -0
- package/src/infrastructure/utilities/activity-tracker.service.ts +205 -0
- package/src/infrastructure/utilities/api-service.ts +237 -0
- package/src/infrastructure/utilities/auth-logout.util.ts +23 -0
- package/src/infrastructure/utilities/classname.util.ts +7 -0
- package/src/infrastructure/utilities/encryption.service.ts +125 -0
- package/src/infrastructure/utilities/form-snapshot.service.ts +304 -0
- package/src/infrastructure/utilities/format.util.ts +40 -0
- package/src/infrastructure/utilities/index.tsx +23 -0
- package/src/infrastructure/utilities/media-label.service.ts +47 -0
- package/src/infrastructure/utilities/modules.ts +11 -0
- package/src/infrastructure/utilities/permission.util.ts +22 -0
- package/src/infrastructure/utilities/push-permission.util.ts +43 -0
- package/src/infrastructure/utilities/route-access.ts +38 -0
- package/src/infrastructure/utilities/socket-service.ts +123 -0
- package/src/infrastructure/utilities/sso-auth.util.ts +188 -0
- package/src/infrastructure/utilities/stringable.util.ts +16 -0
- package/src/infrastructure/utilities/strings.util.ts +18 -0
- package/src/infrastructure/utilities/tauri.util.ts +15 -0
- package/src/infrastructure/utilities/utils.ts +6 -0
- package/src/infrastructure/utilities/values.ts +5 -0
- package/src/presentation/8starlabs-ui/button.tsx +94 -0
- package/src/presentation/8starlabs-ui/flip-clock.tsx +368 -0
- package/src/presentation/8starlabs-ui/heatmap.tsx +430 -0
- package/src/presentation/8starlabs-ui/hooks/index.tsx +2 -0
- package/src/presentation/8starlabs-ui/hooks/use-mobile.ts +21 -0
- package/src/presentation/8starlabs-ui/index.tsx +20 -0
- package/src/presentation/8starlabs-ui/json-viewer.tsx +972 -0
- package/src/presentation/8starlabs-ui/marquee.tsx +134 -0
- package/src/presentation/8starlabs-ui/partition-bar.tsx +198 -0
- package/src/presentation/8starlabs-ui/scroll-fade.tsx +158 -0
- package/src/presentation/8starlabs-ui/status-indicator.tsx +86 -0
- package/src/presentation/8starlabs-ui/system-banner.tsx +50 -0
- package/src/presentation/8starlabs-ui/timeline.tsx +576 -0
- package/src/presentation/8starlabs-ui/transport-badge.tsx +249 -0
- package/src/presentation/8starlabs-ui/ui/copy-button.tsx +60 -0
- package/src/presentation/8starlabs-ui/ui/index.tsx +2 -0
- package/src/presentation/analytics-section.tsx +85 -0
- package/src/presentation/app-sidebar.tsx +234 -0
- package/src/presentation/charts/area-widget.chart-skeleton.tsx +61 -0
- package/src/presentation/charts/area-widget.chart.tsx +128 -0
- package/src/presentation/charts/chart-widgets.types.ts +81 -0
- package/src/presentation/charts/index.tsx +16 -0
- package/src/presentation/charts/line-widget.chart-skeleton.tsx +64 -0
- package/src/presentation/charts/line-widget.chart.tsx +105 -0
- package/src/presentation/charts/pie-widget.chart-skeleton.tsx +56 -0
- package/src/presentation/charts/pie-widget.chart.tsx +168 -0
- package/src/presentation/charts/radar-widget.chart-skeleton.tsx +67 -0
- package/src/presentation/charts/radar-widget.chart.tsx +80 -0
- package/src/presentation/charts/radial-stacked-widget.chart-skeleton.tsx +66 -0
- package/src/presentation/charts/radial-stacked-widget.chart.tsx +120 -0
- package/src/presentation/charts/radial-widget.chart-skeleton.tsx +61 -0
- package/src/presentation/charts/radial-widget.chart.tsx +87 -0
- package/src/presentation/charts/tooltip-widget.chart-skeleton.tsx +64 -0
- package/src/presentation/charts/tooltip-widget.chart.tsx +89 -0
- package/src/presentation/components/activity-actions.tsx +20 -0
- package/src/presentation/components/activity-container.tsx +55 -0
- package/src/presentation/components/activity-content.tsx +20 -0
- package/src/presentation/components/activity-descriptor-parser.tsx +37 -0
- package/src/presentation/components/activity-header.tsx +20 -0
- package/src/presentation/components/activity-loader.tsx +19 -0
- package/src/presentation/components/activity-title.tsx +32 -0
- package/src/presentation/components/activity.tsx +22 -0
- package/src/presentation/components/auth/index.tsx +4 -0
- package/src/presentation/components/auth/sso-callback.view.tsx +44 -0
- package/src/presentation/components/auth/sso-disconnect.view.tsx +41 -0
- package/src/presentation/components/auth/sso-sign-in.view.tsx +29 -0
- package/src/presentation/components/auth-lock-screen.tsx +309 -0
- package/src/presentation/components/auth-session.view.tsx +17 -0
- package/src/presentation/components/data-table.tsx +814 -0
- package/src/presentation/components/dynamic-icon.tsx +16 -0
- package/src/presentation/components/floating-upload.tsx +291 -0
- package/src/presentation/components/index.tsx +40 -0
- package/src/presentation/components/local-time.tsx +170 -0
- package/src/presentation/components/markdown-parser.tsx +201 -0
- package/src/presentation/components/message-scroller.tsx +131 -0
- package/src/presentation/components/module-first-access-config.view.tsx +181 -0
- package/src/presentation/components/module-first-access-pending.view.tsx +38 -0
- package/src/presentation/components/module-guard.tsx +34 -0
- package/src/presentation/components/modules-first-access.guard.tsx +88 -0
- package/src/presentation/components/modules-guard-forbidden.tsx +46 -0
- package/src/presentation/components/notification-details.modal.tsx +145 -0
- package/src/presentation/components/notification-item.tsx +127 -0
- package/src/presentation/components/notifications-popover.tsx +128 -0
- package/src/presentation/components/push-notifications.provider.tsx +65 -0
- package/src/presentation/components/upload-progress-dialog.tsx +140 -0
- package/src/presentation/components/waiting-bar.tsx +22 -0
- package/src/presentation/components/waiting-section.tsx +15 -0
- package/src/presentation/components/waiting.tsx +14 -0
- package/src/presentation/data-grid/data-grid-empty.tsx +24 -0
- package/src/presentation/data-grid/data-grid-search-engine.tsx +32 -0
- package/src/presentation/data-grid/data-grid.tsx +892 -0
- package/src/presentation/data-grid/index.tsx +4 -0
- package/src/presentation/form-screen.tsx +95 -0
- package/src/presentation/icons/custom.tsx +20 -0
- package/src/presentation/icons/glyphIcons.tsx +53 -0
- package/src/presentation/icons/icons.enum.ts +4 -0
- package/src/presentation/icons/icons.util.ts +6 -0
- package/src/presentation/icons/index.tsx +8 -0
- package/src/presentation/icons/lucide.tsx +17 -0
- package/src/presentation/icons/remix.tsx +33 -0
- package/src/presentation/icons/types.ts +27 -0
- package/src/presentation/index.tsx +45 -0
- package/src/presentation/modals/components/ModalBox.tsx +74 -0
- package/src/presentation/modals/components/ModalPortal.tsx +31 -0
- package/src/presentation/modals/components/ModalStepper.tsx +319 -0
- package/src/presentation/modals/components/ModalWrapper.tsx +65 -0
- package/src/presentation/modals/components/index.tsx +5 -0
- package/src/presentation/modals/hooks/index.ts +2 -0
- package/src/presentation/modals/hooks/useModal.ts +38 -0
- package/src/presentation/modals/index.tsx +5 -0
- package/src/presentation/modals/stores/index.ts +2 -0
- package/src/presentation/modals/stores/useModalStore.ts +90 -0
- package/src/presentation/modals/types/index.ts +2 -0
- package/src/presentation/modals/types/modal.type.ts +33 -0
- package/src/presentation/module-menubar.tsx +300 -0
- package/src/presentation/module-widget.tsx +233 -0
- package/src/presentation/nav-documents.tsx +89 -0
- package/src/presentation/nav-main.tsx +62 -0
- package/src/presentation/nav-secondary.tsx +41 -0
- package/src/presentation/nav-user.tsx +109 -0
- package/src/presentation/permission-action-badge.tsx +35 -0
- package/src/presentation/reui/index.tsx +3 -0
- package/src/presentation/reui/stepper.tsx +508 -0
- package/src/presentation/reui/timeline.tsx +226 -0
- package/src/presentation/sheets/index.tsx +2 -0
- package/src/presentation/sheets/legacy-sheet-header.tsx +0 -0
- package/src/presentation/sheets/legacy-sheet.tsx +37 -0
- package/src/presentation/size.enum.ts +19 -0
- package/src/presentation/system/index.tsx +3 -0
- package/src/presentation/system/logo.theme.tsx +21 -0
- package/src/presentation/system/types.ts +10 -0
- package/src/presentation/theme/index.tsx +6 -0
- package/src/presentation/theme/prefer-color-scheme.tsx +50 -0
- package/src/presentation/theme/switch-color-scheme.tsx +16 -0
- package/src/presentation/theme/theme-switcher-button.tsx +20 -0
- package/src/presentation/theme/theme.enum.ts +4 -0
- package/src/presentation/theme/theme.store.ts +87 -0
- package/src/presentation/themes/index.tsx +2 -0
- package/src/presentation/themes/katon/dashboard-layout.tsx +1080 -0
- package/src/presentation/themes/katon/edge-section.tsx +19 -0
- package/src/presentation/themes/katon/header-menubar.tsx +270 -0
- package/src/presentation/themes/katon/header-modules-available.tsx +44 -0
- package/src/presentation/themes/katon/header-modules-recents.tsx +52 -0
- package/src/presentation/themes/katon/header-modules-store.tsx +25 -0
- package/src/presentation/themes/katon/header-options.tsx +13 -0
- package/src/presentation/themes/katon/header-organization-select.tsx +146 -0
- package/src/presentation/themes/katon/header-routines.tsx +74 -0
- package/src/presentation/themes/katon/header-tasks-connected-user.tsx +150 -0
- package/src/presentation/themes/katon/index.tsx +16 -0
- package/src/presentation/themes/katon/main-wrapper.tsx +34 -0
- package/src/presentation/themes/katon/module-item-link.tsx +32 -0
- package/src/presentation/themes/katon/view-frame.tsx +25 -0
- package/src/presentation/themes/katon/view-helmet.tsx +53 -0
- package/src/presentation/themes/katon/view-status.tsx +32 -0
- package/src/presentation/themes/katon/view-wrapper.tsx +15 -0
- package/src/presentation/themes/katon/view.tsx +42 -0
- package/src/presentation/ui/accordion-multiselect.tsx +206 -0
- package/src/presentation/ui/accordion.tsx +81 -0
- package/src/presentation/ui/alert-dialog.tsx +199 -0
- package/src/presentation/ui/alert.tsx +76 -0
- package/src/presentation/ui/aspect-ratio.tsx +11 -0
- package/src/presentation/ui/attachment.tsx +204 -0
- package/src/presentation/ui/avatar.tsx +112 -0
- package/src/presentation/ui/badge.tsx +49 -0
- package/src/presentation/ui/breadcrumb.tsx +122 -0
- package/src/presentation/ui/bubble.tsx +125 -0
- package/src/presentation/ui/button-group.tsx +83 -0
- package/src/presentation/ui/button.tsx +87 -0
- package/src/presentation/ui/calendar.tsx +222 -0
- package/src/presentation/ui/card.tsx +106 -0
- package/src/presentation/ui/carousel.tsx +242 -0
- package/src/presentation/ui/chart.tsx +373 -0
- package/src/presentation/ui/checkbox.tsx +33 -0
- package/src/presentation/ui/clickable.tsx +58 -0
- package/src/presentation/ui/collapsible.tsx +33 -0
- package/src/presentation/ui/color-swatch-selector.tsx +81 -0
- package/src/presentation/ui/combobox.tsx +299 -0
- package/src/presentation/ui/command.tsx +195 -0
- package/src/presentation/ui/context-menu.tsx +263 -0
- package/src/presentation/ui/dialog.tsx +168 -0
- package/src/presentation/ui/direction.tsx +22 -0
- package/src/presentation/ui/drawer.tsx +134 -0
- package/src/presentation/ui/dropdown-menu.tsx +269 -0
- package/src/presentation/ui/empty.tsx +104 -0
- package/src/presentation/ui/field.tsx +238 -0
- package/src/presentation/ui/granularity-selector.tsx +58 -0
- package/src/presentation/ui/hover-card.tsx +44 -0
- package/src/presentation/ui/index.tsx +74 -0
- package/src/presentation/ui/input-group.tsx +156 -0
- package/src/presentation/ui/input-otp.tsx +87 -0
- package/src/presentation/ui/input.tsx +19 -0
- package/src/presentation/ui/item.tsx +196 -0
- package/src/presentation/ui/kpi-card.tsx +203 -0
- package/src/presentation/ui/label-selector.tsx +100 -0
- package/src/presentation/ui/label.tsx +24 -0
- package/src/presentation/ui/legacy-birth-date-input.tsx +43 -0
- package/src/presentation/ui/legacy-color-picker/index.tsx +42 -0
- package/src/presentation/ui/legacy-color-picker/legacy-color-picker.content.tsx +37 -0
- package/src/presentation/ui/legacy-color-picker/legacy-color-picker.context.tsx +42 -0
- package/src/presentation/ui/legacy-color-picker/legacy-color-picker.hex-input.tsx +97 -0
- package/src/presentation/ui/legacy-color-picker/legacy-color-picker.picker.tsx +266 -0
- package/src/presentation/ui/legacy-color-picker/legacy-color-picker.presets.ts +22 -0
- package/src/presentation/ui/legacy-color-picker/legacy-color-picker.quick-list.tsx +49 -0
- package/src/presentation/ui/legacy-color-picker/legacy-color-picker.recent.tsx +43 -0
- package/src/presentation/ui/legacy-color-picker/legacy-color-picker.swatches.tsx +111 -0
- package/src/presentation/ui/legacy-color-picker/legacy-color-picker.trigger.tsx +128 -0
- package/src/presentation/ui/legacy-color-picker/legacy-color-picker.tsx +110 -0
- package/src/presentation/ui/legacy-color-picker/legacy-color-picker.types.ts +1 -0
- package/src/presentation/ui/legacy-color-picker/legacy-color-picker.utils.ts +114 -0
- package/src/presentation/ui/legacy-country-input.tsx +171 -0
- package/src/presentation/ui/legacy-gender-input.tsx +22 -0
- package/src/presentation/ui/legacy-input.tsx +106 -0
- package/src/presentation/ui/legacy-phone-input.tsx +213 -0
- package/src/presentation/ui/legacy-select-input.tsx +114 -0
- package/src/presentation/ui/legacy-text-area.tsx +111 -0
- package/src/presentation/ui/list-items.tsx +59 -0
- package/src/presentation/ui/marker.tsx +69 -0
- package/src/presentation/ui/media-player/index.tsx +12 -0
- package/src/presentation/ui/media-player/media-player.context.tsx +36 -0
- package/src/presentation/ui/media-player/media-player.tsx +167 -0
- package/src/presentation/ui/media-player/player-controls.tsx +207 -0
- package/src/presentation/ui/media-player/players/audio-player.tsx +78 -0
- package/src/presentation/ui/media-player/players/index.tsx +4 -0
- package/src/presentation/ui/media-player/players/video-player.tsx +193 -0
- package/src/presentation/ui/media-player/players/voice-note-player.tsx +108 -0
- package/src/presentation/ui/media-player/use-media-player.hook.ts +263 -0
- package/src/presentation/ui/menubar.tsx +281 -0
- package/src/presentation/ui/message.tsx +92 -0
- package/src/presentation/ui/navigation-menu.tsx +172 -0
- package/src/presentation/ui/pagination.tsx +129 -0
- package/src/presentation/ui/popover.tsx +89 -0
- package/src/presentation/ui/progress.tsx +31 -0
- package/src/presentation/ui/resizable.tsx +50 -0
- package/src/presentation/ui/select.tsx +192 -0
- package/src/presentation/ui/separator.tsx +28 -0
- package/src/presentation/ui/sheet.tsx +147 -0
- package/src/presentation/ui/sidebar.tsx +702 -0
- package/src/presentation/ui/skeleton.tsx +13 -0
- package/src/presentation/ui/slide-to-confirm.tsx +129 -0
- package/src/presentation/ui/slider.tsx +59 -0
- package/src/presentation/ui/sonner.tsx +58 -0
- package/src/presentation/ui/switch.tsx +33 -0
- package/src/presentation/ui/table.tsx +116 -0
- package/src/presentation/ui/tabs.tsx +90 -0
- package/src/presentation/ui/text-gradient.tsx +84 -0
- package/src/presentation/ui/textarea.tsx +18 -0
- package/src/presentation/ui/timeline-steps.tsx +235 -0
- package/src/presentation/ui/toggle-group.tsx +89 -0
- package/src/presentation/ui/toggle.tsx +47 -0
- package/src/presentation/ui/tooltip.tsx +57 -0
- package/src/presentation/uploading/index.tsx +3 -0
- package/src/presentation/uploading/media-upload-field.tsx +152 -0
- package/src/presentation/uploading/storage-media.tsx +210 -0
- package/src/styles.css +403 -0
- package/src/themes.css +1038 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import {useRouter} from "next/navigation"
|
|
4
|
+
import {Button} from "../ui/button"
|
|
5
|
+
import {Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle} from "../ui/empty"
|
|
6
|
+
import {DynamicIcon} from "./dynamic-icon"
|
|
7
|
+
import {ModuleDeclarationInterface} from "../../domain/entities/module.interface"
|
|
8
|
+
import {TriangleAlert} from "lucide-react";
|
|
9
|
+
|
|
10
|
+
interface ModulesGuardForbiddenProps {
|
|
11
|
+
module?: ModuleDeclarationInterface | null
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function ModulesGuardForbiddenView({module}: ModulesGuardForbiddenProps) {
|
|
15
|
+
const router = useRouter()
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div className="flex min-h-svh items-center justify-center p-4">
|
|
19
|
+
<Empty className="max-w-md">
|
|
20
|
+
<EmptyMedia variant="icon" className="size-24 rounded-full aspect-square">
|
|
21
|
+
<DynamicIcon strokeWidth={1} name="ShieldAlertIcon" className="size-18 text-primary"/>
|
|
22
|
+
</EmptyMedia>
|
|
23
|
+
<EmptyHeader>
|
|
24
|
+
<EmptyTitle>Accès refusé</EmptyTitle>
|
|
25
|
+
</EmptyHeader>
|
|
26
|
+
<EmptyDescription>
|
|
27
|
+
{module
|
|
28
|
+
? <>Vous n'avez pas la permission d'accéder au module « {module.name} ».</>
|
|
29
|
+
: <>Vous n'avez pas la permission d'accéder à ce module.</>}
|
|
30
|
+
</EmptyDescription>
|
|
31
|
+
<EmptyDescription className={"p-3"}>
|
|
32
|
+
Si vous avez le droit d'accès, cela peut être du problème de votre connexion internet ou
|
|
33
|
+
renseignez-vous auprès de votre administrateur.
|
|
34
|
+
</EmptyDescription>
|
|
35
|
+
<div className="flex gap-4">
|
|
36
|
+
<Button variant="outline" size="lg" onClick={() => history.go(0)}>
|
|
37
|
+
Actualiser
|
|
38
|
+
</Button>
|
|
39
|
+
<Button size="lg" onClick={() => router.push('/dashboard')}>
|
|
40
|
+
Retour au tableau de bord
|
|
41
|
+
</Button>
|
|
42
|
+
</div>
|
|
43
|
+
</Empty>
|
|
44
|
+
</div>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import {useState} from "react";
|
|
4
|
+
import {useQueryClient} from "@tanstack/react-query";
|
|
5
|
+
import {format} from "date-fns";
|
|
6
|
+
import {fr} from "date-fns/locale";
|
|
7
|
+
import {AlertTriangleIcon, BellRingIcon, CheckIcon, CircleCheckIcon, InfoIcon, MailOpenIcon, ShieldAlertIcon, TimerIcon} from "lucide-react";
|
|
8
|
+
import {toast} from "sonner";
|
|
9
|
+
import {Badge} from "../ui/badge";
|
|
10
|
+
import {Button} from "../ui/button";
|
|
11
|
+
import {Separator} from "../ui/separator";
|
|
12
|
+
import {NotificationsApiService} from "../../application/service/notifications-api-service";
|
|
13
|
+
import {NotificationInterface, NotificationTypeEnum} from "../../domain/entities/notification.interface";
|
|
14
|
+
|
|
15
|
+
export interface NotificationDetailsModalProps {
|
|
16
|
+
notification: NotificationInterface;
|
|
17
|
+
close?: () => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const notificationTypeIcon = {
|
|
21
|
+
[NotificationTypeEnum.ALERT]: AlertTriangleIcon,
|
|
22
|
+
[NotificationTypeEnum.INFORMATION]: InfoIcon,
|
|
23
|
+
[NotificationTypeEnum.CONFIRMATION]: CircleCheckIcon,
|
|
24
|
+
[NotificationTypeEnum.REMINDER]: TimerIcon,
|
|
25
|
+
[NotificationTypeEnum.SECURITY]: ShieldAlertIcon,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const notificationTypeLabel = {
|
|
29
|
+
[NotificationTypeEnum.ALERT]: "Alerte",
|
|
30
|
+
[NotificationTypeEnum.INFORMATION]: "Information",
|
|
31
|
+
[NotificationTypeEnum.CONFIRMATION]: "Confirmation",
|
|
32
|
+
[NotificationTypeEnum.REMINDER]: "Rappel",
|
|
33
|
+
[NotificationTypeEnum.SECURITY]: "Sécurité",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const notificationTypeVariant = {
|
|
37
|
+
[NotificationTypeEnum.ALERT]: "destructive" as const,
|
|
38
|
+
[NotificationTypeEnum.INFORMATION]: "secondary" as const,
|
|
39
|
+
[NotificationTypeEnum.CONFIRMATION]: "default" as const,
|
|
40
|
+
[NotificationTypeEnum.REMINDER]: "outline" as const,
|
|
41
|
+
[NotificationTypeEnum.SECURITY]: "destructive" as const,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
function formatDateTime(date?: string | null): string {
|
|
45
|
+
if (!date) return "Non renseigné";
|
|
46
|
+
try {
|
|
47
|
+
return format(new Date(date), "d MMMM yyyy 'à' HH:mm", {locale: fr});
|
|
48
|
+
} catch {
|
|
49
|
+
return "Date invalide";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function NotificationDetailsModal({notification, close}: NotificationDetailsModalProps) {
|
|
54
|
+
const queryClient = useQueryClient();
|
|
55
|
+
const [isRead, setIsRead] = useState(!!notification.readAt);
|
|
56
|
+
|
|
57
|
+
const title = notification.title ?? notification.subject ?? "Notification";
|
|
58
|
+
const body = notification.body ?? notification.message ?? notification.content ?? "Aucun contenu";
|
|
59
|
+
const TypeIcon = notificationTypeIcon[(notification.type as NotificationTypeEnum) ?? NotificationTypeEnum.INFORMATION] ?? BellRingIcon;
|
|
60
|
+
|
|
61
|
+
const markAsRead = async () => {
|
|
62
|
+
try {
|
|
63
|
+
await NotificationsApiService.markAsRead(notification.id);
|
|
64
|
+
setIsRead(true);
|
|
65
|
+
await queryClient.invalidateQueries({queryKey: ['notifications']});
|
|
66
|
+
} catch (error: any) {
|
|
67
|
+
toast.error("Impossible de marquer la notification comme lue");
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div className="flex flex-col">
|
|
73
|
+
<div className="flex items-start gap-3 p-4 pb-3">
|
|
74
|
+
<div className="flex size-11 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
|
75
|
+
<TypeIcon className="size-5.5"/>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div className="min-w-0 flex-1">
|
|
79
|
+
<h3 className="text-lg font-semibold leading-snug break-words">
|
|
80
|
+
{title}
|
|
81
|
+
</h3>
|
|
82
|
+
<div className="mt-1.5 flex flex-wrap items-center gap-2">
|
|
83
|
+
{notification.type && (
|
|
84
|
+
<Badge variant={notificationTypeVariant[(notification.type as NotificationTypeEnum) ?? NotificationTypeEnum.INFORMATION]}>
|
|
85
|
+
{notificationTypeLabel[(notification.type as NotificationTypeEnum) ?? NotificationTypeEnum.INFORMATION]}
|
|
86
|
+
</Badge>
|
|
87
|
+
)}
|
|
88
|
+
{isRead ? (
|
|
89
|
+
<Badge variant="secondary" className="gap-1">
|
|
90
|
+
<MailOpenIcon/>
|
|
91
|
+
Lue
|
|
92
|
+
</Badge>
|
|
93
|
+
) : (
|
|
94
|
+
<Badge variant="outline" className="gap-1">
|
|
95
|
+
Nouvelle
|
|
96
|
+
</Badge>
|
|
97
|
+
)}
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<Separator/>
|
|
103
|
+
|
|
104
|
+
<div className="p-4">
|
|
105
|
+
<p className="text-sm leading-relaxed whitespace-pre-wrap break-words text-foreground/90">
|
|
106
|
+
{body}
|
|
107
|
+
</p>
|
|
108
|
+
|
|
109
|
+
<div className="mt-5 grid grid-cols-1 gap-3 sm:grid-cols-2">
|
|
110
|
+
<DetailField label="Reçue le" value={formatDateTime(notification.createdAt)}/>
|
|
111
|
+
<DetailField label="Lue le" value={formatDateTime(notification.readAt ?? (isRead ? notification.updatedAt : null))}/>
|
|
112
|
+
<DetailField label="Organisation" value={notification.organizationId ?? "—"}/>
|
|
113
|
+
<DetailField label="ID" value={notification.id}/>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<Separator/>
|
|
118
|
+
|
|
119
|
+
<div className="flex items-center justify-end gap-2 p-3">
|
|
120
|
+
{!isRead && (
|
|
121
|
+
<Button variant="outline" className="gap-1.5" onClick={markAsRead}>
|
|
122
|
+
<CheckIcon/>
|
|
123
|
+
Marquer comme lue
|
|
124
|
+
</Button>
|
|
125
|
+
)}
|
|
126
|
+
<Button variant="default" onClick={close}>
|
|
127
|
+
Fermer
|
|
128
|
+
</Button>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function DetailField({label, value}: { label: string; value: string }) {
|
|
135
|
+
return (
|
|
136
|
+
<div className="flex flex-col gap-0.5 rounded-lg bg-muted/40 px-3 py-2">
|
|
137
|
+
<span className="text-[10px] font-medium text-muted-foreground uppercase tracking-tight">
|
|
138
|
+
{label}
|
|
139
|
+
</span>
|
|
140
|
+
<span className="text-sm font-medium break-words">
|
|
141
|
+
{value}
|
|
142
|
+
</span>
|
|
143
|
+
</div>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import {formatDistanceToNow} from "date-fns";
|
|
4
|
+
import {fr} from "date-fns/locale";
|
|
5
|
+
import {AlertTriangleIcon, BellRingIcon, CheckIcon, CircleCheckIcon, InfoIcon, MailOpenIcon, ShieldAlertIcon, TimerIcon} from "lucide-react";
|
|
6
|
+
import {Badge} from "../ui/badge";
|
|
7
|
+
import {Button} from "../ui/button";
|
|
8
|
+
import {useModal} from "../modals/hooks/useModal";
|
|
9
|
+
import {cn} from "../../infrastructure/utilities/utils";
|
|
10
|
+
import {NotificationDetailsModal, NotificationDetailsModalProps} from "./notification-details.modal";
|
|
11
|
+
import {NotificationInterface, NotificationTypeEnum} from "../../domain/entities/notification.interface";
|
|
12
|
+
|
|
13
|
+
export interface NotificationItemProps {
|
|
14
|
+
notification: NotificationInterface;
|
|
15
|
+
onRead?: (notification: NotificationInterface) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const notificationTypeIcon = {
|
|
19
|
+
[NotificationTypeEnum.ALERT]: AlertTriangleIcon,
|
|
20
|
+
[NotificationTypeEnum.INFORMATION]: InfoIcon,
|
|
21
|
+
[NotificationTypeEnum.CONFIRMATION]: CircleCheckIcon,
|
|
22
|
+
[NotificationTypeEnum.REMINDER]: TimerIcon,
|
|
23
|
+
[NotificationTypeEnum.SECURITY]: ShieldAlertIcon,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const notificationTypeLabel = {
|
|
27
|
+
[NotificationTypeEnum.ALERT]: "Alerte",
|
|
28
|
+
[NotificationTypeEnum.INFORMATION]: "Information",
|
|
29
|
+
[NotificationTypeEnum.CONFIRMATION]: "Confirmation",
|
|
30
|
+
[NotificationTypeEnum.REMINDER]: "Rappel",
|
|
31
|
+
[NotificationTypeEnum.SECURITY]: "Sécurité",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export function NotificationItem({notification, onRead}: NotificationItemProps) {
|
|
35
|
+
const {open, close} = useModal();
|
|
36
|
+
const title = notification.title ?? notification.subject ?? "Notification";
|
|
37
|
+
const body = notification.body ?? notification.message ?? notification.content;
|
|
38
|
+
const isRead = notification.readAt ?? false;
|
|
39
|
+
const TypeIcon = notificationTypeIcon[(notification.type as NotificationTypeEnum) ?? NotificationTypeEnum.INFORMATION] ?? BellRingIcon;
|
|
40
|
+
|
|
41
|
+
const createdAt = notification.createdAt ? formatDistanceToNow(new Date(notification.createdAt), {addSuffix: true, locale: fr}) : undefined;
|
|
42
|
+
|
|
43
|
+
const openDetails = () => {
|
|
44
|
+
const modalId = open(
|
|
45
|
+
(props: NotificationDetailsModalProps) => (
|
|
46
|
+
<NotificationDetailsModal
|
|
47
|
+
notification={props.notification}
|
|
48
|
+
close={() => close(modalId)}
|
|
49
|
+
/>
|
|
50
|
+
),
|
|
51
|
+
{notification, close: () => close(modalId)},
|
|
52
|
+
{
|
|
53
|
+
size: "MD",
|
|
54
|
+
scrollable: true,
|
|
55
|
+
className: "rounded-lg",
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div
|
|
62
|
+
onClick={openDetails}
|
|
63
|
+
className={cn(
|
|
64
|
+
"group/notification flex flex-row gap-3 rounded-lg border border-transparent p-2.5 transition-colors cursor-pointer",
|
|
65
|
+
"hover:bg-muted/60 hover:border-foreground/10",
|
|
66
|
+
!isRead && "bg-primary/5 border-primary/10"
|
|
67
|
+
)}
|
|
68
|
+
>
|
|
69
|
+
<div className="flex size-9 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground">
|
|
70
|
+
<TypeIcon className="size-4.5"/>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<div className="min-w-0 flex-1">
|
|
74
|
+
<div className="flex items-start justify-between gap-2">
|
|
75
|
+
<p className={cn(
|
|
76
|
+
"truncate text-sm font-medium",
|
|
77
|
+
!isRead && "text-foreground"
|
|
78
|
+
)}>
|
|
79
|
+
{title}
|
|
80
|
+
</p>
|
|
81
|
+
{!isRead && (
|
|
82
|
+
<Badge className="shrink-0" variant="default">
|
|
83
|
+
Nouveau
|
|
84
|
+
</Badge>
|
|
85
|
+
)}
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
{body && (
|
|
89
|
+
<p className="mt-0.5 line-clamp-2 text-xs text-muted-foreground">
|
|
90
|
+
{body}
|
|
91
|
+
</p>
|
|
92
|
+
)}
|
|
93
|
+
|
|
94
|
+
<div className="mt-1.5 flex flex-row items-center gap-2">
|
|
95
|
+
{notification.type && (
|
|
96
|
+
<Badge variant="outline" className="h-4 text-[10px]">
|
|
97
|
+
{notificationTypeLabel[notification.type as NotificationTypeEnum] ?? notification.type}
|
|
98
|
+
</Badge>
|
|
99
|
+
)}
|
|
100
|
+
{createdAt && (
|
|
101
|
+
<span className="text-[11px] text-muted-foreground">
|
|
102
|
+
{createdAt}
|
|
103
|
+
</span>
|
|
104
|
+
)}
|
|
105
|
+
{!isRead && onRead && (
|
|
106
|
+
<Button
|
|
107
|
+
size="sm"
|
|
108
|
+
variant="ghost"
|
|
109
|
+
className="ml-auto h-6 gap-1 px-1.5 text-[11px] text-muted-foreground"
|
|
110
|
+
onClick={(event) => {
|
|
111
|
+
event.stopPropagation();
|
|
112
|
+
onRead(notification);
|
|
113
|
+
}}
|
|
114
|
+
>
|
|
115
|
+
<CheckIcon/>
|
|
116
|
+
Marquer lu
|
|
117
|
+
</Button>
|
|
118
|
+
)}
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
{isRead && (
|
|
123
|
+
<MailOpenIcon className="size-4 shrink-0 self-center text-muted-foreground/40"/>
|
|
124
|
+
)}
|
|
125
|
+
</div>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import {useQueryClient} from "@tanstack/react-query";
|
|
4
|
+
import {BellIcon, InboxIcon} from "lucide-react";
|
|
5
|
+
import Link from "next/link";
|
|
6
|
+
import {toast} from "sonner";
|
|
7
|
+
import {Button} from "../ui/button";
|
|
8
|
+
import {Popover, PopoverContent, PopoverHeader, PopoverTitle, PopoverTrigger} from "../ui/popover";
|
|
9
|
+
import {Tabs, TabsContent, TabsList, TabsTrigger} from "../ui/tabs";
|
|
10
|
+
import {Skeleton} from "../ui/skeleton";
|
|
11
|
+
import {Badge} from "../ui/badge";
|
|
12
|
+
import {NotificationItem} from "./notification-item";
|
|
13
|
+
import {useNotifications} from "../../infrastructure/hooks/use-notifications";
|
|
14
|
+
import {NotificationsApiService} from "../../application/service/notifications-api-service";
|
|
15
|
+
import {NotificationInterface} from "../../domain/entities/notification.interface";
|
|
16
|
+
|
|
17
|
+
function NotificationsList({category}: { category: "user" | "organization" }) {
|
|
18
|
+
const {data, isLoading, isError, refetch} = useNotifications(category, {
|
|
19
|
+
limit: 5,
|
|
20
|
+
manualPagination: false,
|
|
21
|
+
read: false
|
|
22
|
+
});
|
|
23
|
+
const queryClient = useQueryClient();
|
|
24
|
+
|
|
25
|
+
const markAsRead = async (notification: NotificationInterface) => {
|
|
26
|
+
try {
|
|
27
|
+
await NotificationsApiService.markAsRead(notification.id);
|
|
28
|
+
await queryClient.invalidateQueries({queryKey: ['notifications']});
|
|
29
|
+
} catch (error: any) {
|
|
30
|
+
toast.error("Impossible de marquer la notification comme lue");
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
if (isLoading) {
|
|
35
|
+
return (
|
|
36
|
+
<div className="flex flex-col gap-2">
|
|
37
|
+
<Skeleton className="h-12 w-full"/>
|
|
38
|
+
<Skeleton className="h-12 w-full"/>
|
|
39
|
+
<Skeleton className="h-12 w-full"/>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (isError || data.length === 0) {
|
|
45
|
+
return (
|
|
46
|
+
<div className="flex flex-col items-center gap-2 py-6 text-center text-muted-foreground">
|
|
47
|
+
<InboxIcon className="size-8 opacity-40"/>
|
|
48
|
+
<p className="text-xs">Aucune notification</p>
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div className="flex flex-col gap-1">
|
|
55
|
+
{data.slice(0, 5).map((notification) => (
|
|
56
|
+
<NotificationItem
|
|
57
|
+
key={notification.id}
|
|
58
|
+
notification={notification}
|
|
59
|
+
onRead={markAsRead}
|
|
60
|
+
/>
|
|
61
|
+
))}
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function NotificationsPopover() {
|
|
67
|
+
const {data: userNotifications} = useNotifications("user", {
|
|
68
|
+
limit: 10,
|
|
69
|
+
manualPagination: false,
|
|
70
|
+
read: false
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const unreadCount = userNotifications.filter((notification) => !notification.readAt).length;
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<Popover>
|
|
77
|
+
<PopoverTrigger asChild>
|
|
78
|
+
<Button variant="ghost" className="relative w-8 h-8" aria-label="Notifications">
|
|
79
|
+
<BellIcon/>
|
|
80
|
+
{unreadCount > 0 && (
|
|
81
|
+
<Badge className="absolute -top-0.5 -right-0.5 h-4 min-w-4 px-1 text-[10px]">
|
|
82
|
+
{unreadCount > 9 ? "9+" : unreadCount}
|
|
83
|
+
</Badge>
|
|
84
|
+
)}
|
|
85
|
+
</Button>
|
|
86
|
+
</PopoverTrigger>
|
|
87
|
+
<PopoverContent
|
|
88
|
+
className="w-96 p-0"
|
|
89
|
+
side={"bottom"} align={"end"}
|
|
90
|
+
sideOffset={8}
|
|
91
|
+
>
|
|
92
|
+
<PopoverHeader className="px-4 pt-3.5 pb-1">
|
|
93
|
+
<PopoverTitle>Notifications</PopoverTitle>
|
|
94
|
+
</PopoverHeader>
|
|
95
|
+
|
|
96
|
+
<Tabs defaultValue="user">
|
|
97
|
+
<div className="px-3">
|
|
98
|
+
<TabsList className="w-full">
|
|
99
|
+
<TabsTrigger value="user" className="flex-1">
|
|
100
|
+
Utilisateur
|
|
101
|
+
</TabsTrigger>
|
|
102
|
+
<TabsTrigger value="organization" className="flex-1">
|
|
103
|
+
Organisation
|
|
104
|
+
</TabsTrigger>
|
|
105
|
+
</TabsList>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<div className="max-h-80 overflow-y-auto p-2">
|
|
109
|
+
<TabsContent value="user">
|
|
110
|
+
<NotificationsList category="user"/>
|
|
111
|
+
</TabsContent>
|
|
112
|
+
<TabsContent value="organization">
|
|
113
|
+
<NotificationsList category="organization"/>
|
|
114
|
+
</TabsContent>
|
|
115
|
+
</div>
|
|
116
|
+
</Tabs>
|
|
117
|
+
|
|
118
|
+
<div className="border-t p-2">
|
|
119
|
+
<Button asChild variant="ghost" className="w-full justify-center gap-1 text-xs">
|
|
120
|
+
<Link href="/notifications">
|
|
121
|
+
Voir toutes les notifications
|
|
122
|
+
</Link>
|
|
123
|
+
</Button>
|
|
124
|
+
</div>
|
|
125
|
+
</PopoverContent>
|
|
126
|
+
</Popover>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import {useEffect} from "react";
|
|
4
|
+
import {toast} from "sonner";
|
|
5
|
+
import {useAuth} from "../../infrastructure/hooks/use-auth";
|
|
6
|
+
import {NotificationsPushService} from "../../application/service/notifications-push.service";
|
|
7
|
+
import {UserPreferencesApiService} from "../../application/service/user-preferences-api.service";
|
|
8
|
+
|
|
9
|
+
export function PushNotificationsProvider() {
|
|
10
|
+
const {user, currentOrganization} = useAuth();
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (!user) {
|
|
14
|
+
NotificationsPushService.stopActivityTracking();
|
|
15
|
+
NotificationsPushService.stopPolling();
|
|
16
|
+
NotificationsPushService.unsubscribe().catch(() => undefined);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Les notifications sont rattachées à une organisation : on attend la
|
|
21
|
+
// sélection de l'organisation avant d'initialiser le canal push, sinon
|
|
22
|
+
// les appels (préférences, liste des notifications) partent sans
|
|
23
|
+
// contexte d'organisation (x-organization-id) et sont rejetés en 400.
|
|
24
|
+
if (!currentOrganization) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const offInApp = NotificationsPushService.onInAppNotification((payload) => {
|
|
29
|
+
toast(payload.title || "Nouvelle notification", {
|
|
30
|
+
description: payload.body || "",
|
|
31
|
+
richColors: true,
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
NotificationsPushService.register()
|
|
36
|
+
.then(async () => {
|
|
37
|
+
NotificationsPushService.startActivityTracking();
|
|
38
|
+
|
|
39
|
+
const pushEnabled = (await UserPreferencesApiService
|
|
40
|
+
.getValue(user.id!, NotificationsPushService.PUSH_PREFERENCE_LABEL)
|
|
41
|
+
.catch(() => undefined)) !== "false";
|
|
42
|
+
|
|
43
|
+
if (!pushEnabled) return;
|
|
44
|
+
|
|
45
|
+
if (NotificationsPushService.isSupported) {
|
|
46
|
+
if (Notification.permission !== "granted") return;
|
|
47
|
+
const subscription = await NotificationsPushService.getSubscription();
|
|
48
|
+
if (!subscription) await NotificationsPushService.subscribe();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Web Push indisponible (ex: Tauri) : canal REST de secours
|
|
53
|
+
await NotificationsPushService.startPolling();
|
|
54
|
+
})
|
|
55
|
+
.catch((error) => console.error("Push notifications initialization failed", error));
|
|
56
|
+
|
|
57
|
+
return () => {
|
|
58
|
+
offInApp();
|
|
59
|
+
NotificationsPushService.stopActivityTracking();
|
|
60
|
+
NotificationsPushService.stopPolling();
|
|
61
|
+
};
|
|
62
|
+
}, [user, currentOrganization]);
|
|
63
|
+
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { CheckCircle2, CloudUpload, XCircle } from "lucide-react"
|
|
5
|
+
import {
|
|
6
|
+
Dialog,
|
|
7
|
+
DialogContent,
|
|
8
|
+
DialogDescription,
|
|
9
|
+
DialogFooter,
|
|
10
|
+
DialogHeader,
|
|
11
|
+
DialogTitle,
|
|
12
|
+
} from "../ui/dialog"
|
|
13
|
+
import { Button } from "../ui/button"
|
|
14
|
+
import { Activity } from "./activity"
|
|
15
|
+
import { Progress } from "../ui/progress"
|
|
16
|
+
import { useUploadStore, type UploadItem } from "../../infrastructure/stores/upload.store"
|
|
17
|
+
import { formatEta, formatFileSize } from "../../infrastructure/utilities/format.util"
|
|
18
|
+
|
|
19
|
+
interface UploadProgressDialogProps {
|
|
20
|
+
open: boolean
|
|
21
|
+
onOpenChange: (open: boolean) => void
|
|
22
|
+
onAllComplete?: () => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function UploadProgressRow({ upload }: { upload: UploadItem }) {
|
|
26
|
+
const isUploading = upload.status === "uploading"
|
|
27
|
+
const isDone = upload.status === "done"
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div className="flex flex-col gap-1.5 rounded-lg border border-border/60 bg-muted/30 p-2.5">
|
|
31
|
+
<div className="flex items-center gap-2 min-w-0">
|
|
32
|
+
{isDone ? (
|
|
33
|
+
<CheckCircle2 className="size-4 shrink-0 text-emerald-500"/>
|
|
34
|
+
) : upload.status === "error" ? (
|
|
35
|
+
<XCircle className="size-4 shrink-0 text-destructive"/>
|
|
36
|
+
) : (
|
|
37
|
+
<Activity.Loader size={16}/>
|
|
38
|
+
)}
|
|
39
|
+
<span className="min-w-0 flex-1 truncate text-sm font-medium">{upload.name}</span>
|
|
40
|
+
<span className="shrink-0 text-xs tabular-nums text-muted-foreground">
|
|
41
|
+
{formatFileSize(upload.size)}
|
|
42
|
+
</span>
|
|
43
|
+
</div>
|
|
44
|
+
{isUploading && (
|
|
45
|
+
<div className="flex items-center gap-2">
|
|
46
|
+
<Progress value={upload.progress} className="h-1.5 flex-1"/>
|
|
47
|
+
<span className="w-11 text-right text-xs tabular-nums text-muted-foreground">
|
|
48
|
+
{Math.round(upload.progress)}%
|
|
49
|
+
</span>
|
|
50
|
+
</div>
|
|
51
|
+
)}
|
|
52
|
+
{isUploading && (
|
|
53
|
+
<span className="text-xs text-muted-foreground">
|
|
54
|
+
{upload.eta ? formatEta(upload.eta) : "Estimation du temps restant…"}
|
|
55
|
+
</span>
|
|
56
|
+
)}
|
|
57
|
+
{upload.status === "error" && (
|
|
58
|
+
<span className="text-xs text-destructive line-clamp-1">{upload.error}</span>
|
|
59
|
+
)}
|
|
60
|
+
</div>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function UploadProgressDialog({ open, onOpenChange, onAllComplete }: UploadProgressDialogProps) {
|
|
65
|
+
const uploads = useUploadStore((state) => state.uploads)
|
|
66
|
+
const hasActiveUploads = useUploadStore((state) => state.hasActiveUploads)
|
|
67
|
+
const didAutoComplete = React.useRef(false)
|
|
68
|
+
|
|
69
|
+
const activeUploads = uploads.filter((u) => u.status === "uploading" || u.status === "idle")
|
|
70
|
+
const failedCount = uploads.filter((u) => u.status === "error").length
|
|
71
|
+
|
|
72
|
+
React.useEffect(() => {
|
|
73
|
+
if (!open) {
|
|
74
|
+
didAutoComplete.current = false
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
if (open && !hasActiveUploads && failedCount === 0 && !didAutoComplete.current) {
|
|
78
|
+
didAutoComplete.current = true
|
|
79
|
+
onAllComplete?.()
|
|
80
|
+
}
|
|
81
|
+
}, [open, hasActiveUploads, failedCount, onAllComplete])
|
|
82
|
+
|
|
83
|
+
const handleComplete = () => {
|
|
84
|
+
didAutoComplete.current = true
|
|
85
|
+
onOpenChange(false)
|
|
86
|
+
onAllComplete?.()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
91
|
+
<DialogContent className="sm:max-w-md" showCloseButton={false}>
|
|
92
|
+
<DialogHeader>
|
|
93
|
+
<DialogTitle className="flex items-center gap-2">
|
|
94
|
+
<CloudUpload className="size-5 text-primary"/>
|
|
95
|
+
Téléversement en cours
|
|
96
|
+
</DialogTitle>
|
|
97
|
+
<DialogDescription>
|
|
98
|
+
{hasActiveUploads
|
|
99
|
+
? "Certains fichiers sont encore en cours de téléversement. Veuillez patienter."
|
|
100
|
+
: failedCount > 0
|
|
101
|
+
? "Certains fichiers n'ont pas pu être téléversés."
|
|
102
|
+
: "Tous les fichiers ont été téléversés avec succès."}
|
|
103
|
+
</DialogDescription>
|
|
104
|
+
</DialogHeader>
|
|
105
|
+
|
|
106
|
+
<div className="flex max-h-64 flex-col gap-2 overflow-y-auto">
|
|
107
|
+
{activeUploads.length === 0 && failedCount === 0 && (
|
|
108
|
+
<p className="text-sm text-muted-foreground">
|
|
109
|
+
Aucun téléversement en attente.
|
|
110
|
+
</p>
|
|
111
|
+
)}
|
|
112
|
+
{activeUploads.map((upload) => (
|
|
113
|
+
<UploadProgressRow key={upload.id} upload={upload}/>
|
|
114
|
+
))}
|
|
115
|
+
{uploads
|
|
116
|
+
.filter((u) => u.status === "error")
|
|
117
|
+
.map((upload) => (
|
|
118
|
+
<UploadProgressRow key={upload.id} upload={upload}/>
|
|
119
|
+
))}
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<DialogFooter>
|
|
123
|
+
<Button
|
|
124
|
+
variant="ghost"
|
|
125
|
+
onClick={() => {
|
|
126
|
+
didAutoComplete.current = true
|
|
127
|
+
onOpenChange(false)
|
|
128
|
+
}}
|
|
129
|
+
disabled={hasActiveUploads}
|
|
130
|
+
>
|
|
131
|
+
Fermer
|
|
132
|
+
</Button>
|
|
133
|
+
<Button onClick={handleComplete} disabled={hasActiveUploads || failedCount > 0}>
|
|
134
|
+
Terminer
|
|
135
|
+
</Button>
|
|
136
|
+
</DialogFooter>
|
|
137
|
+
</DialogContent>
|
|
138
|
+
</Dialog>
|
|
139
|
+
)
|
|
140
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function WaitingBar() {
|
|
2
|
+
return (
|
|
3
|
+
<div className="w-full h-[3px] relative rounded-xl overflow-hidden bg-black/20">
|
|
4
|
+
<div
|
|
5
|
+
className="absolute top-0 -left-[100%] w-full h-full bg-primary rounded-xl animate-progress-infinite"
|
|
6
|
+
style={{ animationDelay: "0ms" }}
|
|
7
|
+
/>
|
|
8
|
+
<div
|
|
9
|
+
className="absolute top-0 -left-[100%] w-full h-full bg-(--chart-2) rounded-xl animate-progress-infinite"
|
|
10
|
+
style={{ animationDelay: "750ms" }}
|
|
11
|
+
/>
|
|
12
|
+
<div
|
|
13
|
+
className="absolute top-0 -left-[100%] w-full h-full bg-(--chart-3) rounded-xl animate-progress-infinite"
|
|
14
|
+
style={{ animationDelay: "100ms" }}
|
|
15
|
+
/>
|
|
16
|
+
<div
|
|
17
|
+
className="absolute top-0 -left-[100%] w-full h-full bg-(--chart-4) rounded-xl animate-progress-infinite"
|
|
18
|
+
style={{ animationDelay: "300ms" }}
|
|
19
|
+
/>
|
|
20
|
+
</div>
|
|
21
|
+
)
|
|
22
|
+
}
|