Merge branch 'feature/project-finance' into finance-module
This commit is contained in:
@@ -47,12 +47,17 @@ FRONTEND_URL=http://localhost:5000
|
|||||||
# STORAGE
|
# STORAGE
|
||||||
STORAGE_PROVIDER=s3 # values s3 or azure
|
STORAGE_PROVIDER=s3 # values s3 or azure
|
||||||
|
|
||||||
# AWS
|
# AWS - SES
|
||||||
AWS_REGION="your_aws_region"
|
AWS_REGION="your_aws_region"
|
||||||
AWS_ACCESS_KEY_ID="your_aws_access_key_id"
|
AWS_ACCESS_KEY_ID="your_aws_access_key_id"
|
||||||
AWS_SECRET_ACCESS_KEY="your_aws_secret_access_key"
|
AWS_SECRET_ACCESS_KEY="your_aws_secret_access_key"
|
||||||
AWS_BUCKET="your_s3_bucket"
|
|
||||||
|
# S3
|
||||||
|
S3_REGION="S3_REGION"
|
||||||
|
S3_BUCKET="your_s3_bucket"
|
||||||
S3_URL="your_s3_url"
|
S3_URL="your_s3_url"
|
||||||
|
S3_ACCESS_KEY_ID="S3_ACCESS_KEY_ID"
|
||||||
|
S3_SECRET_ACCESS_KEY="S3_SECRET_ACCESS_KEY"
|
||||||
|
|
||||||
# Azure Storage
|
# Azure Storage
|
||||||
AZURE_STORAGE_ACCOUNT_NAME="your_storage_account_name"
|
AZURE_STORAGE_ACCOUNT_NAME="your_storage_account_name"
|
||||||
|
|||||||
@@ -117,11 +117,11 @@ export const TASK_DUE_NO_DUE_COLOR = "#a9a9a9";
|
|||||||
export const DEFAULT_PAGE_SIZE = 20;
|
export const DEFAULT_PAGE_SIZE = 20;
|
||||||
|
|
||||||
// S3 Credentials
|
// S3 Credentials
|
||||||
export const REGION = process.env.AWS_REGION || "us-east-1";
|
export const REGION = process.env.S3_REGION || "us-east-1";
|
||||||
export const BUCKET = process.env.AWS_BUCKET || "your-bucket-name";
|
export const BUCKET = process.env.S3_BUCKET || "your-bucket-name";
|
||||||
export const S3_URL = process.env.S3_URL || "https://your-s3-url";
|
export const S3_URL = process.env.S3_URL || "https://your-s3-url";
|
||||||
export const S3_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID || "";
|
export const S3_ACCESS_KEY_ID = process.env.S3_ACCESS_KEY_ID || "";
|
||||||
export const S3_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY || "";
|
export const S3_SECRET_ACCESS_KEY = process.env.S3_SECRET_ACCESS_KEY || "";
|
||||||
|
|
||||||
// Azure Blob Storage Credentials
|
// Azure Blob Storage Credentials
|
||||||
export const STORAGE_PROVIDER = process.env.STORAGE_PROVIDER || "s3";
|
export const STORAGE_PROVIDER = process.env.STORAGE_PROVIDER || "s3";
|
||||||
|
|||||||
@@ -1,26 +1,53 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<link rel="icon" href="./favicon.ico" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<meta name="theme-color" content="#2b2b2b" />
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
||||||
<link
|
|
||||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap"
|
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
<title>Worklenz</title>
|
|
||||||
<!-- Environment configuration -->
|
|
||||||
<script src="/env-config.js"></script>
|
|
||||||
<!-- Unregister service worker -->
|
|
||||||
<script src="/unregister-sw.js"></script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
<head>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<meta charset="utf-8" />
|
||||||
<div id="root"></div>
|
<link rel="icon" href="./favicon.ico" />
|
||||||
<script type="module" src="./src/index.tsx"></script>
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
</body>
|
<meta name="theme-color" content="#2b2b2b" />
|
||||||
</html>
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap"
|
||||||
|
rel="stylesheet" />
|
||||||
|
<title>Worklenz</title>
|
||||||
|
|
||||||
|
<!-- Environment configuration -->
|
||||||
|
<script src="/env-config.js"></script>
|
||||||
|
<!-- Unregister service worker -->
|
||||||
|
<script src="/unregister-sw.js"></script>
|
||||||
|
<!-- Microsoft Clarity -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
if (window.location.hostname === 'app.worklenz.com') {
|
||||||
|
(function (c, l, a, r, i, t, y) {
|
||||||
|
c[a] = c[a] || function () { (c[a].q = c[a].q || []).push(arguments) };
|
||||||
|
t = l.createElement(r); t.async = 1; t.src = "https://www.clarity.ms/tag/dx77073klh";
|
||||||
|
y = l.getElementsByTagName(r)[0]; y.parentNode.insertBefore(t, y);
|
||||||
|
})(window, document, "clarity", "script", "dx77073klh");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<!-- Google Analytics (only on production) -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
if (window.location.hostname === 'app.worklenz.com') {
|
||||||
|
var gaScript = document.createElement('script');
|
||||||
|
gaScript.async = true;
|
||||||
|
gaScript.src = 'https://www.googletagmanager.com/gtag/js?id=G-7KSRKQ1397';
|
||||||
|
document.head.appendChild(gaScript);
|
||||||
|
|
||||||
|
gaScript.onload = function() {
|
||||||
|
window.dataLayer = window.dataLayer || [];
|
||||||
|
function gtag(){dataLayer.push(arguments);}
|
||||||
|
gtag('js', new Date());
|
||||||
|
gtag('config', 'G-7KSRKQ1397');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="./src/index.tsx"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"title": "Appearance",
|
||||||
|
"darkMode": "Dark Mode",
|
||||||
|
"darkModeDescription": "Switch between light and dark mode to customize your viewing experience."
|
||||||
|
}
|
||||||
@@ -10,5 +10,6 @@
|
|||||||
"team-members": "Team Members",
|
"team-members": "Team Members",
|
||||||
"teams": "Teams",
|
"teams": "Teams",
|
||||||
"change-password": "Change Password",
|
"change-password": "Change Password",
|
||||||
"language-and-region": "Language and Region"
|
"language-and-region": "Language and Region",
|
||||||
|
"appearance": "Appearance"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"title": "Apariencia",
|
||||||
|
"darkMode": "Modo Oscuro",
|
||||||
|
"darkModeDescription": "Cambia entre el modo claro y oscuro para personalizar tu experiencia visual."
|
||||||
|
}
|
||||||
@@ -10,5 +10,6 @@
|
|||||||
"team-members": "Miembros del equipo",
|
"team-members": "Miembros del equipo",
|
||||||
"teams": "Equipos",
|
"teams": "Equipos",
|
||||||
"change-password": "Cambiar contraseña",
|
"change-password": "Cambiar contraseña",
|
||||||
"language-and-region": "Idioma y región"
|
"language-and-region": "Idioma y región",
|
||||||
|
"appearance": "Apariencia"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"title": "Aparência",
|
||||||
|
"darkMode": "Modo Escuro",
|
||||||
|
"darkModeDescription": "Alterne entre o modo claro e escuro para personalizar sua experiência de visualização."
|
||||||
|
}
|
||||||
@@ -10,5 +10,6 @@
|
|||||||
"team-members": "Membros da Equipe",
|
"team-members": "Membros da Equipe",
|
||||||
"teams": "Equipes",
|
"teams": "Equipes",
|
||||||
"change-password": "Alterar Senha",
|
"change-password": "Alterar Senha",
|
||||||
"language-and-region": "Idioma e Região"
|
"language-and-region": "Idioma e Região",
|
||||||
|
"appearance": "Aparência"
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,6 @@ const App: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|||||||
<Suspense fallback={<SuspenseFallback />}>
|
<Suspense fallback={<SuspenseFallback />}>
|
||||||
<ThemeWrapper>
|
<ThemeWrapper>
|
||||||
<RouterProvider router={router} future={{ v7_startTransition: true }} />
|
<RouterProvider router={router} future={{ v7_startTransition: true }} />
|
||||||
<PreferenceSelector />
|
|
||||||
</ThemeWrapper>
|
</ThemeWrapper>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ const mainRoutes: RouteObject[] = [
|
|||||||
path: '/worklenz',
|
path: '/worklenz',
|
||||||
element: <MainLayout />,
|
element: <MainLayout />,
|
||||||
children: [
|
children: [
|
||||||
|
{ index: true, element: <Navigate to="home" replace /> },
|
||||||
{ path: 'home', element: <HomePage /> },
|
{ path: 'home', element: <HomePage /> },
|
||||||
{ path: 'projects', element: <ProjectList /> },
|
{ path: 'projects', element: <ProjectList /> },
|
||||||
{
|
{
|
||||||
|
|||||||
BIN
worklenz-frontend/src/assets/images/empty-box.webp
Normal file
BIN
worklenz-frontend/src/assets/images/empty-box.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
BIN
worklenz-frontend/src/assets/images/worklenz-dark-mode.png
Normal file
BIN
worklenz-frontend/src/assets/images/worklenz-dark-mode.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.3 KiB |
BIN
worklenz-frontend/src/assets/images/worklenz-light-mode.png
Normal file
BIN
worklenz-frontend/src/assets/images/worklenz-light-mode.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.1 KiB |
@@ -1,6 +1,6 @@
|
|||||||
import { Flex, Typography } from 'antd';
|
import { Flex, Typography } from 'antd';
|
||||||
import logo from '../assets/images/logo.png';
|
import logo from '@/assets/images/worklenz-light-mode.png';
|
||||||
import logoDark from '@/assets/images/logo-dark-mode.png';
|
import logoDark from '@/assets/images/worklenz-dark-mode.png';
|
||||||
import { useAppSelector } from '@/hooks/useAppSelector';
|
import { useAppSelector } from '@/hooks/useAppSelector';
|
||||||
|
|
||||||
type AuthPageHeaderProp = {
|
type AuthPageHeaderProp = {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ type EmptyListPlaceholderProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const EmptyListPlaceholder = ({
|
const EmptyListPlaceholder = ({
|
||||||
imageSrc = '/assets/images/empty-box.webp',
|
imageSrc = '/src/assets/images/empty-box.webp',
|
||||||
imageHeight = 60,
|
imageHeight = 60,
|
||||||
text,
|
text,
|
||||||
}: EmptyListPlaceholderProps) => {
|
}: EmptyListPlaceholderProps) => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { FloatButton, Space, Tooltip } from 'antd';
|
import { FloatButton, Space, Tooltip } from 'antd';
|
||||||
import { FormatPainterOutlined } from '@ant-design/icons';
|
import { FormatPainterOutlined } from '@ant-design/icons';
|
||||||
import LanguageSelector from '../features/i18n/language-selector';
|
// import LanguageSelector from '../features/i18n/language-selector';
|
||||||
import ThemeSelector from '../features/theme/ThemeSelector';
|
// import ThemeSelector from '../features/theme/ThemeSelector';
|
||||||
|
|
||||||
const PreferenceSelector = () => {
|
const PreferenceSelector = () => {
|
||||||
return (
|
return (
|
||||||
@@ -17,7 +17,7 @@ const PreferenceSelector = () => {
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ThemeSelector />
|
{/* <ThemeSelector /> */}
|
||||||
</Space>
|
</Space>
|
||||||
</FloatButton.Group>
|
</FloatButton.Group>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import logo from '@/assets/images/logo.png';
|
import logo from '@/assets/images/worklenz-light-mode.png';
|
||||||
import logoDark from '@/assets/images/logo-dark-mode.png';
|
import logoDark from '@/assets/images/worklenz-dark-mode.png';
|
||||||
|
|
||||||
import { useAppSelector } from '@/hooks/useAppSelector';
|
import { useAppSelector } from '@/hooks/useAppSelector';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
@@ -20,23 +20,6 @@ const NavbarLogo = () => {
|
|||||||
alt={t('logoAlt')}
|
alt={t('logoAlt')}
|
||||||
style={{ width: '100%', maxWidth: 140 }}
|
style={{ width: '100%', maxWidth: 140 }}
|
||||||
/>
|
/>
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: -1,
|
|
||||||
right: 0,
|
|
||||||
backgroundColor: '#ff5722',
|
|
||||||
color: 'white',
|
|
||||||
fontSize: '7px',
|
|
||||||
padding: '0px 3px',
|
|
||||||
borderRadius: '3px',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
lineHeight: '1.8',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Beta
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
// ThemeSelector.tsx
|
|
||||||
import { Button } from 'antd';
|
|
||||||
import React from 'react';
|
|
||||||
import { useAppDispatch } from '@/hooks/useAppDispatch';
|
|
||||||
import { useAppSelector } from '@/hooks/useAppSelector';
|
|
||||||
import { toggleTheme } from './themeSlice';
|
|
||||||
import { MoonOutlined, SunOutlined } from '@ant-design/icons';
|
|
||||||
|
|
||||||
const ThemeSelector = () => {
|
|
||||||
const themeMode = useAppSelector(state => state.themeReducer.mode);
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
|
|
||||||
const handleDarkModeToggle = () => {
|
|
||||||
dispatch(toggleTheme());
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
type={themeMode === 'dark' ? 'primary' : 'default'}
|
|
||||||
icon={themeMode === 'dark' ? <SunOutlined /> : <MoonOutlined />}
|
|
||||||
shape="circle"
|
|
||||||
onClick={handleDarkModeToggle}
|
|
||||||
className="transition-all duration-300" // Optional: add smooth transition
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ThemeSelector;
|
|
||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
TeamOutlined,
|
TeamOutlined,
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
UserSwitchOutlined,
|
UserSwitchOutlined,
|
||||||
|
BulbOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import React, { ReactNode } from 'react';
|
import React, { ReactNode } from 'react';
|
||||||
import ProfileSettings from '../../pages/settings/profile/profile-settings';
|
import ProfileSettings from '../../pages/settings/profile/profile-settings';
|
||||||
@@ -27,6 +28,7 @@ import TeamsSettings from '../../pages/settings/teams/teams-settings';
|
|||||||
import ChangePassword from '@/pages/settings/change-password/change-password';
|
import ChangePassword from '@/pages/settings/change-password/change-password';
|
||||||
import LanguageAndRegionSettings from '@/pages/settings/language-and-region/language-and-region-settings';
|
import LanguageAndRegionSettings from '@/pages/settings/language-and-region/language-and-region-settings';
|
||||||
import RatecardSettings from '@/pages/settings/ratecard/ratecard-settings';
|
import RatecardSettings from '@/pages/settings/ratecard/ratecard-settings';
|
||||||
|
import AppearanceSettings from '@/pages/settings/appearance/appearance-settings';
|
||||||
|
|
||||||
// type of menu item in settings sidebar
|
// type of menu item in settings sidebar
|
||||||
type SettingMenuItems = {
|
type SettingMenuItems = {
|
||||||
@@ -54,6 +56,13 @@ export const settingsItems: SettingMenuItems[] = [
|
|||||||
icon: React.createElement(NotificationOutlined),
|
icon: React.createElement(NotificationOutlined),
|
||||||
element: React.createElement(NotificationsSettings),
|
element: React.createElement(NotificationsSettings),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'appearance',
|
||||||
|
name: 'appearance',
|
||||||
|
endpoint: 'appearance',
|
||||||
|
icon: React.createElement(BulbOutlined),
|
||||||
|
element: React.createElement(AppearanceSettings),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'change-password',
|
key: 'change-password',
|
||||||
name: 'change-password',
|
name: 'change-password',
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ import { useDocumentTitle } from '@/hooks/useDoumentTItle';
|
|||||||
import { getUserSession, setSession } from '@/utils/session-helper';
|
import { getUserSession, setSession } from '@/utils/session-helper';
|
||||||
import { validateEmail } from '@/utils/validateEmail';
|
import { validateEmail } from '@/utils/validateEmail';
|
||||||
import { sanitizeInput } from '@/utils/sanitizeInput';
|
import { sanitizeInput } from '@/utils/sanitizeInput';
|
||||||
import logo from '@/assets/images/logo.png';
|
import logo from '@/assets/images/worklenz-light-mode.png';
|
||||||
import logoDark from '@/assets/images/logo-dark-mode.png';
|
import logoDark from '@/assets/images/worklenz-dark-mode.png';
|
||||||
|
|
||||||
import './account-setup.css';
|
import './account-setup.css';
|
||||||
import { IAccountSetupRequest } from '@/types/project-templates/project-templates.types';
|
import { IAccountSetupRequest } from '@/types/project-templates/project-templates.types';
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import html2canvas from 'html2canvas';
|
import html2canvas from 'html2canvas';
|
||||||
import jsPDF from 'jspdf';
|
import jsPDF from 'jspdf';
|
||||||
import logo from '@/assets/images/logo.png';
|
import logo from '@/assets/images/worklenz-light-mode.png';
|
||||||
import { evt_project_insights_members_visit, evt_project_insights_overview_visit, evt_project_insights_tasks_visit } from '@/shared/worklenz-analytics-events';
|
import { evt_project_insights_members_visit, evt_project_insights_overview_visit, evt_project_insights_tasks_visit } from '@/shared/worklenz-analytics-events';
|
||||||
import { useMixpanelTracking } from '@/hooks/useMixpanelTracking';
|
import { useMixpanelTracking } from '@/hooks/useMixpanelTracking';
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { Card, Divider, Flex, Switch, Typography } from 'antd';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useAppDispatch } from '@/hooks/useAppDispatch';
|
||||||
|
import { useAppSelector } from '@/hooks/useAppSelector';
|
||||||
|
import { toggleTheme } from '@/features/theme/themeSlice';
|
||||||
|
import { useDocumentTitle } from '@/hooks/useDoumentTItle';
|
||||||
|
import { MoonOutlined, SunOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
const AppearanceSettings = () => {
|
||||||
|
const { t } = useTranslation('settings/appearance');
|
||||||
|
const themeMode = useAppSelector(state => state.themeReducer.mode);
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
useDocumentTitle(t('title'));
|
||||||
|
|
||||||
|
const handleThemeToggle = () => {
|
||||||
|
dispatch(toggleTheme());
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card style={{ width: '100%' }}>
|
||||||
|
<Flex vertical gap={4}>
|
||||||
|
<Flex gap={8} align="center">
|
||||||
|
<Switch
|
||||||
|
checked={themeMode === 'dark'}
|
||||||
|
onChange={handleThemeToggle}
|
||||||
|
checkedChildren={<MoonOutlined />}
|
||||||
|
unCheckedChildren={<SunOutlined />}
|
||||||
|
/>
|
||||||
|
<Typography.Title level={4} style={{ marginBlockEnd: 0 }}>
|
||||||
|
{t('darkMode')}
|
||||||
|
</Typography.Title>
|
||||||
|
</Flex>
|
||||||
|
<Typography.Text
|
||||||
|
style={{ fontSize: 14, color: themeMode === 'dark' ? '#9CA3AF' : '#00000073' }}
|
||||||
|
>
|
||||||
|
{t('darkModeDescription')}
|
||||||
|
</Typography.Text>
|
||||||
|
</Flex>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppearanceSettings;
|
||||||
Reference in New Issue
Block a user