Compare commits

..

4 Commits

Author SHA1 Message Date
Flatlogic Bot
1501c0ca63 avatars fr cards 2025-05-19 14:18:57 +00:00
Flatlogic Bot
29d4007e04 users card 2025-05-19 14:12:06 +00:00
Flatlogic Bot
192c6c627e users 2025-05-19 13:36:36 +00:00
Flatlogic Bot
2900cab17a theme switcher 2025-05-19 12:43:48 +00:00
14 changed files with 240 additions and 38 deletions

5
.gitignore vendored
View File

@ -1,3 +1,8 @@
node_modules/ node_modules/
*/node_modules/ */node_modules/
*/build/ */build/
**/node_modules/
**/build/
.DS_Store
.env

File diff suppressed because one or more lines are too long

View File

@ -114,10 +114,10 @@ app.enable('trust proxy');
app.use( app.use(
'/api/users', '/api/users',
passport.authenticate('jwt', { session: false }),
usersRoutes, usersRoutes,
); );
app.use( app.use(
'/api/demo_requests', '/api/demo_requests',
passport.authenticate('jwt', { session: false }), passport.authenticate('jwt', { session: false }),

View File

@ -12,7 +12,6 @@ const { parse } = require('json2csv');
const { checkCrudPermissions } = require('../middlewares/check-permissions'); const { checkCrudPermissions } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('users'));
/** /**
* @swagger * @swagger
@ -307,9 +306,8 @@ router.get(
wrapAsync(async (req, res) => { wrapAsync(async (req, res) => {
const filetype = req.query.filetype; const filetype = req.query.filetype;
const globalAccess = req.currentUser.app_role.globalAccess; const globalAccess = req.currentUser?.app_role?.globalAccess || false;
const currentUser = req.currentUser || null;
const currentUser = req.currentUser;
const payload = await UsersDBApi.findAll(req.query, globalAccess, { const payload = await UsersDBApi.findAll(req.query, globalAccess, {
currentUser, currentUser,
}); });

View File

@ -0,0 +1 @@
{}

View File

@ -50,6 +50,11 @@ const nextConfig = {
source: '/pricing', source: '/pricing',
destination: '/web_pages/pricing', destination: '/web_pages/pricing',
}, },
{
source: '/users',
destination: '/web_pages/users',
},
]; ];
}, },
}; };

View File

@ -6,6 +6,8 @@ import NavBarItemPlain from './NavBarItemPlain';
import NavBarMenuList from './NavBarMenuList'; import NavBarMenuList from './NavBarMenuList';
import { MenuNavBarItem } from '../interfaces'; import { MenuNavBarItem } from '../interfaces';
import { useAppSelector } from '../stores/hooks'; import { useAppSelector } from '../stores/hooks';
import { ThemeSwitcher } from './ThemeSwitcher';
type Props = { type Props = {
menu: MenuNavBarItem[]; menu: MenuNavBarItem[];
@ -42,6 +44,10 @@ export default function NavBar({ menu, className = '', children }: Props) {
isScrolled && `border-b border-pavitra-400 dark:border-dark-700` isScrolled && `border-b border-pavitra-400 dark:border-dark-700`
}`} }`}
> >
<div className="hidden lg:flex items-center px-4 h-14">
<ThemeSwitcher />
</div>
<div className='flex flex-1 items-stretch h-14'>{children}</div> <div className='flex flex-1 items-stretch h-14'>{children}</div>
<div className='flex-none items-stretch flex h-14 lg:hidden'> <div className='flex-none items-stretch flex h-14 lg:hidden'>
<NavBarItemPlain onClick={handleMenuNavBarToggleClick}> <NavBarItemPlain onClick={handleMenuNavBarToggleClick}>

View File

@ -0,0 +1,52 @@
import React, { useEffect, useState } from 'react';
import { useAppDispatch } from '../stores/hooks';
import { setStyle } from '../stores/styleSlice';
import { StyleKey } from '../interfaces';
import { localStorageStyleKey } from '../config';
const availableThemes: { key: StyleKey; label: string }[] = [
{ key: 'white', label: 'White' },
{ key: 'basic', label: 'Basic' },
{ key: 'minimized', label: 'Min.' },
{ key: 'facebook', label: 'FB' },
];
export const ThemeSwitcher = (): JSX.Element => {
const dispatch = useAppDispatch();
const [theme, setThemeState] = useState<StyleKey>('minimized');
useEffect(() => {
if (typeof localStorage !== 'undefined') {
const saved = (localStorage.getItem(localStorageStyleKey) as StyleKey) || 'minimized';
setThemeState(saved);
dispatch(setStyle(saved));
}
}, [dispatch]);
const handleSelect = (key: StyleKey) => {
setThemeState(key);
localStorage.setItem(localStorageStyleKey, key);
dispatch(setStyle(key));
};
return (
<div className="flex items-center">
<div className="bg-white dark:bg-dark-700 rounded-full shadow px-1 py-1 flex space-x-1">
{availableThemes.map(({ key, label }) => (
<button
key={key}
onClick={() => handleSelect(key)}
className={`rounded-full px-3 py-1 text-xs font-medium transition-colors focus:outline-none
${
theme === key
? 'bg-blue-500 text-white'
: 'bg-transparent text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-dark-600'
}`}
>
{label}
</button>
))}
</div>
</div>
);
};

View File

@ -114,7 +114,7 @@ export interface User {
notes: any[]; notes: any[];
} }
export type StyleKey = 'white' | 'basic'; export type StyleKey = 'white' | 'basic' | 'minimized' | 'facebook';
export type UserForm = { export type UserForm = {
name: string; name: string;

View File

@ -67,6 +67,11 @@ export const webPagesNavBar = [
href: '/pricing', href: '/pricing',
label: 'pricing', label: 'pricing',
}, },
{
href: '/users',
label: 'users',
},
]; ];
export default menuNavBar; export default menuNavBar;

View File

@ -15,8 +15,8 @@ import { hasPermission } from '../helpers/userPermissions';
import { fetchWidgets } from '../stores/roles/rolesSlice'; import { fetchWidgets } from '../stores/roles/rolesSlice';
import { WidgetCreator } from '../components/WidgetCreator/WidgetCreator'; import { WidgetCreator } from '../components/WidgetCreator/WidgetCreator';
import { SmartWidget } from '../components/SmartWidget/SmartWidget'; import { SmartWidget } from '../components/SmartWidget/SmartWidget';
import { useAppDispatch, useAppSelector } from '../stores/hooks'; import { useAppDispatch, useAppSelector } from '../stores/hooks';
const Dashboard = () => { const Dashboard = () => {
const { t } = useTranslation('common'); const { t } = useTranslation('common');
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -24,11 +24,17 @@ const Dashboard = () => {
const corners = useAppSelector((state) => state.style.corners); const corners = useAppSelector((state) => state.style.corners);
const cardsStyle = useAppSelector((state) => state.style.cardsStyle); const cardsStyle = useAppSelector((state) => state.style.cardsStyle);
const loadingMessage = t('pages.dashboard.loading', { {/* Theme switcher relocated to NavBar */}
defaultValue: 'Loading...', <div className='mb-4 flex items-center'>
}); <h2 className='text-xl font-semibold text-gray-800 dark:text-gray-200 flex-1'>
{t('Your workspaces')}
</h2>
{/* <ThemeSwitcher /> */}
</div>
const loadingMessage: number | string | null = null;
const [users, setUsers] = React.useState<number | string | null>(loadingMessage);
const [users, setUsers] = React.useState(loadingMessage);
const [demo_requests, setDemo_requests] = React.useState(loadingMessage); const [demo_requests, setDemo_requests] = React.useState(loadingMessage);
const [notifications, setNotifications] = React.useState(loadingMessage); const [notifications, setNotifications] = React.useState(loadingMessage);
const [reports, setReports] = React.useState(loadingMessage); const [reports, setReports] = React.useState(loadingMessage);

View File

@ -0,0 +1,70 @@
import React, { useEffect, useState } from 'react';
import type { ReactElement } from 'react';
import Head from 'next/head';
import LayoutGuest from '../../layouts/Guest';
import WebSiteHeader from '../../components/WebPageComponents/Header';
import WebSiteFooter from '../../components/WebPageComponents/Footer';
type User = {
id: string;
firstName: string;
lastName: string;
email: string;
avatar?: { url: string }[];
};
export default function Users() {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('/api/users')
.then((res) => res.json())
.then((data) => {
setUsers(data.rows);
setLoading(false);
})
.catch(() => setLoading(false));
}, []);
return (
<div className="flex flex-col min-h-screen">
<Head>
<title>Users - Strategic Intelligence Engine</title>
<meta
name="description"
content="Public list of users for the Strategic Intelligence Engine."
/>
</Head>
<WebSiteHeader projectName="IntelliLedger Consulting" />
<main className="flex-grow bg-white px-4 py-8">
<section className="max-w-4xl mx-auto">
<h1 className="text-4xl font-bold mb-4">Users</h1>
{loading ? (
<p>Loading users...</p>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
{users.map((user) => (
<div key={user.id} className="bg-white shadow p-4 rounded-lg">
<img src={user.avatar?.[0]?.url || '/favicon.svg'} alt={`${user.firstName} ${user.lastName}`} className="w-12 h-12 rounded-full mb-2 object-cover" />
<p className="font-medium text-lg">{user.firstName} {user.lastName}</p>
<p className="text-sm text-gray-500">{user.email}</p>
</div>
))}
</div>
)}
</section>
</main>
<WebSiteFooter projectName="IntelliLedger Consulting" />
</div>
);
}
Users.getLayout = function getLayout(page: ReactElement) {
return <LayoutGuest>{page}</LayoutGuest>;
};

View File

@ -31,30 +31,30 @@ interface StyleState {
} }
const initialState: StyleState = { const initialState: StyleState = {
asideStyle: styles.white.aside, asideStyle: styles.minimized.aside,
asideScrollbarsStyle: styles.white.asideScrollbars, asideScrollbarsStyle: styles.minimized.asideScrollbars,
asideBrandStyle: styles.white.asideBrand, asideBrandStyle: styles.minimized.asideBrand,
asideMenuItemStyle: styles.white.asideMenuItem, asideMenuItemStyle: styles.minimized.asideMenuItem,
asideMenuItemActiveStyle: styles.white.asideMenuItemActive, asideMenuItemActiveStyle: styles.minimized.asideMenuItemActive,
asideMenuDropdownStyle: styles.white.asideMenuDropdown, asideMenuDropdownStyle: styles.minimized.asideMenuDropdown,
navBarItemLabelStyle: styles.white.navBarItemLabel, navBarItemLabelStyle: styles.minimized.navBarItemLabel,
navBarItemLabelHoverStyle: styles.white.navBarItemLabelHover, navBarItemLabelHoverStyle: styles.minimized.navBarItemLabelHover,
navBarItemLabelActiveColorStyle: styles.white.navBarItemLabelActiveColor, navBarItemLabelActiveColorStyle: styles.minimized.navBarItemLabelActiveColor,
overlayStyle: styles.white.overlay, overlayStyle: styles.minimized.overlay,
darkMode: false, darkMode: false,
bgLayoutColor: styles.white.bgLayoutColor, bgLayoutColor: styles.minimized.bgLayoutColor,
iconsColor: styles.white.iconsColor, iconsColor: styles.minimized.iconsColor,
activeLinkColor: styles.white.activeLinkColor, activeLinkColor: styles.minimized.activeLinkColor,
cardsColor: styles.white.cardsColor, cardsColor: styles.minimized.cardsColor,
focusRingColor: styles.white.focusRingColor, focusRingColor: styles.minimized.focusRingColor,
corners: styles.white.corners, corners: styles.minimized.corners,
cardsStyle: styles.white.cardsStyle, cardsStyle: styles.minimized.cardsStyle,
linkColor: styles.white.linkColor, linkColor: styles.minimized.linkColor,
websiteHeder: styles.white.websiteHeder, websiteHeder: styles.minimized.websiteHeder,
borders: styles.white.borders, borders: styles.minimized.borders,
shadow: styles.white.shadow, shadow: styles.minimized.shadow,
websiteSectionStyle: styles.white.websiteSectionStyle, websiteSectionStyle: styles.minimized.websiteSectionStyle,
textSecondary: styles.white.textSecondary, textSecondary: styles.minimized.textSecondary,
}; };
export const styleSlice = createSlice({ export const styleSlice = createSlice({

View File

@ -104,3 +104,55 @@ export const basic: StyleObject = {
websiteSectionStyle: '', websiteSectionStyle: '',
textSecondary: '', textSecondary: '',
}; };
export const minimized: StyleObject = {
aside: 'bg-gray-50 dark:bg-gray-900 dark:text-gray-400 p-1',
asideScrollbars: 'aside-scrollbars-light',
asideBrand: '',
asideMenuItem: 'text-gray-600 dark:text-gray-400 p-0.5 hover:bg-gray-100 dark:hover:bg-dark-800',
asideMenuItemActive: 'font-medium text-gray-800 dark:text-gray-100',
asideMenuDropdown: 'bg-gray-100/50',
navBarItemLabel: 'text-gray-600 text-xs',
navBarItemLabelHover: 'hover:text-gray-700',
navBarItemLabelActiveColor: 'text-gray-900',
overlay: '',
activeLinkColor: 'bg-gray-50/20',
bgLayoutColor: 'bg-gray-50',
iconsColor: 'text-gray-700',
cardsColor: 'bg-gray-50',
focusRingColor: 'focus:outline-none focus:ring-1 focus:ring-gray-500',
corners: 'rounded-sm',
cardsStyle: 'bg-gray-50 border border-gray-200 !p-2',
linkColor: 'text-gray-700',
websiteHeder: 'border-b border-gray-100',
borders: 'border-gray-100',
shadow: '',
websiteSectionStyle: '',
textSecondary: 'text-gray-600 text-xs',
};
export const facebook: StyleObject = {
aside: 'bg-[#F0F2F5] text-[#050505]',
asideScrollbars: 'aside-scrollbars-light',
asideBrand: '',
asideMenuItem: 'text-[#050505] hover:bg-[#e4e6eb]',
asideMenuItemActive: 'font-bold text-[#1877F2]',
asideMenuDropdown: 'bg-[#e4e6eb]',
navBarItemLabel: 'text-[#1877F2]',
navBarItemLabelHover: 'hover:text-[#155DB2]',
navBarItemLabelActiveColor: 'text-[#155DB2]',
overlay: '',
activeLinkColor: 'bg-[#e4e6eb]',
bgLayoutColor: 'bg-[#F0F2F5]',
iconsColor: 'text-[#1877F2]',
cardsColor: 'bg-white',
focusRingColor: 'focus:ring focus:ring-[#1877F2] focus:border-[#1877F2]',
corners: 'rounded',
cardsStyle: 'bg-white border border-[#e4e6eb]',
linkColor: 'text-[#1877F2]',
websiteHeder: 'border-b border-[#e4e6eb]',
borders: 'border-[#e4e6eb]',
shadow: '',
websiteSectionStyle: '',
textSecondary: 'text-[#65676B]',
};