ptechcore_ui 0.0.0
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/eslint.config.js +28 -0
- package/index.html +78 -0
- package/package.json +42 -0
- package/postcss.config.js +6 -0
- package/src/App.tsx +156 -0
- package/src/assets/imgs/login_illustration.png +0 -0
- package/src/components/common/Buttons.tsx +39 -0
- package/src/components/common/Cards.tsx +18 -0
- package/src/components/common/FDrawer.tsx +2448 -0
- package/src/components/common/FDrawer.types.ts +191 -0
- package/src/components/common/Inputs.tsx +409 -0
- package/src/components/common/Modals.tsx +41 -0
- package/src/components/common/Navigations.tsx +0 -0
- package/src/components/common/Toast.tsx +0 -0
- package/src/components/demo/ToastDemo.tsx +73 -0
- package/src/components/layout/Header.tsx +202 -0
- package/src/components/layout/ModernDoubleSidebarLayout.tsx +727 -0
- package/src/components/layout/PrivateLayout.tsx +52 -0
- package/src/components/layout/Sidebar.tsx +182 -0
- package/src/components/ui/Toast.tsx +93 -0
- package/src/contexts/SessionContext.tsx +77 -0
- package/src/contexts/ThemeContext.tsx +58 -0
- package/src/contexts/ToastContext.tsx +94 -0
- package/src/index.css +3 -0
- package/src/main.tsx +10 -0
- package/src/models/Organization.ts +47 -0
- package/src/models/Plan.ts +42 -0
- package/src/models/User.ts +23 -0
- package/src/pages/Analytics.tsx +101 -0
- package/src/pages/CreateOrganization.tsx +215 -0
- package/src/pages/Dashboard.tsx +15 -0
- package/src/pages/Home.tsx +12 -0
- package/src/pages/Profile.tsx +313 -0
- package/src/pages/Settings.tsx +382 -0
- package/src/pages/Team.tsx +180 -0
- package/src/pages/auth/Login.tsx +140 -0
- package/src/pages/auth/Register.tsx +302 -0
- package/src/pages/organizations/DetailEntity.tsx +1002 -0
- package/src/pages/organizations/DetailOrganizations.tsx +1629 -0
- package/src/pages/organizations/ListOrganizations.tsx +270 -0
- package/src/pages/pricings/CartPlan.tsx +486 -0
- package/src/pages/pricings/ListPricing.tsx +321 -0
- package/src/pages/users/CreateUser.tsx +450 -0
- package/src/pages/users/ListUsers.tsx +0 -0
- package/src/services/AuthServices.ts +94 -0
- package/src/services/OrganizationServices.ts +61 -0
- package/src/services/PlanSubscriptionServices.tsx +137 -0
- package/src/services/UserServices.ts +36 -0
- package/src/services/api.ts +64 -0
- package/src/styles/theme.ts +383 -0
- package/src/utils/utils.ts +48 -0
- package/src/vite-env.d.ts +1 -0
- package/tailwind.config.js +158 -0
- package/tsconfig.app.json +24 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +22 -0
- package/vite.config.ts +10 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import Header from './Header';
|
|
3
|
+
import Sidebar from './Sidebar';
|
|
4
|
+
|
|
5
|
+
interface PrivateLayoutProps {
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const RewiseLayout: React.FC<PrivateLayoutProps> = ({ children }) => {
|
|
10
|
+
const [sidebarOpen, setSidebarOpen] = useState(false);
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div className="min-h-screen bg-gray-100">
|
|
14
|
+
{/* Header */}
|
|
15
|
+
<Header
|
|
16
|
+
onMenuClick={() => setSidebarOpen(!sidebarOpen)}
|
|
17
|
+
sidebarOpen={sidebarOpen}
|
|
18
|
+
/>
|
|
19
|
+
|
|
20
|
+
<div className="flex">
|
|
21
|
+
{/* Sidebar */}
|
|
22
|
+
<Sidebar
|
|
23
|
+
isOpen={sidebarOpen}
|
|
24
|
+
onClose={() => setSidebarOpen(false)}
|
|
25
|
+
/>
|
|
26
|
+
|
|
27
|
+
{/* Main Content */}
|
|
28
|
+
<main
|
|
29
|
+
className={`
|
|
30
|
+
flex-1 transition-all duration-300 ease-in-out
|
|
31
|
+
${sidebarOpen ? 'lg:ml-64' : 'lg:ml-16'}
|
|
32
|
+
pt-16
|
|
33
|
+
`}
|
|
34
|
+
>
|
|
35
|
+
<div className="p-6">
|
|
36
|
+
{children}
|
|
37
|
+
</div>
|
|
38
|
+
</main>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
{/* Overlay for mobile */}
|
|
42
|
+
{sidebarOpen && (
|
|
43
|
+
<div
|
|
44
|
+
className="fixed inset-0 z-20 bg-black bg-opacity-50 lg:hidden"
|
|
45
|
+
onClick={() => setSidebarOpen(false)}
|
|
46
|
+
/>
|
|
47
|
+
)}
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export default RewiseLayout;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useLocation, useNavigate } from 'react-router-dom';
|
|
3
|
+
import {
|
|
4
|
+
Home,
|
|
5
|
+
BarChart3,
|
|
6
|
+
Users,
|
|
7
|
+
Calendar,
|
|
8
|
+
FileText,
|
|
9
|
+
Settings,
|
|
10
|
+
HelpCircle,
|
|
11
|
+
ChevronLeft,
|
|
12
|
+
ChevronRight,
|
|
13
|
+
Building2
|
|
14
|
+
} from 'lucide-react';
|
|
15
|
+
|
|
16
|
+
interface SidebarProps {
|
|
17
|
+
isOpen: boolean;
|
|
18
|
+
onClose: () => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface MenuItem {
|
|
22
|
+
id: string;
|
|
23
|
+
label: string;
|
|
24
|
+
icon: React.ComponentType<any>;
|
|
25
|
+
path: string;
|
|
26
|
+
badge?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const Sidebar: React.FC<SidebarProps> = ({ isOpen, onClose }) => {
|
|
30
|
+
const location = useLocation();
|
|
31
|
+
const navigate = useNavigate();
|
|
32
|
+
|
|
33
|
+
const menuItems: MenuItem[] = [
|
|
34
|
+
{
|
|
35
|
+
id: 'dashboard',
|
|
36
|
+
label: 'Tableau de bord',
|
|
37
|
+
icon: Home,
|
|
38
|
+
path: '/dashboard'
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: 'analytics',
|
|
42
|
+
label: 'Analyses',
|
|
43
|
+
icon: BarChart3,
|
|
44
|
+
path: '/analytics'
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: 'team',
|
|
48
|
+
label: 'Équipe',
|
|
49
|
+
icon: Users,
|
|
50
|
+
path: '/team',
|
|
51
|
+
badge: 3
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: 'organizations',
|
|
55
|
+
label: 'Organisations',
|
|
56
|
+
icon: Building2,
|
|
57
|
+
path: '/organizations'
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: 'calendar',
|
|
61
|
+
label: 'Calendrier',
|
|
62
|
+
icon: Calendar,
|
|
63
|
+
path: '/calendar'
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'reports',
|
|
67
|
+
label: 'Rapports',
|
|
68
|
+
icon: FileText,
|
|
69
|
+
path: '/reports'
|
|
70
|
+
}
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
const bottomMenuItems: MenuItem[] = [
|
|
74
|
+
{
|
|
75
|
+
id: 'settings',
|
|
76
|
+
label: 'Paramètres',
|
|
77
|
+
icon: Settings,
|
|
78
|
+
path: '/settings'
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 'help',
|
|
82
|
+
label: 'Aide',
|
|
83
|
+
icon: HelpCircle,
|
|
84
|
+
path: '/help'
|
|
85
|
+
}
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
const handleMenuClick = (path: string) => {
|
|
89
|
+
navigate(path);
|
|
90
|
+
// Close sidebar on mobile after navigation
|
|
91
|
+
if (window.innerWidth < 1024) {
|
|
92
|
+
onClose();
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const isActiveRoute = (path: string) => {
|
|
97
|
+
return location.pathname === path || location.pathname.startsWith(path + '/');
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const MenuItemComponent = ({ item }: { item: MenuItem }) => {
|
|
101
|
+
const Icon = item.icon;
|
|
102
|
+
const isActive = isActiveRoute(item.path);
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<button
|
|
106
|
+
onClick={() => handleMenuClick(item.path)}
|
|
107
|
+
className={`
|
|
108
|
+
w-full flex items-center space-x-3 px-3 py-3 rounded-lg transition-all duration-200
|
|
109
|
+
${isActive
|
|
110
|
+
? 'bg-[#8290A9] text-white shadow-md'
|
|
111
|
+
: 'text-gray-600 hover:bg-gray-100 hover:text-gray-800'
|
|
112
|
+
}
|
|
113
|
+
${!isOpen ? 'justify-center' : ''}
|
|
114
|
+
`}
|
|
115
|
+
title={!isOpen ? item.label : undefined}
|
|
116
|
+
>
|
|
117
|
+
<Icon className={`w-5 h-5 ${isActive ? 'text-white' : ''}`} />
|
|
118
|
+
{isOpen && (
|
|
119
|
+
<>
|
|
120
|
+
<span className="font-medium">{item.label}</span>
|
|
121
|
+
{item.badge && (
|
|
122
|
+
<span className="ml-auto bg-red-500 text-white text-xs rounded-full px-2 py-1 min-w-[20px] h-5 flex items-center justify-center">
|
|
123
|
+
{item.badge}
|
|
124
|
+
</span>
|
|
125
|
+
)}
|
|
126
|
+
</>
|
|
127
|
+
)}
|
|
128
|
+
</button>
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<>
|
|
134
|
+
{/* Sidebar */}
|
|
135
|
+
<aside
|
|
136
|
+
className={`
|
|
137
|
+
fixed left-0 top-16 h-[calc(100vh-4rem)] bg-white border-r border-gray-200 z-30
|
|
138
|
+
transition-all duration-300 ease-in-out
|
|
139
|
+
${isOpen ? 'w-64' : 'w-16'}
|
|
140
|
+
${isOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'}
|
|
141
|
+
`}
|
|
142
|
+
>
|
|
143
|
+
<div className="flex flex-col h-full">
|
|
144
|
+
{/* Main Navigation */}
|
|
145
|
+
<nav className="flex-1 px-3 py-4">
|
|
146
|
+
<div className="space-y-2">
|
|
147
|
+
{menuItems.map((item) => (
|
|
148
|
+
<MenuItemComponent key={item.id} item={item} />
|
|
149
|
+
))}
|
|
150
|
+
</div>
|
|
151
|
+
</nav>
|
|
152
|
+
|
|
153
|
+
{/* Bottom Navigation */}
|
|
154
|
+
<nav className="px-3 py-4 border-t border-gray-200">
|
|
155
|
+
<div className="space-y-2">
|
|
156
|
+
{bottomMenuItems.map((item) => (
|
|
157
|
+
<MenuItemComponent key={item.id} item={item} />
|
|
158
|
+
))}
|
|
159
|
+
</div>
|
|
160
|
+
</nav>
|
|
161
|
+
|
|
162
|
+
{/* Collapse/Expand Button (Desktop only) */}
|
|
163
|
+
<div className="hidden lg:block px-3 py-2 border-t border-gray-200">
|
|
164
|
+
<button
|
|
165
|
+
onClick={onClose}
|
|
166
|
+
className="w-full flex items-center justify-center py-2 text-gray-500 hover:text-gray-700 transition-colors"
|
|
167
|
+
title={isOpen ? 'Réduire la sidebar' : 'Étendre la sidebar'}
|
|
168
|
+
>
|
|
169
|
+
{isOpen ? (
|
|
170
|
+
<ChevronLeft className="w-4 h-4" />
|
|
171
|
+
) : (
|
|
172
|
+
<ChevronRight className="w-4 h-4" />
|
|
173
|
+
)}
|
|
174
|
+
</button>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
</aside>
|
|
178
|
+
</>
|
|
179
|
+
);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
export default Sidebar;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { Toast as ToastType, useToast } from '../../contexts/ToastContext';
|
|
3
|
+
import { CheckCircle, XCircle, AlertTriangle, Info, X } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
interface ToastItemProps {
|
|
6
|
+
toast: ToastType;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const ToastItem: React.FC<ToastItemProps> = ({ toast }) => {
|
|
10
|
+
const { removeToast } = useToast();
|
|
11
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
12
|
+
const [isLeaving, setIsLeaving] = useState(false);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
// Trigger entrance animation
|
|
16
|
+
const timer = setTimeout(() => setIsVisible(true), 10);
|
|
17
|
+
return () => clearTimeout(timer);
|
|
18
|
+
}, []);
|
|
19
|
+
|
|
20
|
+
const handleClose = () => {
|
|
21
|
+
setIsLeaving(true);
|
|
22
|
+
setTimeout(() => {
|
|
23
|
+
removeToast(toast.id);
|
|
24
|
+
}, 300);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const getIcon = () => {
|
|
28
|
+
switch (toast.type) {
|
|
29
|
+
case 'success':
|
|
30
|
+
return <CheckCircle className="w-5 h-5 text-green-600" />;
|
|
31
|
+
case 'error':
|
|
32
|
+
return <XCircle className="w-5 h-5 text-red-600" />;
|
|
33
|
+
case 'warning':
|
|
34
|
+
return <AlertTriangle className="w-5 h-5 text-yellow-600" />;
|
|
35
|
+
case 'info':
|
|
36
|
+
return <Info className="w-5 h-5 text-blue-600" />;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const getBackgroundColor = () => {
|
|
41
|
+
switch (toast.type) {
|
|
42
|
+
case 'success':
|
|
43
|
+
return 'bg-green-50 border-green-200';
|
|
44
|
+
case 'error':
|
|
45
|
+
return 'bg-red-50 border-red-200';
|
|
46
|
+
case 'warning':
|
|
47
|
+
return 'bg-yellow-50 border-yellow-200';
|
|
48
|
+
case 'info':
|
|
49
|
+
return 'bg-blue-50 border-blue-200';
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div
|
|
55
|
+
className={`
|
|
56
|
+
transform transition-all duration-300 ease-in-out
|
|
57
|
+
${isVisible && !isLeaving ? 'translate-x-0 opacity-100' : 'translate-x-full opacity-0'}
|
|
58
|
+
${getBackgroundColor()}
|
|
59
|
+
max-w-sm w-full border rounded-lg p-4 shadow-lg
|
|
60
|
+
flex items-start space-x-3
|
|
61
|
+
`}
|
|
62
|
+
>
|
|
63
|
+
<div className="flex-shrink-0">
|
|
64
|
+
{getIcon()}
|
|
65
|
+
</div>
|
|
66
|
+
<div className="flex-1 min-w-0">
|
|
67
|
+
<p className="text-sm text-gray-900 font-medium">
|
|
68
|
+
{toast.message}
|
|
69
|
+
</p>
|
|
70
|
+
</div>
|
|
71
|
+
<button
|
|
72
|
+
onClick={handleClose}
|
|
73
|
+
className="flex-shrink-0 ml-4 text-gray-400 hover:text-gray-600 transition-colors"
|
|
74
|
+
>
|
|
75
|
+
<X className="w-4 h-4" />
|
|
76
|
+
</button>
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const ToastContainer: React.FC = () => {
|
|
82
|
+
const { toasts } = useToast();
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<div className="fixed top-4 right-4 z-50 space-y-3">
|
|
86
|
+
{toasts.map((toast) => (
|
|
87
|
+
<ToastItem key={toast.id} toast={toast} />
|
|
88
|
+
))}
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export default ToastContainer;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React, { createContext, useContext, useEffect, useState, ReactNode } from 'react';
|
|
2
|
+
import { AuthServices } from '../services/AuthServices';
|
|
3
|
+
import { User } from '../models/User';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
interface SessionContextType {
|
|
9
|
+
isAuthenticated: boolean;
|
|
10
|
+
token: string | null;
|
|
11
|
+
loggedUser: User | null;
|
|
12
|
+
login: (token: string) => void;
|
|
13
|
+
logout: () => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const SessionContext = createContext<SessionContextType | undefined>(undefined);
|
|
17
|
+
|
|
18
|
+
export const useSession = () => {
|
|
19
|
+
const context = useContext(SessionContext);
|
|
20
|
+
if (!context) {
|
|
21
|
+
throw new Error('useSession must be used within a SessionProvider');
|
|
22
|
+
}
|
|
23
|
+
return context;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const SessionProvider = ({ children }: { children: ReactNode }) => {
|
|
27
|
+
const [token, setToken] = useState<string | null>(localStorage.getItem('token'));
|
|
28
|
+
const [loggedUser, setLoggedUser] = useState<User | null>(null);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
const storedToken = localStorage.getItem('token');
|
|
32
|
+
if (storedToken) {
|
|
33
|
+
setToken(storedToken);
|
|
34
|
+
}
|
|
35
|
+
}, []);
|
|
36
|
+
|
|
37
|
+
const login = (newToken: string) => {
|
|
38
|
+
localStorage.setItem('token', newToken);
|
|
39
|
+
setToken(newToken);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const logout = () => {
|
|
43
|
+
localStorage.removeItem('token');
|
|
44
|
+
setToken(null);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (token) {
|
|
49
|
+
AuthServices.getUserInformations(token)
|
|
50
|
+
.then((res) => {
|
|
51
|
+
const result = res as { success: boolean; message?: string; data?: any };
|
|
52
|
+
if (result.success === true) {
|
|
53
|
+
setLoggedUser(result.data.user);
|
|
54
|
+
} else {
|
|
55
|
+
setLoggedUser(null);
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
.catch(() => setLoggedUser(null));
|
|
59
|
+
} else {
|
|
60
|
+
setLoggedUser(null);
|
|
61
|
+
}
|
|
62
|
+
}, [token]);
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<SessionContext.Provider value={{
|
|
68
|
+
isAuthenticated: !!token,
|
|
69
|
+
loggedUser,
|
|
70
|
+
token,
|
|
71
|
+
login,
|
|
72
|
+
logout,
|
|
73
|
+
}}>
|
|
74
|
+
{children}
|
|
75
|
+
</SessionContext.Provider>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React, { createContext, useContext, useState, useEffect } from 'react';
|
|
2
|
+
import { themes, defaultTheme, getThemeCSSVariables, Theme, ThemeType, ThemeContextValue } from '../styles/theme';
|
|
3
|
+
|
|
4
|
+
const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
|
|
5
|
+
|
|
6
|
+
export const useTheme = () => {
|
|
7
|
+
const context = useContext(ThemeContext);
|
|
8
|
+
if (!context) {
|
|
9
|
+
throw new Error('useTheme must be used within a ThemeProvider');
|
|
10
|
+
}
|
|
11
|
+
return context;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
interface ThemeProviderProps {
|
|
15
|
+
children: React.ReactNode;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const ThemeProvider: React.FC<ThemeProviderProps> = ({ children }) => {
|
|
19
|
+
// Récupérer le thème sauvegardé ou utiliser le thème par défaut
|
|
20
|
+
const [themeType, setThemeType] = useState<ThemeType>(() => {
|
|
21
|
+
const saved = localStorage.getItem('wisebook-theme') as ThemeType;
|
|
22
|
+
return saved && saved in themes ? saved : 'minimalist';
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const [theme, setThemeState] = useState<Theme>(themes[themeType] || defaultTheme);
|
|
26
|
+
|
|
27
|
+
// Appliquer les variables CSS du thème
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
const root = document.documentElement;
|
|
30
|
+
const cssVars = getThemeCSSVariables(theme);
|
|
31
|
+
|
|
32
|
+
Object.entries(cssVars).forEach(([key, value]) => {
|
|
33
|
+
root.style.setProperty(key, value);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Appliquer les styles globaux
|
|
37
|
+
root.style.backgroundColor = theme.colors.background;
|
|
38
|
+
root.style.color = theme.colors.text.primary;
|
|
39
|
+
|
|
40
|
+
// Sauvegarder le choix
|
|
41
|
+
localStorage.setItem('wisebook-theme', themeType);
|
|
42
|
+
}, [theme, themeType]);
|
|
43
|
+
|
|
44
|
+
const setTheme = (type: ThemeType) => {
|
|
45
|
+
if (type in themes) {
|
|
46
|
+
setThemeType(type);
|
|
47
|
+
setThemeState(themes[type]);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<ThemeContext.Provider value={{ theme, themeType, setTheme }}>
|
|
53
|
+
{children}
|
|
54
|
+
</ThemeContext.Provider>
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export default ThemeProvider;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import React, { createContext, useContext, useState, useCallback, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface Toast {
|
|
4
|
+
id: string;
|
|
5
|
+
message: string;
|
|
6
|
+
type: 'success' | 'error' | 'warning' | 'info';
|
|
7
|
+
duration?: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface ToastContextType {
|
|
11
|
+
toasts: Toast[];
|
|
12
|
+
addToast: (toast: Omit<Toast, 'id'>) => void;
|
|
13
|
+
removeToast: (id: string) => void;
|
|
14
|
+
success: (message: string, duration?: number) => void;
|
|
15
|
+
error: (message: string, duration?: number) => void;
|
|
16
|
+
warning: (message: string, duration?: number) => void;
|
|
17
|
+
info: (message: string, duration?: number) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const ToastContext = createContext<ToastContextType | undefined>(undefined);
|
|
21
|
+
|
|
22
|
+
export const useToast = (): ToastContextType => {
|
|
23
|
+
const context = useContext(ToastContext);
|
|
24
|
+
if (!context) {
|
|
25
|
+
throw new Error('useToast must be used within a ToastProvider');
|
|
26
|
+
}
|
|
27
|
+
return context;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
interface ToastProviderProps {
|
|
31
|
+
children: ReactNode;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const ToastProvider: React.FC<ToastProviderProps> = ({ children }) => {
|
|
35
|
+
const [toasts, setToasts] = useState<Toast[]>([]);
|
|
36
|
+
|
|
37
|
+
const generateId = (): string => {
|
|
38
|
+
return Date.now().toString(36) + Math.random().toString(36).substr(2);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const addToast = useCallback((toast: Omit<Toast, 'id'>) => {
|
|
42
|
+
const id = generateId();
|
|
43
|
+
const newToast: Toast = {
|
|
44
|
+
id,
|
|
45
|
+
duration: 5000,
|
|
46
|
+
...toast,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
setToasts(prev => [...prev, newToast]);
|
|
50
|
+
|
|
51
|
+
// Auto remove toast after duration
|
|
52
|
+
if (newToast.duration && newToast.duration > 0) {
|
|
53
|
+
setTimeout(() => {
|
|
54
|
+
removeToast(id);
|
|
55
|
+
}, newToast.duration);
|
|
56
|
+
}
|
|
57
|
+
}, []);
|
|
58
|
+
|
|
59
|
+
const removeToast = useCallback((id: string) => {
|
|
60
|
+
setToasts(prev => prev.filter(toast => toast.id !== id));
|
|
61
|
+
}, []);
|
|
62
|
+
|
|
63
|
+
const success = useCallback((message: string, duration?: number) => {
|
|
64
|
+
addToast({ message, type: 'success', duration });
|
|
65
|
+
}, [addToast]);
|
|
66
|
+
|
|
67
|
+
const error = useCallback((message: string, duration?: number) => {
|
|
68
|
+
addToast({ message, type: 'error', duration });
|
|
69
|
+
}, [addToast]);
|
|
70
|
+
|
|
71
|
+
const warning = useCallback((message: string, duration?: number) => {
|
|
72
|
+
addToast({ message, type: 'warning', duration });
|
|
73
|
+
}, [addToast]);
|
|
74
|
+
|
|
75
|
+
const info = useCallback((message: string, duration?: number) => {
|
|
76
|
+
addToast({ message, type: 'info', duration });
|
|
77
|
+
}, [addToast]);
|
|
78
|
+
|
|
79
|
+
const value: ToastContextType = {
|
|
80
|
+
toasts,
|
|
81
|
+
addToast,
|
|
82
|
+
removeToast,
|
|
83
|
+
success,
|
|
84
|
+
error,
|
|
85
|
+
warning,
|
|
86
|
+
info,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<ToastContext.Provider value={value}>
|
|
91
|
+
{children}
|
|
92
|
+
</ToastContext.Provider>
|
|
93
|
+
);
|
|
94
|
+
};
|
package/src/index.css
ADDED
package/src/main.tsx
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Subscription } from "./Plan";
|
|
2
|
+
|
|
3
|
+
export interface Organization {
|
|
4
|
+
id: number;
|
|
5
|
+
legal_name: string;
|
|
6
|
+
trading_name?: string;
|
|
7
|
+
phone?: string;
|
|
8
|
+
email?: string;
|
|
9
|
+
address?: string;
|
|
10
|
+
owner: number;
|
|
11
|
+
active_subscription?: Subscription | null;
|
|
12
|
+
modules?: any[];
|
|
13
|
+
created_at: string;
|
|
14
|
+
updated_at: string;
|
|
15
|
+
is_deleted: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface Entity {
|
|
19
|
+
id: number;
|
|
20
|
+
organization: number;
|
|
21
|
+
legal_name: string;
|
|
22
|
+
trading_name?: string;
|
|
23
|
+
phone?: string;
|
|
24
|
+
email?: string;
|
|
25
|
+
logo?: string;
|
|
26
|
+
legal_representative_name?: string;
|
|
27
|
+
legal_representative_phone?: string;
|
|
28
|
+
legal_representative_email?: string;
|
|
29
|
+
country?: string;
|
|
30
|
+
city?: string;
|
|
31
|
+
address?: string;
|
|
32
|
+
rib?: string;
|
|
33
|
+
iban?: string;
|
|
34
|
+
bank_name?: string;
|
|
35
|
+
bank_address?: string;
|
|
36
|
+
tax_account?: string;
|
|
37
|
+
rccm?: string;
|
|
38
|
+
currency?: string;
|
|
39
|
+
centers_taxes?: string;
|
|
40
|
+
point_of_sale?: string;
|
|
41
|
+
establishment?: string;
|
|
42
|
+
fne_url?: string;
|
|
43
|
+
fne_auth_key?: string;
|
|
44
|
+
regime_taxes?: string;
|
|
45
|
+
balance_sticker_fne?: number;
|
|
46
|
+
|
|
47
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface Module {
|
|
2
|
+
id: number;
|
|
3
|
+
code: string;
|
|
4
|
+
name: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
price: number;
|
|
7
|
+
is_active: boolean;
|
|
8
|
+
price_monthly: number;
|
|
9
|
+
price_yearly: number;
|
|
10
|
+
created_at: string;
|
|
11
|
+
updated_at: string;
|
|
12
|
+
is_deleted: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface Plan {
|
|
16
|
+
id: number;
|
|
17
|
+
name: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
price_monthly: number;
|
|
20
|
+
price_yearly: number;
|
|
21
|
+
max_users: number;
|
|
22
|
+
max_entities: number;
|
|
23
|
+
modules_included: Module[] | number[];
|
|
24
|
+
is_private: boolean;
|
|
25
|
+
private_for?: number | null;
|
|
26
|
+
created_at: string;
|
|
27
|
+
updated_at: string;
|
|
28
|
+
is_deleted: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
export interface Subscription {
|
|
33
|
+
id: number;
|
|
34
|
+
organization: number;
|
|
35
|
+
plan: Plan;
|
|
36
|
+
start_date: string;
|
|
37
|
+
end_date: string;
|
|
38
|
+
is_active: boolean;
|
|
39
|
+
created_at: string;
|
|
40
|
+
updated_at: string;
|
|
41
|
+
extra_modules?: Module[] | number[];
|
|
42
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
export interface User {
|
|
4
|
+
id: number;
|
|
5
|
+
last_login: string | null;
|
|
6
|
+
is_superuser: boolean;
|
|
7
|
+
username: string;
|
|
8
|
+
first_name: string;
|
|
9
|
+
last_name: string;
|
|
10
|
+
email: string;
|
|
11
|
+
is_staff: boolean;
|
|
12
|
+
is_active: boolean;
|
|
13
|
+
date_joined: string;
|
|
14
|
+
is_deleted: boolean;
|
|
15
|
+
deleted_at: string | null;
|
|
16
|
+
phonenumber: string | null;
|
|
17
|
+
groups: any[];
|
|
18
|
+
user_permissions: any[];
|
|
19
|
+
centers_access?: Array<{
|
|
20
|
+
id: number;
|
|
21
|
+
permissions?: number[];
|
|
22
|
+
}>;
|
|
23
|
+
}
|