@greatapps/common 1.1.33 → 1.1.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/components/account/AccountModals.mjs +18 -0
  2. package/dist/components/account/AccountModals.mjs.map +1 -0
  3. package/dist/components/account/ConfigurationsMyAccountModal.mjs +26 -15
  4. package/dist/components/account/ConfigurationsMyAccountModal.mjs.map +1 -1
  5. package/dist/components/account/ConfirmDeleteAccountModal.mjs +45 -0
  6. package/dist/components/account/ConfirmDeleteAccountModal.mjs.map +1 -0
  7. package/dist/components/account/DeleteAccountModal.mjs +123 -0
  8. package/dist/components/account/DeleteAccountModal.mjs.map +1 -0
  9. package/dist/components/account/IsntPossibleDeleteAccountModal.mjs +42 -0
  10. package/dist/components/account/IsntPossibleDeleteAccountModal.mjs.map +1 -0
  11. package/dist/components/layouts/AppMobileNavBar.mjs +55 -0
  12. package/dist/components/layouts/AppMobileNavBar.mjs.map +1 -0
  13. package/dist/components/layouts/AppNavBar.mjs +77 -0
  14. package/dist/components/layouts/AppNavBar.mjs.map +1 -0
  15. package/dist/components/modals/ModalManager.mjs +19 -0
  16. package/dist/components/modals/ModalManager.mjs.map +1 -0
  17. package/dist/components/modals/Modals.mjs +20 -0
  18. package/dist/components/modals/Modals.mjs.map +1 -0
  19. package/dist/index.mjs +14 -2
  20. package/dist/index.mjs.map +1 -1
  21. package/dist/store/useAccountModals.mjs +18 -0
  22. package/dist/store/useAccountModals.mjs.map +1 -0
  23. package/dist/store/useModalManager.mjs +75 -0
  24. package/dist/store/useModalManager.mjs.map +1 -0
  25. package/package.json +1 -1
  26. package/src/components/account/AccountModals.tsx +17 -0
  27. package/src/components/account/ConfigurationsMyAccountModal.tsx +29 -26
  28. package/src/components/account/ConfirmDeleteAccountModal.tsx +55 -0
  29. package/src/components/account/DeleteAccountModal.tsx +130 -0
  30. package/src/components/account/IsntPossibleDeleteAccountModal.tsx +49 -0
  31. package/src/components/layouts/AppMobileNavBar.tsx +73 -0
  32. package/src/components/layouts/AppNavBar.tsx +90 -0
  33. package/src/components/modals/ModalManager.tsx +29 -0
  34. package/src/components/modals/Modals.tsx +29 -0
  35. package/src/index.ts +13 -2
  36. package/src/store/useAccountModals.ts +57 -0
  37. package/src/store/useModalManager.ts +104 -0
@@ -0,0 +1,73 @@
1
+ 'use client';
2
+
3
+ import { ReactNode } from 'react';
4
+ import Image from 'next/image';
5
+ import { ProfilePopover } from './ProfilePopover';
6
+ import { useAccountModals } from '../../store/useAccountModals';
7
+ import type { ProfileMenuItem } from './ProfilePopover';
8
+
9
+ export interface AppMobileNavBarProps {
10
+ appIconActive?: boolean;
11
+ /** Shows the colored indicator bar below the app icon */
12
+ appIconIndicator?: boolean;
13
+ /** Shows the gray indicator bar below the GreatApps logo */
14
+ logoIndicator?: boolean;
15
+ /** Extra slot rendered after the app icon (e.g. project selector) */
16
+ extraLeftSlot?: ReactNode;
17
+ /** Required: the right-side action element (toggle button, sheet button, etc.) */
18
+ rightActionSlot: ReactNode;
19
+ menuItems?: ProfileMenuItem[];
20
+ onCreditsClick?: () => void;
21
+ logoutOverlay?: ReactNode;
22
+ }
23
+
24
+ export function AppMobileNavBar({
25
+ appIconActive = false,
26
+ appIconIndicator = false,
27
+ logoIndicator = false,
28
+ extraLeftSlot,
29
+ rightActionSlot,
30
+ menuItems = [],
31
+ onCreditsClick,
32
+ logoutOverlay,
33
+ }: AppMobileNavBarProps) {
34
+ const { openConfigurations } = useAccountModals();
35
+
36
+ return (
37
+ <div className="fixed top-0 left-0 right-0 flex md:hidden items-center justify-between p-4 bg-white z-50 border-b border-gray-200">
38
+ <div className="flex items-center gap-1.5">
39
+ <div className="relative size-10 flex items-center justify-center">
40
+ <Image src="/icons/great-icon.svg" alt="GreatApps" width={28} height={28} />
41
+ {logoIndicator && (
42
+ <div className="absolute bottom-[-17px] left-1/2 -translate-x-1/2 w-5 h-[3px] bg-gray-950 rounded-t-xl" />
43
+ )}
44
+ </div>
45
+ <div className="relative">
46
+ <div
47
+ className={`size-10 flex items-center justify-center rounded-lg transition-colors duration-300 ${
48
+ appIconActive ? 'bg-pages-300' : 'bg-pages-50'
49
+ }`}
50
+ >
51
+ <Image src="/icons/icon-navbar.svg" alt="GreatPages" width={24} height={24} />
52
+ </div>
53
+ {appIconIndicator && (
54
+ <div className="absolute -bottom-[17px] left-1/2 -translate-x-1/2 w-5 h-[3px] bg-pages-300 rounded-t-xl" />
55
+ )}
56
+ </div>
57
+ {extraLeftSlot}
58
+ </div>
59
+ <div className="flex items-center gap-1.5">
60
+ <ProfilePopover
61
+ side="bottom"
62
+ align="end"
63
+ contentClassName=""
64
+ onEditProfile={() => openConfigurations()}
65
+ onCreditsClick={onCreditsClick}
66
+ menuItems={menuItems}
67
+ logoutOverlay={logoutOverlay}
68
+ />
69
+ {rightActionSlot}
70
+ </div>
71
+ </div>
72
+ );
73
+ }
@@ -0,0 +1,90 @@
1
+ 'use client';
2
+
3
+ import { ReactNode } from 'react';
4
+ import Image from 'next/image';
5
+ import { useRouter } from 'next/navigation';
6
+ import { NavBar } from './NavBar';
7
+ import { ProfilePopover } from './ProfilePopover';
8
+ import { NotificationsPopover } from './NotificationsPopover';
9
+ import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/overlay/Tooltip';
10
+ import { useAccountModals } from '../../store/useAccountModals';
11
+ import type { ProfileMenuItem } from './ProfilePopover';
12
+ import type { NotificationData } from './NotificationsPopover';
13
+
14
+ export interface AppNavBarProps {
15
+ onLogoClick?: () => void;
16
+ logoIndicatorClassName?: string;
17
+ appIconHref?: string;
18
+ appIconActive?: boolean;
19
+ /** Controls the left-side indicator bar on the app icon (opacity transition) */
20
+ appIconIndicator?: boolean;
21
+ showExpandButton?: boolean;
22
+ onExpandClick?: () => void;
23
+ menuItems?: ProfileMenuItem[];
24
+ onCreditsClick?: () => void;
25
+ notifications?: NotificationData[];
26
+ onNotificationsViewAll?: () => void;
27
+ logoutOverlay?: ReactNode;
28
+ }
29
+
30
+ export function AppNavBar({
31
+ onLogoClick,
32
+ logoIndicatorClassName,
33
+ appIconHref = '/',
34
+ appIconActive = false,
35
+ appIconIndicator = false,
36
+ showExpandButton = false,
37
+ onExpandClick,
38
+ menuItems = [],
39
+ onCreditsClick,
40
+ notifications,
41
+ onNotificationsViewAll,
42
+ logoutOverlay,
43
+ }: AppNavBarProps) {
44
+ const router = useRouter();
45
+ const { openConfigurations } = useAccountModals();
46
+
47
+ const handleLogoClick = onLogoClick ?? (() => router.push('/'));
48
+ const handleAppIconClick = () => router.push(appIconHref);
49
+
50
+ return (
51
+ <NavBar
52
+ onLogoClick={handleLogoClick}
53
+ logoIndicatorClassName={logoIndicatorClassName}
54
+ showExpandButton={showExpandButton}
55
+ onExpandClick={onExpandClick}
56
+ appIconSlot={
57
+ <div className="relative">
58
+ <Tooltip>
59
+ <TooltipTrigger asChild onClick={handleAppIconClick}>
60
+ <div
61
+ className={`size-10 flex items-center justify-center rounded-lg hover:bg-pages-100 cursor-pointer transition-colors duration-300 ${
62
+ appIconActive ? 'bg-pages-300' : 'bg-pages-50'
63
+ }`}
64
+ >
65
+ <Image src="/icons/icon-navbar.svg" alt="GreatPages" width={24} height={24} />
66
+ </div>
67
+ </TooltipTrigger>
68
+ <TooltipContent side="right">GreatPages</TooltipContent>
69
+ </Tooltip>
70
+ <div
71
+ className={`absolute -left-4 top-1/2 -translate-y-1/2 w-[3px] h-5 bg-pages-300 rounded-r-xl transition-opacity duration-300 ${
72
+ appIconIndicator ? 'opacity-100' : 'opacity-0'
73
+ }`}
74
+ />
75
+ </div>
76
+ }
77
+ >
78
+ <NotificationsPopover
79
+ notifications={notifications}
80
+ onViewAll={onNotificationsViewAll}
81
+ />
82
+ <ProfilePopover
83
+ onEditProfile={() => openConfigurations()}
84
+ onCreditsClick={onCreditsClick}
85
+ menuItems={menuItems}
86
+ logoutOverlay={logoutOverlay}
87
+ />
88
+ </NavBar>
89
+ );
90
+ }
@@ -0,0 +1,29 @@
1
+ 'use client';
2
+
3
+ import { ComponentType } from 'react';
4
+ import { useModalManager } from '../../store/useModalManager';
5
+
6
+ interface ModalManagerProps {
7
+ registry: Record<string, ComponentType>;
8
+ }
9
+
10
+ export function ModalManager({ registry }: ModalManagerProps) {
11
+ const { modalStack } = useModalManager();
12
+
13
+ if (modalStack.length === 0) return null;
14
+
15
+ return (
16
+ <>
17
+ {modalStack.map((modal) => {
18
+ const ModalComponent = registry[modal.key];
19
+
20
+ if (!ModalComponent) {
21
+ console.warn(`Modal "${modal.key}" not found in registry`);
22
+ return null;
23
+ }
24
+
25
+ return <ModalComponent key={modal.key} />;
26
+ })}
27
+ </>
28
+ );
29
+ }
@@ -0,0 +1,29 @@
1
+ 'use client';
2
+
3
+ import { ComponentType, useEffect } from 'react';
4
+ import AccountModals from '../account/AccountModals';
5
+ import { ModalManager } from './ModalManager';
6
+ import { useAccountModals } from '../../store/useAccountModals';
7
+ import type { ComboboxOption } from '../ui/form/ComboboxField';
8
+
9
+ interface ModalsProps {
10
+ registry: Record<string, ComponentType>;
11
+ hasActiveSubscription?: boolean;
12
+ currencies?: ComboboxOption[];
13
+ onGoToSubscription?: () => void;
14
+ }
15
+
16
+ export function Modals({ registry, hasActiveSubscription, currencies, onGoToSubscription }: ModalsProps) {
17
+ const { updateConfig } = useAccountModals();
18
+
19
+ useEffect(() => {
20
+ updateConfig({ hasActiveSubscription, currencies, onGoToSubscription });
21
+ }, [hasActiveSubscription, currencies, onGoToSubscription, updateConfig]);
22
+
23
+ return (
24
+ <>
25
+ <AccountModals />
26
+ <ModalManager registry={registry} />
27
+ </>
28
+ );
29
+ }
package/src/index.ts CHANGED
@@ -38,6 +38,10 @@ export { parseSchema, parseResult } from './infra/utils/parser';
38
38
  export { MainLayout, MainLayoutContent, MainLayoutSpacer, MainLayoutMain } from './components/layouts/MainLayout';
39
39
  export { NavBar } from './components/layouts/NavBar';
40
40
  export type { NavBarProps } from './components/layouts/NavBar';
41
+ export { AppNavBar } from './components/layouts/AppNavBar';
42
+ export type { AppNavBarProps } from './components/layouts/AppNavBar';
43
+ export { AppMobileNavBar } from './components/layouts/AppMobileNavBar';
44
+ export type { AppMobileNavBarProps } from './components/layouts/AppMobileNavBar';
41
45
  export { SideBarNavigation } from './components/layouts/SideBarNavigation';
42
46
  export { MdSideBarNavigation } from './components/layouts/MdSideBarNavigation';
43
47
  export { NotificationItem } from './components/layouts/NotificationItem';
@@ -96,6 +100,8 @@ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from './comp
96
100
 
97
101
  // Store
98
102
  export { useMdSidebarStore } from './store/useMdSidebarStore';
103
+ export { useModalManager } from './store/useModalManager';
104
+ export type { ModalData } from './store/useModalManager';
99
105
 
100
106
  // Enums
101
107
  export { AccountSectionType } from './enums/AccountSectionType';
@@ -145,8 +151,13 @@ export type {
145
151
  ContactResetResult,
146
152
  } from './modules/accounts/types';
147
153
 
148
- // Account Components
149
- export { default as ConfigurationsMyAccountModal } from './components/account/ConfigurationsMyAccountModal';
154
+ export { default as AccountModals } from './components/account/AccountModals';
155
+ export { useAccountModals } from './store/useAccountModals';
156
+ export type { AccountModalsConfig } from './store/useAccountModals';
157
+
158
+ export { ModalManager } from './components/modals/ModalManager';
159
+ export { Modals } from './components/modals/Modals';
160
+
150
161
  export { default as TwoFactorAuthModal } from './components/account/TwoFactorAuthModal';
151
162
  export { default as DisableTwoFactorAuthModal } from './components/account/DisableTwoFactorAuthModal';
152
163
  export { default as ConfirmGlobalPreferencesModal } from './components/account/ConfirmGlobalPreferencesModal';
@@ -0,0 +1,57 @@
1
+ 'use client';
2
+
3
+ import { create } from 'zustand';
4
+ import type { ComboboxOption } from '../components/ui/form/ComboboxField';
5
+ import type { AccountSectionType } from '../enums/AccountSectionType';
6
+
7
+ type AccountModalType =
8
+ | 'configurations'
9
+ | 'deleteAccount'
10
+ | 'isntPossibleDelete'
11
+ | 'confirmDelete';
12
+
13
+ export interface AccountModalsConfig {
14
+ hasActiveSubscription?: boolean;
15
+ currencies?: ComboboxOption[];
16
+ onGoToSubscription?: () => void;
17
+ }
18
+
19
+ interface AccountModalsState {
20
+ activeModal: AccountModalType | null;
21
+ config: AccountModalsConfig;
22
+ initialSection?: AccountSectionType;
23
+ pagesCount: number;
24
+
25
+ /** Called by <AccountModals> to keep app-level config in sync */
26
+ updateConfig: (config: AccountModalsConfig) => void;
27
+
28
+ openConfigurations: (initialSection?: AccountSectionType) => void;
29
+ openDeleteAccount: (pagesCount?: number) => void;
30
+ openIsntPossibleDelete: () => void;
31
+ openConfirmDelete: (pagesCount?: number) => void;
32
+ close: () => void;
33
+ }
34
+
35
+ export const useAccountModals = create<AccountModalsState>((set) => ({
36
+ activeModal: null,
37
+ config: {},
38
+ initialSection: undefined,
39
+ pagesCount: 0,
40
+
41
+ updateConfig: (config) => set({ config }),
42
+
43
+ openConfigurations: (initialSection) =>
44
+ set({ activeModal: 'configurations', initialSection }),
45
+
46
+ openDeleteAccount: (pagesCount = 0) =>
47
+ set({ activeModal: 'deleteAccount', pagesCount }),
48
+
49
+ openIsntPossibleDelete: () =>
50
+ set({ activeModal: 'isntPossibleDelete' }),
51
+
52
+ openConfirmDelete: (pagesCount = 0) =>
53
+ set({ activeModal: 'confirmDelete', pagesCount }),
54
+
55
+ close: () =>
56
+ set({ activeModal: null, initialSection: undefined }),
57
+ }));
@@ -0,0 +1,104 @@
1
+ 'use client';
2
+
3
+ import { create } from 'zustand';
4
+
5
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6
+ export type ModalData = Record<string, any>;
7
+
8
+ interface ModalStackItem {
9
+ key: string;
10
+ data: ModalData;
11
+ }
12
+
13
+ interface ModalState {
14
+ modalStack: ModalStackItem[];
15
+ activeModal: string | null;
16
+ modalData: ModalData;
17
+ openModal: (key: string, data?: ModalData) => void;
18
+ /** Closes the top modal in the stack */
19
+ closeModal: () => void;
20
+ /** Closes a specific modal by key */
21
+ closeModalByKey: (key: string) => void;
22
+ updateModalData: (data: ModalData) => void;
23
+ isModalOpen: (key: string) => boolean;
24
+ getModalData: (key: string) => ModalData;
25
+ }
26
+
27
+ export const useModalManager = create<ModalState>((set, get) => ({
28
+ modalStack: [],
29
+ activeModal: null,
30
+ modalData: {},
31
+
32
+ openModal: (key, data = {}) => {
33
+ set((state) => {
34
+ const existingIndex = state.modalStack.findIndex((item) => item.key === key);
35
+ if (existingIndex !== -1) {
36
+ const newStack = [...state.modalStack];
37
+ newStack[existingIndex] = { key, data };
38
+ return {
39
+ modalStack: newStack,
40
+ activeModal: newStack[newStack.length - 1]?.key ?? null,
41
+ modalData: newStack[newStack.length - 1]?.data ?? {},
42
+ };
43
+ }
44
+
45
+ const newStack = [...state.modalStack, { key, data }];
46
+ return {
47
+ modalStack: newStack,
48
+ activeModal: key,
49
+ modalData: data,
50
+ };
51
+ });
52
+ },
53
+
54
+ closeModal: () => {
55
+ set((state) => {
56
+ const newStack = state.modalStack.slice(0, -1);
57
+ const topModal = newStack[newStack.length - 1];
58
+ return {
59
+ modalStack: newStack,
60
+ activeModal: topModal?.key ?? null,
61
+ modalData: topModal?.data ?? {},
62
+ };
63
+ });
64
+ },
65
+
66
+ closeModalByKey: (key: string) => {
67
+ set((state) => {
68
+ const newStack = state.modalStack.filter((item) => item.key !== key);
69
+ const topModal = newStack[newStack.length - 1];
70
+ return {
71
+ modalStack: newStack,
72
+ activeModal: topModal?.key ?? null,
73
+ modalData: topModal?.data ?? {},
74
+ };
75
+ });
76
+ },
77
+
78
+ updateModalData: (data) => {
79
+ set((state) => {
80
+ if (state.modalStack.length === 0) return state;
81
+
82
+ const newStack = [...state.modalStack];
83
+ const lastIndex = newStack.length - 1;
84
+ newStack[lastIndex] = {
85
+ ...newStack[lastIndex],
86
+ data: { ...newStack[lastIndex].data, ...data },
87
+ };
88
+
89
+ return {
90
+ modalStack: newStack,
91
+ modalData: newStack[lastIndex].data,
92
+ };
93
+ });
94
+ },
95
+
96
+ isModalOpen: (key: string) => {
97
+ return get().modalStack.some((item) => item.key === key);
98
+ },
99
+
100
+ getModalData: (key: string) => {
101
+ const item = get().modalStack.find((item) => item.key === key);
102
+ return item?.data ?? {};
103
+ },
104
+ }));