Initial commit from ZIP
No files matched your search
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"plugins": [
|
||||
[
|
||||
"@babel/plugin-proposal-optional-chaining"
|
||||
],
|
||||
[
|
||||
"add-react-displayname"
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.eslint.json",
|
||||
"tsconfigRootDir": "./",
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"plugins": ["@typescript-eslint", "prettier"],
|
||||
"extends": [
|
||||
"prettier",
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:@typescript-eslint/recommended-requiring-type-checking",
|
||||
],
|
||||
"rules": {
|
||||
"no-nested-ternary": 0,
|
||||
"no-console": 0,
|
||||
"react/prop-types": 0,
|
||||
"react/jsx-filename-extension": [
|
||||
1,
|
||||
{
|
||||
"extensions": [".js", ".jsx", ".tsx", ".ts"]
|
||||
}
|
||||
],
|
||||
"prettier/prettier": [
|
||||
"error",
|
||||
{
|
||||
"trailingComma": "es5",
|
||||
"singleQuote": true,
|
||||
"printWidth": 120
|
||||
}
|
||||
],
|
||||
"react-hooks/rules-of-hooks": "error",
|
||||
"react-hooks/exhaustive-deps": "warn",
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
.vscode/*
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# JetBrains
|
||||
.idea
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"printWidth": 120,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"useTabs": false,
|
||||
"tabWidth": 2,
|
||||
"jsxSingleQuote": false,
|
||||
"jsxBracketSameLine": false,
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "always",
|
||||
"parser": "typescript",
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `npm start`
|
||||
|
||||
Runs the app in the development mode.<br>
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||
|
||||
The page will reload if you make edits.<br>
|
||||
You will also see any lint errors in the console.
|
||||
|
||||
### `npm test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.<br>
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `npm run build`
|
||||
|
||||
Builds the app for production to the `build` folder.<br>
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.<br>
|
||||
Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `npm run eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
|
||||
|
||||
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
|
||||
|
||||
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
@@ -0,0 +1,2 @@
|
||||
const { useBabelRc, override, useEslintRc } = require('customize-cra');
|
||||
module.exports = override(useBabelRc());
|
||||
@@ -0,0 +1,10 @@
|
||||
declare module '*.svg' {
|
||||
const value: string;
|
||||
export default value;
|
||||
|
||||
function createRef<T>(): RefObject<T>;
|
||||
interface RefObject<T> {
|
||||
// immutable
|
||||
readonly current: T | null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
module.exports = {
|
||||
// The root of your source code, typically /src
|
||||
// `<rootDir>` is a token Jest substitutes
|
||||
roots: ['<rootDir>/src'],
|
||||
modulePaths: ['<rootDir>/src/'],
|
||||
// Jest transformations -- this adds support for TypeScript
|
||||
// using ts-jest
|
||||
transform: {
|
||||
'^.+\\.tsx?$': 'ts-jest',
|
||||
},
|
||||
|
||||
// Runs special logic, such as cleaning up components
|
||||
// when using React Testing Library and adds special
|
||||
// extended assertions to Jest
|
||||
setupFilesAfterEnv: ['@testing-library/react/cleanup-after-each', '@testing-library/jest-dom/extend-expect'],
|
||||
|
||||
// Test spec file resolution pattern
|
||||
// Matches parent folder `__tests__` and filename
|
||||
// should contain `test` or `spec`.
|
||||
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$',
|
||||
modulePathIgnorePatterns: ['node_modules'],
|
||||
// Module file extensions for importing
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
||||
moduleNameMapper: {
|
||||
'\\.(jpg|ico|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
|
||||
'<rootDir>/__mocks__/fileMock.js',
|
||||
'\\.(css|less)$': '<rootDir>/mocks/fileMock.js',
|
||||
},
|
||||
collectCoverage: true,
|
||||
mapCoverage: true,
|
||||
};
|
||||
@@ -0,0 +1,103 @@
|
||||
{
|
||||
"name": "evg-box-web",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@react-pdf/renderer": "^2.0.13",
|
||||
"@sentry/browser": "^5.25.0",
|
||||
"@sentry/react": "^5.25.0",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@types/flat": "^0.0.28",
|
||||
"@types/jest": "^24.0.23",
|
||||
"@types/lodash.debounce": "^4.0.6",
|
||||
"@types/lodash.isequal": "^4.5.5",
|
||||
"@types/lodash.max": "^4.0.6",
|
||||
"@types/logrocket-react": "^3.0.0",
|
||||
"@types/node": "12.7.4",
|
||||
"@types/react": "^16.9.2",
|
||||
"@types/react-datepicker": "^2.9.2",
|
||||
"@types/react-dom": "16.9.0",
|
||||
"@types/react-redux": "^7.1.2",
|
||||
"@types/react-router-dom": "^5.1.2",
|
||||
"@types/redux-mock-store": "^1.0.1",
|
||||
"@types/yup": "^0.26.24",
|
||||
"axios": "^0.19.0",
|
||||
"axios-retry": "^3.1.2",
|
||||
"babel-plugin-add-react-displayname": "^0.0.5",
|
||||
"babel-plugin-react-remove-properties": "^0.3.0",
|
||||
"bulma": "^0.7.5",
|
||||
"bulma-steps-component": "^0.5.3",
|
||||
"bulma-tooltip": "^2.0.2",
|
||||
"classnames": "^2.2.6",
|
||||
"date-fns": "^2.4.1",
|
||||
"filesize": "^5.0.1",
|
||||
"flat": "^5.0.0",
|
||||
"formik": "^2.0.3",
|
||||
"immer": "^5.0.0",
|
||||
"jest": "^24.9.0",
|
||||
"less": "^3.10.3",
|
||||
"lodash.debounce": "4.0.8",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"lodash.max": "^4.0.1",
|
||||
"logrocket": "^1.0.14",
|
||||
"logrocket-react": "4",
|
||||
"node-sass": "^4.12.0",
|
||||
"polish-plurals": "^1.1.0",
|
||||
"query-string": "^6.10.1",
|
||||
"react": "^16.14.0",
|
||||
"react-datepicker": "^2.9.6",
|
||||
"react-dom": "^16.9.0",
|
||||
"react-image-lightbox": "^5.1.0",
|
||||
"react-redux": "^7.1.1",
|
||||
"react-router-dom": "^5.1.2",
|
||||
"react-scripts": "3.1.2",
|
||||
"react-spinners": "^0.6.1",
|
||||
"react-toastify": "^5.4.1",
|
||||
"react-use": "^15.0.2",
|
||||
"redux": "^4.0.4",
|
||||
"redux-devtools-extension": "^2.13.8",
|
||||
"redux-mock-store": "^1.5.3",
|
||||
"reselect": "^4.0.0",
|
||||
"ts-jest": "^24.2.0",
|
||||
"typesafe-actions": "^5.1.0",
|
||||
"typescript": "3.7.2",
|
||||
"use-onclickoutside": "^0.3.1",
|
||||
"yup": "^0.27.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-app-rewired start",
|
||||
"build": "react-app-rewired build",
|
||||
"test": "react-app-rewired test --env=jsdom",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.6.0",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@types/classnames": "^2.2.9",
|
||||
"@typescript-eslint/eslint-plugin": "^2.7.0",
|
||||
"@typescript-eslint/parser": "^2.7.0",
|
||||
"customize-cra": "^0.9.1",
|
||||
"eslint": "^6.4.0",
|
||||
"eslint-config-prettier": "^6.2.0",
|
||||
"eslint-plugin-prettier": "^3.1.0",
|
||||
"eslint-plugin-react": "^7.14.3",
|
||||
"eslint-plugin-react-hooks": "^1.6.1",
|
||||
"prettier": "^1.18.2",
|
||||
"react-app-rewired": "^2.1.5"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,51 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Outbo"
|
||||
/>
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-72520484-8"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'UA-72520484-8');
|
||||
</script>
|
||||
<link rel="apple-touch-icon" href="logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>Outbo</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<div id="modal-root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
@@ -0,0 +1,115 @@
|
||||
import axios, { AxiosError } from 'axios';
|
||||
import { history } from '../App';
|
||||
import { toast } from 'react-toastify';
|
||||
import store from 'store/store';
|
||||
import { setProgress, clearProgress } from 'modules/progress/actions';
|
||||
|
||||
export const TOKEN = 'outbo-token-key';
|
||||
let baseURL = process.env.REACT_APP_BACKEND_URL;
|
||||
|
||||
if (!baseURL) {
|
||||
// throw new Error('API URL not set')
|
||||
// baseURL = 'https://api.outbo.pl/';
|
||||
baseURL = 'https://outbo-api.stage.ermlab.com';
|
||||
// baseURL = 'https://f101aae2d6a5.ngrok.io/';
|
||||
}
|
||||
const api = axios.create({
|
||||
baseURL,
|
||||
timeout: 60000,
|
||||
validateStatus: (status) => status >= 200 && status < 300,
|
||||
});
|
||||
|
||||
api.defaults.transformResponse = [
|
||||
function transformResponse(data) {
|
||||
/*eslint no-param-reassign:0*/
|
||||
const ISO_8601 = /(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2}.\d{3,6})[+-]\d{2}:\d{2}/;
|
||||
const reviver = (key: string, value: any) => {
|
||||
if (typeof value === 'string' && ISO_8601.test(value)) {
|
||||
return new Date(value);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
if (typeof data === 'string') {
|
||||
try {
|
||||
data = JSON.parse(data, reviver);
|
||||
} catch (e) {
|
||||
/* Ignore */
|
||||
}
|
||||
}
|
||||
return data;
|
||||
},
|
||||
];
|
||||
|
||||
api.interceptors.request.use(
|
||||
(config) => {
|
||||
if (localStorage.getItem(TOKEN)) {
|
||||
config.headers['Authorization'] = `Bearer ${localStorage.getItem(TOKEN)}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
async (error) => Promise.reject(error)
|
||||
);
|
||||
|
||||
api.interceptors.request.use(
|
||||
(config) => {
|
||||
// Do something before request is sent
|
||||
store.dispatch(setProgress(0));
|
||||
return config;
|
||||
},
|
||||
async (error: AxiosError) => Promise.reject(error)
|
||||
);
|
||||
|
||||
api.interceptors.response.use(
|
||||
(response) => response,
|
||||
|
||||
async (error: AxiosError) => {
|
||||
if (error.response && [401].includes(error.response.status)) {
|
||||
toast.error('Zaloguj się ponownie.');
|
||||
localStorage.removeItem(TOKEN);
|
||||
history.push('/login');
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
//PROGRSS BAR
|
||||
const calculatePercentage = (loaded: any, total: any) => Math.floor(loaded * 1.0) / total;
|
||||
|
||||
api.defaults.onUploadProgress = (e) => {
|
||||
const percentage = calculatePercentage(e.loaded, e.total);
|
||||
store.dispatch(setProgress(percentage));
|
||||
};
|
||||
api.interceptors.response.use(
|
||||
(response) => {
|
||||
store.dispatch(clearProgress());
|
||||
return response;
|
||||
},
|
||||
async (error: AxiosError) => {
|
||||
store.dispatch(clearProgress());
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
function retryFailedRequest(err: any) {
|
||||
if (err.status === 500 && err.config && !err.config.__isRetryRequest) {
|
||||
err.config.__isRetryRequest = true;
|
||||
return axios(err.config);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
axios.interceptors.response.use(undefined, retryFailedRequest);
|
||||
|
||||
// axios.interceptors.response.use(null, (error) => {
|
||||
// if (error.config && error.response && error.response.status === 401) {
|
||||
// return updateToken().then((token) => {
|
||||
// error.config.headers.xxxx <= set the token
|
||||
// return axios.request(error.onfig);
|
||||
// });
|
||||
// }
|
||||
|
||||
// return Promise.reject(error);
|
||||
// });
|
||||
|
||||
// axiosRetry(api, { retries: 3 });
|
||||
|
||||
export default api;
|
||||
@@ -0,0 +1,116 @@
|
||||
import API from './axiosConfig';
|
||||
import objectToFormDataConverter from 'helpers/objectToFormDataConverter';
|
||||
import { BriefRequest, BriefResponse } from 'interfaces/API/products';
|
||||
import { ReactText } from 'react';
|
||||
|
||||
const getBriefById = async (briefId: number): Promise<BriefResponse | null> => {
|
||||
try {
|
||||
const response = await API.get<BriefResponse>(`/briefs/${briefId}/`);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const patchBriefById = async (briefId: number, data: BriefRequest): Promise<BriefResponse | null> => {
|
||||
try {
|
||||
const { packagingType, ...rest } = data;
|
||||
let formData;
|
||||
if (packagingType === null) {
|
||||
formData = objectToFormDataConverter(rest);
|
||||
} else {
|
||||
formData = objectToFormDataConverter(data);
|
||||
}
|
||||
const response = await API.patch<BriefResponse>(`/briefs/${briefId}/`, formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
});
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const acceptBrief = async (briefId: number): Promise<BriefResponse | null> => {
|
||||
try {
|
||||
const response = await API.patch<BriefResponse>(`/briefs/${briefId}/`, { accept: true });
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const rejectBrief = async (briefId: number): Promise<BriefResponse | null> => {
|
||||
try {
|
||||
const response = await API.patch<BriefResponse>(`/briefs/${briefId}/`, { accept: false });
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const publishBrief = async (briefId: number): Promise<BriefResponse | null> => {
|
||||
try {
|
||||
const response = await API.patch<BriefResponse>(`/briefs/${briefId}/`, { publish: true });
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const deleteBlanking = async (blankingId: number): Promise<number | null> => {
|
||||
try {
|
||||
const response = await API.delete<void>(`/blankings/${blankingId}/`);
|
||||
return response.status;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const deletePhoto = async (photoId: number): Promise<number | null> => {
|
||||
try {
|
||||
const response = await API.delete<void>(`/photos/${photoId}/`);
|
||||
return response.status;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
const cloneBrief = async (copyFrom: number, briefId: number): Promise<BriefResponse | null> => {
|
||||
try {
|
||||
const response = await API.patch<BriefResponse>(`/briefs/${briefId}/`, { cloneFrom: copyFrom });
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const exportBriefToPdf = async (briefId: ReactText, filename: string): Promise<void | null> => {
|
||||
try {
|
||||
const response = await API.get(`/briefs/${briefId}/export-to-pdf/`, { responseType: 'blob' });
|
||||
const file = new Blob([response.data], { type: 'application/pdf' });
|
||||
const fileURL = URL.createObjectURL(file);
|
||||
const link = document.createElement('a');
|
||||
link.href = fileURL;
|
||||
link.download = filename;
|
||||
document.body.append(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
exportBriefToPdf,
|
||||
cloneBrief,
|
||||
deleteBlanking,
|
||||
deletePhoto,
|
||||
getBriefById,
|
||||
acceptBrief,
|
||||
rejectBrief,
|
||||
patchBriefById,
|
||||
publishBrief,
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
import API from './axiosConfig';
|
||||
import { DesignStageResponse, DesignStageFileRequest, DesignStageFileResponse } from 'interfaces/API/draft';
|
||||
import objectToFormDataConverter from 'helpers/objectToFormDataConverter';
|
||||
import { AxiosResponse } from 'axios';
|
||||
|
||||
const getDraftById = async (draftId: number): Promise<DesignStageResponse | null> => {
|
||||
try {
|
||||
const response = await API.get<DesignStageResponse>(`/drafts/${draftId}/`);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const addDraftFile = async (draftId: number, data: DesignStageFileRequest): Promise<DesignStageFileResponse | null> => {
|
||||
try {
|
||||
const formData = objectToFormDataConverter(data);
|
||||
const response = await API.post<DesignStageFileResponse>(`/drafts/${draftId}/files/`, formData);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const acceptDraft = async (draftId: number): Promise<DesignStageResponse | null> => {
|
||||
try {
|
||||
const response = await API.patch<DesignStageResponse>(`/drafts/${draftId}/`, { accept: true });
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const openDraft = async (draftId: number) => {
|
||||
try {
|
||||
const response = await API.get<AxiosResponse>(`/draft-files/${draftId}/open-file/`);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
openDraft,
|
||||
getDraftById,
|
||||
addDraftFile,
|
||||
acceptDraft,
|
||||
};
|
||||
// export const fetchDrafts = async (productId: number): Promise<DraftResponse[] | []> => {
|
||||
// try {
|
||||
// const response = await API.get<DraftResponse[]>('');
|
||||
// return response.data;
|
||||
// } catch (err) {
|
||||
// console.error(err);
|
||||
// return [];
|
||||
// }
|
||||
// };
|
||||
@@ -0,0 +1,10 @@
|
||||
import API from './axiosConfig';
|
||||
|
||||
export const handlePDFdownload = async (url: string, filename: string): Promise<void> => {
|
||||
const response = await API({ url, method: 'GET', responseType: 'blob' });
|
||||
window.open(response.config.url, '_blank');
|
||||
// if (response) {
|
||||
// const blobUrl = URL.createObjectURL(new Blob([response.data], { type: 'application/pdf' }));
|
||||
// window.open(blobUrl);
|
||||
// }
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
import API from './axiosConfig';
|
||||
import {
|
||||
FinalPhotoBriefResponse,
|
||||
FinalPhotoBriefRequest,
|
||||
FinalPhotoBriefPhotoType,
|
||||
} from 'interfaces/API/finalPhotoBrief';
|
||||
import objectToFormDataConverter from 'helpers/objectToFormDataConverter';
|
||||
import { FinalPhotoBrief } from 'organisms/FinalPhotoBrief/FinalPhotoBrief';
|
||||
|
||||
const getFinalPhotoBriefById = async (finalPhotoBriefId: number): Promise<FinalPhotoBriefResponse | null> => {
|
||||
try {
|
||||
const response = await API.get<FinalPhotoBriefResponse>(`/final-photobriefing/${finalPhotoBriefId}/`);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const removeFinalPhotoBriefPhoto = async (url: string): Promise<number | null> => {
|
||||
try {
|
||||
const response = await API.delete<void>(url);
|
||||
return response.status;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const addFinalPhotoBriefItems = async (
|
||||
data: FinalPhotoBriefRequest,
|
||||
finalPhotoBriefId: number
|
||||
): Promise<FinalPhotoBriefResponse | null> => {
|
||||
try {
|
||||
const formData = objectToFormDataConverter(data);
|
||||
const response = await API.patch<FinalPhotoBriefResponse>(`/final-photobriefing/${finalPhotoBriefId}/`, formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
});
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
removeFinalPhotoBriefPhoto,
|
||||
getFinalPhotoBriefById,
|
||||
addFinalPhotoBriefItems,
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
import API from './axiosConfig';
|
||||
import { DesignStageResponse, DesignStageFileRequest, DesignStageFileResponse } from 'interfaces/API/draft';
|
||||
import objectToFormDataConverter from 'helpers/objectToFormDataConverter';
|
||||
import { AxiosResponse } from 'axios';
|
||||
|
||||
const getFirstFinalById = async (firstFinalId: number): Promise<DesignStageResponse | null> => {
|
||||
try {
|
||||
const response = await API.get<DesignStageResponse>(`/ffs/${firstFinalId}/`);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const addFirstFinalFile = async (
|
||||
firstFinalId: number,
|
||||
data: DesignStageFileRequest
|
||||
): Promise<DesignStageFileResponse | null> => {
|
||||
try {
|
||||
const formData = objectToFormDataConverter(data);
|
||||
const response = await API.post<DesignStageFileResponse>(`/ffs/${firstFinalId}/files/`, formData);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const acceptFirstFinalFile = async (firstFinalId: number): Promise<DesignStageResponse | null> => {
|
||||
try {
|
||||
const response = await API.patch<DesignStageResponse>(`/ffs/${firstFinalId}/`, { accept: true });
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const openFirstFinal = async (draftId: number) => {
|
||||
try {
|
||||
const response = await API.get<AxiosResponse>(`/first-final-files/${draftId}/open-file/`);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
openFirstFinal,
|
||||
getFirstFinalById,
|
||||
addFirstFinalFile,
|
||||
acceptFirstFinalFile,
|
||||
};
|
||||
// export const fetchDrafts = async (productId: number): Promise<DraftResponse[] | []> => {
|
||||
// try {
|
||||
// const response = await API.get<DraftResponse[]>('');
|
||||
// return response.data;
|
||||
// } catch (err) {
|
||||
// console.error(err);
|
||||
// return [];
|
||||
// }
|
||||
// };
|
||||
@@ -0,0 +1,61 @@
|
||||
import API from './axiosConfig';
|
||||
|
||||
export enum MessageType {
|
||||
USER = 'USER',
|
||||
SYSTEM_SUCCESS = 'Success',
|
||||
SYSTEM_FAILURE = 'Failure',
|
||||
}
|
||||
|
||||
export type MessageActivityLogItem = {
|
||||
messageType: MessageType;
|
||||
message: string;
|
||||
stage: string;
|
||||
createdBy: number;
|
||||
createdAt: Date;
|
||||
};
|
||||
|
||||
export const fetchProductActivityLog = async (productId: number): Promise<MessageActivityLogItem[] | []> => {
|
||||
try {
|
||||
const response = await API.get<MessageActivityLogItem[]>(`/products/${productId}/messages/`);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchProjectActivityLog = async (projectId: number): Promise<MessageActivityLogItem[] | []> => {
|
||||
try {
|
||||
const response = await API.get<MessageActivityLogItem[]>(`/projects/${projectId}/messages/`);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const postNewProductMessage = async (
|
||||
message: string,
|
||||
productId: number
|
||||
): Promise<MessageActivityLogItem | null> => {
|
||||
try {
|
||||
const response = await API.post<MessageActivityLogItem>(`/products/${productId}/messages/`, { message });
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const postNewProjectMessage = async (
|
||||
message: string,
|
||||
projectId: number
|
||||
): Promise<MessageActivityLogItem | null> => {
|
||||
try {
|
||||
const response = await API.post<MessageActivityLogItem>(`/projects/${projectId}/messages/`, { message });
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,98 @@
|
||||
import API from './axiosConfig';
|
||||
import {
|
||||
PhotoBriefResponse,
|
||||
PhotoBriefCategoryResponse,
|
||||
PhotoBriefItemRequest,
|
||||
PhotoBriefItemResponse,
|
||||
PhotoBriefChoiceRequest,
|
||||
PhotoBriefChoiceResponse,
|
||||
} from 'interfaces/API/photoBrief';
|
||||
import objectToFormDataConverter from 'helpers/objectToFormDataConverter';
|
||||
|
||||
const getPhotoBriefById = async (photoBriefId: number): Promise<PhotoBriefResponse | null> => {
|
||||
try {
|
||||
const response = await API.get<PhotoBriefResponse>(`/photobriefing/${photoBriefId}/`);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const getPhotoBriefCategories = async (): Promise<PhotoBriefCategoryResponse[]> => {
|
||||
try {
|
||||
const response = await API.get<PhotoBriefCategoryResponse[]>('/photobriefing-categories/');
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const addPhotoBriefItem = async (
|
||||
data: PhotoBriefItemRequest,
|
||||
photoBriefId: number
|
||||
): Promise<PhotoBriefItemResponse | null> => {
|
||||
try {
|
||||
const formData = objectToFormDataConverter(data);
|
||||
const response = await API.post<PhotoBriefItemResponse>(`/photobriefing/${photoBriefId}/items/`, formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
});
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const acceptPhotoBriefItem = async (
|
||||
data: PhotoBriefChoiceRequest,
|
||||
photoBriefId: number
|
||||
): Promise<PhotoBriefResponse | null> => {
|
||||
try {
|
||||
const response = await API.patch<PhotoBriefResponse>(`/photobriefing/${photoBriefId}/`, { choice: data });
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const publishPhotoBrief = async (photoBriefId: number): Promise<PhotoBriefResponse | null> => {
|
||||
try {
|
||||
const response = await API.patch<PhotoBriefResponse>(`/photobriefing/${photoBriefId}/`, { publish: true });
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const deletePhotoBriefItem = async (itemId: number): Promise<number | null> => {
|
||||
try {
|
||||
const response = await API.delete<number>(`/photobriefing-items/${itemId}/`);
|
||||
return response.status;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const finishPhotoBrief = async (photoBriefId: number): Promise<PhotoBriefResponse | null> => {
|
||||
try {
|
||||
const response = await API.patch<PhotoBriefResponse>(`/photobriefing/${photoBriefId}/`, { complete: true });
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
finishPhotoBrief,
|
||||
deletePhotoBriefItem,
|
||||
publishPhotoBrief,
|
||||
acceptPhotoBriefItem,
|
||||
addPhotoBriefItem,
|
||||
getPhotoBriefById,
|
||||
getPhotoBriefCategories,
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
import API from './axiosConfig';
|
||||
import { DesignStageResponse, DesignStageFileRequest, DesignStageFileResponse } from 'interfaces/API/draft';
|
||||
import objectToFormDataConverter from 'helpers/objectToFormDataConverter';
|
||||
import { AxiosResponse } from 'axios';
|
||||
|
||||
const getPrintingFileById = async (printingFileId: number): Promise<DesignStageResponse | null> => {
|
||||
try {
|
||||
const response = await API.get<DesignStageResponse>(`/pfs/${printingFileId}/`);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const addPrintingFilesFile = async (
|
||||
printingFileId: number,
|
||||
data: DesignStageFileRequest
|
||||
): Promise<DesignStageFileResponse | null> => {
|
||||
try {
|
||||
const formData = objectToFormDataConverter(data);
|
||||
const response = await API.post<DesignStageFileResponse>(`/pfs/${printingFileId}/files/`, formData);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const acceptPrintingFilesFile = async (printingFileId: number): Promise<DesignStageResponse | null> => {
|
||||
try {
|
||||
const response = await API.patch<DesignStageResponse>(`/pfs/${printingFileId}/`, { accept: true });
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const openPrintingFile = async (pringintFileId: number) => {
|
||||
try {
|
||||
const response = await API.get<AxiosResponse>(`/printing-files-files/${pringintFileId}/open-file/`);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
openPrintingFile,
|
||||
getPrintingFileById,
|
||||
addPrintingFilesFile,
|
||||
acceptPrintingFilesFile,
|
||||
};
|
||||
// export const fetchDrafts = async (productId: number): Promise<DraftResponse[] | []> => {
|
||||
// try {
|
||||
// const response = await API.get<DraftResponse[]>('');
|
||||
// return response.data;
|
||||
// } catch (err) {
|
||||
// console.error(err);
|
||||
// return [];
|
||||
// }
|
||||
// };
|
||||
@@ -0,0 +1,49 @@
|
||||
import API from './axiosConfig';
|
||||
import { ProductResponse, SimpleProductResponse, AnyProductStore } from 'interfaces/API/products';
|
||||
|
||||
export enum MessageType {
|
||||
USER = 'USER',
|
||||
SYSTEM_SUCCESS = 'SYSTEM_SUCCESS',
|
||||
SYSTEM_FAILURE = 'SYSTEM_FAILURE',
|
||||
}
|
||||
|
||||
export type ProductActivityLogItem = {
|
||||
messageType: MessageType;
|
||||
message: string;
|
||||
stage: string;
|
||||
createdBy: number;
|
||||
createdAt: Date;
|
||||
};
|
||||
|
||||
const getProducts = async (): Promise<SimpleProductResponse[] | null> => {
|
||||
try {
|
||||
const response = await API.get<SimpleProductResponse[]>('/products/');
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
//TODO: USED??
|
||||
const getProductById = async (id: number): Promise<ProductResponse | null> => {
|
||||
try {
|
||||
const response = await API.get<ProductResponse>(`/products/${id}/`);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
//TODO: USED??
|
||||
const getProductsByURL = async (url: string): Promise<AnyProductStore[] | null> => {
|
||||
try {
|
||||
const response = await API.get<AnyProductStore[]>(url);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
getProductById,
|
||||
getProducts,
|
||||
getProductsByURL,
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
import API from './axiosConfig';
|
||||
import { ProductStage, ProductStageGroup } from 'interfaces/API/projects';
|
||||
|
||||
export interface ProductStageResponse {
|
||||
id: ProductStage;
|
||||
label: string;
|
||||
group: ProductStageGroup;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface DropdownItem {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export type PackagingType = {
|
||||
value: number;
|
||||
label: string;
|
||||
};
|
||||
|
||||
export type Brand = {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type Category = {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
|
||||
const fetchCategories = async (): Promise<Category[]> => {
|
||||
try {
|
||||
const response = await API.get<Category[]>('/categories/');
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const fetchBrands = async (): Promise<Brand[]> => {
|
||||
try {
|
||||
const response = await API.get<Brand[]>('/brands/');
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const fetchPackagingTypes = async (): Promise<PackagingType[]> => {
|
||||
try {
|
||||
const response = await API.get<PackagingType[]>('/packaging-types/');
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
const fetchProductStages = async (): Promise<ProductStageResponse[]> => {
|
||||
try {
|
||||
const response = await API.get<ProductStageResponse[]>('/stages/');
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
fetchCategories,
|
||||
fetchBrands,
|
||||
fetchPackagingTypes,
|
||||
fetchProductStages,
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
import API from './axiosConfig';
|
||||
import { ProjectResponse, ProjectRequest, SimpleProject } from 'interfaces/API/projects';
|
||||
import { toast } from 'react-toastify';
|
||||
import { ProjectsQueryParams } from '../interfaces/API/projects';
|
||||
|
||||
type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<T, Exclude<keyof T, Keys>> &
|
||||
{
|
||||
[K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>;
|
||||
}[Keys];
|
||||
|
||||
const getProjects = async (params: RequireAtLeastOne<ProjectsQueryParams>): Promise<SimpleProject[] | null> => {
|
||||
try {
|
||||
const response = await API.get<ProjectResponse[]>('/projects/', { params });
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
toast.error('Nie udało się pobrać listy projektów.');
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const getProjectById = async (projectId: number): Promise<ProjectResponse | null> => {
|
||||
try {
|
||||
const response = await API.get<ProjectResponse>(`projects/${projectId}/`);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
toast.error('Wystąpił problem podczas pobierania projektu.');
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const createNewProject = async (data: ProjectRequest): Promise<ProjectResponse | null> => {
|
||||
try {
|
||||
const response = await API.post('/projects/', data);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
toast.error('Wystąpił problem podczas tworzenia projektu.');
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const publishProject = async (projectId: number): Promise<ProjectResponse | null> => {
|
||||
try {
|
||||
const response = await API.patch<ProjectResponse>(`/projects/${projectId}/`, { publish: true });
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
toast.error('Publikacja projektu nie powiodła się');
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
getProjects,
|
||||
getProjectById,
|
||||
createNewProject,
|
||||
publishProject,
|
||||
};
|
||||
@@ -0,0 +1,689 @@
|
||||
import API from './axiosConfig';
|
||||
|
||||
export enum ProductStageGroup {
|
||||
INVALID = 0,
|
||||
GROUP_1 = 1,
|
||||
GROUP_2 = 2,
|
||||
GROUP_3 = 3,
|
||||
GROUP_4 = 4,
|
||||
GROUP_5 = 5,
|
||||
}
|
||||
|
||||
export enum ProductStage {
|
||||
STAGE_1 = 'waiting_for_brief',
|
||||
STAGE_1A = 'waiting_for_brief_accept_by_agency',
|
||||
STAGE_1B = 'waiting_for_brief_accept_by_client',
|
||||
STAGE_2A = 'waiting_for_draft',
|
||||
STAGE_2B = 'waiting_for_draft_accept_by_supplier',
|
||||
STAGE_2C = 'waiting_for_draft_accept_by_client',
|
||||
STAGE_3A = 'waiting_for_ff',
|
||||
STAGE_3B = 'waiting_for_ff_accept_by_supplier',
|
||||
STAGE_3C = 'waiting_for_ff_accept_by_client',
|
||||
STAGE_4A = 'waiting_for_pf',
|
||||
STAGE_4B = 'waiting_for_pf_accept_by_supplier',
|
||||
STAGE_4C = 'waiting_for_pf_accept_by_client',
|
||||
STAGE_5 = 'completed',
|
||||
}
|
||||
export enum PhotoBriefStage {
|
||||
PUBLISHED = 'published',
|
||||
COMPLETED = 'completed',
|
||||
CREATED = 'created',
|
||||
}
|
||||
|
||||
export enum PhotoBriefPhotoType {
|
||||
ARRANGEMENT_PHOTO = 'arrangement_photo',
|
||||
PACKSHOT = 'packshot',
|
||||
CLIPPING = 'clipping',
|
||||
EXTRA_PHOTO = 'extra_photo',
|
||||
}
|
||||
|
||||
export enum PhotoBriefCategory {
|
||||
MODELS = 'models',
|
||||
STYLING = 'styling',
|
||||
SCENERY = 'scenery',
|
||||
}
|
||||
|
||||
export type PhotoBriefRequest = {
|
||||
sessionDate: string;
|
||||
conceptDescription: string;
|
||||
};
|
||||
|
||||
export type PhotoBriefResponse = {
|
||||
id: number;
|
||||
sessionDate: string;
|
||||
conceptDescription: string;
|
||||
stage: PhotoBriefStage;
|
||||
items: PhotoBriefItemResponse[];
|
||||
photos: PhotoBriefPhotoResponse[];
|
||||
acceptable: boolean;
|
||||
editable: boolean;
|
||||
};
|
||||
export type PhotoBriefItemRequest = {
|
||||
category: PhotoBriefCategory;
|
||||
title: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export type PhotoBriefItemResponse = {
|
||||
id: number;
|
||||
category: PhotoBriefCategory;
|
||||
photos: PhotoBriefItemPhotoResponse[];
|
||||
title: string;
|
||||
description: string;
|
||||
isAcceptedByClient: boolean;
|
||||
isAcceptedBySupplier: boolean;
|
||||
editable: boolean;
|
||||
};
|
||||
|
||||
export type PhotoBriefPhotoRequest = {
|
||||
file: File;
|
||||
type: PhotoBriefPhotoType;
|
||||
};
|
||||
|
||||
export type PhotoBriefPhotoResponse = {
|
||||
url: string;
|
||||
type: PhotoBriefPhotoType;
|
||||
filename: string;
|
||||
};
|
||||
|
||||
export type PhotoBriefItemPhotoRequest = {
|
||||
file: File;
|
||||
};
|
||||
|
||||
export type PhotoBriefItemPhotoResponse = {
|
||||
url: string;
|
||||
filename: string;
|
||||
};
|
||||
|
||||
export interface Photo {
|
||||
file?: File;
|
||||
id?: number;
|
||||
url: string;
|
||||
filename: string;
|
||||
}
|
||||
export interface PhotoResponse {
|
||||
url: string;
|
||||
id: number;
|
||||
filename: string;
|
||||
}
|
||||
export interface PhotoRequest {
|
||||
file: File;
|
||||
}
|
||||
export interface Blanking {
|
||||
file?: File;
|
||||
filename: string;
|
||||
size: string;
|
||||
url: string;
|
||||
id?: number;
|
||||
}
|
||||
|
||||
export interface BlankingRequest {
|
||||
file: File;
|
||||
}
|
||||
|
||||
export interface BlankingResponse {
|
||||
url: string;
|
||||
id: number;
|
||||
}
|
||||
|
||||
export type PrintingFile = {
|
||||
id?: number;
|
||||
file?: File;
|
||||
url: string;
|
||||
uploadedAt: Date;
|
||||
uploadedBy: number;
|
||||
filename: string;
|
||||
size: string;
|
||||
description: string;
|
||||
version: string;
|
||||
};
|
||||
|
||||
export type PrintingFileResponse = {
|
||||
id: number;
|
||||
url: string;
|
||||
uploadedAt: Date;
|
||||
uploadedBy: number;
|
||||
description: string;
|
||||
version: string;
|
||||
filename: string;
|
||||
size: string;
|
||||
};
|
||||
|
||||
export type FileListItemProps = {
|
||||
id?: number;
|
||||
file?: File;
|
||||
url: string;
|
||||
uploadedAt: Date;
|
||||
uploadedBy: number;
|
||||
filename: string;
|
||||
size: string;
|
||||
description: string;
|
||||
version: string;
|
||||
};
|
||||
|
||||
export type PrintingFileRequest = {
|
||||
file: File;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export type FirstFinalFile = {
|
||||
id?: number;
|
||||
file?: File;
|
||||
url: string;
|
||||
uploadedAt: Date;
|
||||
uploadedBy: number;
|
||||
filename: string;
|
||||
size: string;
|
||||
description: string;
|
||||
version: string;
|
||||
};
|
||||
|
||||
export type FirstFinalFileResponse = {
|
||||
id: number;
|
||||
url: string;
|
||||
uploadedAt: Date;
|
||||
uploadedBy: number;
|
||||
description: string;
|
||||
version: string;
|
||||
filename: string;
|
||||
size: string;
|
||||
};
|
||||
|
||||
export type FirstFinalFileRequest = {
|
||||
file: File;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export type DraftFile = {
|
||||
id?: number;
|
||||
file?: File;
|
||||
url: string;
|
||||
uploadedAt: Date;
|
||||
uploadedBy: number;
|
||||
filename: string;
|
||||
size: string;
|
||||
description: string;
|
||||
version: string;
|
||||
};
|
||||
|
||||
export type DraftFileResponse = {
|
||||
id: number;
|
||||
url: string;
|
||||
uploadedAt: Date;
|
||||
uploadedBy: number;
|
||||
description: string;
|
||||
version: string;
|
||||
filename: string;
|
||||
size: string;
|
||||
};
|
||||
|
||||
export type ProductFileStageFileRequest = {
|
||||
file: File;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export type DraftFileRequest = {
|
||||
file: File;
|
||||
description: string;
|
||||
};
|
||||
// export type Photo = PhotoRequest | PhotoResponse;
|
||||
// export type Blanking = BlankingRequest | BlankingResponse;
|
||||
|
||||
export interface ProductRequest {
|
||||
productNamePol: string;
|
||||
productNameEng: string;
|
||||
modelNo: string;
|
||||
batchNo: string;
|
||||
ean: string;
|
||||
mainDescription: string;
|
||||
extraDescription: string;
|
||||
material: string;
|
||||
cleaningMethod: string;
|
||||
countryOfOrigin: string;
|
||||
warning: string;
|
||||
arrangementPhotos: string;
|
||||
packshot: string;
|
||||
extraPhoto: string;
|
||||
clippings: string;
|
||||
illustration: string;
|
||||
manual: string;
|
||||
packagingType: number | null;
|
||||
hasPhotos: boolean;
|
||||
hasIllustrations: boolean;
|
||||
isCleaningMethod: boolean;
|
||||
isClipping: boolean;
|
||||
certificate: string;
|
||||
capabilitiesIcons: string;
|
||||
productIcons: string;
|
||||
informationSigns: string;
|
||||
photos: Photo[];
|
||||
blankings: Blanking[];
|
||||
isCountryOfOrigin: boolean;
|
||||
isDescriptionEng: boolean;
|
||||
isDescriptionPol: boolean;
|
||||
isDescriptionPor: boolean;
|
||||
isDescriptionRus: boolean;
|
||||
isExtraPhoto: boolean;
|
||||
isManual: boolean;
|
||||
isMaterial: boolean;
|
||||
isPackshot: boolean;
|
||||
isWarnings: boolean;
|
||||
}
|
||||
|
||||
export interface ProductResponse {
|
||||
acceptable: boolean;
|
||||
editable: boolean;
|
||||
publishable: boolean;
|
||||
id: number;
|
||||
productNamePol: string;
|
||||
productNameEng: string;
|
||||
modelNo: string;
|
||||
batchNo: string;
|
||||
ean: string;
|
||||
mainDescription: string;
|
||||
extraDescription: string;
|
||||
material: string;
|
||||
cleaningMethod: string;
|
||||
countryOfOrigin: string;
|
||||
warning: string;
|
||||
arrangementPhotos: string;
|
||||
packshot: string;
|
||||
extraPhoto: string;
|
||||
clippings: string;
|
||||
illustration: string;
|
||||
manual: string;
|
||||
stage: ProductStage;
|
||||
photos: Photo[];
|
||||
blankings: Blanking[];
|
||||
draftFiles: DraftFile[];
|
||||
firstFinalFiles: FirstFinalFile[];
|
||||
printingFiles: PrintingFile[];
|
||||
photoBrief: PhotoBriefResponse;
|
||||
packagingType: number | null;
|
||||
certificate: string;
|
||||
capabilitiesIcons: string;
|
||||
productIcons: string;
|
||||
informationSigns: string;
|
||||
hasPhotos: boolean;
|
||||
hasIllustrations: boolean;
|
||||
isCleaningMethod: boolean;
|
||||
isClipping: boolean;
|
||||
isCountryOfOrigin: boolean;
|
||||
isDescriptionEng: boolean;
|
||||
isDescriptionPol: boolean;
|
||||
isDescriptionPor: boolean;
|
||||
isDescriptionRus: boolean;
|
||||
isExtraPhoto: boolean;
|
||||
isManual: boolean;
|
||||
isMaterial: boolean;
|
||||
isPackshot: boolean;
|
||||
isWarnings: boolean;
|
||||
}
|
||||
|
||||
export interface ProjectResponse {
|
||||
maxProductStage: string;
|
||||
isPublished: boolean;
|
||||
publishable: boolean;
|
||||
slug: string;
|
||||
name: string;
|
||||
description: string;
|
||||
products: ProductResponse[];
|
||||
category: number;
|
||||
brand: number;
|
||||
printingFilesDate: string;
|
||||
promoDate: string;
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface ProjectRequest {
|
||||
name: string;
|
||||
description: string;
|
||||
category: number | null;
|
||||
brand: number | null;
|
||||
productsAmount: number;
|
||||
printingFilesDate: string;
|
||||
promoDate: string;
|
||||
}
|
||||
|
||||
export const fetchProjects = async (): Promise<ProjectResponse[] | null> => {
|
||||
try {
|
||||
const response = await API.get<ProjectResponse[]>('/projects/');
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchProjectById = async (projectId: number) => {
|
||||
try {
|
||||
const response = await API.get<ProjectResponse>(`/projects/${projectId}/`);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const postNewProject = async (data: ProjectRequest): Promise<ProjectResponse | null> => {
|
||||
try {
|
||||
const response = await API.post<ProjectResponse>('/projects/', data);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const uploadProductPhoto = async (id: number, photo: PhotoRequest): Promise<void | null> => {
|
||||
try {
|
||||
const data = new FormData();
|
||||
data.append('file', photo.file);
|
||||
const response = await API.post<void>(`products/${id}/photos/`, data, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
});
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const removeProductImage = async (photoId: number): Promise<boolean> => {
|
||||
try {
|
||||
await API.delete<void>(`/photos/${photoId}/`);
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const uploadProductBlanking = async (productId: number, blanking: BlankingRequest): Promise<void | null> => {
|
||||
try {
|
||||
const data = new FormData();
|
||||
data.append('file', blanking.file);
|
||||
const response = await API.post<void>(`products/${productId}/blankings/`, data, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
});
|
||||
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const removeProductBlanking = async (blankingId: number): Promise<boolean> => {
|
||||
try {
|
||||
await API.delete<void>(`/blankings/${blankingId}/`);
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const patchProduct = async (id: number, data: Partial<ProductRequest>): Promise<ProductResponse | null> => {
|
||||
try {
|
||||
const { photos, blankings, ...values } = data;
|
||||
if (photos && photos.length > 0) {
|
||||
// TODO: investigate & refactor
|
||||
await photos
|
||||
.filter((photo) => 'file' in photo && photo.file !== undefined)
|
||||
.map(({ file }) => ({ file } as PhotoRequest))
|
||||
.reduce(async (previous, photo) => {
|
||||
await previous;
|
||||
return uploadProductPhoto(id, photo);
|
||||
}, Promise.resolve<void | null>(null));
|
||||
}
|
||||
|
||||
if (blankings && blankings.length > 0) {
|
||||
// TODO: investigate & refactor
|
||||
await blankings
|
||||
.filter((blanking) => 'file' in blanking)
|
||||
.map(({ file }) => ({ file } as BlankingRequest))
|
||||
.reduce(async (previous, blanking) => {
|
||||
await previous;
|
||||
return uploadProductBlanking(id, blanking);
|
||||
}, Promise.resolve<void | null>(null));
|
||||
}
|
||||
|
||||
const response = await API.patch<ProductResponse>(`/products/${id}/`, values);
|
||||
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const approveProduct = async (id: number): Promise<ProductResponse | null> => {
|
||||
try {
|
||||
const response = await API.patch<ProductResponse>(`/products/${id}/`, { accept: true });
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const declineProduct = async (id: number): Promise<ProductResponse | null> => {
|
||||
try {
|
||||
const response = await API.patch<ProductResponse>(`/products/${id}/`, { accept: false });
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const approveProject = async (projectId: number): Promise<ProjectResponse | null> => {
|
||||
try {
|
||||
const response = await API.patch<ProjectResponse>(`/projects/${projectId}/`, { publish: true });
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const announceProduct = async (id: number): Promise<ProductResponse | null> => {
|
||||
try {
|
||||
const response = await API.patch<ProductResponse>(`/products/${id}/`, { publish: true });
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const postDraft = async (draftId: number, draft: DraftFileRequest): Promise<DraftFileResponse | null> => {
|
||||
try {
|
||||
const data = new FormData();
|
||||
data.append('file', draft.file);
|
||||
data.append('description', draft.description);
|
||||
const response = await API.patch<DraftFileResponse>(`/drafts/${draftId}/`, data, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
});
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const approveDraft = async (productId: number): Promise<ProductResponse | null> => {
|
||||
try {
|
||||
const response = await API.patch<ProductResponse>(`/products/${productId}/`, { accept: true });
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchProductById = async (productId: number): Promise<ProductResponse | null> => {
|
||||
try {
|
||||
const response = await API.get<ProductResponse>(`/products/${productId}/`);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const patchPhotoBrief = async (
|
||||
productId: number,
|
||||
photoBrief: PhotoBriefRequest
|
||||
): Promise<PhotoBriefResponse | null> => {
|
||||
try {
|
||||
const response = await API.patch<PhotoBriefResponse>(`/products/${productId}/photo-briefing/`, photoBrief);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const approvePhotoBriefItem = async (
|
||||
photoBriefId: number,
|
||||
category: PhotoBriefCategory,
|
||||
id: number | null
|
||||
): Promise<PhotoBriefResponse | null> => {
|
||||
try {
|
||||
const response = await API.patch<PhotoBriefResponse>(`/photo-briefing/${photoBriefId}/items/`, {
|
||||
category,
|
||||
accept: id,
|
||||
});
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const approvePhotoBrief = async (productId: number) => {
|
||||
try {
|
||||
const response = await API.patch<PhotoBriefResponse>(`/products/${productId}/photo-briefing/`, {
|
||||
accept: true,
|
||||
});
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const patchPublishPhotoBrief = async (productId: number): Promise<PhotoBriefResponse | null> => {
|
||||
try {
|
||||
const response = await API.patch<PhotoBriefResponse>(`/products/${productId}/photo-briefing/`, { publish: true });
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const postPhotoBriefItem = async (
|
||||
photoBriefId: number,
|
||||
photoBriefItem: PhotoBriefItemRequest
|
||||
): Promise<PhotoBriefItemResponse | null> => {
|
||||
try {
|
||||
const response = await API.post<PhotoBriefItemResponse>(`/photo-briefing/${photoBriefId}/items/`, photoBriefItem);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const postPhotoBriefItemPhoto = async (
|
||||
itemId: number,
|
||||
photo: PhotoBriefItemPhotoRequest
|
||||
): Promise<PhotoBriefItemResponse | null> => {
|
||||
try {
|
||||
const data = new FormData();
|
||||
data.append('file', photo.file);
|
||||
const response = await API.post<PhotoBriefItemResponse>(`/photo-briefing-items/${itemId}/photos/`, data);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// export const getProjectBrands = async (): Promise<ProjectResponse[]> => {};
|
||||
|
||||
export const postFirstFinal = async (
|
||||
productId: number,
|
||||
draft: FirstFinalFileRequest
|
||||
): Promise<FirstFinalFileResponse | null> => {
|
||||
try {
|
||||
const data = new FormData();
|
||||
data.append('file', draft.file);
|
||||
data.append('description', draft.description);
|
||||
const response = await API.post<FirstFinalFileResponse>(`/products/${productId}/first-final-files/`, data, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
});
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const approveFirstFinal = async (productId: number): Promise<ProductResponse | null> => {
|
||||
try {
|
||||
const response = await API.patch<ProductResponse>(`/products/${productId}/`, { accept: true });
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const postPrintingFile = async (
|
||||
productId: number,
|
||||
draft: PrintingFileRequest
|
||||
): Promise<PrintingFileResponse | null> => {
|
||||
try {
|
||||
const data = new FormData();
|
||||
data.append('file', draft.file);
|
||||
data.append('description', draft.description);
|
||||
const response = await API.post<PrintingFileResponse>(`/products/${productId}/printing-files/`, data, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
});
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const approvePrintingFile = async (productId: number): Promise<ProductResponse | null> => {
|
||||
try {
|
||||
const response = await API.patch<ProductResponse>(`/products/${productId}/`, { accept: true });
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const postPhotoBriefPhoto = async (
|
||||
photoBriefId: number,
|
||||
photoBriefPhoto: PhotoBriefPhotoRequest
|
||||
): Promise<PhotoBriefPhotoResponse | null> => {
|
||||
try {
|
||||
const data = new FormData();
|
||||
data.append('file', photoBriefPhoto.file);
|
||||
data.append('type', photoBriefPhoto.type);
|
||||
const response = await API.post<PhotoBriefPhotoResponse>(`/photo-briefing/${photoBriefId}/photos/`, data, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
});
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
import API, { TOKEN } from './axiosConfig';
|
||||
import { history } from '../App';
|
||||
import { toast } from 'react-toastify';
|
||||
import {
|
||||
AuthorizationRequest,
|
||||
UserDataResponse,
|
||||
AuthorizationResponse,
|
||||
UserListItem,
|
||||
} from 'interfaces/API/userAuthorization';
|
||||
|
||||
const userAuthorization = async ({ username, password }: AuthorizationRequest): Promise<boolean> => {
|
||||
try {
|
||||
const response = await API.post<AuthorizationResponse>('/token/', { username, password });
|
||||
const token = response.data.token;
|
||||
localStorage.setItem(TOKEN, token);
|
||||
return true;
|
||||
} catch (err) {
|
||||
if (err.response) {
|
||||
toast.error(err.response.data.nonFieldErrors[0]);
|
||||
} else {
|
||||
toast.error('Wystąpił problem. Sprobuj ponownie.');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const fetchCurrentUser = async (): Promise<UserDataResponse | null> => {
|
||||
try {
|
||||
const response = await API.get<UserDataResponse>('/users/me/');
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const logout = async (): Promise<boolean> => {
|
||||
try {
|
||||
localStorage.removeItem(TOKEN);
|
||||
history.push('/login');
|
||||
return await Promise.resolve(true);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return await Promise.resolve(false);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchAllUsers = async (): Promise<UserListItem[] | []> => {
|
||||
try {
|
||||
const response = await API.get<UserListItem[]>('/users/');
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
userAuthorization,
|
||||
fetchCurrentUser,
|
||||
logout,
|
||||
fetchAllUsers,
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import store from './store/store';
|
||||
import './App.scss';
|
||||
import { Switch, Router, Redirect } from 'react-router-dom';
|
||||
import { Login, Main } from 'containers';
|
||||
import { PrivateRoute, NotAuthorizedRoute } from 'helpers/PrivateRoute';
|
||||
import { createBrowserHistory } from 'history';
|
||||
import 'react-image-lightbox/style.css';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
|
||||
export const history = createBrowserHistory();
|
||||
|
||||
const App: React.FC = () => {
|
||||
return (
|
||||
<Router history={history}>
|
||||
<Provider store={store}>
|
||||
<Switch>
|
||||
<NotAuthorizedRoute exact path="/login">
|
||||
<ToastContainer position={toast.POSITION.BOTTOM_LEFT} autoClose={5000} />
|
||||
<Login />
|
||||
</NotAuthorizedRoute>
|
||||
<PrivateRoute path={['/projects/:id/products/:pid', '/projects/new', '/projects/:id', '/']}>
|
||||
<ToastContainer position={toast.POSITION.BOTTOM_RIGHT} autoClose={5000} />
|
||||
<Main />
|
||||
</PrivateRoute>
|
||||
<Redirect to="/" />
|
||||
</Switch>
|
||||
</Provider>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,9 @@
|
||||
const getProductById = jest.fn();
|
||||
const getProducts = jest.fn();
|
||||
const getProductsByURL = jest.fn();
|
||||
|
||||
export default {
|
||||
getProductById,
|
||||
getProducts,
|
||||
getProductsByURL,
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
const getProjects = jest.fn();
|
||||
const getProjectById = jest.fn();
|
||||
const createNewProject = jest.fn();
|
||||
const publishProject = jest.fn();
|
||||
|
||||
export default {
|
||||
getProjects,
|
||||
getProjectById,
|
||||
createNewProject,
|
||||
publishProject,
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
const userAuthorization = jest.fn().mockReturnValue(true);
|
||||
const fetchCurrentUser = jest.fn();
|
||||
|
||||
export default {
|
||||
userAuthorization,
|
||||
fetchCurrentUser,
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export default '';
|
||||
@@ -0,0 +1,3 @@
|
||||
const useCurrentBrief = jest.fn();
|
||||
|
||||
export default useCurrentBrief;
|
||||
@@ -0,0 +1,3 @@
|
||||
const useCurrentProject = jest.fn();
|
||||
|
||||
export default useCurrentProject;
|
||||
@@ -0,0 +1,3 @@
|
||||
const useNumberParams = jest.fn();
|
||||
|
||||
export default useNumberParams;
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from '../App';
|
||||
|
||||
it('renders without crashing', () => {
|
||||
const div = document.createElement('div');
|
||||
ReactDOM.render(<App />, div);
|
||||
ReactDOM.unmountComponentAtNode(div);
|
||||
});
|
||||
@@ -0,0 +1,210 @@
|
||||
import React from 'react';
|
||||
import { cleanup, render, waitForElement, act, fireEvent } from '@testing-library/react';
|
||||
import LeftSidebar from 'organisms/LeftSidebar';
|
||||
import { createMemoryHistory, createLocation } from 'history';
|
||||
import configureStore from 'redux-mock-store'; //ES6 modules
|
||||
import { ProductStage, AnyProject } from 'interfaces/API/projects';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Router, Route, match } from 'react-router-dom';
|
||||
import { ProductStore } from 'interfaces/API/products';
|
||||
import { ProductStageResponse, Category, Brand } from 'API/projectData';
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
const category: Category = {
|
||||
id: 1,
|
||||
name: 'toys',
|
||||
};
|
||||
const brand: Brand = {
|
||||
id: 1,
|
||||
name: 'boys',
|
||||
};
|
||||
const productStage: ProductStageResponse = {
|
||||
id: ProductStage.STAGE_1,
|
||||
label: '1',
|
||||
group: 1,
|
||||
description: 'Briefing',
|
||||
};
|
||||
const printingFilesDate = '2011-02-01';
|
||||
const promoDate = '2011-01-01';
|
||||
|
||||
const renderWithProviderAndRoute = (ui: any, { routeOptions, history, store }: any): any => {
|
||||
return {
|
||||
...render(
|
||||
<Provider store={store}>
|
||||
<Router history={history}>
|
||||
<Route {...routeOptions}>{ui}</Route>
|
||||
</Router>
|
||||
</Provider>
|
||||
),
|
||||
};
|
||||
};
|
||||
const createMockStore = (initialProjectValues: AnyProject[], initialProductValues: ProductStore) => {
|
||||
const mockStore = configureStore();
|
||||
const store = mockStore({
|
||||
brief: { briefs: [] },
|
||||
projects: {
|
||||
projectsList: initialProjectValues,
|
||||
},
|
||||
products: {
|
||||
currentStage: 0,
|
||||
productsList: [{ ...initialProductValues }],
|
||||
},
|
||||
users: {
|
||||
currentUser: {
|
||||
id: 1,
|
||||
organization: {
|
||||
type: 'supplier',
|
||||
},
|
||||
},
|
||||
usersList: [],
|
||||
},
|
||||
activityLog: { messages: [] },
|
||||
drafts: { draftList: [] },
|
||||
firstFinals: { firstFinalList: [] },
|
||||
printingFiles: { printingFilesList: [] },
|
||||
progress: { progress: null },
|
||||
projectData: {
|
||||
categories: [category],
|
||||
brands: [brand],
|
||||
productStages: [productStage],
|
||||
packagingTypes: [],
|
||||
},
|
||||
});
|
||||
return store;
|
||||
};
|
||||
|
||||
const routeConfiguration = (path: string, params?: any) => {
|
||||
const history = createMemoryHistory({ initialEntries: [path] });
|
||||
|
||||
const match: match = {
|
||||
isExact: true,
|
||||
path,
|
||||
url: path,
|
||||
params: {},
|
||||
};
|
||||
const location = createLocation(match.url);
|
||||
return { history, match, location };
|
||||
};
|
||||
|
||||
describe('LeftSidebar', () => {
|
||||
const products: ProductStore = {
|
||||
id: 1,
|
||||
displayName: 'test / test',
|
||||
project: 1,
|
||||
stage: ProductStage.STAGE_1,
|
||||
brief: '',
|
||||
draft: '',
|
||||
firstFinal: '',
|
||||
printingFiles: '',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
const projects: AnyProject[] = [
|
||||
{
|
||||
id: 1,
|
||||
slug: 'firstProjectSlug',
|
||||
name: 'firstProjectName',
|
||||
description: 'firstProjectDesc',
|
||||
printingFilesDate: printingFilesDate,
|
||||
productsAmount: 1,
|
||||
promoDate: promoDate,
|
||||
products: 'test',
|
||||
maxProductStage: ProductStage.STAGE_1A,
|
||||
publishable: false,
|
||||
published: true,
|
||||
brand: brand.id,
|
||||
category: brand.id,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
];
|
||||
const path = '/';
|
||||
let { history, match, location } = routeConfiguration(path);
|
||||
afterEach(() => (history = createMemoryHistory({ initialEntries: [path] })));
|
||||
const store = createMockStore(projects, products);
|
||||
it('should render without crash', () => {
|
||||
renderWithProviderAndRoute(<LeftSidebar />, {
|
||||
routeOptions: { exact: false, path },
|
||||
history,
|
||||
store,
|
||||
});
|
||||
});
|
||||
it('should render project', async () => {
|
||||
const { findByTestId } = renderWithProviderAndRoute(<LeftSidebar />, {
|
||||
routeOptions: { exact: false, path },
|
||||
history,
|
||||
store,
|
||||
});
|
||||
const projectsList = await waitForElement(() => findByTestId('leftSidebarProjects'));
|
||||
expect(projectsList.children.length).toBe(projects.length);
|
||||
});
|
||||
it('should render project properties', async () => {
|
||||
const { getByText } = renderWithProviderAndRoute(<LeftSidebar />, {
|
||||
routeOptions: { exact: false, path },
|
||||
history,
|
||||
store,
|
||||
});
|
||||
// const projects = await waitForElement(() => findByTestId('leftSidebarProjects'));
|
||||
|
||||
//FIND PROJECT SLUG
|
||||
expect(getByText(/firstProjectSlug/i)).toBeInTheDocument();
|
||||
|
||||
//FIND PROJECT NAMES
|
||||
expect(getByText(/FirstProjectName/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render product properties', async () => {
|
||||
const { getByText } = renderWithProviderAndRoute(<LeftSidebar />, {
|
||||
routeOptions: { exact: true, path },
|
||||
history,
|
||||
store,
|
||||
});
|
||||
|
||||
const stamp = getByText(/1/i);
|
||||
expect(getByText(/test \/ test/i)).toBeInTheDocument();
|
||||
expect(stamp.parentElement).toHaveClass('is-background-stage-1');
|
||||
});
|
||||
it('should collapse product list', async () => {
|
||||
const { getByTestId, queryByText } = renderWithProviderAndRoute(<LeftSidebar />, {
|
||||
routeOptions: { exact: false, path },
|
||||
history,
|
||||
store,
|
||||
});
|
||||
const leftSidebarCaret = getByTestId('leftSidebarCaret');
|
||||
expect(leftSidebarCaret).toHaveClass('caret--opened');
|
||||
await act(async () => {
|
||||
fireEvent.click(leftSidebarCaret);
|
||||
});
|
||||
expect(leftSidebarCaret).toHaveClass('caret--closed');
|
||||
expect(leftSidebarCaret).not.toHaveClass('caret--opened');
|
||||
expect(queryByText(/test \/ test/i)).not.toBeInTheDocument();
|
||||
});
|
||||
it('should redirect to project on project click', async () => {
|
||||
const { getByText } = renderWithProviderAndRoute(<LeftSidebar />, {
|
||||
routeOptions: { exact: false, path },
|
||||
history,
|
||||
store,
|
||||
});
|
||||
const projectSlug = getByText(/firstProjectSlug/i);
|
||||
await act(async () => {
|
||||
fireEvent.click(projectSlug);
|
||||
});
|
||||
expect(history.location.pathname).toBe('/projects/1');
|
||||
});
|
||||
it('should redirect to product on product click', async () => {
|
||||
const { getByText } = renderWithProviderAndRoute(<LeftSidebar />, {
|
||||
routeOptions: { exact: false, path },
|
||||
history,
|
||||
store,
|
||||
});
|
||||
|
||||
const productName = getByText(/test \/ test/i);
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(productName);
|
||||
});
|
||||
expect(history.location.pathname).toBe('/projects/1/products/1');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,109 @@
|
||||
import React from 'react';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import LoginForm from 'organisms/LoginForm';
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
import { render, cleanup, waitForElement, fireEvent, act, wait } from '@testing-library/react';
|
||||
import userApi from 'API/userAuthorization';
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
describe('Login', () => {
|
||||
let history = createMemoryHistory({ initialEntries: ['/login'] });
|
||||
|
||||
afterEach(() => (history = createMemoryHistory({ initialEntries: ['/login'] })));
|
||||
|
||||
const dispatch = jest.fn();
|
||||
|
||||
it('renders login form properly', async () => {
|
||||
const { findByTestId } = render(<LoginForm history={history} dispatch={dispatch} />);
|
||||
const passwordField = await waitForElement(() => findByTestId('password-field'));
|
||||
const usernameField = await waitForElement(() => findByTestId('username-field'));
|
||||
const submitButton = await waitForElement(() => findByTestId('submit-button'));
|
||||
|
||||
expect(submitButton.firstChild?.textContent).toBe('Zaloguj');
|
||||
expect(passwordField).toHaveValue('');
|
||||
expect(usernameField).toHaveValue('');
|
||||
});
|
||||
it('should display a blank login form', async () => {
|
||||
const { findByTestId } = render(<LoginForm history={history} dispatch={dispatch} />);
|
||||
|
||||
const loginForm = await findByTestId('login-form');
|
||||
|
||||
expect(loginForm).toHaveFormValues({
|
||||
username: '',
|
||||
password: '',
|
||||
});
|
||||
});
|
||||
it('should allow entering username and password', async () => {
|
||||
const { findByTestId } = render(<LoginForm history={history} dispatch={dispatch} />);
|
||||
const passwordField = await waitForElement(() => findByTestId('password-field'));
|
||||
const usernameField = await waitForElement(() => findByTestId('username-field'));
|
||||
await act(async () => {
|
||||
fireEvent.change(passwordField, { target: { value: 'password' } });
|
||||
fireEvent.change(usernameField, { target: { value: 'username' } });
|
||||
});
|
||||
expect(passwordField).toHaveValue('password');
|
||||
expect(usernameField).toHaveValue('username');
|
||||
});
|
||||
it('should not submit blank form', async () => {
|
||||
const { findByTestId, getByText } = render(<LoginForm history={history} dispatch={dispatch} />);
|
||||
|
||||
const loginForm = await findByTestId('login-form');
|
||||
fireEvent.submit(loginForm);
|
||||
|
||||
const usernameError = await waitForElement(() => getByText('Podaj nazwę użytkownika.'));
|
||||
const passwordError = await waitForElement(() => getByText('Podaj hasło.'));
|
||||
|
||||
expect(usernameError.innerHTML).toEqual('Podaj nazwę użytkownika.');
|
||||
expect(passwordError.innerHTML).toEqual('Podaj hasło.');
|
||||
});
|
||||
it('should submit form with given values and redirects', async () => {
|
||||
const { findByTestId, getByText } = render(<LoginForm history={history} dispatch={dispatch} />);
|
||||
const passwordField = await waitForElement(() => findByTestId('password-field'));
|
||||
const usernameField = await waitForElement(() => findByTestId('username-field'));
|
||||
const loginForm = await findByTestId('login-form');
|
||||
await act(async () => {
|
||||
fireEvent.change(passwordField, { target: { value: 'password' } });
|
||||
fireEvent.change(usernameField, { target: { value: 'username' } });
|
||||
});
|
||||
await act(async () => {
|
||||
fireEvent.submit(loginForm);
|
||||
});
|
||||
expect(userApi.userAuthorization).toHaveBeenCalledTimes(1);
|
||||
expect(history.location.pathname).toBe('/');
|
||||
});
|
||||
it('should display spinner on submitting form', async () => {
|
||||
const { findByTestId } = render(<LoginForm history={history} dispatch={dispatch} />);
|
||||
const submitButton = await waitForElement(() => findByTestId('submit-button'));
|
||||
const passwordField = await waitForElement(() => findByTestId('password-field'));
|
||||
const usernameField = await waitForElement(() => findByTestId('username-field'));
|
||||
const loginForm = await findByTestId('login-form');
|
||||
await act(async () => {
|
||||
fireEvent.change(usernameField, { target: { value: 'username' } });
|
||||
});
|
||||
await act(async () => {
|
||||
fireEvent.change(passwordField, { target: { value: 'password' } });
|
||||
});
|
||||
|
||||
fireEvent.submit(loginForm);
|
||||
expect(submitButton).toHaveClass('is-loading');
|
||||
await wait(() => expect(submitButton).not.toHaveClass('is-loading'));
|
||||
});
|
||||
it('should not redirect on invalid data', async () => {
|
||||
userApi.userAuthorization.mockReturnValueOnce(false);
|
||||
const { findByTestId } = render(<LoginForm history={history} dispatch={dispatch} />);
|
||||
const passwordField = await waitForElement(() => findByTestId('password-field'));
|
||||
const usernameField = await waitForElement(() => findByTestId('username-field'));
|
||||
const loginForm = await findByTestId('login-form');
|
||||
await act(async () => {
|
||||
fireEvent.change(passwordField, { target: { value: 'password' } });
|
||||
});
|
||||
await act(async () => {
|
||||
fireEvent.change(usernameField, { target: { value: 'username' } });
|
||||
});
|
||||
await act(async () => {
|
||||
fireEvent.submit(loginForm);
|
||||
});
|
||||
expect(history.location.pathname).toBe('/login');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,216 @@
|
||||
import React from 'react';
|
||||
import { createMemoryHistory, createLocation } from 'history';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Route, Router, match } from 'react-router';
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
import { render, cleanup, waitForElement, fireEvent, act, wait } from '@testing-library/react';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import ProductBrief from 'organisms/ProductBrief';
|
||||
import useCurrentBrief from 'hooks/useCurrentBrief';
|
||||
import useNumberParams from 'hooks/useNumberParams';
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
const createMockStore = (initialValues: any) => {
|
||||
const mockStore = configureStore();
|
||||
const store = mockStore({
|
||||
projectData: {
|
||||
packagingTypes: [],
|
||||
categories: [],
|
||||
brands: [],
|
||||
},
|
||||
users: {
|
||||
currentUser: {
|
||||
id: 1,
|
||||
organization: {
|
||||
type: 'supplier',
|
||||
},
|
||||
},
|
||||
usersList: [],
|
||||
},
|
||||
projects: {
|
||||
projectsList: [
|
||||
{
|
||||
id: 1,
|
||||
slug: '#WOIM1-4919',
|
||||
name: 'Test123',
|
||||
products: 'http://172.16.62.55:8000/products/?project=1',
|
||||
printingFilesDate: '2019-12-12',
|
||||
maxProductStage: 'waiting_for_draft',
|
||||
published: true,
|
||||
createdAt: '2019-12-12T10:29:02.860Z',
|
||||
updatedAt: '2019-12-13T08:56:52.806Z',
|
||||
},
|
||||
],
|
||||
},
|
||||
products: {
|
||||
productsList: [
|
||||
{
|
||||
id: 1,
|
||||
displayName: 'test / test',
|
||||
project: 1,
|
||||
stage: 'waiting_for_draft',
|
||||
brief: 'http://test/briefs/1/',
|
||||
draft: 'http://test/drafts/1/',
|
||||
firstFinal: null,
|
||||
printingFiles: null,
|
||||
photoBrief: 'http://test/photobriefing/1/',
|
||||
createdAt: '2019-12-12T10:29:02.925Z',
|
||||
updatedAt: '2019-12-13T08:56:52.788Z',
|
||||
},
|
||||
],
|
||||
},
|
||||
brief: {
|
||||
briefs: [
|
||||
{
|
||||
initialValues,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
return store;
|
||||
};
|
||||
|
||||
const renderWithProviderAndRoute = (ui: any, { routeOptions, history, store }: any): any => {
|
||||
return {
|
||||
...render(
|
||||
<Provider store={store}>
|
||||
<Router history={history}>
|
||||
<Route {...routeOptions}>{ui}</Route>
|
||||
</Router>
|
||||
</Provider>
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
const routeConfiguration = (path: string, params?: any) => {
|
||||
const history = createMemoryHistory({ initialEntries: [path] });
|
||||
const match: match<{ id: string; pid: string }> = {
|
||||
isExact: true,
|
||||
path,
|
||||
url: path,
|
||||
params: params ? params : {},
|
||||
};
|
||||
const location = createLocation(match.url);
|
||||
return { history, match, location };
|
||||
};
|
||||
|
||||
describe('ProductBrief', () => {
|
||||
// let history = createMemoryHistory({ initialEntries: ['/login'] });
|
||||
const initialValues = {
|
||||
namePol: '',
|
||||
nameEng: '',
|
||||
modelNo: '',
|
||||
batchNo: '',
|
||||
ean: '',
|
||||
packagingType: null,
|
||||
mainDescription: '',
|
||||
extraDescription: '',
|
||||
photos: [],
|
||||
blankings: [],
|
||||
certificate: '',
|
||||
informationSigns: '',
|
||||
capabilitiesIcons: '',
|
||||
productIcons: '',
|
||||
hasDescriptionEng: false,
|
||||
hasDescriptionPol: false,
|
||||
hasDescriptionPor: false,
|
||||
hasDescriptionRus: false,
|
||||
hasDescriptionSpa: false,
|
||||
illustration: '',
|
||||
cleaningMethod: '',
|
||||
countryOfOrigin: '',
|
||||
manual: '',
|
||||
material: '',
|
||||
warning: '',
|
||||
arrangementPhoto: '',
|
||||
packshot: '',
|
||||
clipping: '',
|
||||
extraPhoto: '',
|
||||
editable: true,
|
||||
acceptable: false,
|
||||
publishable: false,
|
||||
createdAt: '2020-01-10T08:01:07.703Z',
|
||||
updatedAt: '2020-01-10T08:01:07.712Z',
|
||||
id: 1,
|
||||
};
|
||||
const path = `/projects/1/products/1`;
|
||||
let { history, match, location } = routeConfiguration(path, { id: 1, pid: 1 });
|
||||
afterEach(() => (history = createMemoryHistory({ initialEntries: [path] })));
|
||||
|
||||
const dispatch = jest.fn();
|
||||
const store = createMockStore(initialValues);
|
||||
it.only('renders basic product brief properly', async () => {
|
||||
useCurrentBrief.mockReturnValue(initialValues);
|
||||
useNumberParams.mockReturnValue({
|
||||
projectId: 1,
|
||||
productId: 1,
|
||||
});
|
||||
const { getByTestId, getByPlaceholderText, getByText, getByLabelText, getAllByText } = renderWithProviderAndRoute(
|
||||
<ProductBrief dispatch={dispatch} history={history} match={match} location={location} />,
|
||||
{
|
||||
routeOptions: { exact: true, path },
|
||||
history,
|
||||
store,
|
||||
}
|
||||
);
|
||||
|
||||
const productPL = await waitForElement(() => getByPlaceholderText('Nazwa produktu - PL'));
|
||||
const productEN = await waitForElement(() => getByPlaceholderText('Nazwa produktu - EN'));
|
||||
const modelNr = await waitForElement(() => getByPlaceholderText('Model nr'));
|
||||
const batchNo = await waitForElement(() => getByPlaceholderText('Nr. Partii'));
|
||||
const ean = await waitForElement(() => getByPlaceholderText('EAN'));
|
||||
const packagingType = await waitForElement(() => getByText('Rodzaj opakowania'));
|
||||
const photosCheckbox = await waitForElement(() => getByLabelText('Zdjęcia'));
|
||||
const illustrationCheckbox = await waitForElement(() => getByLabelText('Ilustracje'));
|
||||
const manualCheckbox = await waitForElement(() => getByLabelText('Zdjęcia'));
|
||||
const productPhotoPicker = await waitForElement(() => getByTestId('productPhotoPicker'));
|
||||
const productBlankingPicker = await waitForElement(() => getByTestId('productBlankingPicker'));
|
||||
const polishLang = await waitForElement(() => getByLabelText('Polski'));
|
||||
const rusLang = await waitForElement(() => getByLabelText('Rosyjski'));
|
||||
const angLang = await waitForElement(() => getByLabelText('Angielski'));
|
||||
const ptLang = await waitForElement(() => getByLabelText('Portugalski'));
|
||||
const espLang = await waitForElement(() => getByLabelText('Hiszpański'));
|
||||
const material = await waitForElement(() => getByLabelText('Materiał'));
|
||||
const cleaningMethod = await waitForElement(() => getByLabelText('Sposób czyszczenia'));
|
||||
const countryOfOrigin = await waitForElement(() => getByLabelText('Kraj produkcji'));
|
||||
const warning = await waitForElement(() => getByLabelText('Ostrzeżenia'));
|
||||
const certificate = await waitForElement(() => getByPlaceholderText('Certyfikaty'));
|
||||
const informationSigns = await waitForElement(() => getByPlaceholderText('Znaki informacyjne'));
|
||||
const competenceIcons = await waitForElement(() => getByPlaceholderText('Ikony kompetencji'));
|
||||
const productIcons = await waitForElement(() => getByPlaceholderText('Ikony produktowe'));
|
||||
const previousButton = await waitForElement(() => getAllByText('Poprzedni'));
|
||||
const nextButton = await waitForElement(() => getAllByText('Następny'));
|
||||
|
||||
expect(previousButton).toHaveLength(2);
|
||||
expect(nextButton).toHaveLength(2);
|
||||
expect(photosCheckbox.checked).toEqual(false);
|
||||
expect(illustrationCheckbox.checked).toEqual(false);
|
||||
expect(productPL).toBeInTheDocument();
|
||||
expect(productEN).toBeInTheDocument();
|
||||
expect(modelNr).toBeInTheDocument();
|
||||
expect(batchNo).toBeInTheDocument();
|
||||
expect(ean).toBeInTheDocument();
|
||||
expect(packagingType).toBeInTheDocument();
|
||||
expect(photosCheckbox).toBeInTheDocument();
|
||||
expect(illustrationCheckbox).toBeInTheDocument();
|
||||
expect(manualCheckbox).toBeInTheDocument();
|
||||
expect(productPhotoPicker).toBeInTheDocument();
|
||||
expect(productBlankingPicker).toBeInTheDocument();
|
||||
expect(polishLang).toBeInTheDocument();
|
||||
expect(rusLang).toBeInTheDocument();
|
||||
expect(angLang).toBeInTheDocument();
|
||||
expect(ptLang).toBeInTheDocument();
|
||||
expect(espLang).toBeInTheDocument();
|
||||
expect(material).toBeInTheDocument();
|
||||
expect(cleaningMethod).toBeInTheDocument();
|
||||
expect(countryOfOrigin).toBeInTheDocument();
|
||||
expect(warning).toBeInTheDocument();
|
||||
expect(certificate).toBeInTheDocument();
|
||||
expect(informationSigns).toBeInTheDocument();
|
||||
expect(competenceIcons).toBeInTheDocument();
|
||||
expect(productIcons).toBeInTheDocument();
|
||||
// const promoDate = await waitForElement(() => findByDisplayValue(initialValues.printingFilesDate));
|
||||
// const pfDate = await waitForElement(() => findByDisplayValue(initialValues.promoDate));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,441 @@
|
||||
import React from 'react';
|
||||
import { createMemoryHistory, createLocation } from 'history';
|
||||
import ProjectBrief from 'organisms/ProjectBrief/ProjectBrief';
|
||||
import { cleanup, render, waitForElement, fireEvent, act, wait } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
import { Provider } from 'react-redux';
|
||||
import projectApi from 'API/projects';
|
||||
import productApi from 'API/products';
|
||||
import * as productActions from 'modules/products/actions';
|
||||
import { Route, Router, match } from 'react-router';
|
||||
import configureStore from 'redux-mock-store'; //ES6 modules
|
||||
import { Brand, Category } from 'API/projectData';
|
||||
import { format } from 'date-fns';
|
||||
import useCurrentProject from 'hooks/useCurrentProject';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
afterEach(cleanup);
|
||||
const dispatch = jest.fn();
|
||||
|
||||
//MOCKED DATA
|
||||
const category: Category = {
|
||||
id: 1,
|
||||
name: 'toys',
|
||||
};
|
||||
const brand: Brand = {
|
||||
id: 1,
|
||||
name: 'boys',
|
||||
};
|
||||
const printingFilesDate = '2011-02-01';
|
||||
const promoDate = '2011-01-01';
|
||||
|
||||
const createMockStore = (initialValues: any) => {
|
||||
const mockStore = configureStore();
|
||||
const store = mockStore({
|
||||
projectData: {
|
||||
categories: [category],
|
||||
brands: [brand],
|
||||
},
|
||||
users: {
|
||||
currentUser: {
|
||||
id: 1,
|
||||
organization: {
|
||||
type: 'supplier',
|
||||
},
|
||||
},
|
||||
usersList: [],
|
||||
},
|
||||
projects: {
|
||||
projectsList: [{ ...initialValues }],
|
||||
},
|
||||
});
|
||||
return store;
|
||||
};
|
||||
|
||||
const routeConfiguration = (path: string, params?: any) => {
|
||||
const history = createMemoryHistory({ initialEntries: [path] });
|
||||
const match: match = {
|
||||
isExact: true,
|
||||
path,
|
||||
url: path,
|
||||
params: params ? params : {},
|
||||
};
|
||||
const location = createLocation(match.url);
|
||||
return { history, match, location };
|
||||
};
|
||||
|
||||
const renderWithProviderAndRoute = (ui: any, { routeOptions, history, store }: any): any => {
|
||||
return {
|
||||
...render(
|
||||
<Provider store={store}>
|
||||
<Router history={history}>
|
||||
<Route {...routeOptions}>{ui}</Route>
|
||||
</Router>
|
||||
</Provider>
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
describe('new project', () => {
|
||||
const path = `/projects/new`;
|
||||
let { history, match, location } = routeConfiguration(path);
|
||||
beforeEach(() => (history = createMemoryHistory({ initialEntries: [path] })));
|
||||
|
||||
const initialValues = {
|
||||
id: 1,
|
||||
slug: 'test',
|
||||
name: 'test',
|
||||
products: 'test',
|
||||
maxProductStage: 'max',
|
||||
published: false,
|
||||
updatedAt: new Date(),
|
||||
createdAt: new Date(),
|
||||
printingFilesDate,
|
||||
publishable: false,
|
||||
description: 'description',
|
||||
category: category.id,
|
||||
brand: brand.id,
|
||||
promoDate,
|
||||
productsAmount: 1,
|
||||
};
|
||||
const store = createMockStore(initialValues);
|
||||
|
||||
it('should render blank ProjectBrief on /projects/new ', async () => {
|
||||
const { getByTestId } = renderWithProviderAndRoute(
|
||||
<ProjectBrief dispatch={dispatch} history={history} match={match} location={location} />,
|
||||
{ routeOptions: { exact: true, path }, history, store }
|
||||
);
|
||||
|
||||
const categoryDropdown = await waitForElement(() => getByTestId('categoryDropdown'));
|
||||
const brandDropdown = await waitForElement(() => getByTestId('brandDropdown'));
|
||||
const printingFilesDateField = await waitForElement(() => getByTestId('printingFilesDatePicker'));
|
||||
const promotionDateField = await waitForElement(() => getByTestId('poromoDateDatePicker'));
|
||||
const saveButton = await waitForElement(() => getByTestId('projectSaveButton'));
|
||||
const projectName = await waitForElement(() => getByTestId('projectName'));
|
||||
const description = await waitForElement(() => getByTestId('description'));
|
||||
expect(projectName).toBeInTheDocument();
|
||||
expect(description).toBeInTheDocument();
|
||||
expect(printingFilesDateField).toBeInTheDocument();
|
||||
expect(promotionDateField).toBeInTheDocument();
|
||||
expect(saveButton).toBeInTheDocument();
|
||||
expect(brandDropdown).toBeInTheDocument();
|
||||
expect(categoryDropdown).toBeInTheDocument();
|
||||
});
|
||||
it('should allow change form values', async () => {
|
||||
const { findByTestId, findByText, findAllByDisplayValue } = renderWithProviderAndRoute(
|
||||
<ProjectBrief dispatch={store.dispatch} history={history} match={match} location={location} />,
|
||||
{ routeOptions: { exact: true, path }, history, store }
|
||||
);
|
||||
const brandDropdown = await waitForElement(() => findByTestId('brandDropdown'));
|
||||
const categoryDropdown = await waitForElement(() => findByTestId('categoryDropdown'));
|
||||
const projectName = await waitForElement(() => findByTestId('projectName'));
|
||||
const description = await waitForElement(() => findByTestId('description'));
|
||||
const dates = await waitForElement(() => findAllByDisplayValue(format(new Date(), 'yyyy-MM-dd')));
|
||||
//CHECK IF DATE CHANGED
|
||||
fireEvent.change(dates[0], { target: { value: '2011-02-02' } });
|
||||
expect(dates[0]).toHaveValue('2011-02-02');
|
||||
fireEvent.change(dates[1], { target: { value: '2011-02-03' } });
|
||||
expect(dates[1]).toHaveValue('2011-02-03');
|
||||
//CHECK IF PROJECT NAME CHANGED
|
||||
fireEvent.change(projectName, { target: { value: 'projectName' } });
|
||||
expect(projectName).toHaveValue('projectName');
|
||||
//CHECK IF DESCRIPTION CHANGED
|
||||
fireEvent.change(description, { target: { value: 'description' } });
|
||||
expect(description).toHaveValue('description');
|
||||
//CHECK IF BRAND DROPDOWN VALUE CHANGED
|
||||
const brandValue = await waitForElement(() => findByTestId('brandValue'));
|
||||
expect(brandValue.innerHTML).toEqual('Marka');
|
||||
fireEvent.click(brandDropdown);
|
||||
const brandItem = await waitForElement(() => findByText(brand.name));
|
||||
expect(brandItem.innerHTML).toEqual(brand.name);
|
||||
fireEvent.click(brandItem);
|
||||
expect(brandValue.innerHTML).toEqual(brandItem.innerHTML);
|
||||
//CHECK IF CATEGORY DROPDOWN VALUE CHANGED
|
||||
const categoryValue = await waitForElement(() => findByTestId('categoryValue'));
|
||||
expect(categoryValue.innerHTML).toEqual('Kategoria');
|
||||
fireEvent.click(categoryDropdown);
|
||||
const categoryItem = await waitForElement(() => findByText(category.name));
|
||||
expect(categoryItem.innerHTML).toEqual(category.name);
|
||||
fireEvent.click(categoryItem);
|
||||
expect(categoryValue.innerHTML).toEqual(categoryItem.innerHTML);
|
||||
});
|
||||
it('publish button should not be visible if publishabled is false', async () => {
|
||||
const { queryByTestId } = renderWithProviderAndRoute(
|
||||
<ProjectBrief dispatch={store.dispatch} history={history} match={match} location={location} />,
|
||||
{ routeOptions: { exact: true, path }, history, store }
|
||||
);
|
||||
expect(queryByTestId('publishButton')).toBe(null);
|
||||
});
|
||||
it('should not submit blank form', async () => {
|
||||
const { findByTestId, queryAllByTestId } = renderWithProviderAndRoute(
|
||||
<ProjectBrief dispatch={store.dispatch} history={history} match={match} location={location} />,
|
||||
{ routeOptions: { exact: true, path }, history, store }
|
||||
);
|
||||
await act(async () => {
|
||||
const saveButton = await waitForElement(() => findByTestId('projectSaveButton'));
|
||||
fireEvent.click(saveButton);
|
||||
});
|
||||
const errors = queryAllByTestId('inputError');
|
||||
expect(errors).toHaveLength(5);
|
||||
});
|
||||
it('should save proper form and redirect to /products/${pid}', async () => {
|
||||
projectApi.createNewProject.mockReturnValueOnce({
|
||||
id: 1,
|
||||
});
|
||||
productApi.getProductsByURL.mockReturnValueOnce([
|
||||
{
|
||||
id: 1,
|
||||
},
|
||||
]);
|
||||
const { findByTestId, queryAllByTestId } = renderWithProviderAndRoute(
|
||||
<ProjectBrief
|
||||
dispatch={store.dispatch}
|
||||
history={history}
|
||||
match={match}
|
||||
location={location}
|
||||
currentProject={initialValues}
|
||||
/>,
|
||||
{ routeOptions: { exact: true, path }, history, store }
|
||||
);
|
||||
const saveButton = await waitForElement(() => findByTestId('projectSaveButton'));
|
||||
await act(async () => {
|
||||
fireEvent.click(saveButton);
|
||||
const errors = queryAllByTestId('inputError');
|
||||
expect(errors).toHaveLength(0);
|
||||
});
|
||||
expect(projectApi.createNewProject).toHaveBeenCalledTimes(1);
|
||||
expect(productApi.getProductsByURL).toHaveBeenCalledTimes(1);
|
||||
expect(history.location.pathname).toBe('/projects/1/products/1');
|
||||
});
|
||||
|
||||
it('should setCurrentStage to 0', () => {
|
||||
const spy = jest.spyOn(productActions, 'setCurrentStage');
|
||||
renderWithProviderAndRoute(
|
||||
<ProjectBrief dispatch={store.dispatch} history={history} match={match} location={location} />,
|
||||
{ routeOptions: { exact: true, path: '/projects/new' }, history, store }
|
||||
);
|
||||
expect(spy).toHaveBeenCalledWith(0);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('existing project not publishable', () => {
|
||||
const path = `/projects/1`;
|
||||
const { history, match, location } = routeConfiguration(path, { id: 1 });
|
||||
const initialValues = {
|
||||
id: 1,
|
||||
slug: 'test',
|
||||
name: 'test',
|
||||
products: 'test',
|
||||
maxProductStage: 'max',
|
||||
published: false,
|
||||
updatedAt: new Date(),
|
||||
createdAt: new Date(),
|
||||
printingFilesDate,
|
||||
publishable: false,
|
||||
description: 'description',
|
||||
category: category.id,
|
||||
brand: brand.id,
|
||||
promoDate,
|
||||
productsAmount: 1,
|
||||
};
|
||||
const store = createMockStore(initialValues);
|
||||
it('should set initial values', async () => {
|
||||
const { getByTestId, findByDisplayValue } = renderWithProviderAndRoute(
|
||||
<ProjectBrief
|
||||
dispatch={dispatch}
|
||||
history={history}
|
||||
match={match}
|
||||
location={location}
|
||||
currentProject={initialValues}
|
||||
/>,
|
||||
{ routeOptions: { exact: true, path }, history, store }
|
||||
);
|
||||
const projectName = await waitForElement(() => getByTestId('projectName'));
|
||||
const projectDesc = await waitForElement(() => getByTestId('description'));
|
||||
const categoryValue = await waitForElement(() => getByTestId('categoryValue'));
|
||||
const brandValue = await waitForElement(() => getByTestId('brandValue'));
|
||||
const promoDate = await waitForElement(() => findByDisplayValue(initialValues.printingFilesDate));
|
||||
const pfDate = await waitForElement(() => findByDisplayValue(initialValues.promoDate));
|
||||
|
||||
expect(promoDate).toHaveValue(initialValues.printingFilesDate);
|
||||
expect(pfDate).toHaveValue(initialValues.promoDate);
|
||||
expect(categoryValue.innerHTML).toBe(category.name);
|
||||
expect(brandValue.innerHTML).toBe(brand.name);
|
||||
expect(projectName).toHaveValue(initialValues.name);
|
||||
expect(projectDesc).toHaveValue(initialValues.description);
|
||||
//TODO HOW TO CHECK FORMIK VALUES
|
||||
});
|
||||
it('should expand dropdown', async () => {
|
||||
const { getByTestId } = renderWithProviderAndRoute(
|
||||
<ProjectBrief dispatch={store.dispatch} history={history} match={match} location={location} />,
|
||||
{ routeOptions: { exact: true, path }, history, store }
|
||||
);
|
||||
const brand = await waitForElement(() => getByTestId('brandDropdown'));
|
||||
const category = await waitForElement(() => getByTestId('categoryDropdown'));
|
||||
fireEvent.click(brand);
|
||||
fireEvent.click(category);
|
||||
|
||||
expect(brand).toHaveClass('is-active');
|
||||
expect(category).toHaveClass('is-active');
|
||||
});
|
||||
});
|
||||
describe('existing project publishable', () => {
|
||||
beforeAll(() => {
|
||||
ReactDOM.createPortal = jest.fn((element, node) => {
|
||||
return element;
|
||||
});
|
||||
});
|
||||
afterEach(() => {
|
||||
ReactDOM.createPortal.mockClear();
|
||||
});
|
||||
const path = `/projects/1`;
|
||||
const { history, match, location } = routeConfiguration(path, { id: 1 });
|
||||
const initialValues = {
|
||||
id: 1,
|
||||
slug: 'test',
|
||||
name: 'test',
|
||||
products: 'test',
|
||||
maxProductStage: 'max',
|
||||
published: false,
|
||||
updatedAt: new Date(),
|
||||
createdAt: new Date(),
|
||||
printingFilesDate,
|
||||
publishable: true,
|
||||
description: 'description',
|
||||
category: category.id,
|
||||
brand: brand.id,
|
||||
promoDate,
|
||||
productsAmount: 1,
|
||||
};
|
||||
const store = createMockStore(initialValues);
|
||||
it('publish button should be in DOM but disabled', async () => {
|
||||
useCurrentProject.mockReturnValue({ ...initialValues, publishable: false });
|
||||
const { findByTestId } = renderWithProviderAndRoute(
|
||||
<ProjectBrief
|
||||
dispatch={store.dispatch}
|
||||
history={history}
|
||||
match={match}
|
||||
location={location}
|
||||
currentProject={{ ...initialValues, publishable: false }}
|
||||
/>,
|
||||
{ routeOptions: { exact: true, path }, history, store }
|
||||
);
|
||||
const publishButton = await waitForElement(() => findByTestId('publishButton'));
|
||||
expect(publishButton).toBeInTheDocument();
|
||||
expect(publishButton).toBeDisabled();
|
||||
});
|
||||
it('publish button should be in DOM and not disabled', async () => {
|
||||
useCurrentProject.mockReturnValue(initialValues);
|
||||
const { findByTestId } = renderWithProviderAndRoute(
|
||||
<ProjectBrief
|
||||
dispatch={store.dispatch}
|
||||
history={history}
|
||||
match={match}
|
||||
location={location}
|
||||
currentProject={initialValues}
|
||||
/>,
|
||||
{ routeOptions: { exact: true, path }, history, store }
|
||||
);
|
||||
const publishButton = await waitForElement(() => findByTestId('publishButton'));
|
||||
expect(publishButton).toBeInTheDocument();
|
||||
expect(publishButton).not.toBeDisabled();
|
||||
});
|
||||
it('should open modal and spin spinner', async () => {
|
||||
projectApi.publishProject.mockReturnValueOnce({ ...initialValues, published: true });
|
||||
useCurrentProject.mockReturnValue(initialValues);
|
||||
productApi.getProductsByURL.mockReturnValueOnce([
|
||||
{
|
||||
id: 1,
|
||||
},
|
||||
]);
|
||||
const { findByTestId, findByText } = renderWithProviderAndRoute(
|
||||
<ProjectBrief
|
||||
dispatch={store.dispatch}
|
||||
history={history}
|
||||
match={match}
|
||||
location={location}
|
||||
currentProject={initialValues}
|
||||
/>,
|
||||
{ routeOptions: { exact: true, path }, history, store }
|
||||
);
|
||||
const publishButton = await waitForElement(() => findByTestId('publishButton'));
|
||||
expect(publishButton).toBeInTheDocument();
|
||||
await act(async () => {
|
||||
fireEvent.click(publishButton);
|
||||
});
|
||||
const modal = await waitForElement(() => findByText('Publikacja projektu'));
|
||||
const modalCancel = await waitForElement(() => findByTestId('modalCancel'));
|
||||
const modalSubmit = await waitForElement(() => findByTestId('modalSubmit'));
|
||||
expect(modal).toBeInTheDocument();
|
||||
expect(modalSubmit).toBeInTheDocument();
|
||||
expect(modalCancel).toBeInTheDocument();
|
||||
fireEvent.click(modalSubmit);
|
||||
expect(modalSubmit).toHaveClass('is-loading');
|
||||
await wait(() => expect(modalSubmit).not.toHaveClass('is-loading'));
|
||||
|
||||
expect(modal).not.toBeInTheDocument();
|
||||
});
|
||||
describe('Published Project', () => {
|
||||
const path = `/projects/1`;
|
||||
const { history, match, location } = routeConfiguration(path, { id: 1 });
|
||||
const initialValues = {
|
||||
id: 1,
|
||||
slug: 'test',
|
||||
name: 'test',
|
||||
products: 'test',
|
||||
maxProductStage: 'max',
|
||||
published: true,
|
||||
updatedAt: new Date(),
|
||||
createdAt: new Date(),
|
||||
printingFilesDate,
|
||||
publishable: false,
|
||||
description: 'description',
|
||||
category: category.id,
|
||||
brand: brand.id,
|
||||
promoDate,
|
||||
productsAmount: 1,
|
||||
};
|
||||
const store = createMockStore(initialValues);
|
||||
it('should not display publish button when published is true', async () => {
|
||||
useCurrentProject.mockReturnValue(initialValues);
|
||||
const { queryByTestId } = renderWithProviderAndRoute(
|
||||
<ProjectBrief
|
||||
dispatch={store.dispatch}
|
||||
history={history}
|
||||
match={match}
|
||||
location={location}
|
||||
currentProject={initialValues}
|
||||
/>,
|
||||
{ routeOptions: { exact: true, path }, history, store }
|
||||
);
|
||||
expect(queryByTestId('publishButton')).toBe(null);
|
||||
});
|
||||
it('should have disabled fields', async () => {
|
||||
useCurrentProject.mockReturnValue(initialValues);
|
||||
const { findByText, findByTestId, findByDisplayValue } = renderWithProviderAndRoute(
|
||||
<ProjectBrief
|
||||
dispatch={store.dispatch}
|
||||
history={history}
|
||||
match={match}
|
||||
location={location}
|
||||
currentProject={initialValues}
|
||||
/>,
|
||||
{ routeOptions: { exact: true, path }, history, store }
|
||||
);
|
||||
const brandValue = await waitForElement(() => findByTestId('brandValue'));
|
||||
const categoryValue = await waitForElement(() => findByTestId('categoryValue'));
|
||||
const projectName = await waitForElement(() => findByTestId('projectName'));
|
||||
const description = await waitForElement(() => findByTestId('description'));
|
||||
const promo = await waitForElement(() => findByDisplayValue(promoDate));
|
||||
const pfDate = await waitForElement(() => findByDisplayValue(printingFilesDate));
|
||||
|
||||
expect(categoryValue.parentElement).toBeDisabled();
|
||||
expect(brandValue.parentElement).toBeDisabled();
|
||||
expect(promo).toBeDisabled();
|
||||
expect(pfDate).toBeDisabled();
|
||||
expect(projectName).toBeDisabled();
|
||||
expect(description).toBeDisabled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24.149" height="29.649" viewBox="0 0 24.149 29.649">
|
||||
<g id="PDF_Icon_zaakceptowanu" transform="translate(0 0)">
|
||||
<path id="Path_84" data-name="Path 84" d="M528.335,109.472l-1.323-1.54-1.259,1.081,1.973,2.3a.83.83,0,0,0,.629.289h.013a.829.829,0,0,0,.633-.309l3.416-4.24-1.292-1.041Z" transform="translate(-511.762 -85.918)"/>
|
||||
<rect id="Rectangle_36" data-name="Rectangle 36" width="10.172" height="1.223" transform="translate(4.884 5.255)" />
|
||||
<rect id="Rectangle_37" data-name="Rectangle 37" width="10.172" height="1.223" transform="translate(4.884 9.584)" />
|
||||
<rect id="Rectangle_38" data-name="Rectangle 38" width="4.931" height="1.223" transform="translate(4.884 18.242)" />
|
||||
<rect id="Rectangle_39" data-name="Rectangle 39" width="10.172" height="1.223" transform="translate(4.884 13.913)" />
|
||||
<path id="Path_85" data-name="Path 85" d="M478.233,57.373a.983.983,0,0,1-.982-.983V36.48a.983.983,0,0,1,.982-.983H491.99a.983.983,0,0,1,.982.983V48.556a7.51,7.51,0,0,1,2.109.464V36.084a2.751,2.751,0,0,0-2.751-2.751H477.893a2.751,2.751,0,0,0-2.751,2.751v20.7a2.751,2.751,0,0,0,2.751,2.751h7.774a7.512,7.512,0,0,1-.716-2.164Z" transform="translate(-475.142 -33.333)" />
|
||||
<path id="Path_86" data-name="Path 86" d="M519.644,90.857a6.874,6.874,0,1,0,6.874,6.873A6.881,6.881,0,0,0,519.644,90.857Zm0,12.087a5.214,5.214,0,1,1,5.214-5.214A5.22,5.22,0,0,1,519.644,102.944Z" transform="translate(-502.369 -74.955)" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="6" height="10.001" viewBox="0 0 6 10.001">
|
||||
<path id="arrow_icon" data-name="arrow icon" d="M5,6l5-3V.125L5,3.144,0,0V3Z" transform="translate(0 10.001) rotate(-90)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 223 B |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36">
|
||||
<g id="arrow_small_button" data-name="arrow small button" transform="translate(-11662 -1616)">
|
||||
<circle id="placeholder" cx="18" cy="18" r="18" transform="translate(11662 1616)" fill="rgba(245,140,57,0.5)"/>
|
||||
<g id="arrow_icon" data-name="arrow icon">
|
||||
<path id="arrow_icon-2" data-name="arrow icon" d="M11682.009,1628.991l-7,7.428,1.726,1.535,4.264-4.86v9.916l2.034-.064v-9.852l4.284,4.86,1.69-1.651Z" transform="translate(-2 -2)" fill="#fff"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 566 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="29" height="31" viewBox="0 0 29 31"><g><g><g><path fill="#ccc" d="M4.73 7.05V5.728h11V7.05z"/></g><g><path fill="#ccc" d="M4.73 11.403V10.08h11v1.323z"/></g><g><path fill="#ccc" d="M4.73 20.107v-1.322h11v1.322z"/></g><g><path fill="#ccc" d="M4.73 15.755v-1.322h11v1.322z"/></g><g><path fill="#ccc" d="M26.753 4.916L20.22 3.398v2.235l5.606 1.302c.569.133.914.667.772 1.194l-4.227 15.586-.048.177-.382 1.41-.226.835-.36 1.324c-.143.527-.72.847-1.289.715l-2.81-.653a4.035 4.035 0 0 1-.766.074H7.96l11.892 2.764c1.594.37 3.21-.526 3.61-2.001l1.086-4.005.26-.957.35-1.288.08-.299L28.914 8.26c.4-1.476-.567-2.972-2.161-3.343z"/></g><g><path fill="#ccc" d="M3.343 24.451c-.588 0-1.063-.439-1.063-.981V3.587c0-.542.475-.981 1.063-.981h14.875c.587 0 1.063.44 1.063.981V23.47c0 .542-.476.981-1.063.981zM21.56 3.191c0-1.517-1.332-2.747-2.975-2.747H2.976C1.331.444 0 1.674 0 3.192v20.673c0 1.518 1.332 2.748 2.975 2.748h15.61c1.644 0 2.976-1.23 2.976-2.748z"/></g></g></g></svg>
|
||||
|
After Width: | Height: | Size: 1013 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" viewBox="0 0 18 20">
|
||||
<path id="icon" d="M11138,984h5v5h-5Zm12,9h-14V982h14Zm0-16h-1v-2h-2v2h-8v-2h-2v2h-1a1.991,1.991,0,0,0-1.99,2l-.01,14a2,2,0,0,0,2,2h14a2.006,2.006,0,0,0,2-2V979A2.006,2.006,0,0,0,11150,977Z" transform="translate(-11134 -975)" fill="#ccc"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 333 B |
@@ -0,0 +1,3 @@
|
||||
<svg id="comment_icon" data-name="comment icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path id="comment_icon-2" data-name="comment icon" d="M16263,1136.8a.8.8,0,0,0-.8-.8h-10.4a.8.8,0,0,0-.8.8V1148l3.2-2.995h8a.8.8,0,0,0,.8-.8Zm1.011,2.2v7.021H16254l.01,2.165a.815.815,0,0,0,.813.813h8.938l3.241,3v-12.188a.815.815,0,0,0-.813-.812Z" transform="translate(-16251 -1136)" fill="#ccc"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 433 B |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="21.632" height="29.662" viewBox="0 0 21.632 29.662">
|
||||
<g id="PDF_Icon-poprawki" transform="translate(0 0)">
|
||||
<rect id="Rectangle_40" data-name="Rectangle 40" width="10.011" height="1.204" transform="translate(4.807 5.172)" fill="#ccc"/>
|
||||
<rect id="Rectangle_41" data-name="Rectangle 41" width="10.011" height="1.204" transform="translate(4.807 9.433)" fill="#ccc"/>
|
||||
<path id="Path_87" data-name="Path 87" d="M482.352,388.384h-4.167a.967.967,0,0,1-.967-.967V367.822a.967.967,0,0,1,.967-.967h13.54a.967.967,0,0,1,.967.967v13.427c.035-.008.069-.018.1-.025a2.37,2.37,0,0,1,.457-.044,2.626,2.626,0,0,1,1.514.488V367.433a2.708,2.708,0,0,0-2.708-2.708H477.85a2.708,2.708,0,0,0-2.708,2.708v20.374a2.708,2.708,0,0,0,2.708,2.708h6.8l-.243-.2A11.231,11.231,0,0,1,482.352,388.384Z" transform="translate(-475.142 -364.725)" fill="#ccc"/>
|
||||
<path id="Path_88" data-name="Path 88" d="M516.666,423.332a13.944,13.944,0,0,0-2.006-6,1.862,1.862,0,0,0-1.838-.738,1.387,1.387,0,0,0-.522.215,1.651,1.651,0,0,0-1.76-.8,1.335,1.335,0,0,0-.726.457,1.767,1.767,0,0,0-1.542-.5,1.467,1.467,0,0,0-.579.238l-1.03-2.708a1.619,1.619,0,0,0-1.242-1.089,1.517,1.517,0,0,0-1.414.537,1.62,1.62,0,0,0-.2,1.639l2.529,6.654c-1.56-.662-2.777-1.037-3.362-.5a1.348,1.348,0,0,0-.5,1.05c.026.877.963,1.638,2.261,2.691a15.3,15.3,0,0,1,4.077,4.365.453.453,0,0,0,.56.209l6.925-2.632a.453.453,0,0,0,.292-.43,11.243,11.243,0,0,1,.038-1.317A11.889,11.889,0,0,0,516.666,423.332Zm-.944,1.27c-.022.264-.045.536-.044,1.087l-6.267,2.382a16.861,16.861,0,0,0-4.107-4.292c-.984-.8-1.913-1.552-1.926-2.015,0-.044,0-.161.2-.35.027-.014.27-.108,1.2.2.652.215,1.38.538,1.965.8l.21.093a.453.453,0,0,0,.606-.575l-2.912-7.662a.723.723,0,0,1,.06-.745.617.617,0,0,1,.567-.215.723.723,0,0,1,.54.517l1.361,3.581.008.02.856,2.253a.453.453,0,1,0,.847-.322l-.825-2.171a.428.428,0,0,1,.373-.322c.342-.063.837.119,1.111.841l.646,1.7a.453.453,0,0,0,.847-.322l-.622-1.638c-.018-.3.1-.5.331-.551a.818.818,0,0,1,.887.616l.151.4h0l.477,1.257a.453.453,0,0,0,.847-.321l-.424-1.118a.378.378,0,0,1,.308-.239.965.965,0,0,1,.9.345,13.067,13.067,0,0,1,1.859,5.513A11.034,11.034,0,0,1,515.722,424.6Z" transform="translate(-495.036 -399.424)" fill="#ccc"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><g><g><g><g opacity=".3"><path d="M0 10C0 4.477 4.477 0 10 0s10 4.477 10 10-4.477 10-10 10S0 15.523 0 10z"/></g></g><g><g><path fill="#ccc" d="M13 7.604L12.396 7 10 9.396 7.605 7 7 7.604 9.396 10 7 12.396l.605.604L10 10.604 12.396 13l.604-.604L10.604 10z"/></g></g></g></g></svg>
|
||||
|
After Width: | Height: | Size: 362 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="15.999" height="20" viewBox="0 0 15.999 20">
|
||||
<path id="doc_icon" data-name="doc icon" d="M12567,176v-5.5l5.5,5.5Zm-7-7a2,2,0,0,0-1.989,2l-.01,16a2,2,0,0,0,1.99,2H12572a2.005,2.005,0,0,0,2-2V175l-6-6Z" transform="translate(-12558 -169)" fill="#d1d1d1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 309 B |
@@ -0,0 +1,3 @@
|
||||
<svg id="done_icon" data-name="done icon" xmlns="http://www.w3.org/2000/svg" width="13.135" height="10.732" viewBox="0 0 13.135 10.732">
|
||||
<path id="done_icon-2" data-name="done icon" d="M16253.653,1056.216l-1.653,1.6,4.179,4.179,8.956-8.955-1.465-1.777-7.491,7.643Z" transform="translate(-16252 -1051.267)" fill="#50d166"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 332 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 10 10">
|
||||
<path id="icon" d="M6126,1482l-5-5.3,1.234-1.095,3.046,3.467V1472l1.453.046v7.028l3.059-3.467,1.208,1.178-5,5.216Z" transform="translate(-6120.999 -1472)" fill="#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 262 B |
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22.559" height="29.647" viewBox="0 0 22.559 29.647">
|
||||
<g id="PDF_icon_nowyplik" transform="translate(0 0)">
|
||||
<rect id="Rectangle_42" data-name="Rectangle 42" width="11.508" height="1.384" transform="translate(5.525 5.946)" fill="#ccc"/>
|
||||
<rect id="Rectangle_43" data-name="Rectangle 43" width="11.508" height="1.384" transform="translate(5.525 10.844)" fill="#ccc"/>
|
||||
<rect id="Rectangle_44" data-name="Rectangle 44" width="11.508" height="1.384" transform="translate(5.525 20.639)" fill="#ccc"/>
|
||||
<rect id="Rectangle_45" data-name="Rectangle 45" width="11.508" height="1.384" transform="translate(5.525 15.742)" fill="#ccc"/>
|
||||
<path id="Path_89" data-name="Path 89" d="M70.876,33.333H54.543a3.113,3.113,0,0,0-3.113,3.113V59.867a3.113,3.113,0,0,0,3.113,3.113H70.876a3.113,3.113,0,0,0,3.113-3.113V36.446A3.113,3.113,0,0,0,70.876,33.333ZM71.6,55.054v4.365a1.112,1.112,0,0,1-1.112,1.112H54.927a1.112,1.112,0,0,1-1.112-1.112V36.894a1.112,1.112,0,0,1,1.112-1.112H70.492A1.112,1.112,0,0,1,71.6,36.894Z" transform="translate(-51.43 -33.333)" fill="#ccc"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="24" viewBox="0 0 20 24"><g><g><path fill="#fff" d="M15.554 5.092v1.454H4.444V5.092zm0 3.636v1.454H4.444V8.728zm0 3.637v1.453H4.444v-1.453zM9.63 16v1.453H4.444v-1.453zM20 22.544V1.458C20 .654 19.333 0 18.513 0H1.488C.668 0 0 .654 0 1.458v21.086C0 23.347.667 24 1.488 24h17.025c.82 0 1.487-.653 1.487-1.456z"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 381 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="5" height="5" viewBox="0 0 5 5">
|
||||
<g id="Group_71" data-name="Group 71" transform="translate(-6146.5 -1465)">
|
||||
<line id="Line_5" data-name="Line 5" x2="3" transform="translate(6146.5 1465.5)" fill="rgba(0,0,0,0)" stroke="#fff" stroke-width="1"/>
|
||||
<line id="Line_6" data-name="Line 6" x2="4" transform="translate(6146.5 1467.5)" fill="rgba(0,0,0,0)" stroke="#fff" stroke-width="1"/>
|
||||
<line id="Line_7" data-name="Line 7" x2="5" transform="translate(6146.5 1469.5)" fill="rgba(0,0,0,0)" stroke="#fff" stroke-width="1"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 589 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 10 10">
|
||||
<path id="icon" d="M6126,1482l-5-5.3,1.234-1.095,3.046,3.467V1472l1.453.046v7.028l3.059-3.467,1.208,1.178-5,5.216Z" transform="translate(-6120.999 -1472)" fill="#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 262 B |
@@ -0,0 +1,26 @@
|
||||
export { ReactComponent as OptionDots } from './menu.svg';
|
||||
export { ReactComponent as Caret } from './arrow.svg';
|
||||
export { ReactComponent as OutboRocket } from './outbo_rocket.svg';
|
||||
export { ReactComponent as OutboLogo } from './outbo_logo.svg';
|
||||
export { ReactComponent as PollIcon } from './poll.svg';
|
||||
export { ReactComponent as DocIcon } from './doc.svg';
|
||||
export { ReactComponent as PuzzelIcon } from './puzzel.svg';
|
||||
export { ReactComponent as SearchIcon } from './search.svg';
|
||||
export { ReactComponent as CirclePlus } from './plus_circle.svg';
|
||||
export { ReactComponent as CalendarIcon } from './calendar.svg';
|
||||
export { default as outboLoginBg } from './outbo_login_bg.png';
|
||||
export { ReactComponent as LockIcon } from './lock_icon.svg';
|
||||
export { ReactComponent as UserIcon } from './user_icon.svg';
|
||||
export { ReactComponent as Delete } from './delete.svg';
|
||||
export { ReactComponent as BlankingIcon } from './blanking_icon.svg';
|
||||
export { ReactComponent as FileIcon } from './file_icon.svg';
|
||||
export { ReactComponent as PhotoIcon } from './photo_icon.svg';
|
||||
export { ReactComponent as DoneIcon } from './done_icon.svg';
|
||||
export { ReactComponent as CommentIcon } from './comment_icon.svg';
|
||||
export { ReactComponent as VerticalLine } from './vertical_line.svg';
|
||||
export { ReactComponent as CircleArrow } from './arrow_circle.svg';
|
||||
export { ReactComponent as CorrectedFileIcon } from './corrected_file_icon.svg';
|
||||
export { ReactComponent as AcceptedFileIcon } from './accepted_file_icon.svg';
|
||||
export { ReactComponent as DraftFileIcon } from './draft_file_icon.svg';
|
||||
export { ReactComponent as DownloadIcon } from './download_icon.svg';
|
||||
export { ReactComponent as FilterIcon } from './filter_icon.svg';
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="17" viewBox="0 0 13 17">
|
||||
<g id="lock_icon" data-name="lock icon" transform="translate(-17612 -267)">
|
||||
<path id="lock_icon-2" data-name="lock icon" d="M17621,273h-5v-1.569a2.5,2.5,0,0,1,5,0Zm-2.5,7a1.5,1.5,0,1,1,1.5-1.5A1.5,1.5,0,0,1,17618.5,280Zm4.875-7.333h-.812v-1.619a4.063,4.063,0,0,0-8.125,0v1.619h-.814a1.627,1.627,0,0,0-1.623,1.619v8.1a1.627,1.627,0,0,0,1.623,1.619h9.752a1.627,1.627,0,0,0,1.625-1.619v-8.1A1.628,1.628,0,0,0,17623.375,272.667Z" fill="#e6e6e6"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 547 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="17.999" height="4" viewBox="0 0 17.999 4">
|
||||
<path id="menu_icon" data-name="menu icon" d="M12577,287a2,2,0,1,0-2-2A2,2,0,0,0,12577,287Zm-7,0a2,2,0,1,0-2-2A2,2,0,0,0,12570,287Zm-7,0a2,2,0,1,0-2-2A2,2,0,0,0,12563,287Z" transform="translate(-12561 -283)" fill="#d1d1d1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 324 B |
|
After Width: | Height: | Size: 2.3 MiB |
@@ -0,0 +1,35 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="115.121" height="71.15" viewBox="0 0 115.121 71.15">
|
||||
<g id="Logo_OUTBO" transform="translate(-54.904 -78.624)">
|
||||
<g id="Group_62" data-name="Group 62" transform="translate(75.747 78.624)">
|
||||
<path id="Path_36" data-name="Path 36" d="M147.366,79.1l-.162-.025a8.769,8.769,0,0,0-.154.969c-.08,1.6-.142,3.2-.211,4.8a4.324,4.324,0,0,0,.306,2.062.72.72,0,0,1,.013.322c-.03.54-.094,1.08-.09,1.619a11.476,11.476,0,0,1,.087,1.648A11.2,11.2,0,0,1,147,92.16a3.885,3.885,0,0,0,.064,1.249c.029.464.082.929.072,1.393-.021.908-.107,1.814-.108,2.722,0,.4.143.788-.1,1.165a.238.238,0,0,0,.03.227c.409.218.394.6.356.963a12.629,12.629,0,0,0-.229,1.431c.007.513-.281,1.035.028,1.55.054.09-.017.252-.025.382s-.008.272-.011.421a2.608,2.608,0,0,1,.359-.008c.18.024.5.034.51.1a.776.776,0,0,1-.085.611c-.645.684-1.332,1.329-2,1.992a2.85,2.85,0,0,0-.239.312l.106.108c.291-.243.578-.491.873-.729a3.2,3.2,0,0,1,.908-.682,2.354,2.354,0,0,0,1.222-1.054c.289-.4.495-.931.881-1.183a4.85,4.85,0,0,0,1.135-.8c.03-.035.107-.04.162-.041.748-.021,1.5-.056,2.244-.053a13.451,13.451,0,0,0,2.476.074c.1-.017.2-.016.292-.032a5.743,5.743,0,0,1,1.024-.172q3.37.054,6.739.159a9.435,9.435,0,0,1,1.627.174,1.287,1.287,0,0,1,.953.719c1.226.03,2.462.053,3.7.095.319.011.635.113.954.116.631.007,1.262-.027,1.893-.041a5.05,5.05,0,0,0,.6,0,.734.734,0,0,0,.442-.182c.053-.065-.051-.278-.113-.411-.121-.259-.269-.506-.395-.764s-.031-.386.249-.383.564.013.8.019l-.27-.789c-.547.007-.725-.371-.708-.926.026-.849.028-1.7.06-2.548a1.094,1.094,0,0,1,.161-.376,1.454,1.454,0,0,1-.157-.448c.008-.575.023-1.152.08-1.724.106-1.059.306-2.112.349-3.172a16.1,16.1,0,0,0-.146-3.116c-.053-.343.139-.638,0-.977a3.65,3.65,0,0,1-.134-.978c-.04-.359.025-.666.337-.76a3.948,3.948,0,0,1-.129-.738c.007-.417.09-.832.1-1.249,0-.281-.1-.562-.1-.843,0-1.021.043-2.041.028-3.061,0-.2-.167-.518-.312-.553a.945.945,0,0,0-.686.2,3.356,3.356,0,0,0-.708.752q-1.017,1.446-1.992,2.92c-1.066,1.6-2.112,3.222-3.186,4.821a3.358,3.358,0,0,1-.778.84.627.627,0,0,0-.33.693,1.993,1.993,0,0,1-.509-.513c-.187-.3-.271-.663-.668-.8-.086-.029-.147-.191-.181-.3-.049-.163-.04-.343-.093-.5-.373-1.117-.744-2.234-1.14-3.343a1.513,1.513,0,0,0-.764-.964,1.552,1.552,0,0,1-.61-.878c-.326-.993-.574-2.011-.871-3.014a.77.77,0,0,0-1.057-.587.423.423,0,0,0-.379.441,3.158,3.158,0,0,1-.173,1.01c-.459,1.083-.963,2.147-1.455,3.216-.531,1.152-1.047,2.311-1.608,3.448-.535,1.083-1.12,2.142-1.714,3.269-.13-.179-.235-.31-.325-.451-.462-.728-.918-1.46-1.38-2.188-.422-.665-.836-1.336-1.276-1.988-.5-.749-1.032-1.483-1.553-2.221-.579-.821-1.174-1.63-1.737-2.461C148.013,80.208,147.7,79.646,147.366,79.1Z" transform="translate(-145.624 -78.624)" fill="#fff"/>
|
||||
<path id="Path_37" data-name="Path 37" d="M147.366,79.1c.334.548.647,1.11,1.006,1.64.563.831,1.158,1.64,1.737,2.461.521.738,1.048,1.472,1.553,2.221.44.653.854,1.323,1.276,1.988.462.728.918,1.46,1.38,2.188.089.141.2.272.325.451.593-1.127,1.179-2.185,1.714-3.269.562-1.137,1.078-2.3,1.608-3.448.492-1.069,1-2.133,1.455-3.216a3.158,3.158,0,0,0,.173-1.01.423.423,0,0,1,.379-.441.77.77,0,0,1,1.057.587c.3,1,.546,2.021.871,3.014a1.552,1.552,0,0,0,.61.878,1.513,1.513,0,0,1,.764.964c.4,1.109.767,2.226,1.14,3.343.054.16.045.341.093.5.034.112.095.274.181.3.4.134.48.5.668.8a1.993,1.993,0,0,0,.509.513.627.627,0,0,1,.33-.693,3.358,3.358,0,0,0,.778-.84c1.074-1.6,2.12-3.217,3.186-4.821q.978-1.472,1.992-2.92a3.356,3.356,0,0,1,.708-.752.945.945,0,0,1,.686-.2c.145.035.309.357.312.553.016,1.02-.027,2.041-.028,3.061,0,.281.108.562.1.843-.006.417-.089.832-.1,1.249a3.948,3.948,0,0,0,.129.738c-.311.094-.377.4-.337.76a3.65,3.65,0,0,0,.134.978c.141.339-.051.634,0,.977a16.1,16.1,0,0,1,.146,3.116c-.043,1.06-.244,2.113-.349,3.172-.057.572-.073,1.149-.08,1.724a1.454,1.454,0,0,0,.157.448,1.094,1.094,0,0,0-.161.376c-.032.849-.034,1.7-.06,2.548-.017.555.161.932.708.926l.27.789c-.235-.006-.516-.015-.8-.019s-.374.127-.249.383.273.5.395.764c.063.133.166.347.113.411a.734.734,0,0,1-.442.182,5.05,5.05,0,0,1-.6,0c-.631.014-1.262.048-1.893.041-.318,0-.635-.105-.954-.116-1.235-.042-2.471-.065-3.7-.095a1.287,1.287,0,0,0-.953-.719,9.435,9.435,0,0,0-1.627-.174q-3.369-.1-6.739-.159a5.743,5.743,0,0,0-1.024.172c-.1.016-.2.015-.292.032a13.451,13.451,0,0,1-2.476-.074c-.748,0-1.5.032-2.244.053-.056,0-.132.006-.162.041a4.85,4.85,0,0,1-1.135.8c-.386.252-.593.779-.881,1.183a2.354,2.354,0,0,1-1.222,1.054,3.2,3.2,0,0,0-.908.682c-.295.238-.582.486-.873.729l-.106-.108a2.85,2.85,0,0,1,.239-.312c.668-.663,1.355-1.308,2-1.992a.776.776,0,0,0,.085-.611c-.015-.07-.33-.08-.51-.1a2.608,2.608,0,0,0-.359.008c0-.15,0-.286.011-.421s.079-.292.025-.382c-.309-.515-.021-1.037-.028-1.55a12.629,12.629,0,0,1,.229-1.431c.037-.359.052-.744-.356-.963a.238.238,0,0,1-.03-.227c.242-.376.1-.769.1-1.165,0-.907.088-1.814.108-2.722.011-.463-.042-.929-.072-1.393A3.885,3.885,0,0,1,147,92.16a11.2,11.2,0,0,0,.154-1.672,11.476,11.476,0,0,0-.087-1.648c0-.539.06-1.079.09-1.619a.72.72,0,0,0-.013-.322,4.324,4.324,0,0,1-.306-2.062c.07-1.6.131-3.2.211-4.8a8.769,8.769,0,0,1,.154-.969Zm6.594,21.763c.274.007.544.007.813.022.154.008.307.038.46.061a1.213,1.213,0,0,0,.282.045c.357-.036.711-.119,1.067-.122s.736.094,1.1.1q2.094.011,4.189-.018c.278,0,.553-.1.832-.112a5.664,5.664,0,0,1,.83.022c.183.016.361.1.543.107q2.266.068,4.531.114c.994.021,1.988.031,2.983.042.368,0,.711-.064.783-.5a2.893,2.893,0,0,0,.057-.937,8.351,8.351,0,0,1-.156-2.322c.11-1.045.156-2.1.179-3.15s-.016-2.125-.023-3.188c0-.673.019-1.348,0-2.02-.015-.451-.093-.9-.137-1.349-.051-.514-.078-1.031-.158-1.54a.48.48,0,0,0-.336-.276,5.593,5.593,0,0,0-.832-.033l-.027-.135c.288-.081.582-.147.862-.25.111-.041.267-.161.272-.251.04-.755.049-1.512.067-2.268l-.126-.005c-.289.391-.587.776-.863,1.176s-.528.823-.793,1.233c-.42.651-.421.65.241,1.025.014.008.015.041.026.076-.106.046-.209.1-.314.136a3.733,3.733,0,0,0-1.945,1.608c-.159.272-.4.5-.541.777a11.691,11.691,0,0,0-1.306,2.481.357.357,0,0,1-.151.2c-.488.3-.547.613-.2,1.079.055.074.119.192.1.263-.107.338-.242.667-.373,1.015l-.357.034-1.16-1.63.541-.491c-.163-.119-.347-.183-.4-.3-.145-.327-.307-.264-.529-.1q-1.622,1.21-3.254,2.408a2.012,2.012,0,0,1-.411.179,11.4,11.4,0,0,1,1.074-1.079c.5-.468.978-.963,1.5-1.406.348-.3.761-.516,1.137-.781a.832.832,0,0,0,.037-1.286c-.156-.144-.372-.473-.317-.557.315-.477-.088-.523-.352-.677l.138-.087c-.354.042-.322-.2-.358-.439a6.468,6.468,0,0,0-.223-.832,29.52,29.52,0,0,0-2.256-4.782,4.981,4.981,0,0,0-.454-.626,59.177,59.177,0,0,0-4.568,9.841l-.387-.031c-.014.111-.019.2-.035.279-.039.206.067.485-.305.51-.341.023-.484-.1-.535-.45-.03-.2-.165-.508-.318-.556-.34-.106-.423-.358-.53-.614-.083-.2-.111-.422-.206-.612-.183-.367-.173-.916-.8-.9-.038,0-.081-.076-.116-.12-.556-.7-1.123-1.387-1.662-2.1-.564-.743-1.1-1.506-1.647-2.263-.151-.209-.282-.432-.437-.637-.142-.187-.3-.359-.523-.614-.062.674-.148,1.2-.148,1.724,0,.453.142.907.139,1.36-.01,1.913.031,3.831-.1,5.735a22.5,22.5,0,0,0,.112,4.773,6.512,6.512,0,0,1,.066,1.568c-.074.8-.063,1.608-.08,2.414-.011.535-.006.534.547.487.1-.008.2.008.3.01a5.307,5.307,0,0,0,3.536-.8l-.4.663,1.97-.121Zm-5.616,2.064-.023.942.107.022,1.031-1.3Zm22.305-.728-.012.176c.431.082.86.181,1.295.234.095.012.209-.138.314-.213-.09-.1-.18-.284-.269-.283C171.534,102.112,171.092,102.163,170.649,102.2Zm-.2.311,0-.2a3.2,3.2,0,0,0-1.76.153Z" transform="translate(-145.624 -78.624)"/>
|
||||
<path id="Path_38" data-name="Path 38" d="M161.912,110.527l.165.191-1.97.121.4-.663a5.307,5.307,0,0,1-3.536.8c-.1,0-.2-.018-.3-.01-.552.047-.558.049-.547-.487.017-.805.006-1.614.08-2.414a6.512,6.512,0,0,0-.066-1.568,22.5,22.5,0,0,1-.112-4.773c.135-1.9.095-3.823.1-5.735,0-.453-.14-.907-.139-1.36,0-.525.086-1.05.148-1.724.219.255.381.427.523.614.155.2.287.428.437.637.546.757,1.083,1.52,1.647,2.263.539.71,1.106,1.4,1.662,2.1.035.044.078.121.116.12.623-.011.613.538.8.9.095.191.123.414.206.612.107.256.19.508.53.614.153.048.289.352.318.556.051.345.193.473.535.45.372-.025.267-.3.305-.51.016-.083.021-.168.035-.279l.387.031a59.177,59.177,0,0,1,4.568-9.841,4.98,4.98,0,0,1,.454.626,29.523,29.523,0,0,1,2.256,4.782,6.468,6.468,0,0,1,.223.832c.036.235,0,.481.358.439l-.138.087c.264.154.667.2.352.677-.055.084.16.413.317.557a.832.832,0,0,1-.037,1.286c-.376.265-.788.485-1.137.781-.522.443-1,.938-1.5,1.406a11.4,11.4,0,0,0-1.074,1.079,2.013,2.013,0,0,0,.411-.179q1.633-1.2,3.254-2.408c.222-.166.384-.229.529.1.054.122.237.186.4.3l-.541.491,1.16,1.63.357-.034c.131-.348.266-.677.373-1.015.023-.071-.041-.189-.1-.263-.345-.465-.286-.779.2-1.079a.357.357,0,0,0,.151-.2,11.692,11.692,0,0,1,1.306-2.481c.14-.279.383-.505.541-.777a3.733,3.733,0,0,1,1.945-1.608c.105-.041.208-.09.314-.136-.011-.035-.012-.068-.026-.076-.661-.375-.661-.374-.241-1.025.265-.411.516-.831.793-1.233s.574-.785.863-1.176l.126.005c-.018.756-.028,1.513-.067,2.268,0,.091-.162.211-.272.251-.28.1-.573.169-.862.25l.027.135a5.589,5.589,0,0,1,.832.033.48.48,0,0,1,.336.276c.08.509.107,1.027.158,1.54.045.45.122.9.137,1.349.023.673,0,1.347,0,2.02.007,1.063.047,2.126.023,3.188s-.069,2.105-.179,3.15a8.352,8.352,0,0,0,.156,2.322,2.892,2.892,0,0,1-.057.937c-.072.435-.415.5-.783.5-.994-.011-1.989-.021-2.983-.042q-2.266-.048-4.531-.114c-.182-.005-.36-.091-.543-.107a5.665,5.665,0,0,0-.83-.022c-.279.017-.554.109-.832.112q-2.094.027-4.189.018c-.368,0-.737-.1-1.1-.1s-.711.087-1.067.122a1.213,1.213,0,0,1-.282-.045c-.153-.023-.306-.052-.46-.061C162.455,110.534,162.185,110.533,161.912,110.527Z" transform="translate(-153.576 -88.29)" fill="#a72c88"/>
|
||||
<path id="Path_39" data-name="Path 39" d="M157.386,183.269l1.115-.335-1.031,1.3-.107-.022Z" transform="translate(-154.666 -158.969)" fill="#fff"/>
|
||||
<path id="Path_40" data-name="Path 40" d="M254.508,180.934c.443-.034.885-.085,1.328-.087.089,0,.179.184.269.283-.105.075-.22.225-.314.213-.435-.053-.864-.152-1.295-.234Z" transform="translate(-229.483 -157.361)" fill="#fff"/>
|
||||
<path id="Path_41" data-name="Path 41" d="M247.766,181.674,246,181.631a3.2,3.2,0,0,1,1.76-.153Z" transform="translate(-222.941 -157.791)" fill="#fff"/>
|
||||
</g>
|
||||
<g id="Group_63" data-name="Group 63" transform="translate(54.904 100.326)">
|
||||
<path id="Path_42" data-name="Path 42" d="M55.378,200.113l-.025.162a8.773,8.773,0,0,0,.969.154c1.6.08,3.2.142,4.8.211a4.322,4.322,0,0,0,2.062-.305.72.72,0,0,1,.322-.013c.54.03,1.08.094,1.619.09a11.472,11.472,0,0,1,1.648-.087,11.194,11.194,0,0,1,1.672.154,3.884,3.884,0,0,0,1.248-.064c.464-.029.929-.082,1.393-.072.908.021,1.815.107,2.722.108.4,0,.788-.143,1.165.1a.239.239,0,0,0,.227-.03c.218-.409.6-.394.963-.357a12.626,12.626,0,0,0,1.431.229c.513-.007,1.035.281,1.55-.028.09-.054.253.017.382.025s.272.008.421.011a2.6,2.6,0,0,1-.008-.359c.024-.18.034-.5.1-.51a.776.776,0,0,1,.611.085c.684.645,1.329,1.332,1.992,2a2.8,2.8,0,0,0,.312.239l.108-.106c-.243-.291-.491-.578-.729-.873a3.2,3.2,0,0,1-.682-.908,2.352,2.352,0,0,0-1.054-1.222c-.4-.289-.931-.495-1.183-.882a4.853,4.853,0,0,0-.8-1.135c-.035-.03-.04-.107-.041-.162-.021-.748-.056-1.5-.053-2.243a13.432,13.432,0,0,0,.074-2.476c-.017-.1-.015-.2-.032-.292a5.744,5.744,0,0,1-.172-1.024q.054-3.37.159-6.739a9.433,9.433,0,0,1,.174-1.627,1.287,1.287,0,0,1,.719-.953c.03-1.226.053-2.462.095-3.7.011-.319.113-.635.116-.954.007-.631-.027-1.262-.041-1.893a5.043,5.043,0,0,0,0-.6.735.735,0,0,0-.182-.443c-.065-.053-.278.051-.411.113-.259.121-.506.269-.764.395s-.386.031-.383-.249.013-.564.019-.8l-.789.27c.006.547-.371.725-.926.708-.849-.026-1.7-.028-2.548-.06a1.093,1.093,0,0,1-.376-.161A1.451,1.451,0,0,1,72.8,174c-.575-.008-1.152-.023-1.724-.08-1.059-.106-2.112-.306-3.172-.349a16.116,16.116,0,0,0-3.116.146c-.343.054-.638-.138-.977,0a3.649,3.649,0,0,1-.978.134c-.359.04-.666-.025-.76-.337a3.942,3.942,0,0,1-.738.129c-.417-.007-.832-.09-1.249-.1-.281,0-.562.1-.843.1-1.021,0-2.041-.043-3.061-.028-.2,0-.518.167-.553.312a.945.945,0,0,0,.2.686,3.363,3.363,0,0,0,.752.708q1.446,1.017,2.92,1.992c1.6,1.066,3.222,2.112,4.821,3.186a3.353,3.353,0,0,1,.839.778.627.627,0,0,0,.693.33,1.994,1.994,0,0,1-.513.509c-.3.187-.663.271-.8.668-.029.086-.191.147-.3.181-.163.049-.343.04-.5.093-1.117.373-2.234.744-3.343,1.14a1.513,1.513,0,0,0-.964.764,1.552,1.552,0,0,1-.878.61c-.993.326-2.011.574-3.014.871a.77.77,0,0,0-.587,1.057.423.423,0,0,0,.441.379,3.159,3.159,0,0,1,1.01.173c1.083.459,2.147.963,3.216,1.455,1.152.531,2.311,1.047,3.448,1.608,1.083.535,2.142,1.121,3.269,1.714-.179.13-.31.236-.451.325-.728.462-1.46.918-2.188,1.38-.665.422-1.336.836-1.988,1.276-.749.5-1.483,1.032-2.221,1.553-.82.579-1.63,1.174-2.461,1.737C56.488,199.466,55.926,199.779,55.378,200.113Z" transform="translate(-54.904 -173.083)" fill="#fff"/>
|
||||
<path id="Path_43" data-name="Path 43" d="M55.378,200.113c.548-.334,1.11-.647,1.64-1.006.831-.563,1.64-1.158,2.461-1.737.738-.521,1.472-1.048,2.221-1.553.653-.44,1.323-.854,1.988-1.276.728-.462,1.46-.918,2.188-1.38.141-.089.272-.2.451-.325-1.127-.593-2.185-1.179-3.269-1.714-1.137-.561-2.3-1.077-3.448-1.608-1.069-.492-2.133-1-3.216-1.455a3.159,3.159,0,0,0-1.01-.173.423.423,0,0,1-.441-.379.77.77,0,0,1,.587-1.057c1-.3,2.021-.546,3.014-.871a1.552,1.552,0,0,0,.878-.61,1.513,1.513,0,0,1,.964-.764c1.109-.4,2.226-.767,3.343-1.14.161-.054.341-.045.5-.093.112-.034.274-.095.3-.181.134-.4.5-.48.8-.668a1.994,1.994,0,0,0,.513-.509.627.627,0,0,1-.693-.33,3.353,3.353,0,0,0-.839-.778c-1.6-1.074-3.217-2.12-4.821-3.186q-1.472-.978-2.92-1.992a3.363,3.363,0,0,1-.752-.708.945.945,0,0,1-.2-.686c.035-.145.357-.309.553-.312,1.02-.016,2.041.027,3.061.028.281,0,.562-.108.843-.1.417.006.832.089,1.249.1a3.942,3.942,0,0,0,.738-.129c.094.311.4.377.76.337a3.649,3.649,0,0,0,.978-.134c.34-.141.634.051.977,0a16.116,16.116,0,0,1,3.116-.146c1.06.043,2.113.244,3.172.349.572.057,1.149.073,1.724.08a1.451,1.451,0,0,0,.448-.157,1.093,1.093,0,0,0,.376.161c.849.032,1.7.034,2.548.06.555.017.932-.161.926-.708l.789-.27c-.006.235-.015.516-.019.8s.127.374.383.249.5-.273.764-.395c.133-.062.347-.166.411-.113a.735.735,0,0,1,.182.443,5.043,5.043,0,0,1,0,.6c.014.631.048,1.262.041,1.893,0,.318-.105.635-.116.954-.042,1.235-.065,2.471-.095,3.7a1.287,1.287,0,0,0-.719.953,9.433,9.433,0,0,0-.174,1.627q-.1,3.369-.159,6.739a5.744,5.744,0,0,0,.172,1.024c.016.1.015.2.032.292a13.432,13.432,0,0,1-.074,2.476c0,.748.032,1.5.053,2.243,0,.056.006.132.041.162a4.853,4.853,0,0,1,.8,1.135c.252.387.779.593,1.183.882a2.352,2.352,0,0,1,1.054,1.222,3.2,3.2,0,0,0,.682.908c.238.295.486.582.729.873l-.108.106a2.8,2.8,0,0,1-.312-.239c-.663-.668-1.308-1.355-1.992-2a.776.776,0,0,0-.611-.085c-.07.015-.08.33-.1.51a2.6,2.6,0,0,0,.008.359c-.149,0-.286,0-.421-.011s-.292-.079-.382-.025c-.515.309-1.037.021-1.55.028a12.626,12.626,0,0,1-1.431-.229c-.359-.037-.744-.052-.963.357a.239.239,0,0,1-.227.03c-.377-.242-.769-.1-1.165-.1-.907,0-1.814-.088-2.722-.108-.463-.011-.928.042-1.393.072a3.884,3.884,0,0,1-1.248.064,11.194,11.194,0,0,0-1.672-.154,11.472,11.472,0,0,0-1.648.087c-.539,0-1.079-.06-1.619-.09a.72.72,0,0,0-.322.013,4.322,4.322,0,0,1-2.062.305c-1.6-.069-3.2-.131-4.8-.211a8.773,8.773,0,0,1-.969-.154Zm21.763-6.594c.007-.274.007-.544.022-.814.009-.154.038-.307.061-.46a1.209,1.209,0,0,0,.045-.281c-.036-.357-.119-.711-.122-1.067s.094-.736.1-1.1q.011-2.094-.018-4.189c0-.278-.1-.553-.112-.832a5.632,5.632,0,0,1,.022-.83c.016-.183.1-.362.107-.544q.068-2.265.114-4.531c.021-.994.031-1.988.042-2.983,0-.368-.064-.711-.5-.783a2.892,2.892,0,0,0-.937-.057,8.342,8.342,0,0,1-2.322.155c-1.045-.11-2.1-.156-3.15-.179s-2.125.016-3.188.023c-.673,0-1.348-.019-2.02,0-.451.015-.9.093-1.349.137-.514.051-1.031.078-1.54.158a.48.48,0,0,0-.276.336,5.587,5.587,0,0,0-.033.832l-.135.027c-.081-.288-.147-.582-.25-.862-.041-.111-.161-.268-.251-.272-.755-.04-1.512-.049-2.268-.067l-.005.126c.391.289.776.587,1.176.862s.822.528,1.233.793c.651.42.65.421,1.025-.241.008-.014.041-.015.076-.026.046.106.1.209.136.314a3.734,3.734,0,0,0,1.608,1.945c.272.159.5.4.777.541a11.691,11.691,0,0,0,2.481,1.306.357.357,0,0,1,.2.15c.3.488.613.547,1.079.2.074-.055.192-.119.263-.1.338.107.667.242,1.015.373l.034.357-1.63,1.16-.491-.541c-.119.163-.183.347-.3.4-.327.145-.264.306-.1.529q1.21,1.622,2.407,3.254a2,2,0,0,1,.179.411,11.369,11.369,0,0,1-1.079-1.074c-.468-.5-.963-.978-1.406-1.5-.3-.348-.516-.761-.781-1.137a.832.832,0,0,0-1.286-.037c-.144.156-.473.372-.557.317-.477-.315-.523.088-.677.352l-.087-.138c.042.354-.2.322-.439.358a6.463,6.463,0,0,0-.832.223,29.519,29.519,0,0,0-4.782,2.256,4.965,4.965,0,0,0-.626.454,59.169,59.169,0,0,0,9.841,4.568l-.031.387c.111.014.2.019.279.035.206.039.486-.067.51.305.023.342-.105.484-.45.535-.2.03-.508.165-.556.318-.105.34-.357.423-.614.53-.2.083-.422.111-.613.206-.367.183-.916.173-.9.8,0,.038-.076.08-.12.116-.7.555-1.387,1.123-2.1,1.662-.743.564-1.506,1.1-2.262,1.647-.209.15-.432.282-.637.437-.187.142-.359.3-.614.523.674.063,1.2.148,1.724.148.453,0,.907-.142,1.36-.139,1.913.01,3.831-.03,5.735.105a22.493,22.493,0,0,0,4.773-.112,6.513,6.513,0,0,1,1.568-.066c.8.074,1.608.063,2.414.08.536.011.534.006.487-.547-.008-.1.008-.2.01-.3a5.305,5.305,0,0,0-.8-3.536l.663.4-.121-1.97Zm2.064,5.616.942.023.022-.107-1.3-1.031Zm-.728-22.305.176.012c.082-.431.181-.86.234-1.295.012-.095-.138-.209-.214-.315-.1.09-.284.18-.283.269C78.392,175.945,78.443,176.387,78.477,176.83Zm.311.2-.2,0a3.2,3.2,0,0,0,.153,1.76Z" transform="translate(-54.904 -173.083)"/>
|
||||
<path id="Path_44" data-name="Path 44" d="M86.807,199.99l.191-.165.121,1.97-.663-.4a5.305,5.305,0,0,1,.8,3.536c0,.1-.018.2-.01.3.047.552.049.558-.487.547-.805-.017-1.613-.006-2.414-.08a6.512,6.512,0,0,0-1.568.066A22.5,22.5,0,0,1,78,205.88c-1.9-.135-3.823-.095-5.735-.1-.453,0-.907.14-1.36.139-.525,0-1.05-.086-1.724-.148.255-.219.427-.381.614-.523.2-.155.428-.286.637-.437.757-.546,1.519-1.083,2.262-1.647.71-.539,1.4-1.106,2.1-1.662.044-.036.121-.078.12-.116-.011-.623.538-.613.9-.8.191-.1.414-.123.613-.206.256-.107.508-.19.614-.53.048-.153.352-.289.556-.318.345-.051.473-.193.45-.535-.025-.372-.3-.266-.51-.305-.083-.016-.168-.021-.279-.035l.031-.387a59.167,59.167,0,0,1-9.841-4.568,4.967,4.967,0,0,1,.626-.454,29.522,29.522,0,0,1,4.782-2.256,6.468,6.468,0,0,1,.832-.223c.235-.036.481,0,.439-.358l.087.138c.154-.264.2-.667.677-.352.084.055.413-.16.557-.317a.832.832,0,0,1,1.286.037c.265.376.485.788.781,1.137.443.522.938,1,1.406,1.5a11.37,11.37,0,0,0,1.079,1.074,1.994,1.994,0,0,0-.179-.411q-1.2-1.633-2.407-3.254c-.166-.222-.229-.384.1-.529.122-.054.186-.237.3-.4l.491.541,1.63-1.16-.034-.357c-.348-.131-.677-.266-1.015-.373-.071-.023-.189.041-.263.1-.465.345-.779.286-1.079-.2a.357.357,0,0,0-.2-.15,11.693,11.693,0,0,1-2.481-1.306c-.279-.14-.506-.383-.777-.541a3.733,3.733,0,0,1-1.608-1.945c-.041-.105-.09-.208-.136-.314-.035.011-.068.012-.076.026-.375.661-.374.661-1.025.241-.411-.265-.83-.516-1.233-.793s-.785-.574-1.176-.862l.005-.126c.756.018,1.513.027,2.268.067.091,0,.211.162.251.272.1.28.169.573.25.862l.135-.027a5.587,5.587,0,0,1,.033-.832.48.48,0,0,1,.276-.336c.509-.08,1.027-.107,1.54-.158.45-.045.9-.122,1.349-.137.673-.023,1.347,0,2.02,0,1.063-.007,2.126-.047,3.188-.023s2.105.069,3.15.179a8.342,8.342,0,0,0,2.322-.155,2.893,2.893,0,0,1,.937.057c.435.072.5.415.5.783-.011.994-.021,1.989-.042,2.983q-.048,2.266-.114,4.531c-.005.182-.091.361-.107.544a5.633,5.633,0,0,0-.022.83c.017.279.109.554.112.832q.027,2.094.018,4.189c0,.368-.1.737-.1,1.1s.087.711.122,1.067a1.209,1.209,0,0,1-.045.281c-.023.153-.052.306-.061.46C86.814,199.446,86.813,199.716,86.807,199.99Z" transform="translate(-64.57 -179.554)" fill="#f5613c"/>
|
||||
<path id="Path_45" data-name="Path 45" d="M159.549,282.74l-.335-1.115,1.3,1.031-.022.107Z" transform="translate(-135.249 -256.688)" fill="#fff"/>
|
||||
<path id="Path_46" data-name="Path 46" d="M157.214,184.038c-.034-.443-.085-.885-.087-1.328,0-.089.184-.179.283-.269.076.105.225.22.214.315-.053.435-.152.864-.234,1.295Z" transform="translate(-133.641 -180.291)" fill="#fff"/>
|
||||
<path id="Path_47" data-name="Path 47" d="M157.954,190.264l-.043,1.763a3.2,3.2,0,0,1-.153-1.76Z" transform="translate(-134.071 -186.317)" fill="#fff"/>
|
||||
</g>
|
||||
<g id="Group_64" data-name="Group 64" transform="translate(77.162 121.619)">
|
||||
<path id="Path_48" data-name="Path 48" d="M178.813,293.443l.162.025a8.767,8.767,0,0,0,.154-.969c.08-1.6.142-3.2.211-4.8a4.325,4.325,0,0,0-.306-2.062.719.719,0,0,1-.013-.322c.03-.54.094-1.08.09-1.619a11.509,11.509,0,0,1-.087-1.648,11.2,11.2,0,0,1,.154-1.672,3.885,3.885,0,0,0-.064-1.248c-.029-.464-.082-.929-.072-1.393.021-.908.107-1.814.108-2.722,0-.4-.143-.788.1-1.165a.238.238,0,0,0-.03-.227c-.409-.218-.394-.6-.356-.963a12.642,12.642,0,0,0,.229-1.431c-.007-.513.281-1.035-.028-1.55-.054-.09.017-.253.025-.382s.008-.272.011-.421a2.6,2.6,0,0,1-.359.008c-.18-.024-.5-.034-.51-.1a.776.776,0,0,1,.085-.611c.645-.684,1.332-1.329,2-1.992a2.848,2.848,0,0,0,.239-.312l-.106-.109c-.291.244-.578.491-.873.729a3.2,3.2,0,0,1-.908.682,2.353,2.353,0,0,0-1.222,1.054,4.313,4.313,0,0,1-.881,1.183,4.862,4.862,0,0,0-1.135.8c-.03.035-.107.04-.162.041-.748.021-1.5.056-2.244.053a13.453,13.453,0,0,0-2.476-.074c-.1.017-.2.016-.292.032a5.747,5.747,0,0,1-1.024.172q-3.37-.055-6.739-.16a9.409,9.409,0,0,1-1.627-.174,1.287,1.287,0,0,1-.953-.719c-1.226-.03-2.462-.053-3.7-.095-.319-.011-.635-.113-.954-.116-.631-.007-1.262.027-1.893.041a5.1,5.1,0,0,0-.6,0,.735.735,0,0,0-.443.182c-.053.065.051.278.113.411.121.259.269.506.395.763s.031.386-.249.383-.564-.013-.8-.019l.27.789c.547-.006.725.371.708.926-.026.849-.028,1.7-.059,2.549a1.1,1.1,0,0,1-.161.376,1.448,1.448,0,0,1,.157.448c-.008.575-.023,1.152-.08,1.723-.106,1.059-.307,2.112-.35,3.172a16.121,16.121,0,0,0,.146,3.117c.053.343-.138.638,0,.977a3.651,3.651,0,0,1,.134.978c.04.359-.025.666-.337.76a3.948,3.948,0,0,1,.129.738c-.007.417-.09.833-.1,1.249,0,.28.1.562.1.843,0,1.021-.043,2.041-.028,3.061,0,.2.167.518.312.553a.947.947,0,0,0,.686-.2,3.362,3.362,0,0,0,.708-.752q1.017-1.446,1.992-2.92c1.066-1.6,2.112-3.222,3.186-4.821a3.348,3.348,0,0,1,.778-.839.627.627,0,0,0,.33-.693,1.986,1.986,0,0,1,.509.513c.187.3.271.663.668.8.086.029.147.191.181.3.049.163.04.343.093.5.373,1.117.744,2.234,1.14,3.343a1.514,1.514,0,0,0,.764.964,1.553,1.553,0,0,1,.61.878c.326.993.574,2.011.871,3.013a.77.77,0,0,0,1.057.588.423.423,0,0,0,.379-.441,3.167,3.167,0,0,1,.173-1.009c.46-1.083.963-2.147,1.455-3.216.531-1.152,1.047-2.311,1.608-3.448.535-1.083,1.12-2.142,1.714-3.269.13.179.235.31.325.451.462.728.918,1.46,1.38,2.188.422.665.836,1.335,1.276,1.988.5.749,1.032,1.483,1.553,2.221.579.821,1.174,1.63,1.737,2.461C178.166,292.333,178.479,292.895,178.813,293.443Z" transform="translate(-151.783 -265.761)" fill="#fff"/>
|
||||
<path id="Path_49" data-name="Path 49" d="M178.813,293.443c-.334-.548-.647-1.11-1.006-1.64-.563-.831-1.158-1.64-1.737-2.461-.521-.738-1.048-1.472-1.553-2.221-.44-.653-.854-1.324-1.276-1.988-.462-.728-.918-1.46-1.38-2.188-.089-.141-.2-.272-.325-.451-.593,1.127-1.179,2.185-1.714,3.269-.562,1.137-1.078,2.3-1.608,3.448-.492,1.069-1,2.133-1.455,3.216a3.167,3.167,0,0,0-.173,1.009.423.423,0,0,1-.379.441.77.77,0,0,1-1.057-.588c-.3-1-.546-2.021-.871-3.013a1.553,1.553,0,0,0-.61-.878,1.514,1.514,0,0,1-.764-.964c-.4-1.109-.767-2.226-1.14-3.343-.054-.16-.045-.341-.093-.5-.034-.112-.095-.274-.181-.3-.4-.134-.48-.5-.668-.8a1.986,1.986,0,0,0-.509-.513.627.627,0,0,1-.33.693,3.348,3.348,0,0,0-.778.839c-1.074,1.6-2.12,3.217-3.186,4.821q-.978,1.472-1.992,2.92a3.362,3.362,0,0,1-.708.752.947.947,0,0,1-.686.2c-.145-.035-.309-.357-.312-.553-.016-1.02.027-2.041.028-3.061,0-.281-.108-.563-.1-.843.006-.417.089-.832.1-1.249a3.948,3.948,0,0,0-.129-.738c.311-.094.377-.4.337-.76a3.651,3.651,0,0,0-.134-.978c-.141-.339.051-.634,0-.977a16.121,16.121,0,0,1-.146-3.117c.043-1.06.244-2.113.35-3.172.057-.572.072-1.148.08-1.723a1.448,1.448,0,0,0-.157-.448,1.1,1.1,0,0,0,.161-.376c.032-.849.033-1.7.059-2.549.017-.555-.161-.932-.708-.926l-.27-.789c.235.006.516.015.8.019s.374-.127.249-.383-.273-.5-.395-.763c-.062-.133-.166-.347-.113-.411a.735.735,0,0,1,.443-.182,5.1,5.1,0,0,1,.6,0c.631-.014,1.262-.048,1.893-.041.318,0,.635.105.954.116,1.235.042,2.471.065,3.7.095a1.287,1.287,0,0,0,.953.719,9.409,9.409,0,0,0,1.627.174q3.369.1,6.739.16a5.747,5.747,0,0,0,1.024-.172c.1-.016.2-.015.292-.032a13.453,13.453,0,0,1,2.476.074c.748,0,1.5-.032,2.244-.053.056,0,.132-.006.162-.041a4.862,4.862,0,0,1,1.135-.8,4.313,4.313,0,0,0,.881-1.183,2.353,2.353,0,0,1,1.222-1.054,3.2,3.2,0,0,0,.908-.682c.295-.238.582-.486.873-.729l.106.109a2.848,2.848,0,0,1-.239.312c-.668.663-1.355,1.308-2,1.992a.776.776,0,0,0-.085.611c.015.07.33.08.51.1a2.6,2.6,0,0,0,.359-.008c0,.15,0,.286-.011.421s-.079.292-.025.382c.309.515.021,1.037.028,1.55a12.642,12.642,0,0,1-.229,1.431c-.037.359-.052.744.356.963a.238.238,0,0,1,.03.227c-.242.377-.1.769-.1,1.165,0,.907-.087,1.814-.108,2.722-.011.463.042.929.072,1.393a3.885,3.885,0,0,1,.064,1.248,11.2,11.2,0,0,0-.154,1.672,11.509,11.509,0,0,0,.087,1.648c0,.539-.06,1.079-.09,1.619a.719.719,0,0,0,.013.322,4.325,4.325,0,0,1,.306,2.062c-.07,1.6-.131,3.2-.211,4.8a8.767,8.767,0,0,1-.154.969Zm-6.594-21.763c-.274-.007-.544-.007-.813-.022-.154-.008-.307-.038-.46-.061a1.226,1.226,0,0,0-.282-.045c-.357.036-.711.119-1.067.122s-.736-.094-1.1-.1q-2.094-.011-4.189.018c-.278,0-.553.1-.832.112a5.667,5.667,0,0,1-.83-.022c-.183-.016-.361-.1-.543-.107q-2.266-.067-4.531-.114c-.994-.021-1.988-.031-2.983-.042-.368,0-.711.064-.783.5a2.9,2.9,0,0,0-.057.937,8.353,8.353,0,0,1,.156,2.322c-.11,1.045-.156,2.1-.179,3.15s.016,2.125.023,3.188c0,.674-.019,1.348,0,2.021.015.451.093.9.137,1.349.051.514.078,1.031.158,1.54a.479.479,0,0,0,.336.276,5.566,5.566,0,0,0,.832.033l.026.135c-.288.081-.582.147-.862.25-.111.041-.267.161-.272.251-.04.755-.049,1.512-.067,2.268l.126.005c.289-.392.587-.776.863-1.177s.528-.822.793-1.233c.42-.651.421-.65-.241-1.025-.014-.008-.015-.041-.026-.076.106-.046.209-.1.314-.136a3.734,3.734,0,0,0,1.945-1.608c.159-.272.4-.5.541-.777a11.693,11.693,0,0,0,1.306-2.481.358.358,0,0,1,.151-.2c.488-.3.547-.613.2-1.079-.055-.074-.119-.192-.1-.263.107-.338.242-.667.373-1.015l.357-.034,1.16,1.63-.541.491c.163.119.347.183.4.3.145.327.306.264.529.1q1.622-1.21,3.254-2.408a2,2,0,0,1,.411-.178,11.4,11.4,0,0,1-1.074,1.079c-.5.468-.978.963-1.5,1.406-.348.3-.761.516-1.137.781a.832.832,0,0,0-.037,1.286c.156.144.372.473.317.557-.315.477.088.523.352.677l-.138.087c.354-.042.322.2.358.439a6.479,6.479,0,0,0,.223.832,29.508,29.508,0,0,0,2.256,4.782,4.964,4.964,0,0,0,.454.627,59.179,59.179,0,0,0,4.568-9.841l.387.031c.014-.111.019-.2.035-.279.039-.206-.067-.485.305-.51.341-.023.484.105.535.45.03.2.165.508.318.556.34.106.423.358.53.614.083.2.111.422.206.612.183.367.173.916.8.9.038,0,.081.076.116.12.556.7,1.123,1.386,1.662,2.1.564.743,1.1,1.506,1.647,2.263.15.209.282.432.437.637.142.187.3.359.523.614.063-.674.148-1.2.148-1.724,0-.454-.142-.907-.139-1.361.01-1.912-.031-3.83.1-5.735a22.493,22.493,0,0,0-.112-4.773,6.509,6.509,0,0,1-.066-1.568c.074-.8.063-1.609.08-2.414.011-.535.006-.534-.547-.487-.1.008-.2-.008-.3-.01a5.307,5.307,0,0,0-3.536.8l.4-.663-1.97.121Zm5.616-2.064.023-.942-.107-.023-1.031,1.3Zm-22.305.727.012-.176c-.431-.082-.86-.181-1.295-.234-.095-.012-.209.138-.314.213.09.1.18.284.269.283C154.645,270.429,155.087,270.378,155.53,270.344Zm.2-.311,0,.2a3.194,3.194,0,0,0,1.76-.153Z" transform="translate(-151.783 -265.761)"/>
|
||||
<path id="Path_50" data-name="Path 50" d="M178.69,289.984l-.165-.191,1.97-.121-.4.663a5.308,5.308,0,0,1,3.536-.8c.1,0,.2.018.3.01.552-.047.558-.049.547.487-.017.805-.006,1.614-.08,2.414a6.511,6.511,0,0,0,.066,1.568,22.494,22.494,0,0,1,.112,4.773c-.135,1.9-.095,3.823-.1,5.735,0,.453.14.907.139,1.361,0,.525-.086,1.05-.148,1.724-.219-.255-.381-.427-.523-.614-.155-.205-.287-.428-.437-.637-.546-.757-1.083-1.52-1.647-2.263-.539-.71-1.106-1.4-1.662-2.1-.035-.044-.078-.121-.116-.12-.623.011-.613-.538-.8-.9-.095-.191-.123-.414-.206-.612-.107-.256-.19-.508-.53-.614-.153-.048-.289-.352-.318-.556-.051-.345-.193-.473-.535-.45-.372.025-.267.3-.305.51-.016.083-.021.168-.035.279l-.387-.031a59.178,59.178,0,0,1-4.568,9.841,4.964,4.964,0,0,1-.454-.627,29.508,29.508,0,0,1-2.256-4.782,6.477,6.477,0,0,1-.223-.832c-.036-.235,0-.481-.358-.439l.138-.087c-.264-.154-.667-.2-.352-.677.055-.084-.16-.413-.317-.557a.832.832,0,0,1,.037-1.286c.376-.265.788-.485,1.137-.781.522-.443,1-.938,1.5-1.406a11.4,11.4,0,0,0,1.074-1.079,2,2,0,0,0-.411.179q-1.633,1.2-3.254,2.408c-.222.166-.384.229-.529-.1-.054-.122-.237-.186-.4-.3l.541-.491-1.16-1.63-.357.034c-.131.348-.266.677-.373,1.015-.023.071.041.189.1.263.345.465.286.778-.2,1.079a.358.358,0,0,0-.151.2,11.691,11.691,0,0,1-1.306,2.481c-.14.279-.383.505-.541.777a3.734,3.734,0,0,1-1.945,1.608c-.105.041-.208.09-.314.136.012.035.012.068.026.076.661.375.661.374.241,1.025-.265.411-.516.831-.793,1.233s-.574.785-.863,1.177l-.126-.005c.018-.756.028-1.513.067-2.268,0-.091.162-.211.272-.251.28-.1.573-.169.862-.25l-.026-.135a5.558,5.558,0,0,1-.832-.033.479.479,0,0,1-.336-.276c-.08-.509-.107-1.026-.158-1.54-.045-.45-.122-.9-.137-1.349-.023-.673,0-1.347,0-2.021-.007-1.063-.047-2.126-.023-3.188s.069-2.105.179-3.15a8.353,8.353,0,0,0-.156-2.322,2.9,2.9,0,0,1,.057-.937c.072-.435.415-.5.783-.5.994.011,1.989.021,2.983.042q2.266.048,4.531.114c.182.005.36.091.543.107a5.659,5.659,0,0,0,.83.022c.279-.017.554-.109.832-.112q2.094-.027,4.189-.018c.369,0,.737.1,1.1.1s.711-.087,1.067-.122a1.222,1.222,0,0,1,.282.045c.153.023.306.053.46.061C178.146,289.977,178.416,289.978,178.69,289.984Z" transform="translate(-158.254 -284.065)" fill="#f3d124"/>
|
||||
<path id="Path_51" data-name="Path 51" d="M261.441,279.306l-1.115.335,1.031-1.3.107.023Z" transform="translate(-235.388 -275.451)" fill="#fff"/>
|
||||
<path id="Path_52" data-name="Path 52" d="M162.739,284.332c-.442.034-.885.085-1.328.087-.089,0-.179-.184-.269-.283.105-.075.22-.225.314-.213.435.053.864.152,1.295.234Z" transform="translate(-158.992 -279.75)" fill="#fff"/>
|
||||
<path id="Path_53" data-name="Path 53" d="M168.964,284.355l1.763.043a3.193,3.193,0,0,1-1.76.153Z" transform="translate(-165.017 -280.083)" fill="#fff"/>
|
||||
</g>
|
||||
<g id="Group_65" data-name="Group 65" transform="translate(83.56 105.109)">
|
||||
<path id="Path_54" data-name="Path 54" d="M195.737,214.673c-.415.394-.831,1.028-1.443.722-3.083,3.5-8.244,2.865-12.159,1.531a11.271,11.271,0,0,1-1.225-12.356c.416-.612,1.75-1.749,1.946-.306-.722,3.914-1.859,7,0,10.716,2.887,1.837,7.741.722,10.606-1.028a8.105,8.105,0,0,0,1.247-6.714c-2.581-3.193-6.692-.088-9.885-.722-.722-1.444,1.225-2.056,2.252-2.471,3.3-.2,6.911-1.028,9.185,1.749C197.705,208.79,196.984,211.874,195.737,214.673Z" transform="translate(-179.63 -201.343)"/>
|
||||
<path id="Path_55" data-name="Path 55" d="M279.441,215.783c-2.734-.722-5.751-2.909-6.123-5.926-.437-3.652-1.465-8.048-.459-11.24.547,0,1.312-.022,1.378.722.481,4.658-.634,10.781,3.477,13.974,2.821.656,3.018-2.909,4.2-4.57,0-3.106.831-6.123.175-9.229.656-.634,1.64-.809,2.012.2C284.186,205.176,283.552,215.761,279.441,215.783Z" transform="translate(-251.11 -197.532)"/>
|
||||
<path id="Path_56" data-name="Path 56" d="M354.66,200.075c-.525-1.465-1.181-.35-2.843-.853l-.722.328c-.525,5.445.109,9.753-.415,14.87-.525.416-1.356.219-2.1.219.109-5.445.831-10.584,1.05-15.833-1.465-2.187-7.654.437-4.811-3.236,2.712,1.771,6.386-1.465,8.376,1.465,1.684.109,2.93,1.99,4.724,1.246.94,1.378,3.871.328,3.236,2.843C358.859,202.7,357.284,199.244,354.66,200.075Zm.219-.94v.416s.678-.219.525-.416C355.272,198.982,354.879,199.135,354.879,199.135Z" transform="translate(-306.299 -195.186)"/>
|
||||
<path id="Path_57" data-name="Path 57" d="M437.463,205.217a5.635,5.635,0,0,1,1.05,3.805,16.041,16.041,0,0,0-2.493,2.362c-1.115-.765-2.3,1.312-3.543.131-.2.131-.35.481-.612.35-2.143-2.428.481-5.6-.35-8.507.131-2.078-1.05-6.342.35-6.889,1.181-.459-.568,5.8,2.493,6.889a3.291,3.291,0,0,0,3.587-.831c.219-1.881.722-4.658-1.312-6.1-1.29-.919-2.909-1.859-4.505-1.028-.2-.35-.9-.569-.612-1.181,1.247-.7,2.69-.066,3.937.2a15.463,15.463,0,0,1,3.543,2.843c.612,1.946,1.159,4.221,0,6.1C438.491,204.168,437.945,204.452,437.463,205.217Zm-2.012,0c-.766-.2-1.378-.7-2.275-.481-.219,1.662-.35,3.587-.481,5.117,1.443.481,2.624-.7,3.805-1.531C437.113,207.076,436.086,206.18,435.452,205.217Z" transform="translate(-373.249 -193.899)"/>
|
||||
<path id="Path_58" data-name="Path 58" d="M496.215,214.673c-.415.394-.831,1.028-1.443.722-3.083,3.5-8.244,2.865-12.159,1.531a11.271,11.271,0,0,1-1.225-12.356c.416-.612,1.75-1.749,1.946-.306-.722,3.914-1.859,7,0,10.716,2.887,1.837,7.741.722,10.606-1.028a8.105,8.105,0,0,0,1.247-6.714c-2.581-3.193-6.692-.088-9.885-.722-.722-1.444,1.225-2.056,2.252-2.471,3.3-.2,6.911-1.028,9.185,1.749C498.183,208.79,497.462,211.874,496.215,214.673Z" transform="translate(-411.073 -201.343)"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 32 KiB |
@@ -0,0 +1 @@
|
||||
<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 376.2 376.19"><title>outbo_rocket</title><path d="M444,72.21a1.48,1.48,0,0,0,0-.19,2.57,2.57,0,0,0-.06-.4,2.38,2.38,0,0,0,0-.26,1.29,1.29,0,0,0-.07-.21,1.61,1.61,0,0,0-.12-.33,1.84,1.84,0,0,0-.09-.25l-.13-.24-.15-.25-.14-.22-.2-.25-.17-.19c-.06-.06-.11-.12-.18-.18l-.25-.22-.19-.15-.21-.14a1.92,1.92,0,0,0-.31-.18,1.18,1.18,0,0,0-.19-.11l-.26-.1a2,2,0,0,0-.34-.12l-.2-.07a1.37,1.37,0,0,0-.28-.05A2.38,2.38,0,0,0,440,68a1.52,1.52,0,0,0-.21,0l-.42,0c-3.56-.17-28.39-.93-62.82,7.72l-1.38.35-1.59.41-2.24.6-1.08.3c-1.17.32-2.35.65-3.54,1h0C326.79,90,269.65,116.71,222.87,178.2a124.2,124.2,0,0,0-69.46,11c-37.12,17.7-65.79,53.42-85.23,106.16a4.48,4.48,0,0,0,7,5c.59-.48,57.29-46.29,99.17-33.91-1.77,4.57-3.49,9.25-5.15,14l-2.05-2a4.47,4.47,0,0,0-6.33,0l-12.3,12.3a4.47,4.47,0,0,0,0,6.33l9.81,9.81-15.9,15.9a4.47,4.47,0,0,0,0,6.33l40.42,40.42a4.48,4.48,0,0,0,6.33,0l15.9-15.9,9.81,9.8a4.48,4.48,0,0,0,6.33,0l12.3-12.3a4.47,4.47,0,0,0,0-6.33l-2-2q7.14-2.49,14-5.16c12.38,41.89-33.43,98.59-33.91,99.18a4.48,4.48,0,0,0,3.47,7.31,4.36,4.36,0,0,0,1.54-.28c52.75-19.43,88.47-48.1,106.17-85.23a124.16,124.16,0,0,0,11-69.45c61.5-46.78,88.23-103.94,99.83-143.85C445.7,103.75,444.07,73.48,444,72.21ZM82.29,284.13c18.28-42.63,43.45-71.8,75-86.84a115.07,115.07,0,0,1,59.48-10.75,362,362,0,0,0-39,71.6C143.56,247.48,102.65,270.37,82.29,284.13ZM186.06,360,152,325.94l12.73-12.73,34.09,34.09Zm32-6.09-9.8-9.8h0l-40.42-40.42h0l-9.81-9.81,6-6,56.12,56.12h0l3.91,3.91Zm96.62.79c-15,31.52-44.21,56.7-86.84,75,13.76-20.36,36.65-61.27,26-95.43a362.14,362.14,0,0,0,71.61-39A115.2,115.2,0,0,1,314.71,354.73Zm11.59-71.15c-23.17,17.31-50,32.12-79.74,44h0c-7.09,2.84-14.51,5.56-22.08,8.12l-48.18-48.18c2.56-7.56,5.28-15,8.12-22.09,11.89-29.74,26.7-56.57,44-79.74,43.78-58.6,97.26-85.21,136-97.29a76.77,76.77,0,0,0,59.14,59.14C411.51,186.32,384.9,239.8,326.3,283.58ZM435.14,77c0,.34,0,.7,0,1.08v.28c0,.33,0,.67,0,1,0,.09,0,.17,0,.26,0,.44,0,.91,0,1.39v.19c0,.41,0,.84,0,1.29,0,.15,0,.3,0,.45,0,.4,0,.81,0,1.23,0,.14,0,.28,0,.43,0,.55-.05,1.13-.08,1.72,0,.1,0,.2,0,.3q0,.75-.09,1.53c0,.19,0,.39,0,.58,0,.48-.06,1-.1,1.47,0,.18,0,.35,0,.53-.05.67-.1,1.36-.16,2.06l0,.32c-.05.6-.11,1.22-.17,1.84l-.06.67c-.05.56-.11,1.13-.17,1.71,0,.2,0,.41-.07.61-.08.78-.16,1.57-.26,2.37,0,.11,0,.22,0,.33-.08.7-.17,1.41-.26,2.13l-.09.74c-.09.64-.17,1.3-.27,1.95l-.09.67c-.12.88-.25,1.76-.39,2.67a2.51,2.51,0,0,1,0,.27q-.18,1.22-.39,2.46c0,.26-.08.52-.13.79-.12.71-.24,1.44-.37,2.17l-.12.72c-.18,1-.35,1.94-.54,2.93,0,.07,0,.14,0,.21-.17.92-.36,1.84-.54,2.77-.06.27-.11.55-.17.82-.16.79-.33,1.59-.5,2.39-.06.25-.11.5-.16.75-.23,1-.47,2.11-.71,3.18,0,.05,0,.09,0,.14-.23,1-.47,2-.72,3l-.21.85c-.21.85-.42,1.7-.65,2.56-.06.27-.13.54-.2.8-.28,1.09-.57,2.18-.88,3.28a67.87,67.87,0,0,1-53.06-53.06l3.36-.9.48-.12,3.06-.77.36-.09a238.46,238.46,0,0,1,54.82-7.13V77A0,0,0,0,1,435.14,77Z" transform="translate(-67.91 -67.9)"/><path d="M318.37,155.72a37.91,37.91,0,1,0,37.91,37.91A38,38,0,0,0,318.37,155.72Zm0,66.87a29,29,0,1,1,29-29A29,29,0,0,1,318.37,222.59Z" transform="translate(-67.91 -67.9)"/><path d="M153,359a4.47,4.47,0,0,0-6.33,0L94,411.72a4.48,4.48,0,0,0,6.33,6.33L153,365.33A4.47,4.47,0,0,0,153,359Z" transform="translate(-67.91 -67.9)"/><path d="M173.23,379.23a4.47,4.47,0,0,0-6.33,0l-34.43,34.43A4.47,4.47,0,1,0,138.8,420l34.43-34.42A4.47,4.47,0,0,0,173.23,379.23Z" transform="translate(-67.91 -67.9)"/><path d="M210.35,382.56a4.47,4.47,0,0,0-6.33,0l-36.08,36.08a4.48,4.48,0,0,0,3.17,7.64,4.43,4.43,0,0,0,3.16-1.31l36.08-36.08A4.47,4.47,0,0,0,210.35,382.56Z" transform="translate(-67.91 -67.9)"/><path d="M129.44,308a4.48,4.48,0,1,0-6.33-6.33L87,337.73a4.48,4.48,0,0,0,6.33,6.33Z" transform="translate(-67.91 -67.9)"/><path d="M98.34,379.53l34.43-34.43a4.48,4.48,0,1,0-6.33-6.33L92,373.2a4.48,4.48,0,0,0,6.33,6.33Z" transform="translate(-67.91 -67.9)"/></svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
|
||||
<g id="photo_icon" data-name="photo icon" transform="translate(0)">
|
||||
<path id="photo_icon-2" data-name="photo icon" d="M11221.5,1637.5l2.5,3.01,3.5-4.51,4.5,6h-14Zm12.5,5.5v-14a2.006,2.006,0,0,0-2-2h-14a2.005,2.005,0,0,0-2,2v14a2.005,2.005,0,0,0,2,2h14A2.006,2.006,0,0,0,11234,1643Z" transform="translate(-11216 -1627)" fill="#fff"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 437 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36">
|
||||
<g id="arrow_small_button" data-name="arrow small button" transform="translate(-11662 -1616)">
|
||||
<path id="placeholder" d="M18,0A18,18,0,1,1,0,18,18,18,0,0,1,18,0Z" transform="translate(11662 1616)" fill="rgba(245,140,57,0.75)"/>
|
||||
<text id="_" data-name="+" transform="translate(11686 1641)" fill="#fff" font-size="20" font-family="Roboto-Medium, Roboto" font-weight="500"><tspan x="-11.143" y="0">+</tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 520 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="12" viewBox="0 0 18 12">
|
||||
<path id="poll_icon" data-name="poll icon" d="M12558,228h12v-2h-12Zm0-7v2h18v-2Zm0,12h6v-2h-6Z" transform="translate(-12558 -221)" fill="#d1d1d1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 241 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="21" height="21" viewBox="0 0 21 21">
|
||||
<path id="widgets_icon" data-name="widgets icon" d="M3525.5,226H3524v-4a2,2,0,0,0-2-2h-4v-1.5a2.5,2.5,0,1,0-5,0V220h-4a2,2,0,0,0-1.99,2v3.8h1.489a2.7,2.7,0,1,1,0,5.4H3507V235a2.006,2.006,0,0,0,2,2h3.8v-1.5a2.7,2.7,0,1,1,5.4,0V237h3.8a2,2,0,0,0,2-2v-4h1.5a2.5,2.5,0,1,0,0-5Z" transform="translate(-3507 -216)" fill="#e4943a"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 419 B |
@@ -0,0 +1,3 @@
|
||||
<svg id="search_icon" data-name="search icon" xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 13 13">
|
||||
<path id="search_icon-2" data-name="search icon" d="M20097,1411a3,3,0,1,0-3-3A3,3,0,0,0,20097,1411Zm2.773,1.16a4.987,4.987,0,1,1,1.387-1.385l3.84,3.848-1.379,1.377Z" transform="translate(-20092 -1403)" fill="#ccc"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 350 B |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 15 15">
|
||||
<g id="user_icon" data-name="user icon" transform="translate(-17610 -198)">
|
||||
<path id="user_icon-2" data-name="user icon" d="M17617.5,208c-2.5,0-7.5,1.117-7.5,3.333V213h15v-1.667C17625,209.117,17620,208,17617.5,208Zm.5-2a4,4,0,1,0-4-4A4,4,0,0,0,17618,206Z" fill="#e6e6e6"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 377 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="2" height="20" viewBox="0 0 2 41">
|
||||
<path id="Op_component_1" data-name="Op component 1" d="M0,0H2V41H0V0Z" fill="#f5f5f5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 180 B |
@@ -0,0 +1,8 @@
|
||||
import React, { HTMLAttributes } from 'react';
|
||||
import cx from 'classnames';
|
||||
|
||||
const BriefHeader: React.FC<HTMLAttributes<HTMLSpanElement>> = ({ className, ...props }) => {
|
||||
return <span className={cx('brief__title', className)} {...props} />;
|
||||
};
|
||||
|
||||
export default BriefHeader;
|
||||
@@ -0,0 +1,21 @@
|
||||
import React, { InputHTMLAttributes } from 'react';
|
||||
import cx from 'classnames';
|
||||
|
||||
const ButtonCheckbox: React.FC<InputHTMLAttributes<HTMLInputElement>> = ({ children, className, ...props }) => (
|
||||
<div className="button-checkbox">
|
||||
<input type="checkbox" id={props.name} {...props} />
|
||||
<label
|
||||
htmlFor={props.id || props.name}
|
||||
className={cx([
|
||||
'button is-light-grey',
|
||||
'button-checkbox',
|
||||
{ 'button-checkbox--is-active': props.checked },
|
||||
className,
|
||||
])}
|
||||
>
|
||||
<span>{children}</span>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default ButtonCheckbox;
|
||||
@@ -0,0 +1,11 @@
|
||||
import React, { InputHTMLAttributes } from 'react';
|
||||
import cx from 'classnames';
|
||||
|
||||
const Checkbox: React.FC<InputHTMLAttributes<HTMLInputElement>> = ({ className, children, ...props }) => (
|
||||
<>
|
||||
<input type="checkbox" className={cx('form-checkbox', className)} id={props.name} {...props} />
|
||||
<label htmlFor={props.id || props.name}>{children}</label>
|
||||
</>
|
||||
);
|
||||
|
||||
export default Checkbox;
|
||||
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import { ClimbingBoxLoader } from 'react-spinners';
|
||||
|
||||
export const ClimbingLoader = (): JSX.Element => {
|
||||
return (
|
||||
<div className="hero is-fullheight">
|
||||
<div className="container columns has-items-centered">
|
||||
<div className="column">
|
||||
<ClimbingBoxLoader color="#f58c39" size={1.5} sizeUnit="rem" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import DatePicker from 'react-datepicker';
|
||||
import cx from 'classnames';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import { CalendarIcon } from 'assets';
|
||||
import { parse } from 'date-fns';
|
||||
export type DatePickerChangeHandler = (
|
||||
date: Date | null,
|
||||
event: React.SyntheticEvent<Element, Event> | undefined
|
||||
) => void;
|
||||
|
||||
interface DatePickerProps {
|
||||
onChange: DatePickerChangeHandler;
|
||||
selected: string;
|
||||
disabled: boolean;
|
||||
dataTestid?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
const Datepicker: React.FC<DatePickerProps> = ({ onChange, selected, disabled, dataTestid, title }) => {
|
||||
return (
|
||||
<div className="datepicker" data-testid={dataTestid}>
|
||||
<DatePicker
|
||||
onChange={onChange}
|
||||
selected={parse(selected, 'yyyy-MM-dd', new Date())}
|
||||
dateFormat="yyyy-MM-dd"
|
||||
className={cx(['input', 'is-input-border'])}
|
||||
disabled={disabled}
|
||||
title={title}
|
||||
/>
|
||||
<CalendarIcon />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Datepicker;
|
||||
@@ -0,0 +1,160 @@
|
||||
import React, { useState, useCallback, useRef, useContext, useEffect } from 'react';
|
||||
import { Caret } from '../assets/index';
|
||||
import cx from 'classnames';
|
||||
import useOnClickOutside from 'use-onclickoutside';
|
||||
|
||||
export type CurrentValue = {
|
||||
value: number;
|
||||
label: string;
|
||||
};
|
||||
|
||||
type ContextValues = {
|
||||
open: boolean;
|
||||
disabled: boolean;
|
||||
fullwidth: boolean;
|
||||
current: CurrentValue | undefined;
|
||||
onChange: (value: CurrentValue) => void;
|
||||
toggleOpen: () => void;
|
||||
};
|
||||
|
||||
type ItemProps = {
|
||||
label: string;
|
||||
value: number;
|
||||
};
|
||||
|
||||
const DropdownContext = React.createContext<ContextValues>({
|
||||
open: false,
|
||||
fullwidth: false,
|
||||
disabled: false,
|
||||
current: undefined,
|
||||
onChange: () => {},
|
||||
toggleOpen: () => {},
|
||||
});
|
||||
|
||||
const useDropdownContext = (): ContextValues => {
|
||||
const context = useContext(DropdownContext);
|
||||
if (!context) {
|
||||
throw new Error(`NOPE`);
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
const Item: React.FC<ItemProps> = ({ label, value }) => {
|
||||
const { onChange } = useDropdownContext();
|
||||
return (
|
||||
<a className="dropdown-item" onClick={(): void => onChange({ label, value })}>
|
||||
{label}
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
const Label: React.FC<{ valueDataTestid?: string }> = ({ children, valueDataTestid }) => {
|
||||
const { current, fullwidth, disabled } = useDropdownContext();
|
||||
return (
|
||||
<div className="dropdown-trigger">
|
||||
<button
|
||||
className={cx(['button', 'is-outlined', { 'is-fullwidth': fullwidth }])}
|
||||
aria-haspopup="true"
|
||||
aria-controls="dropdown-menu"
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
>
|
||||
<span className={cx({ 'is-dimmed': !current })} data-testid={valueDataTestid}>
|
||||
{current ? current.label : children}
|
||||
</span>
|
||||
<span className={cx('icon is-small', { 'is-dimmed': !current })}>
|
||||
<Caret className={cx('caret--opened')} />
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Menu: React.FC = ({ children }) => {
|
||||
return (
|
||||
<div className="dropdown-menu" id="dropdown-menu" role="menu">
|
||||
<div className="dropdown-content">{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
type CompoundComponents = {
|
||||
Item: React.FC<ItemProps>;
|
||||
Menu: React.FC;
|
||||
Label: React.FC<{ valueDataTestid?: string }>;
|
||||
};
|
||||
|
||||
type DropdownProps = {
|
||||
fullwidth: boolean;
|
||||
disabled: boolean;
|
||||
onChange: (value: number) => void;
|
||||
initial?: CurrentValue;
|
||||
dataTestid?: string;
|
||||
title?: string;
|
||||
};
|
||||
|
||||
const Dropdown: React.FC<DropdownProps> & CompoundComponents = ({
|
||||
onChange,
|
||||
title,
|
||||
disabled,
|
||||
fullwidth,
|
||||
children,
|
||||
initial,
|
||||
dataTestid,
|
||||
}) => {
|
||||
const [current, setCurrent] = useState<CurrentValue | undefined>(initial);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrent(initial);
|
||||
}, [initial]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
({ value, label }) => {
|
||||
if (!disabled) {
|
||||
setCurrent({ value, label });
|
||||
onChange && onChange(value);
|
||||
}
|
||||
},
|
||||
[disabled, onChange]
|
||||
);
|
||||
|
||||
const toggleOpen = useCallback(() => {
|
||||
if (!disabled) {
|
||||
setOpen((open) => !open);
|
||||
}
|
||||
}, [disabled]);
|
||||
|
||||
const node = useRef<HTMLHeadingElement>(null);
|
||||
|
||||
useOnClickOutside(node, () => setOpen(false));
|
||||
|
||||
return (
|
||||
<DropdownContext.Provider
|
||||
value={{
|
||||
open,
|
||||
fullwidth,
|
||||
disabled,
|
||||
current,
|
||||
onChange: handleChange,
|
||||
toggleOpen,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={cx(['dropdown', open && 'is-active', { 'is-fullwidth': fullwidth }])}
|
||||
onClick={toggleOpen}
|
||||
ref={node}
|
||||
data-testid={dataTestid}
|
||||
title={title}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</DropdownContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
Dropdown.Item = Item;
|
||||
Dropdown.Menu = Menu;
|
||||
Dropdown.Label = Label;
|
||||
|
||||
export default Dropdown;
|
||||
@@ -0,0 +1,8 @@
|
||||
import React, { HTMLAttributes } from 'react';
|
||||
import cx from 'classnames';
|
||||
|
||||
const FieldInputError: React.FC<HTMLAttributes<HTMLDivElement>> = ({ className, ...props }) => {
|
||||
return <div className={cx('error', className)} {...props} data-testid="inputError" />;
|
||||
};
|
||||
|
||||
export default FieldInputError;
|
||||
@@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
function HorizontalLine(): JSX.Element {
|
||||
return <div className="horizontal-line" />;
|
||||
}
|
||||
|
||||
export default HorizontalLine;
|
||||
@@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import cx from 'classnames';
|
||||
type ModalFooterProps = {
|
||||
action: any;
|
||||
text: string;
|
||||
cancelAction: () => void;
|
||||
isSubmitting: boolean;
|
||||
};
|
||||
|
||||
export const ModalFooter: React.FC<ModalFooterProps> = ({ action, text, cancelAction, isSubmitting }) => {
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
className={cx({ 'is-loading': isSubmitting }, 'button is-orange-lighten mr-1')}
|
||||
type="button"
|
||||
onClick={async (): Promise<void> => {
|
||||
await action();
|
||||
}}
|
||||
>
|
||||
<span>{text}</span>
|
||||
</button>
|
||||
<button type="button" className="button is-light-grey" onClick={cancelAction}>
|
||||
<span>Anuluj</span>
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import React, { InputHTMLAttributes } from 'react';
|
||||
import cx from 'classnames';
|
||||
import { FieldTextInputInterface } from 'interfaces/InputInterfaces';
|
||||
import debounce from 'lodash.debounce';
|
||||
|
||||
interface SearchInput {
|
||||
searchFunc: (searchValue: string) => void;
|
||||
}
|
||||
|
||||
const SearchInput: React.FC<SearchInput & FieldTextInputInterface & InputHTMLAttributes<HTMLInputElement>> = ({
|
||||
field,
|
||||
form,
|
||||
searchFunc,
|
||||
className,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<input
|
||||
className={cx('input', className)}
|
||||
{...field}
|
||||
{...props}
|
||||
title={props.value ? props.placeholder : undefined}
|
||||
onChange={(e) => {
|
||||
form.handleChange(e);
|
||||
searchFunc(e.target.value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchInput;
|
||||
@@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import { FieldProps } from 'formik';
|
||||
|
||||
type SelectProps = {
|
||||
disabled: boolean;
|
||||
dataTestid?: string;
|
||||
title?: string;
|
||||
};
|
||||
|
||||
const Select: React.FC<FieldProps & SelectProps> = ({ field, form, children, dataTestid, title, ...props }) => {
|
||||
return (
|
||||
<div className="row columns">
|
||||
<div className="column has-items-right">
|
||||
<span>Ilość produktów: </span>
|
||||
</div>
|
||||
<div className="column has-items-right is-narrow ml-2">
|
||||
<div className="select is-input-border brief__input--select">
|
||||
<select
|
||||
value={field.value}
|
||||
disabled={props.disabled}
|
||||
onChange={(event: React.FormEvent<HTMLSelectElement>): void =>
|
||||
form.setFieldValue(field.name, parseInt((event.target as HTMLInputElement).value, 10))
|
||||
}
|
||||
data-testid={dataTestid}
|
||||
title={title}
|
||||
>
|
||||
{children}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Select;
|
||||
@@ -0,0 +1,35 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import cx from 'classnames';
|
||||
import { useHistory } from 'react-router';
|
||||
import useLocationParams from '../hooks/useLocationParams';
|
||||
|
||||
type StageBarLinkProps = {
|
||||
target: string;
|
||||
currentStage: number;
|
||||
group: number;
|
||||
index: number;
|
||||
};
|
||||
|
||||
export const StageBarLink: React.FC<StageBarLinkProps> = ({ target, index, currentStage, group }) => {
|
||||
const disabled = useMemo(() => group < index, [group, index]);
|
||||
const active = useMemo(() => currentStage === index, [currentStage, index]);
|
||||
const history = useHistory();
|
||||
const [params, location] = useLocationParams();
|
||||
return (
|
||||
<div
|
||||
onClick={!disabled ? (): void => history.push({ pathname: target, search: params.toString() }) : (): void => {}}
|
||||
className={cx('stage-bar__box', {
|
||||
'stage-bar__box--active': active,
|
||||
})}
|
||||
>
|
||||
<span
|
||||
className={cx('stage-bar__box__item', {
|
||||
'stage-bar__box__item--active': active,
|
||||
'stage-bar__box__item--disabled': disabled,
|
||||
})}
|
||||
>
|
||||
{index}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
import React, { TextareaHTMLAttributes } from 'react';
|
||||
import cx from 'classnames';
|
||||
|
||||
const TextArea: React.FC<TextareaHTMLAttributes<HTMLTextAreaElement>> = ({ className, ...props }) => {
|
||||
return (
|
||||
<textarea
|
||||
className={cx('textarea is-input-border is-mobile', className)}
|
||||
{...props}
|
||||
title={props.value ? props.placeholder : undefined}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default TextArea;
|
||||
@@ -0,0 +1,15 @@
|
||||
import React, { InputHTMLAttributes } from 'react';
|
||||
import cx from 'classnames';
|
||||
import { FieldTextInputInterface } from '../interfaces/InputInterfaces';
|
||||
|
||||
const TextInput: React.FC<InputHTMLAttributes<HTMLInputElement>> = ({ className, ...props }) => {
|
||||
return (
|
||||
<input
|
||||
className={cx('input', 'is-input-border', className)}
|
||||
{...props}
|
||||
title={props.value ? props.placeholder : undefined}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default TextInput;
|
||||
@@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
const PDFTemplate: React.FC = () => {
|
||||
return <div></div>;
|
||||
};
|
||||
|
||||
export default PDFTemplate;
|
||||
@@ -0,0 +1,44 @@
|
||||
// Set your brand colors
|
||||
|
||||
$orange: #f58c39;
|
||||
$pink: #fa7c91;
|
||||
$beige-light: #d0d1cd;
|
||||
$beige-lighter: #eff0eb;
|
||||
$bg-light:#fff;
|
||||
|
||||
// Update Bulma's global variables
|
||||
$family-sans-serif: "Nunito", sans-serif;
|
||||
$grey-dark: #b7b7b7 !default;
|
||||
$grey-light: $beige-light;
|
||||
$primary: $orange;
|
||||
|
||||
$link: $beige-light;
|
||||
|
||||
$link-hover-border: $beige-light;
|
||||
$link-active-border: $beige-light;
|
||||
$link-focus-border: $beige-light;
|
||||
|
||||
// Update some of Bulma's component variables
|
||||
$body-background-color: $beige-lighter;
|
||||
$control-border-width: 2px;
|
||||
$input-border-color: transparent;
|
||||
$input-shadow: none;
|
||||
|
||||
$avatar-size: 30px;
|
||||
$options-dots-size: 20px;
|
||||
|
||||
$dropdown-height: 3rem;
|
||||
|
||||
// Import only what you need from Bulma
|
||||
|
||||
// @import "../node_modules/bulma/sass/utilities/_all.sass";
|
||||
@import "./scss/CustomUtilities.scss";
|
||||
@import "../node_modules/bulma/sass/base/_all.sass";
|
||||
@import "../node_modules/bulma/sass/grid/_all.sass";
|
||||
@import "../node_modules/bulma/sass/components/_all.sass";
|
||||
@import "../node_modules/bulma/sass/form/_all.sass";
|
||||
@import "../node_modules/bulma/sass/elements/_all.sass";
|
||||
@import "../node_modules/bulma/sass/layout/_all.sass";
|
||||
@import "../node_modules/bulma-steps-component/bulma-steps.sass";
|
||||
@import "./scss/Datepicker.scss";
|
||||
@import "../node_modules/bulma-tooltip/src/sass/index.sass";
|
||||
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
|
||||
interface DropdownItemProps {
|
||||
text: string;
|
||||
icon: JSX.Element;
|
||||
logout?: any;
|
||||
}
|
||||
|
||||
const DropdownItem: React.FC<DropdownItemProps> = ({ text, icon, logout = () => {} }) => {
|
||||
return (
|
||||
<a className="navbar-item" onClick={() => logout()}>
|
||||
{icon}
|
||||
<span className="ml-1">{text}</span>
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
export default DropdownItem;
|
||||
@@ -0,0 +1,54 @@
|
||||
import React from 'react';
|
||||
import { OptionDots, OutboLogo, DocIcon } from 'assets';
|
||||
import DropdownItem from './DropdownItem';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { AppState } from 'modules/rootReducer';
|
||||
import { UserActionTypes, logoutUser } from 'modules/users/actions';
|
||||
import { Dispatch } from 'redux';
|
||||
|
||||
const Navbar: React.FC = () => {
|
||||
const dispatch = useDispatch<Dispatch<UserActionTypes>>();
|
||||
const user = useSelector((state: AppState) => state.users.currentUser);
|
||||
return (
|
||||
<nav className="navbar has-shadow" role="navigation" aria-label="main navigation">
|
||||
<div className="navbar-brand">
|
||||
<a className="navbar-item navbar-item__logo">
|
||||
<OutboLogo style={{ maxHeight: '2.75rem' }} className="navbar-item__logo" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="navbar-menu">
|
||||
<div className="navbar-end">
|
||||
{user && (
|
||||
<div className="navbar-item">
|
||||
<div className="avatar">{user.photo && <img src={user.photo} alt={'avatar'} />}</div>
|
||||
<div className="navbar-item rows" style={{ paddingTop: 0, paddingBottom: 0 }}>
|
||||
<div>{user.fullName}</div>
|
||||
<div className="font-small">{user.organization.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="navbar-item has-dropdown is-hoverable">
|
||||
<div className="navbar-item">
|
||||
<div className="navbar-item__dots">
|
||||
<OptionDots />
|
||||
</div>
|
||||
<div className="navbar-dropdown is-right">
|
||||
<DropdownItem
|
||||
text="Logout"
|
||||
icon={<DocIcon />}
|
||||
logout={(): void => {
|
||||
logoutUser(dispatch);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
export default Navbar;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './Navbar';
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'modules/rootReducer';
|
||||
|
||||
interface StoreProviderProps {
|
||||
selectors: any;
|
||||
component: React.ComponentType;
|
||||
}
|
||||
|
||||
const StoreProvider: React.FC<StoreProviderProps> = ({ component: Component, selectors, ...props }) => {
|
||||
// const result = useSelector((state:AppState)=> selectors.map((selector:any)=>selector(state)));
|
||||
const result = useSelector((state: AppState) =>
|
||||
Object.keys(selectors).reduce((previous, current) => ({ ...previous, [current]: selectors[current](state) }), {})
|
||||
);
|
||||
|
||||
return <Component {...props} {...result} />;
|
||||
};
|
||||
|
||||
export default StoreProvider;
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default as Login } from '../pages/Login';
|
||||
export { default as Main } from '../pages/Main';
|
||||
@@ -0,0 +1 @@
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import { Route, Redirect, useLocation, RouteProps } from 'react-router-dom';
|
||||
import { TOKEN } from 'API/axiosConfig';
|
||||
import { parse } from 'query-string';
|
||||
|
||||
export const PrivateRoute: React.FC<RouteProps> = ({ children, ...rest }) => {
|
||||
const location = useLocation();
|
||||
return (
|
||||
<Route
|
||||
{...rest}
|
||||
render={(props) =>
|
||||
localStorage.getItem(TOKEN) ? children : <Redirect to={`/login?next=${location.pathname}`} />
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export const NotAuthorizedRoute: React.FC<RouteProps> = ({ children, ...rest }) => {
|
||||
const location = useLocation();
|
||||
const query = parse(location.search);
|
||||
const redirectUrl = query.next ? query : '/';
|
||||
return (
|
||||
<Route {...rest} render={(props) => (!localStorage.getItem(TOKEN) ? children : <Redirect to={redirectUrl} />)} />
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import { PhotoFormModel, BlankingFormModel, BlankingRequest, PhotoRequest } from 'interfaces/API/products';
|
||||
|
||||
const isProperPhoto = (maybeProperPhoto: PhotoFormModel): maybeProperPhoto is PhotoFormModel & { file: File } => {
|
||||
return typeof maybeProperPhoto['file'] !== 'undefined' && maybeProperPhoto['file'] instanceof File;
|
||||
};
|
||||
|
||||
const castToPhotoRequest = ({ file }: PhotoFormModel & { file: File }): PhotoRequest => {
|
||||
return { file };
|
||||
};
|
||||
|
||||
const isProperBlanking = (
|
||||
maybeProperBlanking: BlankingFormModel
|
||||
): maybeProperBlanking is BlankingFormModel & { file: File } => {
|
||||
return typeof maybeProperBlanking['file'] !== 'undefined' && maybeProperBlanking['file'] instanceof File;
|
||||
};
|
||||
|
||||
const castToBlankingRequest = ({ file }: BlankingFormModel & { file: File }): BlankingRequest => {
|
||||
return { file };
|
||||
};
|
||||
|
||||
export const filterPhotos = (array: PhotoFormModel[]): PhotoRequest[] =>
|
||||
array.filter(isProperPhoto).map(castToPhotoRequest);
|
||||
|
||||
export const filterBlankings = (array: BlankingFormModel[]): BlankingRequest[] =>
|
||||
array.filter(isProperBlanking).map(castToBlankingRequest);
|
||||
@@ -0,0 +1,44 @@
|
||||
export const handleFileSelect = (e: any, push: any): void => {
|
||||
const { files } = e.target;
|
||||
Array.from<File>(files).forEach((file: File): void => {
|
||||
push({
|
||||
file,
|
||||
filename: file.name,
|
||||
size: file.size,
|
||||
url: URL.createObjectURL(file),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const handleImageSelect = (e: any, push: any): void => {
|
||||
const { files } = e.target;
|
||||
|
||||
Array.from<File>(files).forEach((file: File): void => {
|
||||
push({
|
||||
file,
|
||||
url: URL.createObjectURL(file),
|
||||
filename: file.name,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const downloadItem = (file: any): void => {
|
||||
const downloadUrl = file.url;
|
||||
const link = document.createElement('a');
|
||||
link.href = downloadUrl;
|
||||
link.setAttribute('download', file.file ? file.file.name : 'file.pdf');
|
||||
link.setAttribute('target', '_blank');
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
};
|
||||
|
||||
export const downloadByURL = (url: string, filename: string): void => {
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.setAttribute('download', filename ? filename : 'file.pdf');
|
||||
link.setAttribute('target', '_blank');
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
import { AnyProductStore, SimpleProductResponse, ProductStore } from 'interfaces/API/products';
|
||||
|
||||
// const isSimpleProduct = (x: AnyProductStore): x is SimpleProductResponse => {
|
||||
// return typeof (x as ProductStore).briefConfig === 'undefined';
|
||||
// };
|
||||
|
||||
// const isFullProduct = (x: AnyProductStore): x is ProductStore => {
|
||||
// return typeof (x as ProductStore).briefConfig !== 'undefined';
|
||||
// };
|
||||
// export { isSimpleProduct, isFullProduct };
|
||||
@@ -0,0 +1,11 @@
|
||||
import { SimpleProject, Project, AnyProject } from 'interfaces/API/projects';
|
||||
|
||||
const isSimpleProject = (x: AnyProject): x is SimpleProject => {
|
||||
return typeof (x as Project).productsAmount === 'undefined';
|
||||
};
|
||||
|
||||
const isFullProject = (x: AnyProject): x is Project => {
|
||||
return typeof (x as Project).productsAmount !== 'undefined';
|
||||
};
|
||||
|
||||
export { isSimpleProject, isFullProject };
|
||||
@@ -0,0 +1,60 @@
|
||||
interface FlattenOptions {
|
||||
delimiter?: string;
|
||||
safe?: boolean;
|
||||
maxDepth?: number;
|
||||
transformKey?: (x: string) => string;
|
||||
}
|
||||
|
||||
interface Flatten {
|
||||
<TTarget, TResult>(target: TTarget, options?: FlattenOptions): TResult;
|
||||
}
|
||||
|
||||
function isBuffer(val: any): boolean {
|
||||
// eslint-disable-next-line no-sparse-arrays
|
||||
return ![, null].includes(val) && val.constructor === Buffer;
|
||||
}
|
||||
|
||||
function keyIdentity(key: string): string {
|
||||
return key;
|
||||
}
|
||||
|
||||
const flatten: Flatten = (target, opts = {}) => {
|
||||
const objDelimiter = opts.delimiter || '.';
|
||||
const arrDelimiter = opts.delimiter || '-';
|
||||
const transformKey = opts.transformKey || keyIdentity;
|
||||
const output: any = {};
|
||||
|
||||
function step(object: any, prev?: any, currentDepth = 1): any {
|
||||
Object.keys(object).forEach(function(key) {
|
||||
const value = object[key];
|
||||
const isarray = opts.safe && Array.isArray(value);
|
||||
const type = Object.prototype.toString.call(value);
|
||||
const isbuffer = isBuffer(value);
|
||||
const isobject = type === '[object Object]' || type === '[object Array]';
|
||||
|
||||
const newKey = prev
|
||||
? prev + (Array.isArray(object) ? arrDelimiter : objDelimiter) + transformKey(key)
|
||||
: transformKey(key);
|
||||
|
||||
if (!isarray && !isbuffer && isobject && (!opts.maxDepth || currentDepth < opts.maxDepth)) {
|
||||
return step(value, newKey, currentDepth + 1);
|
||||
}
|
||||
|
||||
output[newKey] = value;
|
||||
});
|
||||
}
|
||||
step(target);
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
const objectToFormDataConverter = (object: any) => {
|
||||
const flattenedObject: any = flatten(object);
|
||||
|
||||
const formData = new FormData();
|
||||
Object.keys(flattenedObject).forEach((item) => {
|
||||
formData.append(item, flattenedObject[item]);
|
||||
});
|
||||
return formData;
|
||||
};
|
||||
export default objectToFormDataConverter;
|
||||
@@ -0,0 +1,11 @@
|
||||
import { PhotoBriefPhotoFormModel, PhotoBriefPhotoRequest } from 'interfaces/API/photoBrief';
|
||||
|
||||
export const isProperPhoto = (
|
||||
maybeProperPhoto: PhotoBriefPhotoFormModel
|
||||
): maybeProperPhoto is PhotoBriefPhotoFormModel & { file: File } => {
|
||||
return typeof maybeProperPhoto['file'] !== 'undefined' && maybeProperPhoto['file'] instanceof File;
|
||||
};
|
||||
|
||||
export const castToPhotoRequest = ({ file }: PhotoBriefPhotoFormModel & { file: File }): PhotoBriefPhotoRequest => {
|
||||
return { file };
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
enum Routes {
|
||||
NEW_PROJECT = '/projects/new',
|
||||
}
|
||||
export default Routes;
|
||||
@@ -0,0 +1,3 @@
|
||||
export type WithId<T> = T & { id: number };
|
||||
|
||||
export type WithoutId<T> = Omit<Text, 'id'>;
|
||||