@greatapps/common 1.1.88 → 1.1.91

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 (46) hide show
  1. package/dist/components/navigation/AppNavigation.mjs +26 -0
  2. package/dist/components/navigation/AppNavigation.mjs.map +1 -0
  3. package/dist/components/navigation/AppNavigationContext.mjs +13 -0
  4. package/dist/components/navigation/AppNavigationContext.mjs.map +1 -0
  5. package/dist/components/navigation/NavItem.mjs +25 -0
  6. package/dist/components/navigation/NavItem.mjs.map +1 -0
  7. package/dist/components/navigation/ProjectSelectorPopover.mjs +136 -0
  8. package/dist/components/navigation/ProjectSelectorPopover.mjs.map +1 -0
  9. package/dist/components/navigation/shells/DesktopShell.mjs +11 -0
  10. package/dist/components/navigation/shells/DesktopShell.mjs.map +1 -0
  11. package/dist/components/navigation/shells/MdShell.mjs +11 -0
  12. package/dist/components/navigation/shells/MdShell.mjs.map +1 -0
  13. package/dist/components/navigation/shells/MobileShell.mjs +27 -0
  14. package/dist/components/navigation/shells/MobileShell.mjs.map +1 -0
  15. package/dist/components/navigation/subcomponents/CreditsCard.mjs +30 -0
  16. package/dist/components/navigation/subcomponents/CreditsCard.mjs.map +1 -0
  17. package/dist/components/navigation/subcomponents/NavItems.mjs +19 -0
  18. package/dist/components/navigation/subcomponents/NavItems.mjs.map +1 -0
  19. package/dist/components/navigation/subcomponents/PlanCard.mjs +25 -0
  20. package/dist/components/navigation/subcomponents/PlanCard.mjs.map +1 -0
  21. package/dist/components/navigation/subcomponents/ProjectSelector.mjs +122 -0
  22. package/dist/components/navigation/subcomponents/ProjectSelector.mjs.map +1 -0
  23. package/dist/components/navigation/types.mjs +1 -0
  24. package/dist/components/navigation/types.mjs.map +1 -0
  25. package/dist/index.mjs +4 -0
  26. package/dist/index.mjs.map +1 -1
  27. package/dist/infra/api/client.mjs +8 -6
  28. package/dist/infra/api/client.mjs.map +1 -1
  29. package/dist/store/useMobileNavbarSheet.mjs +11 -0
  30. package/dist/store/useMobileNavbarSheet.mjs.map +1 -0
  31. package/package.json +1 -1
  32. package/src/components/navigation/AppNavigation.tsx +36 -0
  33. package/src/components/navigation/AppNavigationContext.ts +17 -0
  34. package/src/components/navigation/NavItem.tsx +31 -0
  35. package/src/components/navigation/ProjectSelectorPopover.tsx +152 -0
  36. package/src/components/navigation/shells/DesktopShell.tsx +15 -0
  37. package/src/components/navigation/shells/MdShell.tsx +15 -0
  38. package/src/components/navigation/shells/MobileShell.tsx +29 -0
  39. package/src/components/navigation/subcomponents/CreditsCard.tsx +30 -0
  40. package/src/components/navigation/subcomponents/NavItems.tsx +31 -0
  41. package/src/components/navigation/subcomponents/PlanCard.tsx +29 -0
  42. package/src/components/navigation/subcomponents/ProjectSelector.tsx +146 -0
  43. package/src/components/navigation/types.ts +6 -0
  44. package/src/index.ts +6 -0
  45. package/src/infra/api/client.ts +9 -6
  46. package/src/store/useMobileNavbarSheet.ts +15 -0
@@ -0,0 +1,146 @@
1
+ 'use client';
2
+
3
+ import { useState, useRef, RefObject } from 'react';
4
+ import Image from 'next/image';
5
+ import { useAppNavigationContext } from '../AppNavigationContext';
6
+ import { ProjectSelectorPopover } from '../ProjectSelectorPopover';
7
+ import { UsersSelectorPopover } from '../../layouts/UsersSelectorPopover';
8
+ import { Button, Command, CommandEmpty, CommandInput, CommandItem, CommandList } from '../../../index';
9
+ import { ChevronDown, Plus, Settings } from 'lucide-react';
10
+ import type { NavigationProject } from '../types';
11
+
12
+ export interface ProjectSelectorProps {
13
+ projects: NavigationProject[];
14
+ selectedProject: NavigationProject | null;
15
+ isLoading?: boolean;
16
+ onSelectProject: (project: NavigationProject) => void;
17
+ onAddProject: () => void;
18
+ onManageProjects: () => void;
19
+ }
20
+
21
+ function ProjectItemSkeleton() {
22
+ return (
23
+ <div className="flex items-center gap-2 p-2">
24
+ <div className="size-8 rounded-lg bg-gray-100 animate-pulse shrink-0" />
25
+ <div className="h-3.5 w-32 rounded bg-gray-100 animate-pulse" />
26
+ </div>
27
+ );
28
+ }
29
+
30
+ export function ProjectSelector({
31
+ projects,
32
+ selectedProject,
33
+ isLoading,
34
+ onSelectProject,
35
+ onAddProject,
36
+ onManageProjects,
37
+ }: ProjectSelectorProps) {
38
+ const { breakpoint } = useAppNavigationContext();
39
+ const [isPopoverOpen, setIsPopoverOpen] = useState(false);
40
+ const [activeSection, setActiveSection] = useState<'projects' | null>(null);
41
+ const titleRef = useRef<HTMLDivElement>(null);
42
+
43
+ // Desktop and md: title + popover + users selector (same visual as current gpages sidebars)
44
+ if (breakpoint === 'desktop' || breakpoint === 'md') {
45
+ return (
46
+ <div className="flex items-center justify-between">
47
+ <div className="flex items-center gap-2 truncate select-none" ref={titleRef}>
48
+ {isLoading ? (
49
+ <div className="h-4 w-32 rounded bg-gray-100 animate-pulse" />
50
+ ) : (
51
+ <span
52
+ className="cursor-pointer paragraph-medium-semibold text-gray-600 truncate"
53
+ onClick={() => setIsPopoverOpen((v) => !v)}
54
+ >
55
+ {selectedProject?.title && selectedProject.title.length > 15
56
+ ? `${selectedProject.title.slice(0, 15)}…`
57
+ : selectedProject?.title}
58
+ </span>
59
+ )}
60
+ <ProjectSelectorPopover
61
+ open={isPopoverOpen}
62
+ setOpen={setIsPopoverOpen}
63
+ titleRef={titleRef as unknown as RefObject<HTMLSpanElement | null>}
64
+ projects={projects}
65
+ selectedProject={selectedProject}
66
+ onSelectProject={onSelectProject}
67
+ onAddProject={onAddProject}
68
+ onManageProjects={onManageProjects}
69
+ />
70
+ </div>
71
+ <div className="flex items-center gap-2">
72
+ <UsersSelectorPopover projectId={selectedProject?.id} />
73
+ </div>
74
+ </div>
75
+ );
76
+ }
77
+
78
+ // Mobile: inline collapsible accordion
79
+ return (
80
+ <div className="flex flex-col gap-2.5">
81
+ <div className="flex items-center justify-between">
82
+ <div className="flex items-center gap-1">
83
+ <span className="paragraph-medium-semibold text-gray-600">{selectedProject?.title}</span>
84
+ <ChevronDown
85
+ size={20}
86
+ onClick={() => setActiveSection(activeSection === 'projects' ? null : 'projects')}
87
+ className={`transition-transform duration-200 cursor-pointer ${
88
+ activeSection === 'projects' ? 'rotate-180 text-gray-950' : 'text-gray-400'
89
+ }`}
90
+ />
91
+ </div>
92
+ <UsersSelectorPopover projectId={selectedProject?.id} />
93
+ </div>
94
+ {activeSection === 'projects' && (
95
+ <div className="flex flex-col p-3 gap-2 border border-gray-200 rounded-lg animate-in fade-in duration-400">
96
+ <div className="flex items-center justify-between">
97
+ <span className="paragraph-small-semibold">Projetos</span>
98
+ <div className="flex items-center gap-1.5">
99
+ <Button variant="secondary" className="h-8! paragraph-small-semibold text-gray-950" onClick={onAddProject}>
100
+ <Plus size={16} />
101
+ Adicionar
102
+ </Button>
103
+ <Button variant="secondary" className="p-0 size-8" onClick={onManageProjects}>
104
+ <Settings size={18} />
105
+ </Button>
106
+ </div>
107
+ </div>
108
+ <Command className="gap-2">
109
+ <CommandInput placeholder="Busque aqui" />
110
+ <CommandList className="custom-scrollbar paragraph-small-medium text-gray-600 h-[162px]">
111
+ <CommandEmpty>Nenhum resultado encontrado.</CommandEmpty>
112
+ {isLoading ? (
113
+ <>
114
+ <ProjectItemSkeleton />
115
+ <ProjectItemSkeleton />
116
+ </>
117
+ ) : (
118
+ projects.map((project) => (
119
+ <CommandItem
120
+ key={project.id}
121
+ value={project.title}
122
+ className={selectedProject?.id === project.id ? 'bg-gray-50' : ''}
123
+ onSelect={() => { onSelectProject(project); setActiveSection(null); }}
124
+ >
125
+ <Image
126
+ alt="Icon Project"
127
+ src={
128
+ project.photo
129
+ ? `${process.env.NEXT_PUBLIC_STORAGE_URL}/${project.photo}`
130
+ : '/images/Logomark.png'
131
+ }
132
+ width={32}
133
+ height={32}
134
+ className="rounded-lg"
135
+ />
136
+ {project.title}
137
+ </CommandItem>
138
+ ))
139
+ )}
140
+ </CommandList>
141
+ </Command>
142
+ </div>
143
+ )}
144
+ </div>
145
+ );
146
+ }
@@ -0,0 +1,6 @@
1
+ export interface NavigationProject {
2
+ id: number;
3
+ title: string;
4
+ photo?: string | null;
5
+ total_users?: string;
6
+ }
package/src/index.ts CHANGED
@@ -105,6 +105,12 @@ export type {
105
105
  export { NavBarItem } from "./components/layouts/NavBarItem";
106
106
  export type { NavBarItemProps } from "./components/layouts/NavBarItem";
107
107
 
108
+ // Navigation
109
+ export { AppNavigation } from './components/navigation/AppNavigation';
110
+ export type { NavItemConfig } from './components/navigation/subcomponents/NavItems';
111
+ export type { NavigationProject } from './components/navigation/types';
112
+ export { useMobileNavbarSheet } from './store/useMobileNavbarSheet';
113
+
108
114
  // UI - Buttons
109
115
  export { Button, buttonVariants } from "./components/ui/buttons/Button";
110
116
  export { CopyButton } from "./components/ui/buttons/CopyButton";
@@ -25,16 +25,19 @@ class ApiClient {
25
25
  return url;
26
26
  }
27
27
 
28
- private async getDefaultHeaders(token: string): Promise<HeadersInit> {
28
+ private async getDefaultHeaders(token: string, isFormData = false): Promise<HeadersInit> {
29
29
  console.log('[ApiClient] getDefaultHeaders', {
30
30
  token: token.substring(0, 20) + '...',
31
31
  });
32
32
 
33
33
  const headers: HeadersInit = {
34
- 'Content-Type': 'application/json',
35
34
  authorization: token,
36
35
  };
37
36
 
37
+ if (!isFormData) {
38
+ headers['Content-Type'] = 'application/json';
39
+ }
40
+
38
41
  return headers;
39
42
  }
40
43
 
@@ -53,7 +56,7 @@ class ApiClient {
53
56
 
54
57
  const [url, defaultHeaders] = await Promise.all([
55
58
  this.buildUrl(endpoint, id),
56
- this.getDefaultHeaders(token),
59
+ this.getDefaultHeaders(token, fetchConfig.body instanceof FormData),
57
60
  ]);
58
61
 
59
62
  console.log('[ApiClient] Fetching', {
@@ -114,7 +117,7 @@ class ApiClient {
114
117
  return this.request<T>(endpoint, {
115
118
  ...config,
116
119
  method: 'POST',
117
- body: data ? JSON.stringify(data) : undefined,
120
+ body: data instanceof FormData ? data : data ? JSON.stringify(data) : undefined,
118
121
  });
119
122
  }
120
123
 
@@ -122,7 +125,7 @@ class ApiClient {
122
125
  return this.request<T>(endpoint, {
123
126
  ...config,
124
127
  method: 'PUT',
125
- body: data ? JSON.stringify(data) : undefined,
128
+ body: data instanceof FormData ? data : data ? JSON.stringify(data) : undefined,
126
129
  });
127
130
  }
128
131
 
@@ -130,7 +133,7 @@ class ApiClient {
130
133
  return this.request<T>(endpoint, {
131
134
  ...config,
132
135
  method: 'PATCH',
133
- body: data ? JSON.stringify(data) : undefined,
136
+ body: data instanceof FormData ? data : data ? JSON.stringify(data) : undefined,
134
137
  });
135
138
  }
136
139
 
@@ -0,0 +1,15 @@
1
+ import { create } from 'zustand';
2
+
3
+ interface MobileNavbarSheetStore {
4
+ isOpen: boolean;
5
+ open: () => void;
6
+ close: () => void;
7
+ toggle: () => void;
8
+ }
9
+
10
+ export const useMobileNavbarSheet = create<MobileNavbarSheetStore>((set) => ({
11
+ isOpen: false,
12
+ open: () => set({ isOpen: true }),
13
+ close: () => set({ isOpen: false }),
14
+ toggle: () => set((state) => ({ isOpen: !state.isOpen })),
15
+ }));