Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1501c0ca63 | ||
|
|
29d4007e04 | ||
|
|
192c6c627e | ||
|
|
2900cab17a |
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,3 +1,8 @@
|
||||
node_modules/
|
||||
*/node_modules/
|
||||
*/build/
|
||||
|
||||
**/node_modules/
|
||||
**/build/
|
||||
.DS_Store
|
||||
.env
|
||||
File diff suppressed because one or more lines are too long
@ -114,10 +114,10 @@ app.enable('trust proxy');
|
||||
|
||||
app.use(
|
||||
'/api/users',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
usersRoutes,
|
||||
);
|
||||
|
||||
|
||||
app.use(
|
||||
'/api/demo_requests',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
|
||||
@ -12,7 +12,6 @@ const { parse } = require('json2csv');
|
||||
|
||||
const { checkCrudPermissions } = require('../middlewares/check-permissions');
|
||||
|
||||
router.use(checkCrudPermissions('users'));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
@ -307,9 +306,8 @@ router.get(
|
||||
wrapAsync(async (req, res) => {
|
||||
const filetype = req.query.filetype;
|
||||
|
||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
||||
|
||||
const currentUser = req.currentUser;
|
||||
const globalAccess = req.currentUser?.app_role?.globalAccess || false;
|
||||
const currentUser = req.currentUser || null;
|
||||
const payload = await UsersDBApi.findAll(req.query, globalAccess, {
|
||||
currentUser,
|
||||
});
|
||||
|
||||
1
frontend/json/runtimeError.json
Normal file
1
frontend/json/runtimeError.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
||||
@ -50,6 +50,11 @@ const nextConfig = {
|
||||
source: '/pricing',
|
||||
destination: '/web_pages/pricing',
|
||||
},
|
||||
|
||||
{
|
||||
source: '/users',
|
||||
destination: '/web_pages/users',
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
@ -6,6 +6,8 @@ import NavBarItemPlain from './NavBarItemPlain';
|
||||
import NavBarMenuList from './NavBarMenuList';
|
||||
import { MenuNavBarItem } from '../interfaces';
|
||||
import { useAppSelector } from '../stores/hooks';
|
||||
import { ThemeSwitcher } from './ThemeSwitcher';
|
||||
|
||||
|
||||
type Props = {
|
||||
menu: MenuNavBarItem[];
|
||||
@ -42,6 +44,10 @@ export default function NavBar({ menu, className = '', children }: Props) {
|
||||
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-none items-stretch flex h-14 lg:hidden'>
|
||||
<NavBarItemPlain onClick={handleMenuNavBarToggleClick}>
|
||||
|
||||
52
frontend/src/components/ThemeSwitcher.tsx
Normal file
52
frontend/src/components/ThemeSwitcher.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@ -114,7 +114,7 @@ export interface User {
|
||||
notes: any[];
|
||||
}
|
||||
|
||||
export type StyleKey = 'white' | 'basic';
|
||||
export type StyleKey = 'white' | 'basic' | 'minimized' | 'facebook';
|
||||
|
||||
export type UserForm = {
|
||||
name: string;
|
||||
|
||||
@ -67,6 +67,11 @@ export const webPagesNavBar = [
|
||||
href: '/pricing',
|
||||
label: 'pricing',
|
||||
},
|
||||
{
|
||||
href: '/users',
|
||||
label: 'users',
|
||||
},
|
||||
|
||||
];
|
||||
|
||||
export default menuNavBar;
|
||||
|
||||
@ -15,8 +15,8 @@ import { hasPermission } from '../helpers/userPermissions';
|
||||
import { fetchWidgets } from '../stores/roles/rolesSlice';
|
||||
import { WidgetCreator } from '../components/WidgetCreator/WidgetCreator';
|
||||
import { SmartWidget } from '../components/SmartWidget/SmartWidget';
|
||||
|
||||
import { useAppDispatch, useAppSelector } from '../stores/hooks';
|
||||
|
||||
const Dashboard = () => {
|
||||
const { t } = useTranslation('common');
|
||||
const dispatch = useAppDispatch();
|
||||
@ -24,11 +24,17 @@ const Dashboard = () => {
|
||||
const corners = useAppSelector((state) => state.style.corners);
|
||||
const cardsStyle = useAppSelector((state) => state.style.cardsStyle);
|
||||
|
||||
const loadingMessage = t('pages.dashboard.loading', {
|
||||
defaultValue: 'Loading...',
|
||||
});
|
||||
{/* Theme switcher relocated to NavBar */}
|
||||
<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 [notifications, setNotifications] = React.useState(loadingMessage);
|
||||
const [reports, setReports] = React.useState(loadingMessage);
|
||||
|
||||
70
frontend/src/pages/web_pages/users.tsx
Normal file
70
frontend/src/pages/web_pages/users.tsx
Normal 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>;
|
||||
};
|
||||
@ -31,30 +31,30 @@ interface StyleState {
|
||||
}
|
||||
|
||||
const initialState: StyleState = {
|
||||
asideStyle: styles.white.aside,
|
||||
asideScrollbarsStyle: styles.white.asideScrollbars,
|
||||
asideBrandStyle: styles.white.asideBrand,
|
||||
asideMenuItemStyle: styles.white.asideMenuItem,
|
||||
asideMenuItemActiveStyle: styles.white.asideMenuItemActive,
|
||||
asideMenuDropdownStyle: styles.white.asideMenuDropdown,
|
||||
navBarItemLabelStyle: styles.white.navBarItemLabel,
|
||||
navBarItemLabelHoverStyle: styles.white.navBarItemLabelHover,
|
||||
navBarItemLabelActiveColorStyle: styles.white.navBarItemLabelActiveColor,
|
||||
overlayStyle: styles.white.overlay,
|
||||
asideStyle: styles.minimized.aside,
|
||||
asideScrollbarsStyle: styles.minimized.asideScrollbars,
|
||||
asideBrandStyle: styles.minimized.asideBrand,
|
||||
asideMenuItemStyle: styles.minimized.asideMenuItem,
|
||||
asideMenuItemActiveStyle: styles.minimized.asideMenuItemActive,
|
||||
asideMenuDropdownStyle: styles.minimized.asideMenuDropdown,
|
||||
navBarItemLabelStyle: styles.minimized.navBarItemLabel,
|
||||
navBarItemLabelHoverStyle: styles.minimized.navBarItemLabelHover,
|
||||
navBarItemLabelActiveColorStyle: styles.minimized.navBarItemLabelActiveColor,
|
||||
overlayStyle: styles.minimized.overlay,
|
||||
darkMode: false,
|
||||
bgLayoutColor: styles.white.bgLayoutColor,
|
||||
iconsColor: styles.white.iconsColor,
|
||||
activeLinkColor: styles.white.activeLinkColor,
|
||||
cardsColor: styles.white.cardsColor,
|
||||
focusRingColor: styles.white.focusRingColor,
|
||||
corners: styles.white.corners,
|
||||
cardsStyle: styles.white.cardsStyle,
|
||||
linkColor: styles.white.linkColor,
|
||||
websiteHeder: styles.white.websiteHeder,
|
||||
borders: styles.white.borders,
|
||||
shadow: styles.white.shadow,
|
||||
websiteSectionStyle: styles.white.websiteSectionStyle,
|
||||
textSecondary: styles.white.textSecondary,
|
||||
bgLayoutColor: styles.minimized.bgLayoutColor,
|
||||
iconsColor: styles.minimized.iconsColor,
|
||||
activeLinkColor: styles.minimized.activeLinkColor,
|
||||
cardsColor: styles.minimized.cardsColor,
|
||||
focusRingColor: styles.minimized.focusRingColor,
|
||||
corners: styles.minimized.corners,
|
||||
cardsStyle: styles.minimized.cardsStyle,
|
||||
linkColor: styles.minimized.linkColor,
|
||||
websiteHeder: styles.minimized.websiteHeder,
|
||||
borders: styles.minimized.borders,
|
||||
shadow: styles.minimized.shadow,
|
||||
websiteSectionStyle: styles.minimized.websiteSectionStyle,
|
||||
textSecondary: styles.minimized.textSecondary,
|
||||
};
|
||||
|
||||
export const styleSlice = createSlice({
|
||||
|
||||
@ -104,3 +104,55 @@ export const basic: StyleObject = {
|
||||
websiteSectionStyle: '',
|
||||
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]',
|
||||
};
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user