Compare commits
14 Commits
shared-con
...
js-in-ts
Author | SHA1 | Date | |
---|---|---|---|
![]() |
321019d36a | ||
![]() |
d070e976b0 | ||
![]() |
0caf6bfabb | ||
![]() |
b842d7938f | ||
![]() |
cebbf84375 | ||
![]() |
8608431490 | ||
![]() |
78ba18b176 | ||
![]() |
f8c30c8526 | ||
![]() |
693c9b85a5 | ||
![]() |
70bb7defd1 | ||
![]() |
160377ca31 | ||
![]() |
2c0ce77a4e | ||
![]() |
77fbb0c9da | ||
![]() |
5971425d23 |
5
.github/workflows/playwright.yml
vendored
5
.github/workflows/playwright.yml
vendored
@@ -4,6 +4,11 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
pull_request:
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- 'packages/backend/**'
|
||||||
|
- 'packages/e2e-tests/**'
|
||||||
|
- 'packages/web/**'
|
||||||
|
- '!packages/backend/src/apps/**'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
@@ -11,7 +11,7 @@ export default {
|
|||||||
readOnly: false,
|
readOnly: false,
|
||||||
value: null,
|
value: null,
|
||||||
placeholder: null,
|
placeholder: null,
|
||||||
description: 'Host name of your Odoo Server',
|
description: 'Host name of your Odoo Server (e.g. sub.domain.com without the protocol)',
|
||||||
clickToCopy: false,
|
clickToCopy: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -25,6 +25,27 @@ export default {
|
|||||||
description: 'Port that the host is running on, defaults to 443 (HTTPS)',
|
description: 'Port that the host is running on, defaults to 443 (HTTPS)',
|
||||||
clickToCopy: false,
|
clickToCopy: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'secure',
|
||||||
|
label: 'Secure',
|
||||||
|
type: 'dropdown' as const,
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: 'true',
|
||||||
|
description: 'True if the host communicates via secure protocol.',
|
||||||
|
variables: false,
|
||||||
|
clickToCopy: false,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'True',
|
||||||
|
value: 'true',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'False',
|
||||||
|
value: 'false',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'databaseName',
|
key: 'databaseName',
|
||||||
label: 'Database Name',
|
label: 'Database Name',
|
||||||
@@ -40,7 +61,7 @@ export default {
|
|||||||
key: 'email',
|
key: 'email',
|
||||||
label: 'Email Address',
|
label: 'Email Address',
|
||||||
type: 'string' as const,
|
type: 'string' as const,
|
||||||
requires: true,
|
required: true,
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
value: null,
|
value: null,
|
||||||
placeholder: null,
|
placeholder: null,
|
||||||
|
@@ -32,8 +32,10 @@ export const asyncMethodCall = async <T = number>($: IGlobalVariable, { method,
|
|||||||
export const getClient = ($: IGlobalVariable, { path = 'common' }) => {
|
export const getClient = ($: IGlobalVariable, { path = 'common' }) => {
|
||||||
const host = $.auth.data.host as string;
|
const host = $.auth.data.host as string;
|
||||||
const port = Number($.auth.data.port as string);
|
const port = Number($.auth.data.port as string);
|
||||||
|
const secure = $.auth.data.secure === 'true';
|
||||||
|
const createClientFunction = secure ? xmlrpc.createSecureClient : xmlrpc.createClient;
|
||||||
|
|
||||||
return xmlrpc.createClient(
|
return createClientFunction(
|
||||||
{
|
{
|
||||||
host,
|
host,
|
||||||
port,
|
port,
|
||||||
|
@@ -49,6 +49,7 @@ type AppConfig = {
|
|||||||
smtpPassword: string;
|
smtpPassword: string;
|
||||||
fromEmail: string;
|
fromEmail: string;
|
||||||
isCloud: boolean;
|
isCloud: boolean;
|
||||||
|
isMation: boolean;
|
||||||
isSelfHosted: boolean;
|
isSelfHosted: boolean;
|
||||||
paddleVendorId: number;
|
paddleVendorId: number;
|
||||||
paddleVendorAuthCode: string;
|
paddleVendorAuthCode: string;
|
||||||
@@ -127,6 +128,7 @@ const appConfig: AppConfig = {
|
|||||||
fromEmail: process.env.FROM_EMAIL,
|
fromEmail: process.env.FROM_EMAIL,
|
||||||
isCloud: process.env.AUTOMATISCH_CLOUD === 'true',
|
isCloud: process.env.AUTOMATISCH_CLOUD === 'true',
|
||||||
isSelfHosted: process.env.AUTOMATISCH_CLOUD !== 'true',
|
isSelfHosted: process.env.AUTOMATISCH_CLOUD !== 'true',
|
||||||
|
isMation: process.env.MATION === 'true',
|
||||||
paddleVendorId: Number(process.env.PADDLE_VENDOR_ID),
|
paddleVendorId: Number(process.env.PADDLE_VENDOR_ID),
|
||||||
paddleVendorAuthCode: process.env.PADDLE_VENDOR_AUTH_CODE,
|
paddleVendorAuthCode: process.env.PADDLE_VENDOR_AUTH_CODE,
|
||||||
paddlePublicKey: process.env.PADDLE_PUBLIC_KEY,
|
paddlePublicKey: process.env.PADDLE_PUBLIC_KEY,
|
||||||
|
@@ -9,6 +9,7 @@ describe('graphQL getAutomatischInfo query', () => {
|
|||||||
query {
|
query {
|
||||||
getAutomatischInfo {
|
getAutomatischInfo {
|
||||||
isCloud
|
isCloud
|
||||||
|
isMation
|
||||||
license {
|
license {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
@@ -24,6 +25,7 @@ describe('graphQL getAutomatischInfo query', () => {
|
|||||||
jest.spyOn(license, 'getLicense').mockResolvedValue(false);
|
jest.spyOn(license, 'getLicense').mockResolvedValue(false);
|
||||||
|
|
||||||
jest.replaceProperty(appConfig, 'isCloud', false);
|
jest.replaceProperty(appConfig, 'isCloud', false);
|
||||||
|
jest.replaceProperty(appConfig, 'isMation', false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return empty license data', async () => {
|
it('should return empty license data', async () => {
|
||||||
@@ -36,6 +38,7 @@ describe('graphQL getAutomatischInfo query', () => {
|
|||||||
data: {
|
data: {
|
||||||
getAutomatischInfo: {
|
getAutomatischInfo: {
|
||||||
isCloud: false,
|
isCloud: false,
|
||||||
|
isMation: false,
|
||||||
license: {
|
license: {
|
||||||
id: null,
|
id: null,
|
||||||
name: null,
|
name: null,
|
||||||
@@ -77,6 +80,7 @@ describe('graphQL getAutomatischInfo query', () => {
|
|||||||
data: {
|
data: {
|
||||||
getAutomatischInfo: {
|
getAutomatischInfo: {
|
||||||
isCloud: true,
|
isCloud: true,
|
||||||
|
isMation: false,
|
||||||
license: {
|
license: {
|
||||||
expireAt: '2025-08-09T10:56:54.144Z',
|
expireAt: '2025-08-09T10:56:54.144Z',
|
||||||
id: '123123',
|
id: '123123',
|
||||||
@@ -105,6 +109,69 @@ describe('graphQL getAutomatischInfo query', () => {
|
|||||||
const expectedResponsePayload = {
|
const expectedResponsePayload = {
|
||||||
data: {
|
data: {
|
||||||
getAutomatischInfo: {
|
getAutomatischInfo: {
|
||||||
|
isCloud: false,
|
||||||
|
isMation: false,
|
||||||
|
license: {
|
||||||
|
expireAt: '2025-08-09T10:56:54.144Z',
|
||||||
|
id: '123123',
|
||||||
|
name: 'Test License',
|
||||||
|
verified: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(response.body).toEqual(expectedResponsePayload);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('and with mation flag enabled', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.replaceProperty(appConfig, 'isCloud', false);
|
||||||
|
jest.replaceProperty(appConfig, 'isMation', true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return all license data', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/graphql')
|
||||||
|
.send({ query })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const expectedResponsePayload = {
|
||||||
|
data: {
|
||||||
|
getAutomatischInfo: {
|
||||||
|
isCloud: false,
|
||||||
|
isMation: true,
|
||||||
|
license: {
|
||||||
|
expireAt: '2025-08-09T10:56:54.144Z',
|
||||||
|
id: '123123',
|
||||||
|
name: 'Test License',
|
||||||
|
verified: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(response.body).toEqual(expectedResponsePayload);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('and with mation flag disabled', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.replaceProperty(appConfig, 'isCloud', false);
|
||||||
|
jest.replaceProperty(appConfig, 'isMation', false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return all license data', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/graphql')
|
||||||
|
.send({ query })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const expectedResponsePayload = {
|
||||||
|
data: {
|
||||||
|
getAutomatischInfo: {
|
||||||
|
isMation: false,
|
||||||
isCloud: false,
|
isCloud: false,
|
||||||
license: {
|
license: {
|
||||||
expireAt: '2025-08-09T10:56:54.144Z',
|
expireAt: '2025-08-09T10:56:54.144Z',
|
||||||
|
@@ -13,6 +13,7 @@ const getAutomatischInfo = async () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
isCloud: appConfig.isCloud,
|
isCloud: appConfig.isCloud,
|
||||||
|
isMation: appConfig.isMation,
|
||||||
license: computedLicense,
|
license: computedLicense,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
10
packages/backend/src/graphql/queries/get-use-js-file.js
Normal file
10
packages/backend/src/graphql/queries/get-use-js-file.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import appConfig from '../../config/app';
|
||||||
|
|
||||||
|
const getUseJsFile = async () => {
|
||||||
|
return {
|
||||||
|
canInvoke: true,
|
||||||
|
appConfig,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getUseJsFile;
|
@@ -6,6 +6,7 @@ import getApps from './queries/get-apps';
|
|||||||
import getAutomatischInfo from './queries/get-automatisch-info';
|
import getAutomatischInfo from './queries/get-automatisch-info';
|
||||||
import getBillingAndUsage from './queries/get-billing-and-usage.ee';
|
import getBillingAndUsage from './queries/get-billing-and-usage.ee';
|
||||||
import getConfig from './queries/get-config.ee';
|
import getConfig from './queries/get-config.ee';
|
||||||
|
import getUseJsFile from './queries/get-use-js-file.js';
|
||||||
import getConnectedApps from './queries/get-connected-apps';
|
import getConnectedApps from './queries/get-connected-apps';
|
||||||
import getCurrentUser from './queries/get-current-user';
|
import getCurrentUser from './queries/get-current-user';
|
||||||
import getDynamicData from './queries/get-dynamic-data';
|
import getDynamicData from './queries/get-dynamic-data';
|
||||||
@@ -68,6 +69,7 @@ const queryResolvers = {
|
|||||||
healthcheck,
|
healthcheck,
|
||||||
listSamlAuthProviders,
|
listSamlAuthProviders,
|
||||||
testConnection,
|
testConnection,
|
||||||
|
getUseJsFile,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default queryResolvers;
|
export default queryResolvers;
|
||||||
|
@@ -11,6 +11,7 @@ type Query {
|
|||||||
getConnectedApps(name: String): [App]
|
getConnectedApps(name: String): [App]
|
||||||
testConnection(id: String!): Connection
|
testConnection(id: String!): Connection
|
||||||
getFlow(id: String!): Flow
|
getFlow(id: String!): Flow
|
||||||
|
getUseJsFile: JSONObject
|
||||||
getFlows(
|
getFlows(
|
||||||
limit: Int!
|
limit: Int!
|
||||||
offset: Int!
|
offset: Int!
|
||||||
@@ -646,6 +647,7 @@ type AppHealth {
|
|||||||
|
|
||||||
type GetAutomatischInfo {
|
type GetAutomatischInfo {
|
||||||
isCloud: Boolean
|
isCloud: Boolean
|
||||||
|
isMation: Boolean
|
||||||
license: License
|
license: License
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,11 +2,12 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
|
"allowJs": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"lib": ["es2021"],
|
"lib": ["es2021"],
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": false,
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"paths": {
|
"paths": {
|
||||||
"*": ["../../node_modules/*", "node_modules/*", "src/types/*"]
|
"*": ["../../node_modules/*", "node_modules/*", "src/types/*"]
|
||||||
|
@@ -12,7 +12,9 @@ connection in Automatisch. If any of the steps are outdated, please let us know!
|
|||||||
1. Enter necessary information in the form.
|
1. Enter necessary information in the form.
|
||||||
1. Check **Enable OAuth Settings** checkbox.
|
1. Check **Enable OAuth Settings** checkbox.
|
||||||
1. Copy **OAuth Redirect URL** from Automatisch and paste it to the **Callback URL** field.
|
1. Copy **OAuth Redirect URL** from Automatisch and paste it to the **Callback URL** field.
|
||||||
1. Add any scopes you plan to use in the **Selected OAuth Scopes** section.
|
1. Add any scopes you plan to use in the **Selected OAuth Scopes** section. We suggest `full` and `refresh_token, offline_access` scopes.
|
||||||
|
1. Uncheck "Require Proof Key for Code Exchange (PKCE) Extension for Supported Authorization Flows" checkbox.
|
||||||
|
1. Check "Enable Authorization Code and Credentials Flow" checkbox
|
||||||
1. Click on the **Save** button at the bottom of the page.
|
1. Click on the **Save** button at the bottom of the page.
|
||||||
1. Acknowledge the information and click on the **Continue** button.
|
1. Acknowledge the information and click on the **Continue** button.
|
||||||
1. In the **API (Enable OAuth Settings)** section, click the **Manager Consumer Details** button.
|
1. In the **API (Enable OAuth Settings)** section, click the **Manager Consumer Details** button.
|
||||||
|
@@ -2,7 +2,7 @@ import styled from '@emotion/styled';
|
|||||||
|
|
||||||
export const LogoImage = styled('img')(() => ({
|
export const LogoImage = styled('img')(() => ({
|
||||||
maxWidth: 200,
|
maxWidth: 200,
|
||||||
maxHeight: 50,
|
maxHeight: 22,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
}));
|
}));
|
||||||
|
22
packages/web/src/components/DefaultLogo/index.tsx
Normal file
22
packages/web/src/components/DefaultLogo/index.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import * as React from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import MationLogo from 'components/MationLogo';
|
||||||
|
import useAutomatischInfo from 'hooks/useAutomatischInfo';
|
||||||
|
|
||||||
|
const DefaultLogo = () => {
|
||||||
|
const { isMation, loading } = useAutomatischInfo();
|
||||||
|
|
||||||
|
if (loading) return <React.Fragment />;
|
||||||
|
|
||||||
|
if (isMation) return <MationLogo />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Typography variant="h6" component="h1" data-test="typography-logo" noWrap>
|
||||||
|
<FormattedMessage id="brandText" />
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DefaultLogo;
|
@@ -12,6 +12,7 @@ import * as URLS from 'config/urls';
|
|||||||
import useVersion from 'hooks/useVersion';
|
import useVersion from 'hooks/useVersion';
|
||||||
import AppBar from 'components/AppBar';
|
import AppBar from 'components/AppBar';
|
||||||
import Drawer from 'components/Drawer';
|
import Drawer from 'components/Drawer';
|
||||||
|
import useAutomatischInfo from 'hooks/useAutomatischInfo';
|
||||||
|
|
||||||
type PublicLayoutProps = {
|
type PublicLayoutProps = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@@ -38,19 +39,36 @@ const drawerLinks = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const generateDrawerBottomLinks = ({ notificationBadgeContent = 0 }) => [
|
type GenerateDrawerBottomLinksOptions = {
|
||||||
{
|
isMation: boolean;
|
||||||
Icon: NotificationsIcon,
|
loading: boolean;
|
||||||
primary: 'settingsDrawer.notifications',
|
notificationBadgeContent: number;
|
||||||
to: URLS.UPDATES,
|
};
|
||||||
badgeContent: notificationBadgeContent,
|
|
||||||
},
|
const generateDrawerBottomLinks = ({
|
||||||
];
|
isMation,
|
||||||
|
loading,
|
||||||
|
notificationBadgeContent = 0,
|
||||||
|
}: GenerateDrawerBottomLinksOptions) => {
|
||||||
|
if (loading || isMation) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
Icon: NotificationsIcon,
|
||||||
|
primary: 'settingsDrawer.notifications',
|
||||||
|
to: URLS.UPDATES,
|
||||||
|
badgeContent: notificationBadgeContent,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
export default function PublicLayout({
|
export default function PublicLayout({
|
||||||
children,
|
children,
|
||||||
}: PublicLayoutProps): React.ReactElement {
|
}: PublicLayoutProps): React.ReactElement {
|
||||||
const version = useVersion();
|
const version = useVersion();
|
||||||
|
const { isMation, loading } = useAutomatischInfo();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('lg'));
|
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('lg'));
|
||||||
const [isDrawerOpen, setDrawerOpen] = React.useState(!matchSmallScreens);
|
const [isDrawerOpen, setDrawerOpen] = React.useState(!matchSmallScreens);
|
||||||
@@ -60,6 +78,8 @@ export default function PublicLayout({
|
|||||||
|
|
||||||
const drawerBottomLinks = generateDrawerBottomLinks({
|
const drawerBottomLinks = generateDrawerBottomLinks({
|
||||||
notificationBadgeContent: version.newVersionCount,
|
notificationBadgeContent: version.newVersionCount,
|
||||||
|
loading,
|
||||||
|
isMation,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@@ -1,8 +1,7 @@
|
|||||||
import Typography from '@mui/material/Typography';
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
import CustomLogo from 'components/CustomLogo/index.ee';
|
import CustomLogo from 'components/CustomLogo/index.ee';
|
||||||
|
import DefaultLogo from 'components/DefaultLogo';
|
||||||
import useConfig from 'hooks/useConfig';
|
import useConfig from 'hooks/useConfig';
|
||||||
|
|
||||||
const Logo = () => {
|
const Logo = () => {
|
||||||
@@ -13,11 +12,7 @@ const Logo = () => {
|
|||||||
|
|
||||||
if (logoSvgData) return <CustomLogo />;
|
if (logoSvgData) return <CustomLogo />;
|
||||||
|
|
||||||
return (
|
return <DefaultLogo />;
|
||||||
<Typography variant="h6" component="h1" data-test="typography-logo" noWrap>
|
|
||||||
<FormattedMessage id="brandText" />
|
|
||||||
</Typography>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Logo;
|
export default Logo;
|
||||||
|
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="115" height="22" viewBox="0 0 411 77" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M276.991 15.3555C281.231 15.3555 284.669 11.918 284.669 7.67773C284.669 3.43744 281.231 0 276.991 0C272.751 0 269.313 3.43744 269.313 7.67773C269.313 11.918 272.751 15.3555 276.991 15.3555ZM367.497 50.0315C367.497 41.5346 374.385 34.6464 382.882 34.6464C391.379 34.6464 398.267 41.5346 398.267 50.0315V71.2076H409.851V50.0315C409.851 35.1371 397.777 23.0627 382.882 23.0627C367.988 23.0627 355.914 35.1371 355.914 50.0315V71.2076H367.497V50.0315ZM271.199 71.2071V28.8539H282.783V71.2071H271.199ZM237.933 34.6464V71.2076H249.517V34.6464H259.608V23.0627H249.517V7.67718H237.933V23.0627H227.843V34.6464H237.933ZM176.899 50.0296C176.899 58.5265 183.787 65.4146 192.284 65.4146V76.9983C177.389 76.9983 165.315 64.924 165.315 50.0296C165.315 35.1351 177.389 23.0608 192.284 23.0608C207.178 23.0608 219.252 35.1359 219.252 50.0303L219.253 71.2068H207.669L207.669 50.0303C207.669 41.5334 200.781 34.6445 192.284 34.6445C183.787 34.6445 176.899 41.5326 176.899 50.0296ZM71.0145 50.0315C71.0145 41.5346 77.9026 34.6464 86.3995 34.6464C94.8965 34.6464 101.785 41.5346 101.785 50.0315V71.2071H113.368V50.0315C113.368 41.5346 120.256 34.6464 128.753 34.6464C137.25 34.6464 144.138 41.5346 144.138 50.0315L144.138 71.2071H155.722L155.722 50.0315C155.722 35.1371 143.647 23.0627 128.753 23.0627C120.165 23.0627 112.515 27.0767 107.576 33.3308C102.637 27.0767 94.9873 23.0627 86.3995 23.0627C71.5051 23.0627 59.4308 35.1371 59.4308 50.0315V71.2071H71.0145V50.0315ZM44.0459 65.4162V76.9999H1.69178V65.4162H44.0459ZM292.376 50.0305C292.376 64.925 304.45 76.9993 319.345 76.9993C334.239 76.9993 346.313 64.9257 346.313 50.0313C346.313 35.1369 334.239 23.0618 319.345 23.0618C304.45 23.0618 292.376 35.1361 292.376 50.0305ZM319.345 65.4157C310.848 65.4157 303.96 58.5276 303.96 50.0306C303.96 41.5337 310.848 34.6456 319.345 34.6456C327.842 34.6456 334.729 41.5345 334.729 50.0314C334.729 58.5283 327.842 65.4157 319.345 65.4157Z" fill="#ffffff"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
8
packages/web/src/components/MationLogo/index.tsx
Normal file
8
packages/web/src/components/MationLogo/index.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { ReactComponent as MationLogoSvg } from './assets/mation-logo.svg';
|
||||||
|
|
||||||
|
const MationLogo = () => {
|
||||||
|
return <MationLogoSvg />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MationLogo;
|
@@ -7,19 +7,20 @@ import * as React from 'react';
|
|||||||
|
|
||||||
import { IJSONObject } from '@automatisch/types';
|
import { IJSONObject } from '@automatisch/types';
|
||||||
import useConfig from 'hooks/useConfig';
|
import useConfig from 'hooks/useConfig';
|
||||||
import theme from 'styles/theme';
|
import useAutomatischInfo from 'hooks/useAutomatischInfo';
|
||||||
|
import { defaultTheme, mationTheme } from 'styles/theme';
|
||||||
|
|
||||||
type ThemeProviderProps = {
|
type ThemeProviderProps = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
const customizeTheme = (defaultTheme: typeof theme, config: IJSONObject) => {
|
const customizeTheme = (theme: typeof defaultTheme, config: IJSONObject) => {
|
||||||
// `clone` is needed so that the new theme reference triggers re-render
|
// `clone` is needed so that the new theme reference triggers re-render
|
||||||
const shallowDefaultTheme = clone(defaultTheme);
|
const shallowDefaultTheme = clone(theme);
|
||||||
|
|
||||||
for (const key in config) {
|
for (const key in config) {
|
||||||
const value = config[key];
|
const value = config[key];
|
||||||
const exists = get(defaultTheme, key);
|
const exists = get(theme, key);
|
||||||
|
|
||||||
if (exists) {
|
if (exists) {
|
||||||
set(shallowDefaultTheme, key, value);
|
set(shallowDefaultTheme, key, value);
|
||||||
@@ -33,18 +34,21 @@ const ThemeProvider = ({
|
|||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: ThemeProviderProps): React.ReactElement => {
|
}: ThemeProviderProps): React.ReactElement => {
|
||||||
const { config, loading } = useConfig();
|
const { isMation, loading: automatischInfoLoading } = useAutomatischInfo();
|
||||||
|
const { config, loading: configLoading } = useConfig();
|
||||||
|
|
||||||
const customTheme = React.useMemo(() => {
|
const customTheme = React.useMemo(() => {
|
||||||
if (!config) return theme;
|
const installationTheme = isMation ? mationTheme : defaultTheme;
|
||||||
|
|
||||||
const customTheme = customizeTheme(theme, config);
|
if (configLoading || automatischInfoLoading) return installationTheme;
|
||||||
|
|
||||||
|
const customTheme = customizeTheme(installationTheme, config || {});
|
||||||
|
|
||||||
return customTheme;
|
return customTheme;
|
||||||
}, [config]);
|
}, [configLoading, config, isMation, automatischInfoLoading]);
|
||||||
|
|
||||||
// TODO: maybe a global loading state for the custom theme?
|
// TODO: maybe a global loading state for the custom theme?
|
||||||
if (loading) return <></>;
|
if (automatischInfoLoading || configLoading) return <></>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseThemeProvider theme={customTheme} {...props}>
|
<BaseThemeProvider theme={customTheme} {...props}>
|
||||||
|
@@ -5,11 +5,15 @@ import { GET_AUTOMATISCH_INFO } from 'graphql/queries/get-automatisch-info';
|
|||||||
|
|
||||||
export type AutomatischInfoContextParams = {
|
export type AutomatischInfoContextParams = {
|
||||||
isCloud: boolean;
|
isCloud: boolean;
|
||||||
|
isMation: boolean;
|
||||||
|
loading: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AutomatischInfoContext =
|
export const AutomatischInfoContext =
|
||||||
React.createContext<AutomatischInfoContextParams>({
|
React.createContext<AutomatischInfoContextParams>({
|
||||||
isCloud: false,
|
isCloud: false,
|
||||||
|
isMation: false,
|
||||||
|
loading: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
type AutomatischInfoProviderProps = {
|
type AutomatischInfoProviderProps = {
|
||||||
@@ -23,13 +27,15 @@ export const AutomatischInfoProvider = (
|
|||||||
const { data, loading } = useQuery(GET_AUTOMATISCH_INFO);
|
const { data, loading } = useQuery(GET_AUTOMATISCH_INFO);
|
||||||
|
|
||||||
const isCloud = data?.getAutomatischInfo?.isCloud;
|
const isCloud = data?.getAutomatischInfo?.isCloud;
|
||||||
|
const isMation = data?.getAutomatischInfo?.isMation;
|
||||||
|
|
||||||
const value = React.useMemo(() => {
|
const value = React.useMemo(() => {
|
||||||
return {
|
return {
|
||||||
isCloud,
|
isCloud,
|
||||||
loading
|
isMation,
|
||||||
|
loading,
|
||||||
};
|
};
|
||||||
}, [isCloud, loading]);
|
}, [isCloud, isMation, loading]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AutomatischInfoContext.Provider value={value}>
|
<AutomatischInfoContext.Provider value={value}>
|
||||||
|
@@ -4,6 +4,7 @@ export const GET_AUTOMATISCH_INFO = gql`
|
|||||||
query GetAutomatischInfo {
|
query GetAutomatischInfo {
|
||||||
getAutomatischInfo {
|
getAutomatischInfo {
|
||||||
isCloud
|
isCloud
|
||||||
|
isMation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@@ -3,6 +3,8 @@ import { AutomatischInfoContext } from 'contexts/AutomatischInfo';
|
|||||||
|
|
||||||
type UseAutomatischInfoReturn = {
|
type UseAutomatischInfoReturn = {
|
||||||
isCloud: boolean;
|
isCloud: boolean;
|
||||||
|
isMation: boolean;
|
||||||
|
loading: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function useAutomatischInfo(): UseAutomatischInfoReturn {
|
export default function useAutomatischInfo(): UseAutomatischInfoReturn {
|
||||||
@@ -10,5 +12,7 @@ export default function useAutomatischInfo(): UseAutomatischInfoReturn {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
isCloud: automatischInfoContext.isCloud,
|
isCloud: automatischInfoContext.isCloud,
|
||||||
|
isMation: automatischInfoContext.isMation,
|
||||||
|
loading: automatischInfoContext.loading,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
|
|
||||||
@@ -7,6 +8,8 @@ import Container from 'components/Container';
|
|||||||
import NotificationCard from 'components/NotificationCard';
|
import NotificationCard from 'components/NotificationCard';
|
||||||
import PageTitle from 'components/PageTitle';
|
import PageTitle from 'components/PageTitle';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
|
import useAutomatischInfo from 'hooks/useAutomatischInfo';
|
||||||
|
import * as URLS from 'config/urls';
|
||||||
|
|
||||||
interface INotification {
|
interface INotification {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -16,8 +19,19 @@ interface INotification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Updates(): React.ReactElement {
|
export default function Updates(): React.ReactElement {
|
||||||
|
const navigate = useNavigate();
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const { notifications } = useNotifications();
|
const { notifications } = useNotifications();
|
||||||
|
const { isMation, loading } = useAutomatischInfo();
|
||||||
|
|
||||||
|
React.useEffect(
|
||||||
|
function redirectToHomepageInMation() {
|
||||||
|
if (!loading && isMation) {
|
||||||
|
navigate(URLS.DASHBOARD);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[loading, isMation]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ py: 3 }}>
|
<Box sx={{ py: 3 }}>
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
import { deepmerge } from '@mui/utils';
|
||||||
|
import type { Theme } from '@mui/material/styles';
|
||||||
import { createTheme, alpha } from '@mui/material/styles';
|
import { createTheme, alpha } from '@mui/material/styles';
|
||||||
import { cardActionAreaClasses } from '@mui/material/CardActionArea';
|
import { cardActionAreaClasses } from '@mui/material/CardActionArea';
|
||||||
|
|
||||||
@@ -6,7 +8,7 @@ export const primaryMainColor = '#0059F7';
|
|||||||
export const primaryLightColor = '#4286FF';
|
export const primaryLightColor = '#4286FF';
|
||||||
export const primaryDarkColor = '#001F52';
|
export const primaryDarkColor = '#001F52';
|
||||||
|
|
||||||
const extendedTheme = createTheme({
|
export const defaultTheme = createTheme({
|
||||||
palette: {
|
palette: {
|
||||||
primary: {
|
primary: {
|
||||||
main: primaryMainColor,
|
main: primaryMainColor,
|
||||||
@@ -280,4 +282,24 @@ const extendedTheme = createTheme({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default extendedTheme;
|
export const mationTheme = createTheme(deepmerge(defaultTheme, {
|
||||||
|
palette: {
|
||||||
|
primary: {
|
||||||
|
main: '#2962FF',
|
||||||
|
light: '#448AFF',
|
||||||
|
dark: '#2962FF',
|
||||||
|
contrastText: '#fff',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
MuiAppBar: {
|
||||||
|
styleOverrides: {
|
||||||
|
root: ({ theme }: { theme: Theme }) => ({
|
||||||
|
zIndex: theme.zIndex.drawer + 1,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default defaultTheme;
|
||||||
|
Reference in New Issue
Block a user