@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, TextInput } from 'flowbite-react';
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: "mt-0 border-t-0 pb-1 pt-0", children: pages.map((item) => (_jsx(AppSidebarItem, { ...item }, item.label))) }) }));
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: _jsx("div", { className: "flex h-full flex-col justify-between", children: _jsxs("div", { className: "py-2", children: [sidebarHeader && _jsx("div", { className: "mb-4 px-3", children: sidebarHeader }), _jsx("form", { className: "pb-3", children: _jsx(TextInput, { icon: HiSearch, type: "search", placeholder: tLib('sidebar.searchPlaceholder', 'Search'), required: true, size: 32 }) }), _jsx(AppSidebarMenu, {})] }) }) }), _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" })] }));
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: _jsx("div", { className: "flex h-full flex-col justify-between", children: _jsxs("div", { className: "py-2", children: [sidebarHeader && _jsx("div", { className: "mb-4 px-3", children: sidebarHeader }), _jsx(AppSidebarMenu, {})] }) }) }));
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 { generateSecureRandomString } from '../../shared';
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 generateFileName = (originalName) => {
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 page items
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 page items
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
- }), [isCollapsed, isOpenMobile, pages, pathname, linkComponent, sidebarHeader]);
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-1 {
2927
- padding-bottom: 0.25rem;
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.197",
4
+ "version": "0.8.199",
5
5
  "publishConfig": {
6
6
  "access": "public",
7
7
  "provenance": false