@pagamio/frontend-commons-lib 0.8.197 → 0.8.199
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
CHANGED
|
@@ -39,6 +39,21 @@ yarn add pagamio-frontend-commons-lib
|
|
|
39
39
|
|
|
40
40
|
`import { DateInput, TextInput } from 'pagamio-frontend-commons-lib';`
|
|
41
41
|
|
|
42
|
+
### :gear: Environment Variables
|
|
43
|
+
|
|
44
|
+
Some utilities (for example the `useImageUpload` hook and `ImageUploader` component) require an API endpoint that issues presigned upload URLs. Make sure your host application exposes the following public environment variable before using those helpers:
|
|
45
|
+
|
|
46
|
+
| Variable | Description |
|
|
47
|
+
| --- | --- |
|
|
48
|
+
| `NEXT_PUBLIC_UPLOAD_URL_ENDPOINT` | HTTP endpoint that returns presigned URLs for file uploads. |
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# .env (per app)
|
|
52
|
+
NEXT_PUBLIC_UPLOAD_URL_ENDPOINT=https://<your-upload-service>/upload-url
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This value is resolved at runtime;
|
|
56
|
+
|
|
42
57
|
## :shield: **RBAC Module**
|
|
43
58
|
|
|
44
59
|
The Role-Based Access Control (RBAC) module provides a flexible system for implementing permission-based access control in your applications.
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Sidebar
|
|
3
|
-
import { HiSearch } from 'react-icons/hi';
|
|
2
|
+
import { Sidebar } from 'flowbite-react';
|
|
4
3
|
import { twMerge } from 'tailwind-merge';
|
|
5
4
|
import React, { useEffect, useState } from 'react';
|
|
6
5
|
import { useAppSidebarContext } from '../../context';
|
|
7
6
|
import { useLibTranslations, useTranslation } from '../../translations';
|
|
8
7
|
const AppSidebarMenu = () => {
|
|
9
8
|
const { pages } = useAppSidebarContext();
|
|
10
|
-
return (_jsx(Sidebar.Items, { children: _jsx(Sidebar.ItemGroup, { className:
|
|
9
|
+
return (_jsx(Sidebar.Items, { children: pages.map((section, index) => (_jsx(Sidebar.ItemGroup, { className: twMerge('mt-0 pb-2 pt-0', index > 0 && 'border-t border-gray-200 dark:border-gray-700 pt-4 mt-4'), children: section.items?.map((item) => _jsx(AppSidebarItem, { ...item }, item.label)) }, section.label || index))) }));
|
|
11
10
|
};
|
|
12
11
|
const AppSidebarItem = ({ href, target, icon, label, items, badge, forceDropdown }) => {
|
|
13
12
|
const { pathname, linkComponent: Link } = useAppSidebarContext();
|
|
@@ -50,15 +49,14 @@ const AppSidebarItem = ({ href, target, icon, label, items, badge, forceDropdown
|
|
|
50
49
|
'text-primary-700 bg-primary-100/80 hover:bg-primary-100/80 dark:text-primary-300 dark:bg-primary-900/40 dark:hover:bg-primary-900/40'), children: t(label) }));
|
|
51
50
|
};
|
|
52
51
|
const AppMobileSidebar = () => {
|
|
53
|
-
const { mobile: { isOpen, close }, sidebarHeader, } = useAppSidebarContext();
|
|
52
|
+
const { mobile: { isOpen, close }, sidebarHeader, sidebarFooter, } = useAppSidebarContext();
|
|
54
53
|
const { t } = useTranslation();
|
|
55
|
-
const { tLib } = useLibTranslations();
|
|
56
54
|
if (!isOpen)
|
|
57
55
|
return null;
|
|
58
|
-
return (_jsxs(_Fragment, { children: [_jsx(Sidebar, { "aria-label": t('sidebar.mobileAriaLabel', 'Sidebar with multi-level dropdown example'), className: twMerge('fixed inset-y-0 left-0 z-20 hidden h-full shrink-0 flex-col border-r border-gray-200 pt-16 dark:border-gray-700 md:flex', isOpen && 'flex'), id: "sidebar", children:
|
|
56
|
+
return (_jsxs(_Fragment, { children: [_jsx(Sidebar, { "aria-label": t('sidebar.mobileAriaLabel', 'Sidebar with multi-level dropdown example'), className: twMerge('fixed inset-y-0 left-0 z-20 hidden h-full shrink-0 flex-col border-r border-gray-200 pt-16 dark:border-gray-700 md:flex', isOpen && 'flex'), id: "sidebar", children: _jsxs("div", { className: "flex h-full flex-col justify-between", children: [_jsxs("div", { className: "flex-1 overflow-y-auto py-2", children: [sidebarHeader && _jsx("div", { className: "mb-4 px-3", children: sidebarHeader }), _jsx("div", { className: "px-3", children: _jsx(AppSidebarMenu, {}) })] }), sidebarFooter && _jsx("div", { className: "border-t border-gray-200 dark:border-gray-700 p-3", children: sidebarFooter })] }) }), _jsx("div", { onClick: close, "aria-hidden": "true", className: "fixed inset-0 z-10 h-full w-full bg-gray-900/50 pt-16 dark:bg-gray-900/90" })] }));
|
|
59
57
|
};
|
|
60
58
|
const AppDesktopSidebar = () => {
|
|
61
|
-
const { desktop: { isCollapsed, setCollapsed }, sidebarHeader, } = useAppSidebarContext();
|
|
59
|
+
const { desktop: { isCollapsed, setCollapsed }, sidebarHeader, sidebarFooter, } = useAppSidebarContext();
|
|
62
60
|
const [isPreview, setIsPreview] = useState(isCollapsed);
|
|
63
61
|
const { tLib } = useLibTranslations();
|
|
64
62
|
useEffect(() => {
|
|
@@ -78,7 +76,7 @@ const AppDesktopSidebar = () => {
|
|
|
78
76
|
setCollapsed(true);
|
|
79
77
|
},
|
|
80
78
|
};
|
|
81
|
-
return (_jsx(Sidebar, { onMouseEnter: preview.enable, onMouseLeave: preview.disable, "aria-label": tLib('sidebar.desktopAriaLabel', 'Sidebar with multi-level dropdown example'), collapsed: isCollapsed, className: twMerge('fixed inset-y-0 left-0 z-20 flex h-full shrink-0 flex-col border-r border-gray-200 pt-16 duration-75 dark:border-gray-700', isCollapsed && 'hidden w-16'), id: "sidebar", children:
|
|
79
|
+
return (_jsx(Sidebar, { onMouseEnter: preview.enable, onMouseLeave: preview.disable, "aria-label": tLib('sidebar.desktopAriaLabel', 'Sidebar with multi-level dropdown example'), collapsed: isCollapsed, className: twMerge('fixed inset-y-0 left-0 z-20 flex h-full shrink-0 flex-col border-r border-gray-200 pt-16 duration-75 dark:border-gray-700 bg-white dark:bg-gray-900', isCollapsed && 'hidden w-16'), id: "sidebar", children: _jsxs("div", { className: "flex h-full flex-col justify-between", children: [_jsxs("div", { className: "flex-1 overflow-y-auto py-2", children: [sidebarHeader && _jsx("div", { className: "mb-4 px-3", children: sidebarHeader }), _jsx("div", { className: "px-2", children: _jsx(AppSidebarMenu, {}) })] }), sidebarFooter && _jsx("div", { className: "border-t border-gray-200 dark:border-gray-700 p-3", children: sidebarFooter })] }) }));
|
|
82
80
|
};
|
|
83
81
|
const AppDashboardSidebar = () => {
|
|
84
82
|
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "md:hidden", children: _jsx(AppMobileSidebar, {}) }), _jsx("div", { className: "hidden md:block", children: _jsx(AppDesktopSidebar, {}) })] }));
|
|
@@ -4,8 +4,7 @@ import { Image as ImageIcon, Loader2, Upload, X } from 'lucide-react';
|
|
|
4
4
|
import { useDropzone } from 'react-dropzone';
|
|
5
5
|
import { useCallback, useState } from 'react';
|
|
6
6
|
import { Button, cn, useToast } from '../..';
|
|
7
|
-
import {
|
|
8
|
-
import { uploadFileWithXHR } from '../../shared/utils/functionHelper';
|
|
7
|
+
import { useImageUpload } from '../../shared/hooks/useImageUpload';
|
|
9
8
|
import { Progress } from './Progress';
|
|
10
9
|
const ImageUploader = ({ project, env, onUploadSuccess, onError, className, disabled = false, maxFileSize = 5 * 1024 * 1024, // 5MB default
|
|
11
10
|
acceptedFileTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'], placeholder = 'Click to upload or drag and drop an image', showPreview = true, value, onChange, }) => {
|
|
@@ -14,53 +13,7 @@ acceptedFileTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'], plac
|
|
|
14
13
|
const [uploadProgress, setUploadProgress] = useState(0);
|
|
15
14
|
const [error, setError] = useState(null);
|
|
16
15
|
const { addToast } = useToast();
|
|
17
|
-
const
|
|
18
|
-
const timestamp = Date.now();
|
|
19
|
-
const randomString = generateSecureRandomString();
|
|
20
|
-
const extension = originalName.split('.').pop() || 'jpg';
|
|
21
|
-
return `${timestamp}_${randomString}.${extension}`;
|
|
22
|
-
};
|
|
23
|
-
const getPresignedUrl = async (fileName, contentType) => {
|
|
24
|
-
// This should be configurable or injected, but for now using a default endpoint
|
|
25
|
-
const endpoint = process.env.NEXT_PUBLIC_UPLOAD_URL_ENDPOINT ||
|
|
26
|
-
'https://faas-ams3-2a2df116.doserverless.co/api/v1/web/fn-2c9f1cfc-1296-4367-89d8-12409763dae6/default/upload-url';
|
|
27
|
-
const response = await fetch(endpoint, {
|
|
28
|
-
method: 'POST',
|
|
29
|
-
headers: {
|
|
30
|
-
'Content-Type': 'application/json',
|
|
31
|
-
},
|
|
32
|
-
body: JSON.stringify({
|
|
33
|
-
project,
|
|
34
|
-
env,
|
|
35
|
-
fileName,
|
|
36
|
-
contentType,
|
|
37
|
-
}),
|
|
38
|
-
});
|
|
39
|
-
if (!response.ok) {
|
|
40
|
-
throw new Error(`Failed to get upload URL: ${response.statusText}`);
|
|
41
|
-
}
|
|
42
|
-
// --- FIX: unwrap .data if present ---
|
|
43
|
-
const json = await response.json();
|
|
44
|
-
const data = json.data || json;
|
|
45
|
-
return {
|
|
46
|
-
uploadURL: data.uploadURL,
|
|
47
|
-
publicURL: data.publicURL,
|
|
48
|
-
};
|
|
49
|
-
};
|
|
50
|
-
const uploadFile = async (file) => {
|
|
51
|
-
const fileName = generateFileName(file.name);
|
|
52
|
-
try {
|
|
53
|
-
// Get pre-signed URL
|
|
54
|
-
const { uploadURL, publicURL } = await getPresignedUrl(fileName, file.type);
|
|
55
|
-
// Upload file using XMLHttpRequest
|
|
56
|
-
await uploadFileWithXHR(uploadURL, file);
|
|
57
|
-
return publicURL;
|
|
58
|
-
}
|
|
59
|
-
catch (error) {
|
|
60
|
-
console.error('Upload error:', error);
|
|
61
|
-
throw error;
|
|
62
|
-
}
|
|
63
|
-
};
|
|
16
|
+
const { uploadFile } = useImageUpload({ project, env });
|
|
64
17
|
const handleFileUpload = async (file) => {
|
|
65
18
|
if (disabled)
|
|
66
19
|
return;
|
|
@@ -15,10 +15,11 @@ interface AppSidebarPageItem {
|
|
|
15
15
|
* @interface AppSidebarContextProps
|
|
16
16
|
* @property {Object} desktop - Desktop sidebar state and controls
|
|
17
17
|
* @property {Object} mobile - Mobile sidebar state and controls
|
|
18
|
-
* @property {AppSidebarPageItem[]} pages - Array of sidebar
|
|
18
|
+
* @property {AppSidebarPageItem[]} pages - Array of sidebar section items (each section has items property)
|
|
19
19
|
* @property {string} pathname - Current route pathname
|
|
20
20
|
* @property {React.ElementType} linkComponent - Component used for navigation links
|
|
21
21
|
* @property {React.ReactNode} sidebarHeader - Optional custom header to render at top of sidebar
|
|
22
|
+
* @property {React.ReactNode} sidebarFooter - Optional custom footer to render at bottom of sidebar
|
|
22
23
|
*/
|
|
23
24
|
interface AppSidebarContextProps {
|
|
24
25
|
desktop: {
|
|
@@ -35,16 +36,18 @@ interface AppSidebarContextProps {
|
|
|
35
36
|
pathname: string;
|
|
36
37
|
linkComponent: React.ElementType;
|
|
37
38
|
sidebarHeader?: React.ReactNode;
|
|
39
|
+
sidebarFooter?: React.ReactNode;
|
|
38
40
|
}
|
|
39
41
|
/**
|
|
40
42
|
* Props for the AppSidebarProvider component
|
|
41
43
|
* @interface AppSidebarProviderProps
|
|
42
44
|
* @property {boolean} initialCollapsed - Initial collapsed state of desktop sidebar
|
|
43
|
-
* @property {AppSidebarPageItem[]} pages - Array of sidebar
|
|
45
|
+
* @property {AppSidebarPageItem[]} pages - Array of sidebar section items (each section has items property)
|
|
44
46
|
* @property {string} pathname - Current route pathname
|
|
45
47
|
* @property {React.ElementType} linkComponent - Component used for navigation links
|
|
46
48
|
* @property {ReactNode} children - Child components
|
|
47
49
|
* @property {React.ReactNode} sidebarHeader - Optional custom header to render at top of sidebar
|
|
50
|
+
* @property {React.ReactNode} sidebarFooter - Optional custom footer to render at bottom of sidebar
|
|
48
51
|
*/
|
|
49
52
|
interface AppSidebarProviderProps extends PropsWithChildren {
|
|
50
53
|
initialCollapsed: boolean;
|
|
@@ -52,12 +55,13 @@ interface AppSidebarProviderProps extends PropsWithChildren {
|
|
|
52
55
|
pathname: string;
|
|
53
56
|
linkComponent: React.ElementType;
|
|
54
57
|
sidebarHeader?: React.ReactNode;
|
|
58
|
+
sidebarFooter?: React.ReactNode;
|
|
55
59
|
}
|
|
56
60
|
/**
|
|
57
61
|
* Provider component for sidebar state management
|
|
58
62
|
* @param {AppSidebarProviderProps} props - Component props
|
|
59
63
|
*/
|
|
60
|
-
declare const AppSidebarProvider: ({ initialCollapsed, children, pages, pathname, linkComponent, sidebarHeader, }: AppSidebarProviderProps) => import("react/jsx-runtime").JSX.Element;
|
|
64
|
+
declare const AppSidebarProvider: ({ initialCollapsed, children, pages, pathname, linkComponent, sidebarHeader, sidebarFooter, }: AppSidebarProviderProps) => import("react/jsx-runtime").JSX.Element;
|
|
61
65
|
/**
|
|
62
66
|
* Hook for accessing sidebar context
|
|
63
67
|
* @throws {Error} When used outside AppSidebarProvider
|
|
@@ -8,7 +8,7 @@ const AppSidebarContext = createContext(null);
|
|
|
8
8
|
* Provider component for sidebar state management
|
|
9
9
|
* @param {AppSidebarProviderProps} props - Component props
|
|
10
10
|
*/
|
|
11
|
-
const AppSidebarProvider = ({ initialCollapsed, children, pages, pathname, linkComponent, sidebarHeader, }) => {
|
|
11
|
+
const AppSidebarProvider = ({ initialCollapsed, children, pages, pathname, linkComponent, sidebarHeader, sidebarFooter, }) => {
|
|
12
12
|
const [isOpenMobile, setIsOpenMobile] = useState(false);
|
|
13
13
|
const [isCollapsed, setIsCollapsed] = useState(initialCollapsed);
|
|
14
14
|
function handleSetCollapsed(value) {
|
|
@@ -29,7 +29,8 @@ const AppSidebarProvider = ({ initialCollapsed, children, pages, pathname, linkC
|
|
|
29
29
|
pathname,
|
|
30
30
|
linkComponent,
|
|
31
31
|
sidebarHeader,
|
|
32
|
-
|
|
32
|
+
sidebarFooter,
|
|
33
|
+
}), [isCollapsed, isOpenMobile, pages, pathname, linkComponent, sidebarHeader, sidebarFooter]);
|
|
33
34
|
return _jsx(AppSidebarContext.Provider, { value: value, children: children });
|
|
34
35
|
};
|
|
35
36
|
/**
|
package/lib/styles.css
CHANGED
|
@@ -2208,9 +2208,6 @@ input[type="range"]::-ms-fill-lower {
|
|
|
2208
2208
|
.border-t {
|
|
2209
2209
|
border-top-width: 1px;
|
|
2210
2210
|
}
|
|
2211
|
-
.border-t-0 {
|
|
2212
|
-
border-top-width: 0px;
|
|
2213
|
-
}
|
|
2214
2211
|
.border-t-4 {
|
|
2215
2212
|
border-top-width: 4px;
|
|
2216
2213
|
}
|
|
@@ -2923,8 +2920,8 @@ input[type="range"]::-ms-fill-lower {
|
|
|
2923
2920
|
.pb-0 {
|
|
2924
2921
|
padding-bottom: 0px;
|
|
2925
2922
|
}
|
|
2926
|
-
.pb-
|
|
2927
|
-
padding-bottom: 0.
|
|
2923
|
+
.pb-2 {
|
|
2924
|
+
padding-bottom: 0.5rem;
|
|
2928
2925
|
}
|
|
2929
2926
|
.pb-2\.5 {
|
|
2930
2927
|
padding-bottom: 0.625rem;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pagamio/frontend-commons-lib",
|
|
3
3
|
"description": "Pagamio library for Frontend reusable components like the form engine and table container",
|
|
4
|
-
"version": "0.8.
|
|
4
|
+
"version": "0.8.199",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
7
7
|
"provenance": false
|