@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.
- package/dist/components/navigation/AppNavigation.mjs +26 -0
- package/dist/components/navigation/AppNavigation.mjs.map +1 -0
- package/dist/components/navigation/AppNavigationContext.mjs +13 -0
- package/dist/components/navigation/AppNavigationContext.mjs.map +1 -0
- package/dist/components/navigation/NavItem.mjs +25 -0
- package/dist/components/navigation/NavItem.mjs.map +1 -0
- package/dist/components/navigation/ProjectSelectorPopover.mjs +136 -0
- package/dist/components/navigation/ProjectSelectorPopover.mjs.map +1 -0
- package/dist/components/navigation/shells/DesktopShell.mjs +11 -0
- package/dist/components/navigation/shells/DesktopShell.mjs.map +1 -0
- package/dist/components/navigation/shells/MdShell.mjs +11 -0
- package/dist/components/navigation/shells/MdShell.mjs.map +1 -0
- package/dist/components/navigation/shells/MobileShell.mjs +27 -0
- package/dist/components/navigation/shells/MobileShell.mjs.map +1 -0
- package/dist/components/navigation/subcomponents/CreditsCard.mjs +30 -0
- package/dist/components/navigation/subcomponents/CreditsCard.mjs.map +1 -0
- package/dist/components/navigation/subcomponents/NavItems.mjs +19 -0
- package/dist/components/navigation/subcomponents/NavItems.mjs.map +1 -0
- package/dist/components/navigation/subcomponents/PlanCard.mjs +25 -0
- package/dist/components/navigation/subcomponents/PlanCard.mjs.map +1 -0
- package/dist/components/navigation/subcomponents/ProjectSelector.mjs +122 -0
- package/dist/components/navigation/subcomponents/ProjectSelector.mjs.map +1 -0
- package/dist/components/navigation/types.mjs +1 -0
- package/dist/components/navigation/types.mjs.map +1 -0
- package/dist/index.mjs +4 -0
- package/dist/index.mjs.map +1 -1
- package/dist/infra/api/client.mjs +8 -6
- package/dist/infra/api/client.mjs.map +1 -1
- package/dist/store/useMobileNavbarSheet.mjs +11 -0
- package/dist/store/useMobileNavbarSheet.mjs.map +1 -0
- package/package.json +1 -1
- package/src/components/navigation/AppNavigation.tsx +36 -0
- package/src/components/navigation/AppNavigationContext.ts +17 -0
- package/src/components/navigation/NavItem.tsx +31 -0
- package/src/components/navigation/ProjectSelectorPopover.tsx +152 -0
- package/src/components/navigation/shells/DesktopShell.tsx +15 -0
- package/src/components/navigation/shells/MdShell.tsx +15 -0
- package/src/components/navigation/shells/MobileShell.tsx +29 -0
- package/src/components/navigation/subcomponents/CreditsCard.tsx +30 -0
- package/src/components/navigation/subcomponents/NavItems.tsx +31 -0
- package/src/components/navigation/subcomponents/PlanCard.tsx +29 -0
- package/src/components/navigation/subcomponents/ProjectSelector.tsx +146 -0
- package/src/components/navigation/types.ts +6 -0
- package/src/index.ts +6 -0
- package/src/infra/api/client.ts +9 -6
- 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
|
+
}
|
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";
|
package/src/infra/api/client.ts
CHANGED
|
@@ -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
|
+
}));
|