Compare commits
51 Commits
9e9cd81a54
...
master
Author | SHA1 | Date | |
---|---|---|---|
a77a31d01c | |||
970a948cec | |||
4dbcc540d6 | |||
836ec0aef2 | |||
6c4da8c181 | |||
aa1c1c3737 | |||
00a9a82bcb | |||
01f82b1365 | |||
afd2361689 | |||
cfeac6b920 | |||
d26a91b65c | |||
0b3d809e3b | |||
7d7b6d88b9 | |||
38735c7982 | |||
671d0ed650 | |||
d645964266 | |||
3610415657 | |||
69f65846c2 | |||
74e0ef19db | |||
decd4faf22 | |||
1abe2ddfaf | |||
b8b4c31b45 | |||
e5d0e50102 | |||
098e865e91 | |||
3fcd4b4154 | |||
8211ed629c | |||
5a968ff026 | |||
60ecbd331a | |||
916796ef8e | |||
fccedac8f2 | |||
e442179718 | |||
3f33dc4d65 | |||
8647164413 | |||
c29df33a03 | |||
203d2f8cd0 | |||
2c4c89ef80 | |||
e31bb7ff0f | |||
564996604c | |||
02a0f1435c | |||
2751eda1e8 | |||
70562c1f74 | |||
ea64e06b3e | |||
b75309f4af | |||
d32ce4a685 | |||
|
10789d7e39 | ||
55316d5509 | |||
9458061f29 | |||
ce92499528 | |||
ea33a44326 | |||
6270a6f194 | |||
1464bc084a |
2
.env
2
.env
@ -3,3 +3,5 @@ REACT_APP_KC_REALM=gonito-dev
|
||||
REACT_APP_KC_CLIENT_ID=gonito-dev-localhost
|
||||
|
||||
REACT_APP_API=https://gonito-back-dev.csi.wmi.amu.edu.pl/api
|
||||
# https://gonito.net/api
|
||||
# https://gonito-back-dev.csi.wmi.amu.edu.pl/api
|
@ -14,13 +14,6 @@
|
||||
"eqeqeq": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"quotes": [
|
||||
2,
|
||||
"single",
|
||||
{
|
||||
"avoidEscape": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
225
src/App.js
225
src/App.js
@ -1,225 +0,0 @@
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import theme from './utils/theme';
|
||||
import LandingPage from './pages/LandingPage';
|
||||
import Challenges from './pages/Challanges/Challenges';
|
||||
import { BrowserRouter, Route, Routes } from 'react-router-dom';
|
||||
import NavBar from './components/navigation/NavBar';
|
||||
import {
|
||||
CHALLENGE_PAGE,
|
||||
CHALLENGES_PAGE,
|
||||
IS_MOBILE,
|
||||
POLICY_PRIVACY_PAGE,
|
||||
LOGIN_REQUIRED_PAGES,
|
||||
ROOT_URL,
|
||||
CHALLENGE_SECTIONS,
|
||||
} from './utils/globals';
|
||||
import KeyCloakService from './services/KeyCloakService';
|
||||
import React from 'react';
|
||||
import LoggedBar from './components/navigation/LoggedBar';
|
||||
import addUser from './api/addUser';
|
||||
import Loading from './components/generic/Loading';
|
||||
import { FlexColumn } from './utils/containers';
|
||||
import PopupMessage from './components/generic/PopupMessage';
|
||||
import PolicyPrivacy from './pages/PolicyPrivacy';
|
||||
import Challenge from './components/specific_challenge/Challenge';
|
||||
|
||||
const App = () => {
|
||||
const [loggedBarVisible, setLoggedBarVisible] = React.useState('100vw');
|
||||
const [loggedBarHover, setLoggedBarHover] = React.useState(false);
|
||||
const [popUpHeader, setPopUpHeader] = React.useState('');
|
||||
const [popUpMessage, setPopUpMessage] = React.useState('');
|
||||
const [confirmPopUpHandler, setConfirmPopUpHandler] = React.useState(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (sessionStorage.getItem('logout') === 'yes') {
|
||||
const pageName = window.location.pathname.split('/').at(-1);
|
||||
if (LOGIN_REQUIRED_PAGES.includes(pageName)) {
|
||||
window.location.replace(`${ROOT_URL}/challenges`);
|
||||
}
|
||||
}
|
||||
|
||||
if (sessionStorage.getItem('logged') !== 'yes') {
|
||||
if (KeyCloakService.isLoggedIn()) {
|
||||
sessionStorage.setItem('logged', 'yes');
|
||||
addUser();
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
sessionStorage.getItem('logged') === 'yes' &&
|
||||
(window.location.pathname === `${POLICY_PRIVACY_PAGE}/login` ||
|
||||
window.location.pathname === `${POLICY_PRIVACY_PAGE}/register`)
|
||||
) {
|
||||
window.location.replace(`${ROOT_URL}/challenges`);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (sessionStorage.getItem('logged') === 'yes') {
|
||||
if (!KeyCloakService.isLoggedIn()) {
|
||||
KeyCloakService.doLogin();
|
||||
}
|
||||
}
|
||||
}, 1500);
|
||||
});
|
||||
|
||||
const popUpMessageHandler = (header, message, confirmHandler) => {
|
||||
setPopUpHeader(header);
|
||||
setPopUpMessage(message);
|
||||
if (confirmHandler !== null && confirmHandler !== undefined) {
|
||||
setConfirmPopUpHandler(() => confirmHandler());
|
||||
} else {
|
||||
setConfirmPopUpHandler(null);
|
||||
}
|
||||
};
|
||||
|
||||
const popUpMessageRender = () => {
|
||||
if (popUpHeader !== '' || popUpMessage !== '') {
|
||||
return (
|
||||
<PopupMessage
|
||||
header={popUpHeader}
|
||||
message={popUpMessage}
|
||||
confirmHandler={confirmPopUpHandler}
|
||||
popUpMessageHandler={popUpMessageHandler}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const loggedBarVisibleHandler = () => {
|
||||
if (loggedBarVisible === '0' && !loggedBarHover)
|
||||
setLoggedBarVisible('100vw');
|
||||
else setLoggedBarVisible('0');
|
||||
};
|
||||
|
||||
const loggedBarHoverTrue = () => {
|
||||
setLoggedBarHover(true);
|
||||
};
|
||||
|
||||
const loggedBarHoverFalse = () => {
|
||||
setLoggedBarHover(false);
|
||||
};
|
||||
|
||||
const renderApp = () => {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<ThemeProvider theme={theme}>
|
||||
{popUpMessageRender()}
|
||||
<NavBar
|
||||
loggedBarVisibleHandler={loggedBarVisibleHandler}
|
||||
popUpMessageHandler={popUpMessageHandler}
|
||||
/>
|
||||
{!IS_MOBILE() ? (
|
||||
<LoggedBar
|
||||
visible={loggedBarVisible}
|
||||
loggedBarVisibleHandler={loggedBarVisibleHandler}
|
||||
loggedBarHoverTrue={loggedBarHoverTrue}
|
||||
loggedBarHoverFalse={loggedBarHoverFalse}
|
||||
username={KeyCloakService.getUsername()}
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
<Routes>
|
||||
<Route
|
||||
path={`${CHALLENGE_PAGE}/:challengeId`}
|
||||
element={<Challenge section={CHALLENGE_SECTIONS.LEADERBOARD} />}
|
||||
/>
|
||||
<Route
|
||||
path={`${CHALLENGE_PAGE}/:challengeId/leaderboard`}
|
||||
element={<Challenge section={CHALLENGE_SECTIONS.LEADERBOARD} />}
|
||||
/>
|
||||
<Route
|
||||
path={`${CHALLENGE_PAGE}/:challengeId/allentries`}
|
||||
element={<Challenge section={CHALLENGE_SECTIONS.ALL_ENTRIES} />}
|
||||
/>
|
||||
<Route
|
||||
path={`${CHALLENGE_PAGE}/:challengeId/readme`}
|
||||
element={<Challenge section={CHALLENGE_SECTIONS.README} />}
|
||||
/>
|
||||
<Route
|
||||
path={`${CHALLENGE_PAGE}/:challengeId/howto`}
|
||||
element={
|
||||
<Challenge
|
||||
popUpMessageHandler={popUpMessageHandler}
|
||||
section={CHALLENGE_SECTIONS.HOW_TO}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={`${CHALLENGE_PAGE}/:challengeId/myentries`}
|
||||
element={<Challenge section={CHALLENGE_SECTIONS.MY_ENTRIES} />}
|
||||
/>
|
||||
<Route
|
||||
path={`${CHALLENGE_PAGE}/:challengeId/submit`}
|
||||
element={<Challenge section={CHALLENGE_SECTIONS.SUBMIT} />}
|
||||
/>
|
||||
<Route path={CHALLENGES_PAGE} element={<Challenges />} />
|
||||
<Route
|
||||
path={POLICY_PRIVACY_PAGE}
|
||||
element={
|
||||
<PolicyPrivacy popUpMessageHandler={popUpMessageHandler} />
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={`${POLICY_PRIVACY_PAGE}/login`}
|
||||
element={
|
||||
<PolicyPrivacy
|
||||
popUpMessageHandler={popUpMessageHandler}
|
||||
beforeLogin
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={`${POLICY_PRIVACY_PAGE}/register`}
|
||||
element={
|
||||
<PolicyPrivacy
|
||||
popUpMessageHandler={popUpMessageHandler}
|
||||
beforeRegister
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{KeyCloakService.isLoggedIn() ? (
|
||||
<>
|
||||
<Route exact path="/" element={<Challenges />} />
|
||||
<Route element={<Challenges />} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Route
|
||||
exact
|
||||
path="/"
|
||||
element={
|
||||
<LandingPage popUpMessageHandler={popUpMessageHandler} />
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
element={
|
||||
<LandingPage popUpMessageHandler={popUpMessageHandler} />
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Routes>
|
||||
</ThemeProvider>
|
||||
</BrowserRouter>
|
||||
);
|
||||
};
|
||||
|
||||
if (sessionStorage.getItem('logged') === 'yes') {
|
||||
if (KeyCloakService.isLoggedIn()) {
|
||||
return renderApp();
|
||||
} else {
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<FlexColumn width="100vw" height="100vh">
|
||||
<Loading />
|
||||
</FlexColumn>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return renderApp();
|
||||
}
|
||||
};
|
||||
|
||||
export default App;
|
33
src/App/App.js
Normal file
33
src/App/App.js
Normal file
@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import theme from '../utils/theme';
|
||||
import PopUpMessageManager from './components/PopUpMessageManager';
|
||||
import RoutingManager from './components/RoutingManager';
|
||||
import NavigationManager from './components/NavigationManager';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import startManage from './functions/startManage';
|
||||
import startApp from './functions/startApp';
|
||||
|
||||
const App = () => {
|
||||
React.useMemo(() => {
|
||||
startManage();
|
||||
}, []);
|
||||
|
||||
const renderApp = React.useCallback(() => {
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<BrowserRouter>
|
||||
<PopUpMessageManager>
|
||||
<NavigationManager>
|
||||
<RoutingManager />
|
||||
</NavigationManager>
|
||||
</PopUpMessageManager>
|
||||
</BrowserRouter>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}, []);
|
||||
|
||||
return startApp(renderApp);
|
||||
};
|
||||
|
||||
export default App;
|
47
src/App/components/NavigationManager.js
Normal file
47
src/App/components/NavigationManager.js
Normal file
@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
import NavBar from '../../components/navigation/NavBar/NavBar';
|
||||
import LoggedBar from '../../components/navigation/LoggedBar';
|
||||
import KeyCloakService from '../../services/KeyCloakService';
|
||||
import { CHILDREN_WITH_PROPS, IS_MOBILE } from '../../utils/globals';
|
||||
|
||||
const NavigationManager = ({children, popUpMessageHandler}) => {
|
||||
const [loggedBarVisible, setLoggedBarVisible] = React.useState('100vw');
|
||||
const [loggedBarHover, setLoggedBarHover] = React.useState(false);
|
||||
const [navOptions, setNavOptions] = React.useState(true);
|
||||
|
||||
const loggedBarVisibleHandler = React.useCallback(() => {
|
||||
if (loggedBarVisible === '0' && !loggedBarHover)
|
||||
setLoggedBarVisible('100vw');
|
||||
else setLoggedBarVisible('0');
|
||||
}, [loggedBarHover, loggedBarVisible]);
|
||||
|
||||
const hideNavOptions = React.useCallback(() => {
|
||||
setNavOptions(false);
|
||||
}, []);
|
||||
|
||||
const showNavOptions = React.useCallback(() => {
|
||||
setNavOptions(true);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<NavBar
|
||||
loggedBarVisibleHandler={loggedBarVisibleHandler}
|
||||
popUpMessageHandler={popUpMessageHandler}
|
||||
navOptions={navOptions}
|
||||
/>
|
||||
{!IS_MOBILE() && (
|
||||
<LoggedBar
|
||||
visible={loggedBarVisible}
|
||||
loggedBarVisibleHandler={loggedBarVisibleHandler}
|
||||
loggedBarHoverTrue={() => setLoggedBarHover(true)}
|
||||
loggedBarHoverFalse={() => setLoggedBarHover(false)}
|
||||
username={KeyCloakService.getUsername()}
|
||||
/>
|
||||
)}
|
||||
{CHILDREN_WITH_PROPS(children, { hideNavOptions, showNavOptions, popUpMessageHandler })}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default NavigationManager;
|
44
src/App/components/PopUpMessageManager.js
Normal file
44
src/App/components/PopUpMessageManager.js
Normal file
@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import PopupMessage from '../../components/generic/PopupMessage';
|
||||
import { CHILDREN_WITH_PROPS } from '../../utils/globals';
|
||||
|
||||
const PopUpMessageManager = (props) => {
|
||||
const [popUpHeader, setPopUpHeader] = React.useState('');
|
||||
const [popUpMessage, setPopUpMessage] = React.useState('');
|
||||
const [borderColor, setBorderColor] = React.useState(null);
|
||||
const [confirmPopUpHandler, setConfirmPopUpHandler] = React.useState(null);
|
||||
|
||||
const popUpMessageHandler = (header, message, confirmHandler=null, borderColor=null) => {
|
||||
setPopUpHeader(header);
|
||||
setPopUpMessage(message);
|
||||
setBorderColor(borderColor);
|
||||
if (confirmHandler !== null && confirmHandler !== undefined) {
|
||||
setConfirmPopUpHandler(() => confirmHandler());
|
||||
} else {
|
||||
setConfirmPopUpHandler(null);
|
||||
}
|
||||
};
|
||||
|
||||
const popUpMessageRender = () => {
|
||||
if (popUpHeader !== '' || popUpMessage !== '') {
|
||||
return (
|
||||
<PopupMessage
|
||||
header={popUpHeader}
|
||||
message={popUpMessage}
|
||||
confirmHandler={confirmPopUpHandler}
|
||||
borderColor={borderColor}
|
||||
popUpMessageHandler={popUpMessageHandler}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{popUpMessageRender()}
|
||||
{CHILDREN_WITH_PROPS(props.children, { popUpMessageHandler })}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PopUpMessageManager;
|
146
src/App/components/RoutingManager.js
Normal file
146
src/App/components/RoutingManager.js
Normal file
@ -0,0 +1,146 @@
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import {
|
||||
CHALLENGES_PAGE,
|
||||
CHALLENGE_PAGE,
|
||||
CHALLENGE_SECTIONS,
|
||||
POLICY_PRIVACY_PAGE,
|
||||
} from '../../utils/globals';
|
||||
import Challenge from '../../pages/Challenge';
|
||||
import Challenges from '../../pages/Challanges';
|
||||
import PolicyPrivacy from '../../pages/PolicyPrivacy';
|
||||
import LandingPage from '../../pages/LandingPage';
|
||||
import KeyCloakService from '../../services/KeyCloakService';
|
||||
|
||||
const RoutingManager = (props) => {
|
||||
return (
|
||||
<Routes>
|
||||
<Route
|
||||
path={`${CHALLENGE_PAGE}/:challengeId`}
|
||||
element={
|
||||
<Challenge
|
||||
section={CHALLENGE_SECTIONS.LEADERBOARD}
|
||||
popUpMessageHandler={props.popUpMessageHandler}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={`${CHALLENGE_PAGE}/:challengeId/leaderboard`}
|
||||
element={
|
||||
<Challenge
|
||||
section={CHALLENGE_SECTIONS.LEADERBOARD}
|
||||
popUpMessageHandler={props.popUpMessageHandler}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={`${CHALLENGE_PAGE}/:challengeId/allentries`}
|
||||
element={
|
||||
<Challenge
|
||||
section={CHALLENGE_SECTIONS.ALL_ENTRIES}
|
||||
popUpMessageHandler={props.popUpMessageHandler}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={`${CHALLENGE_PAGE}/:challengeId/readme`}
|
||||
element={<Challenge section={CHALLENGE_SECTIONS.README} />}
|
||||
/>
|
||||
<Route
|
||||
path={`${CHALLENGE_PAGE}/:challengeId/howto`}
|
||||
element={
|
||||
<Challenge
|
||||
popUpMessageHandler={props.popUpMessageHandler}
|
||||
section={CHALLENGE_SECTIONS.HOW_TO}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={`${CHALLENGE_PAGE}/:challengeId/myentries`}
|
||||
element={
|
||||
<Challenge
|
||||
section={CHALLENGE_SECTIONS.MY_ENTRIES}
|
||||
popUpMessageHandler={props.popUpMessageHandler}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={`${CHALLENGE_PAGE}/:challengeId/submit`}
|
||||
element={
|
||||
<Challenge
|
||||
section={CHALLENGE_SECTIONS.SUBMIT}
|
||||
popUpMessageHandler={props.popUpMessageHandler}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={CHALLENGES_PAGE}
|
||||
element={<Challenges popUpMessageHandler={props.popUpMessageHandler} />}
|
||||
/>
|
||||
<Route
|
||||
path={POLICY_PRIVACY_PAGE}
|
||||
element={
|
||||
<PolicyPrivacy popUpMessageHandler={props.popUpMessageHandler} />
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={`${POLICY_PRIVACY_PAGE}/login`}
|
||||
element={
|
||||
<PolicyPrivacy
|
||||
popUpMessageHandler={props.popUpMessageHandler}
|
||||
beforeLogin
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={`${POLICY_PRIVACY_PAGE}/register`}
|
||||
element={
|
||||
<PolicyPrivacy
|
||||
popUpMessageHandler={props.popUpMessageHandler}
|
||||
beforeRegister
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{KeyCloakService.isLoggedIn() ? (
|
||||
<>
|
||||
<Route
|
||||
exact
|
||||
path="/"
|
||||
element={
|
||||
<Challenges popUpMessageHandler={props.popUpMessageHandler} />
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
element={
|
||||
<Challenges popUpMessageHandler={props.popUpMessageHandler} />
|
||||
}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Route
|
||||
exact
|
||||
path="/"
|
||||
element={
|
||||
<LandingPage
|
||||
popUpMessageHandler={props.popUpMessageHandler}
|
||||
showNavOptions={props.showNavOptions}
|
||||
hideNavOptions={props.hideNavOptions}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
element={
|
||||
<LandingPage
|
||||
popUpMessageHandler={props.popUpMessageHandler}
|
||||
showNavOptions={props.showNavOptions}
|
||||
hideNavOptions={props.hideNavOptions}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Routes>
|
||||
);
|
||||
};
|
||||
|
||||
export default RoutingManager;
|
29
src/App/functions/startApp.js
Normal file
29
src/App/functions/startApp.js
Normal file
@ -0,0 +1,29 @@
|
||||
import Loading from '../../components/generic/Loading';
|
||||
import { FlexColumn } from '../../utils/containers';
|
||||
import SESSION_STORAGE from '../../utils/sessionStorage';
|
||||
import KeyCloakService from '../../services/KeyCloakService';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import theme from '../../utils/theme';
|
||||
|
||||
const startApp = (renderApp) => {
|
||||
if (
|
||||
sessionStorage.getItem(SESSION_STORAGE.LOGGED) ===
|
||||
SESSION_STORAGE.STATIC_VALUE.YES
|
||||
) {
|
||||
if (KeyCloakService.isLoggedIn()) {
|
||||
return renderApp();
|
||||
} else {
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<FlexColumn width="100vw" height="100vh">
|
||||
<Loading />
|
||||
</FlexColumn>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return renderApp();
|
||||
}
|
||||
};
|
||||
|
||||
export default startApp;
|
55
src/App/functions/startManage.js
Normal file
55
src/App/functions/startManage.js
Normal file
@ -0,0 +1,55 @@
|
||||
import {
|
||||
POLICY_PRIVACY_PAGE,
|
||||
LOGIN_REQUIRED_PAGES,
|
||||
ROOT_URL,
|
||||
} from '../../utils/globals';
|
||||
import addUser from '../../api/addUser';
|
||||
import SESSION_STORAGE from '../../utils/sessionStorage';
|
||||
import KeyCloakService from '../../services/KeyCloakService';
|
||||
|
||||
const startManage = () => {
|
||||
if (
|
||||
sessionStorage.getItem(SESSION_STORAGE.LOGOUT) ===
|
||||
SESSION_STORAGE.STATIC_VALUE.YES
|
||||
) {
|
||||
const pageName = window.location.pathname.split('/').at(-1);
|
||||
if (LOGIN_REQUIRED_PAGES.includes(pageName)) {
|
||||
window.location.replace(`${ROOT_URL}/challenges`);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
sessionStorage.getItem(SESSION_STORAGE.LOGGED) !==
|
||||
SESSION_STORAGE.STATIC_VALUE.YES
|
||||
) {
|
||||
if (KeyCloakService.isLoggedIn()) {
|
||||
sessionStorage.setItem(
|
||||
SESSION_STORAGE.LOGGED,
|
||||
SESSION_STORAGE.STATIC_VALUE.YES
|
||||
);
|
||||
addUser();
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
sessionStorage.getItem(SESSION_STORAGE.LOGGED) ===
|
||||
SESSION_STORAGE.STATIC_VALUE.YES &&
|
||||
(window.location.pathname === `${POLICY_PRIVACY_PAGE}/login` ||
|
||||
window.location.pathname === `${POLICY_PRIVACY_PAGE}/register`)
|
||||
) {
|
||||
window.location.replace(`${ROOT_URL}/challenges`);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (
|
||||
sessionStorage.getItem(SESSION_STORAGE.LOGGED) ===
|
||||
SESSION_STORAGE.STATIC_VALUE.YES
|
||||
) {
|
||||
if (!KeyCloakService.isLoggedIn()) {
|
||||
KeyCloakService.doLogin();
|
||||
}
|
||||
}
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
export default startManage;
|
1
src/App/index.js
Normal file
1
src/App/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './App';
|
@ -1,15 +1,19 @@
|
||||
import KeyCloakService from '../services/KeyCloakService';
|
||||
import { API } from '../utils/globals';
|
||||
import SUBMIT_ACTION from '../pages/Submit/model/SubmitActionEnum';
|
||||
|
||||
const challengeSubmission = (
|
||||
challengeName,
|
||||
repoUrl,
|
||||
repoBranch,
|
||||
description,
|
||||
setLoading
|
||||
submissionTags,
|
||||
dispatch
|
||||
) => {
|
||||
const tagNames = submissionTags.map((tag) => tag.name).join(',');
|
||||
const details = {
|
||||
f1: description,
|
||||
f2: tagNames,
|
||||
f3: repoUrl,
|
||||
f4: repoBranch,
|
||||
};
|
||||
@ -17,6 +21,8 @@ const challengeSubmission = (
|
||||
for (let property in details) {
|
||||
let encodedKey = encodeURIComponent(property);
|
||||
let encodedValue = encodeURIComponent(details[property]);
|
||||
if (property === 'f2')
|
||||
encodedValue = encodedValue.replaceAll('%2C', '%2C+');
|
||||
formBody.push(encodedKey + '=' + encodedValue);
|
||||
}
|
||||
formBody = formBody.join('&');
|
||||
@ -30,9 +36,14 @@ const challengeSubmission = (
|
||||
})
|
||||
.then((resp) => resp.json())
|
||||
.then((data) => {
|
||||
setLoading(true);
|
||||
dispatch({ type: SUBMIT_ACTION.TOGGLE_SUBMISSION_LOADING });
|
||||
const processUrl = API.replace('/api', '');
|
||||
window.location.replace(`${processUrl}/open-view-progress/${data}#form`);
|
||||
if (Number.isInteger(Number(data))) {
|
||||
console.log(`${processUrl}/open-view-progress/${data}#form`);
|
||||
window.location.replace(
|
||||
`${processUrl}/open-view-progress/${data}#form`
|
||||
);
|
||||
}
|
||||
// console.log(data);
|
||||
|
||||
// fetch(`${API}/view-progress-with-web-sockets/${data}`)
|
||||
|
43
src/api/deleteSubmission.js
Normal file
43
src/api/deleteSubmission.js
Normal file
@ -0,0 +1,43 @@
|
||||
import KeyCloakService from '../services/KeyCloakService';
|
||||
import { API } from '../utils/globals';
|
||||
import theme from '../utils/theme';
|
||||
|
||||
const deleteSubmission = async (
|
||||
item,
|
||||
deletedItems,
|
||||
setDeletedItems,
|
||||
popUpMessageHandler
|
||||
) => {
|
||||
fetch(`${API}/delete-submission/${item.id}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
|
||||
Authorization: `Bearer ${KeyCloakService.getToken()}`,
|
||||
},
|
||||
})
|
||||
.then((resp) => resp.text())
|
||||
.then((data) => {
|
||||
if (data === 'deleted') {
|
||||
let newDeletedItems = deletedItems.slice();
|
||||
newDeletedItems.push(item);
|
||||
setDeletedItems(newDeletedItems);
|
||||
popUpMessageHandler('Complete', `Submission "${item.id}" deleted`);
|
||||
} else if (data.includes('<!doctype html>') && data.includes('Login')) {
|
||||
popUpMessageHandler(
|
||||
'Error',
|
||||
'You have to be login in to edit submission!',
|
||||
null,
|
||||
theme.colors.red
|
||||
);
|
||||
} else {
|
||||
popUpMessageHandler(
|
||||
'Error',
|
||||
"You can't delete this submission!",
|
||||
null,
|
||||
theme.colors.red
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default deleteSubmission;
|
47
src/api/editSubmission.js
Normal file
47
src/api/editSubmission.js
Normal file
@ -0,0 +1,47 @@
|
||||
import KeyCloakService from '../services/KeyCloakService';
|
||||
import { API } from '../utils/globals';
|
||||
import theme from '../utils/theme';
|
||||
|
||||
const editSubmission = async (
|
||||
submisssion,
|
||||
tags,
|
||||
description,
|
||||
popUpMessageHandler
|
||||
) => {
|
||||
tags = tags.replaceAll(',', '%2C');
|
||||
fetch(`${API}/edit-submission/${submisssion}/${tags}/${description}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
|
||||
Authorization: `Bearer ${KeyCloakService.getToken()}`,
|
||||
},
|
||||
})
|
||||
.then((resp) => resp.text())
|
||||
.then((data) => {
|
||||
console.log(data);
|
||||
if (data === 'Submission changed') {
|
||||
popUpMessageHandler(
|
||||
'Submission changed!',
|
||||
`Submission ${submisssion} edited`,
|
||||
null,
|
||||
theme.colors.green
|
||||
);
|
||||
} else if (data === 'Only owner can edit a submission!') {
|
||||
popUpMessageHandler('Error', data, null, theme.colors.red);
|
||||
} else if (data.includes('<!doctype html>') && data.includes('Login')) {
|
||||
popUpMessageHandler(
|
||||
'Error',
|
||||
'You have to be login in to edit submission!',
|
||||
null,
|
||||
theme.colors.red
|
||||
);
|
||||
} else {
|
||||
if (data.length > 650) {
|
||||
data = `${data.slice(0, 650)}...`;
|
||||
}
|
||||
popUpMessageHandler('Error', data, null, theme.colors.red);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default editSubmission;
|
@ -1,19 +1,62 @@
|
||||
import { API } from '../utils/globals';
|
||||
import KeyCloakService from '../services/KeyCloakService';
|
||||
|
||||
const getChallengeLeaderboard = async (
|
||||
setDataState,
|
||||
const getChallengeLeaderboard = (
|
||||
endpoint,
|
||||
challengeName,
|
||||
setLoading
|
||||
setDataStates,
|
||||
setLoadingState,
|
||||
setScoreSorted
|
||||
) => {
|
||||
await fetch(`${API}/leaderboard/${challengeName}`)
|
||||
fetch(`${API}/${endpoint}/${challengeName}`, {
|
||||
headers: { Authorization: `Bearer ${KeyCloakService.getToken()}` },
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
setDataState(data.entries);
|
||||
let item = {};
|
||||
let result = [];
|
||||
let initSetScoreSorted = [];
|
||||
let tests = data.tests;
|
||||
for (let submission of data.entries) {
|
||||
for (let evaluation of submission.evaluations) {
|
||||
item = {
|
||||
...item,
|
||||
evaluations: {
|
||||
...item.evaluations,
|
||||
[`${evaluation.test.metric}.${evaluation.test.name}`]:
|
||||
evaluation.score,
|
||||
},
|
||||
};
|
||||
}
|
||||
for (let test of tests) {
|
||||
if (!item.evaluations) {
|
||||
item.evaluations = {};
|
||||
}
|
||||
if (!Object.hasOwn(item.evaluations, `${test.metric}.${test.name}`)) {
|
||||
item = {
|
||||
...item,
|
||||
evaluations: {
|
||||
...item.evaluations,
|
||||
[`${test.metric}.${test.name}`]: -999999999,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
item = {
|
||||
...item.evaluations,
|
||||
...submission,
|
||||
};
|
||||
result.push(item);
|
||||
item = {};
|
||||
}
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
for (let _ of tests) {
|
||||
initSetScoreSorted.push(false);
|
||||
}
|
||||
for (let setDataState of setDataStates) setDataState(result);
|
||||
if (setScoreSorted) setScoreSorted(initSetScoreSorted);
|
||||
if (setLoadingState) setLoadingState(false);
|
||||
});
|
||||
|
||||
if (setLoading) {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
export default getChallengeLeaderboard;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import CHALLENGES_ACTION from '../pages/Challanges/model/ChallengesActionEnum';
|
||||
import CHALLENGES_ACTION from '../pages/Challanges/model/ChallengesActions';
|
||||
import { API } from '../utils/globals';
|
||||
|
||||
const getChallenges = (dispatch) => {
|
||||
|
@ -1,19 +1,18 @@
|
||||
import { API } from '../utils/globals';
|
||||
import KeyCloakService from '../services/KeyCloakService';
|
||||
|
||||
const getAllEntries = (
|
||||
const getEntries = (
|
||||
endpoint,
|
||||
challengeName,
|
||||
setDataOriginalState,
|
||||
setDataState,
|
||||
setDataStates,
|
||||
setLoadingState,
|
||||
setScoreSorted
|
||||
) => {
|
||||
fetch(`${API}/challenge-all-submissions/${challengeName}`, {
|
||||
fetch(`${API}/${endpoint}/${challengeName}`, {
|
||||
headers: { Authorization: `Bearer ${KeyCloakService.getToken()}` },
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
if (setDataOriginalState) setDataOriginalState(data);
|
||||
let item = {};
|
||||
let result = [];
|
||||
let initSetScoreSorted = [];
|
||||
@ -30,37 +29,35 @@ const getAllEntries = (
|
||||
};
|
||||
}
|
||||
for (let test of tests) {
|
||||
if (item.evaluations) {
|
||||
if (
|
||||
!Object.hasOwn(item.evaluations, `${test.metric}.${test.name}`)
|
||||
) {
|
||||
item = {
|
||||
...item,
|
||||
evaluations: {
|
||||
...item.evaluations,
|
||||
[`${test.metric}.${test.name}`]: '-1',
|
||||
},
|
||||
};
|
||||
}
|
||||
if (!item.evaluations) {
|
||||
item.evaluations = {};
|
||||
}
|
||||
if (!Object.hasOwn(item.evaluations, `${test.metric}.${test.name}`)) {
|
||||
item = {
|
||||
...item,
|
||||
evaluations: {
|
||||
...item.evaluations,
|
||||
[`${test.metric}.${test.name}`]: -999999999,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
item = {
|
||||
...item,
|
||||
id: submission.id,
|
||||
submitter: submission.submitter,
|
||||
when: submission.when,
|
||||
...item.evaluations,
|
||||
...submission,
|
||||
};
|
||||
result.push(item);
|
||||
item = {};
|
||||
}
|
||||
result = result.filter((item) => !item.deleted);
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
for (let _ of tests) {
|
||||
initSetScoreSorted.push(false);
|
||||
}
|
||||
setDataState(result);
|
||||
for (let setDataState of setDataStates) setDataState(result);
|
||||
if (setScoreSorted) setScoreSorted(initSetScoreSorted);
|
||||
if (setLoadingState) setLoadingState(false);
|
||||
});
|
||||
};
|
||||
|
||||
export default getAllEntries;
|
||||
export default getEntries;
|
@ -1,16 +1,15 @@
|
||||
import {API} from '../utils/globals';
|
||||
import { API } from '../utils/globals';
|
||||
import KeyCloakService from '../services/KeyCloakService';
|
||||
|
||||
const getFullUser = (setDataState, setLoadingState) => {
|
||||
fetch(`${API}/full-user-info`, {
|
||||
headers: {'Authorization': `Bearer ${KeyCloakService.getToken()}`}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
setDataState(data);
|
||||
if (setLoadingState)
|
||||
setLoadingState(false);
|
||||
});
|
||||
const getFullUser = async (setDataState, setLoadingState) => {
|
||||
fetch(`${API}/full-user-info`, {
|
||||
headers: { Authorization: `Bearer ${KeyCloakService.getToken()}` },
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
setDataState(data);
|
||||
if (setLoadingState) setLoadingState(false);
|
||||
});
|
||||
};
|
||||
|
||||
export default getFullUser;
|
@ -1,68 +0,0 @@
|
||||
import { API } from '../utils/globals';
|
||||
import KeyCloakService from '../services/KeyCloakService';
|
||||
|
||||
const getMyEntries = (
|
||||
challengeName,
|
||||
setDataOriginalState,
|
||||
setDataStateForSearch,
|
||||
setDataState,
|
||||
setLoadingState,
|
||||
setScoreSorted
|
||||
) => {
|
||||
fetch(`${API}/challenge-my-submissions/${challengeName}`, {
|
||||
headers: { Authorization: `Bearer ${KeyCloakService.getToken()}` },
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
setDataOriginalState(data);
|
||||
let item = {};
|
||||
let result = [];
|
||||
let initSetScoreSorted = [];
|
||||
let tests = data.tests;
|
||||
for (let submission of data.submissions) {
|
||||
for (let evaluation of submission.evaluations) {
|
||||
item = {
|
||||
...item,
|
||||
evaluations: {
|
||||
...item.evaluations,
|
||||
[`${evaluation.test.metric}.${evaluation.test.name}`]:
|
||||
evaluation.score,
|
||||
},
|
||||
};
|
||||
}
|
||||
for (let test of tests) {
|
||||
if (item.evaluations) {
|
||||
if (
|
||||
!Object.hasOwn(item.evaluations, `${test.metric}.${test.name}`)
|
||||
) {
|
||||
item = {
|
||||
...item,
|
||||
evaluations: {
|
||||
...item.evaluations,
|
||||
[`${test.metric}.${test.name}`]: '-1',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
item = {
|
||||
...item,
|
||||
id: submission.id,
|
||||
submitter: submission.submitter,
|
||||
when: submission.when,
|
||||
};
|
||||
result.push(item);
|
||||
item = {};
|
||||
}
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
for (let _ of tests) {
|
||||
initSetScoreSorted.push(false);
|
||||
}
|
||||
setScoreSorted(initSetScoreSorted);
|
||||
setDataStateForSearch(result);
|
||||
setDataState(result);
|
||||
setLoadingState(false);
|
||||
});
|
||||
};
|
||||
|
||||
export default getMyEntries;
|
@ -1,10 +1,10 @@
|
||||
import { API } from '../utils/globals';
|
||||
|
||||
const getTags = (setTags) => {
|
||||
const getTags = (setState) => {
|
||||
fetch(`${API}/list-tags`)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
setTags(data);
|
||||
setState(data);
|
||||
});
|
||||
};
|
||||
|
||||
|
39
src/assets/csi_full_logo.svg
Normal file
39
src/assets/csi_full_logo.svg
Normal file
@ -0,0 +1,39 @@
|
||||
<svg width="396" height="110" viewBox="0 0 396 110" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M126.589 80.1711V90.4774H116.231V86.1171H105.374V87.7027H100.594V86.1171H90.136V90.4774H79.7778V80.1711H90.136V84.5315H100.594V82.9459H105.374V84.5315H116.231V80.1711H126.589Z" fill="#E52713"/>
|
||||
<path d="M147.404 58.1712V68.4775H136.947V64.1171H116.23V68.4775H105.872V64.1171H88.0443V72.3423H69.8179V64.1171H49.1017V65.7027H44.321V64.1171H27.5886V68.4775H17.1309V58.1712H27.5886V62.5315H44.321V60.9459H49.1017V62.5315H69.8179V54.2072H88.0443V62.5315H105.872V58.1712H116.23V62.5315H136.947V58.1712H147.404Z" fill="#E52713"/>
|
||||
<path d="M153.779 35.1802V45.4865H143.42V41.1262H129.477V42.7118H124.596V41.1262H109.856V47.964H94.518V41.1262H81.3712V42.7118H76.5905V41.1262H63.9416V51.7298H40.9346V41.1262H30.7756V45.4865H20.4175V41.1262H10.3581V45.4865H0V35.1802H10.3581V39.5406H20.4175V35.1802H30.7756V39.5406H40.9346V28.8379H63.9416V39.5406H76.5905V37.955H81.3712V39.5406H94.518V32.7028H109.856V39.5406H124.596V37.955H129.477V39.5406H143.42V35.1802H153.779Z" fill="#E52713"/>
|
||||
<path d="M134.656 15.8558V26.1621H124.298V21.8018H104.976V23.3874H100.195V21.8018H78.0847V26.1621H67.7265V21.8018H32.7678V26.1621H22.4097V15.8558H32.7678V20.2162H67.7265V15.8558H78.0847V20.2162H100.195V18.6306H104.976V20.2162H124.298V15.8558H134.656Z" fill="#E52713"/>
|
||||
<path d="M104.877 0V10.3063H94.5186V5.94594H81.8698V7.63063H77.0891V5.94594H64.5398V10.3063H54.1816V0H64.5398V4.36036H77.0891V2.77477H81.8698V4.36036H94.5186V0H104.877Z" fill="#E52713"/>
|
||||
<path d="M117.625 99.6937H107.267V110H117.625V99.6937Z" fill="#E52713"/>
|
||||
<path d="M175.69 17.9369C175.69 11.991 180.272 7.13513 186.646 7.13513C190.53 7.13513 193.917 9.11711 195.71 12.2883L193.817 13.3784C192.522 10.9009 189.734 9.21621 186.646 9.21621C181.367 9.21621 177.782 13.0811 177.782 17.9369C177.782 22.7928 181.367 26.6576 186.646 26.6576C189.734 26.6576 192.522 24.973 193.817 22.4955L195.71 23.4865C194.016 26.5586 190.53 28.6396 186.646 28.6396C180.272 28.6396 175.69 23.8829 175.69 17.9369Z" fill="black"/>
|
||||
<path d="M214.135 26.3603V28.3423H201.884V7.53149H214.035V9.51348H204.075V16.8468H213.338V18.8288H204.075V26.3603H214.135Z" fill="black"/>
|
||||
<path d="M235.947 7.53149V28.3423H234.154L222.501 11.3964V28.3423H220.41V7.53149H222.202L233.855 24.4774V7.53149H235.947Z" fill="black"/>
|
||||
<path d="M256.563 9.41435H250.089V28.2432H247.998V9.41435H241.524V7.43237H256.464V9.41435H256.563Z" fill="black"/>
|
||||
<path d="M274.491 26.3603V28.3423H262.241V7.53149H274.392V9.51348H264.432V16.8468H273.694V18.8288H264.432V26.3603H274.491Z" fill="black"/>
|
||||
<path d="M288.634 20.018H282.758V28.3423H280.666V7.53149H289.032C292.518 7.53149 295.307 10.3063 295.307 13.7747C295.307 16.5495 293.414 19.027 290.825 19.7207L295.904 28.3423H293.514L288.634 20.018ZM282.857 18.036H289.132C291.423 18.036 293.315 16.1531 293.315 13.7747C293.315 11.3964 291.423 9.51348 289.132 9.51348H282.857V18.036Z" fill="black"/>
|
||||
<path d="M313.931 9.41435V17.045H323.194V19.027H313.931V28.2432H311.84V7.43237H323.592V9.41435H313.931Z" fill="black"/>
|
||||
<path d="M328.672 17.9369C328.672 11.991 333.353 7.13513 339.528 7.13513C345.703 7.13513 350.285 11.991 350.285 17.9369C350.285 23.8829 345.603 28.7387 339.428 28.7387C333.253 28.7387 328.672 23.8829 328.672 17.9369ZM348.193 17.9369C348.193 13.0811 344.408 9.21621 339.428 9.21621C334.449 9.21621 330.664 13.0811 330.664 17.9369C330.664 22.7928 334.449 26.6576 339.428 26.6576C344.408 26.6576 348.193 22.7928 348.193 17.9369Z" fill="black"/>
|
||||
<path d="M364.527 20.018H358.651V28.3423H356.559V7.53149H364.925C368.411 7.53149 371.2 10.3063 371.2 13.7747C371.2 16.5495 369.308 19.027 366.718 19.7207L371.798 28.3423H369.407L364.527 20.018ZM358.651 18.036H364.925C367.216 18.036 369.108 16.1531 369.108 13.7747C369.108 11.3964 367.216 9.51348 364.925 9.51348H358.651V18.036Z" fill="black"/>
|
||||
<path d="M188.837 56.8829H178.877L176.885 62.036H174.694L182.762 41.2252H184.953L193.02 62.036H190.829L188.837 56.8829ZM188.14 54.9009L183.957 43.8018L179.774 54.9009H188.14Z" fill="black"/>
|
||||
<path d="M206.366 53.7117H200.49V62.036H198.398V41.2252H206.765C210.251 41.2252 213.039 44 213.039 47.4685C213.039 50.2432 211.147 52.7207 208.557 53.4144L213.637 62.036H211.247L206.366 53.7117ZM200.49 51.7297H206.765C209.055 51.7297 210.948 49.8468 210.948 47.4685C210.948 45.0901 209.055 43.2072 206.765 43.2072H200.49V51.7297Z" fill="black"/>
|
||||
<path d="M232.063 43.1081H225.589V61.9369H223.497V43.1081H217.023V41.1261H231.963V43.1081H232.063Z" fill="black"/>
|
||||
<path d="M239.831 41.2252V62.036H237.739V41.2252H239.831Z" fill="black"/>
|
||||
<path d="M249.392 43.1081V50.7387H258.655V52.7207H249.392V61.9369H247.301V41.1261H259.053V43.1081H249.392Z" fill="black"/>
|
||||
<path d="M267.42 41.2252V62.036H265.328V41.2252H267.42Z" fill="black"/>
|
||||
<path d="M273.694 51.6307C273.694 45.6847 278.276 40.8289 284.65 40.8289C288.534 40.8289 291.921 42.8108 293.713 45.982L291.821 47.0721C290.526 44.5946 287.738 42.9099 284.65 42.9099C279.371 42.9099 275.786 46.7748 275.786 51.6307C275.786 56.4865 279.371 60.3514 284.65 60.3514C287.738 60.3514 290.526 58.6667 291.821 56.1892L293.713 57.1802C292.02 60.2523 288.534 62.3334 284.65 62.3334C278.276 62.3334 273.694 57.5766 273.694 51.6307Z" fill="black"/>
|
||||
<path d="M301.98 41.2252V62.036H299.888V41.2252H301.98Z" fill="black"/>
|
||||
<path d="M321.401 56.8829H311.441L309.449 62.036H307.258L315.326 41.2252H317.517L325.584 62.036H323.393L321.401 56.8829ZM320.604 54.9009L316.421 43.8018L312.238 54.9009H320.604Z" fill="black"/>
|
||||
<path d="M342.616 60.054V62.036H330.963V41.2252H333.054V60.054H342.616Z" fill="black"/>
|
||||
<path d="M178.977 74.9189V95.7297H176.885V74.9189H178.977Z" fill="black"/>
|
||||
<path d="M201.984 74.9189V95.7297H200.191L188.538 78.7837V95.7297H186.447V74.9189H188.24L199.892 91.8648V74.9189H201.984Z" fill="black"/>
|
||||
<path d="M222.601 76.8017H216.127V95.6306H214.035V76.8017H207.562V74.8198H222.501V76.8017H222.601Z" fill="black"/>
|
||||
<path d="M240.528 93.7477V95.7297H228.278V74.9189H240.429V76.9009H230.469V84.2342H239.732V86.2162H230.469V93.7477H240.528Z" fill="black"/>
|
||||
<path d="M258.456 93.7477V95.7297H246.803V74.9189H248.894V93.7477H258.456Z" fill="black"/>
|
||||
<path d="M275.786 93.7477V95.7297H264.133V74.9189H266.224V93.7477H275.786Z" fill="black"/>
|
||||
<path d="M283.554 74.9189V95.7297H281.463V74.9189H283.554Z" fill="black"/>
|
||||
<path d="M311.043 86.9099C311.043 91.8649 307.258 96.1261 300.784 96.1261C294.311 96.1261 289.829 91.2703 289.829 85.3243C289.829 79.3784 294.41 74.5225 300.784 74.5225C304.669 74.5225 308.055 76.6036 309.848 79.6757L307.955 80.7658C306.661 78.2883 303.872 76.6036 300.784 76.6036C295.506 76.6036 291.92 80.4685 291.92 85.3243C291.92 90.1802 295.506 94.045 300.784 94.045C305.665 94.045 308.453 91.2703 308.852 87.8018H300.585V85.8198H311.043V86.9099Z" fill="black"/>
|
||||
<path d="M329.568 93.7477V95.7297H317.318V74.9189H329.369V76.9009H319.409V84.2342H328.672V86.2162H319.409V93.7477H329.568Z" fill="black"/>
|
||||
<path d="M351.38 74.9189V95.7297H349.587L337.934 78.7837V95.7297H335.843V74.9189H337.636L349.288 91.8648V74.9189H351.38Z" fill="black"/>
|
||||
<path d="M357.655 85.3243C357.655 79.3784 362.236 74.5225 368.611 74.5225C372.495 74.5225 375.881 76.5045 377.674 79.6757L375.782 80.7658C374.487 78.2883 371.698 76.6036 368.611 76.6036C363.332 76.6036 359.746 80.4685 359.746 85.3243C359.746 90.1802 363.332 94.045 368.611 94.045C371.698 94.045 374.487 92.3604 375.782 89.8829L377.674 90.8739C375.981 93.9459 372.495 96.027 368.611 96.027C362.137 96.027 357.655 91.2703 357.655 85.3243Z" fill="black"/>
|
||||
<path d="M396 93.7477V95.7297H383.75V74.9189H395.9V76.9009H385.941V84.2342H395.203V86.2162H385.941V93.7477H396Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 7.5 KiB |
8
src/assets/csi_logo.svg
Normal file
8
src/assets/csi_logo.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg width="154" height="110" viewBox="0 0 154 110" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M126.771 80.1709V90.4772H116.398V86.1168H105.526V87.7024H100.738V86.1168H90.2656V90.4772H79.8926V80.1709H90.2656V84.5313H100.738V82.9457H105.526V84.5313H116.398V80.1709H126.771Z" fill="#E52713"/>
|
||||
<path d="M147.617 58.171V68.4773H137.144V64.1169H116.398V68.4773H106.025V64.1169H88.1713V72.3422H69.9187V64.1169H49.1726V65.7025H44.385V64.1169H27.6286V68.4773H17.1558V58.171H27.6286V62.5314H44.385V60.9458H49.1726V62.5314H69.9187V54.207H88.1713V62.5314H106.025V58.171H116.398V62.5314H137.144V58.171H147.617Z" fill="#E52713"/>
|
||||
<path d="M154 35.1802V45.4865H143.627V41.1262H129.663V42.7118H124.776V41.1262H110.014V47.964H94.6541V41.1262H81.4883V42.7118H76.7008V41.1262H64.0337V51.7298H40.9935V41.1262H30.82V45.4865H20.4469V41.1262H10.3731V45.4865H0V35.1802H10.3731V39.5406H20.4469V35.1802H30.82V39.5406H40.9935V28.8379H64.0337V39.5406H76.7008V37.955H81.4883V39.5406H94.6541V32.7028H110.014V39.5406H124.776V37.955H129.663V39.5406H143.627V35.1802H154Z" fill="#E52713"/>
|
||||
<path d="M134.85 15.856V26.1623H124.477V21.8019H105.127V23.3875H100.34V21.8019H78.1971V26.1623H67.824V21.8019H32.815V26.1623H22.4419V15.856H32.815V20.2163H67.824V15.856H78.1971V20.2163H100.34V18.6307H105.127V20.2163H124.477V15.856H134.85Z" fill="#E52713"/>
|
||||
<path d="M105.028 0V10.3063H94.6548V5.94594H81.9877V7.63063H77.2002V5.94594H64.6328V10.3063H54.2598V0H64.6328V4.36036H77.2002V2.77477H81.9877V4.36036H94.6548V0H105.028Z" fill="#E52713"/>
|
||||
<path d="M117.794 99.6938H107.421V110H117.794V99.6938Z" fill="#E52713"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
33
src/assets/csi_logo_content.svg
Normal file
33
src/assets/csi_logo_content.svg
Normal file
@ -0,0 +1,33 @@
|
||||
<svg width="221" height="88" viewBox="0 0 221 88" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.68457 10.6815C1.68457 4.80179 6.23903 0 12.5757 0C16.437 0 19.8034 1.95992 21.5856 5.09578L19.7044 6.17373C18.4172 3.72384 15.645 2.05791 12.5757 2.05791C7.32813 2.05791 3.76378 5.87974 3.76378 10.6815C3.76378 15.4833 7.32813 19.3052 12.5757 19.3052C15.645 19.3052 18.4172 17.6392 19.7044 15.1893L21.5856 16.1693C19.9024 19.2072 16.437 21.2651 12.5757 21.2651C6.23903 21.2651 1.68457 16.5613 1.68457 10.6815Z" fill="#343434"/>
|
||||
<path d="M39.9019 19.0113V20.9712H27.7236V0.39209H39.8028V2.352H29.9018V9.60369H39.1098V11.5636H29.9018V19.0113H39.9019Z" fill="#343434"/>
|
||||
<path d="M61.5857 0.39209V20.9712H59.8035L48.2193 4.21392V20.9712H46.1401V0.39209H47.9223L59.5065 17.1494V0.39209H61.5857Z" fill="#343434"/>
|
||||
<path d="M82.0804 2.25337H75.6447V20.8726H73.5655V2.25337H67.1299V0.293457H81.9814V2.25337H82.0804Z" fill="#343434"/>
|
||||
<path d="M99.9023 19.0113V20.9712H87.7241V0.39209H99.8033V2.352H89.9023V9.60369H99.1103V11.5636H89.9023V19.0113H99.9023Z" fill="#343434"/>
|
||||
<path d="M113.962 12.7396H108.12V20.9712H106.041V0.39209H114.358C117.823 0.39209 120.595 3.13597 120.595 6.56582C120.595 9.3097 118.714 11.7596 116.14 12.4456L121.19 20.9712H118.813L113.962 12.7396ZM108.219 10.7796H114.457C116.734 10.7796 118.615 8.91772 118.615 6.56582C118.615 4.21392 116.734 2.352 114.457 2.352H108.219V10.7796Z" fill="#343434"/>
|
||||
<path d="M139.11 2.25337V9.79904H148.318V11.759H139.11V20.8726H137.031V0.293457H148.714V2.25337H139.11Z" fill="#343434"/>
|
||||
<path d="M153.764 10.6815C153.764 4.80179 158.417 0 164.556 0C170.694 0 175.249 4.80179 175.249 10.6815C175.249 16.5613 170.595 21.3631 164.457 21.3631C158.318 21.3631 153.764 16.5613 153.764 10.6815ZM173.17 10.6815C173.17 5.87974 169.407 2.05791 164.457 2.05791C159.506 2.05791 155.744 5.87974 155.744 10.6815C155.744 15.4833 159.506 19.3052 164.457 19.3052C169.407 19.3052 173.17 15.4833 173.17 10.6815Z" fill="#343434"/>
|
||||
<path d="M189.407 12.7396H183.566V20.9712H181.486V0.39209H189.803C193.269 0.39209 196.041 3.13597 196.041 6.56582C196.041 9.3097 194.16 11.7596 191.585 12.4456L196.635 20.9712H194.259L189.407 12.7396ZM183.566 10.7796H189.803C192.08 10.7796 193.962 8.91772 193.962 6.56582C193.962 4.21392 192.08 2.352 189.803 2.352H183.566V10.7796Z" fill="#343434"/>
|
||||
<path d="M14.7537 49.1938H4.85275L2.87255 54.2896H0.694336L8.71414 33.7104H10.8924L18.9122 54.2896H16.7339L14.7537 49.1938ZM14.0607 47.2339L9.90226 36.2583L5.74385 47.2339H14.0607Z" fill="#343434"/>
|
||||
<path d="M32.1796 46.0579H26.338V54.2896H24.2588V33.7104H32.5756C36.041 33.7104 38.8132 36.4543 38.8132 39.8842C38.8132 42.6281 36.9321 45.078 34.3578 45.7639L39.4073 54.2896H37.0311L32.1796 46.0579ZM26.338 44.098H32.5756C34.8528 44.098 36.734 42.2361 36.734 39.8842C36.734 37.5323 34.8528 35.6704 32.5756 35.6704H26.338V44.098Z" fill="#343434"/>
|
||||
<path d="M57.7244 35.5722H51.2888V54.1914H49.2096V35.5722H42.7739V33.6123H57.6254V35.5722H57.7244Z" fill="#343434"/>
|
||||
<path d="M65.4464 33.7104V54.2896H63.3672V33.7104H65.4464Z" fill="#343434"/>
|
||||
<path d="M74.9518 35.5722V43.1179H84.1597V45.0778H74.9518V54.1914H72.8726V33.6123H84.5557V35.5722H74.9518Z" fill="#343434"/>
|
||||
<path d="M92.8726 33.7104V54.2896H90.7935V33.7104H92.8726Z" fill="#343434"/>
|
||||
<path d="M99.1104 43.9999C99.1104 38.1202 103.665 33.3184 110.001 33.3184C113.863 33.3184 117.229 35.2783 119.011 38.4141L117.13 39.4921C115.843 37.0422 113.071 35.3763 110.001 35.3763C104.754 35.3763 101.19 39.1981 101.19 43.9999C101.19 48.8017 104.754 52.6235 110.001 52.6235C113.071 52.6235 115.843 50.9576 117.13 48.5077L119.011 49.4877C117.328 52.5255 113.863 54.5834 110.001 54.5834C103.665 54.5834 99.1104 49.8796 99.1104 43.9999Z" fill="#343434"/>
|
||||
<path d="M127.229 33.7104V54.2896H125.149V33.7104H127.229Z" fill="#343434"/>
|
||||
<path d="M146.535 49.1938H136.635L134.654 54.2896H132.476L140.496 33.7104H142.674L150.694 54.2896H148.516L146.535 49.1938ZM145.743 47.2339L141.585 36.2583L137.427 47.2339H145.743Z" fill="#343434"/>
|
||||
<path d="M167.625 52.3296V54.2896H156.041V33.7104H158.12V52.3296H167.625Z" fill="#343434"/>
|
||||
<path d="M4.95177 67.0288V87.6079H2.87256V67.0288H4.95177Z" fill="#343434"/>
|
||||
<path d="M27.823 67.0288V87.6079H26.0408L14.4567 70.8506V87.6079H12.3774V67.0288H14.1596L25.7438 83.7861V67.0288H27.823Z" fill="#343434"/>
|
||||
<path d="M48.3182 68.8911H41.8825V87.5103H39.8033V68.8911H33.3677V66.9312H48.2192V68.8911H48.3182Z" fill="#343434"/>
|
||||
<path d="M66.1401 85.648V87.6079H53.9619V67.0288H66.0411V68.9887H56.1401V76.2404H65.3481V78.2003H56.1401V85.648H66.1401Z" fill="#343434"/>
|
||||
<path d="M83.9616 85.648V87.6079H72.3774V67.0288H74.4567V85.648H83.9616Z" fill="#343434"/>
|
||||
<path d="M101.189 85.648V87.6079H89.605V67.0288H91.6842V85.648H101.189Z" fill="#343434"/>
|
||||
<path d="M108.912 67.0288V87.6079H106.833V67.0288H108.912Z" fill="#343434"/>
|
||||
<path d="M136.239 78.8862C136.239 83.786 132.476 87.9998 126.041 87.9998C119.605 87.9998 115.149 83.198 115.149 77.3183C115.149 71.4385 119.704 66.6367 126.041 66.6367C129.902 66.6367 133.268 68.6946 135.05 71.7325L133.169 72.8104C131.882 70.3606 129.11 68.6946 126.041 68.6946C120.793 68.6946 117.229 72.5165 117.229 77.3183C117.229 82.12 120.793 85.9419 126.041 85.9419C130.892 85.9419 133.664 83.198 134.06 79.7681H125.843V77.8082H136.239V78.8862Z" fill="#343434"/>
|
||||
<path d="M154.655 85.648V87.6079H142.477V67.0288H154.457V68.9887H144.556V76.2404H153.764V78.2003H144.556V85.648H154.655Z" fill="#343434"/>
|
||||
<path d="M176.338 67.0288V87.6079H174.555L162.971 70.8506V87.6079H160.892V67.0288H162.674L174.258 83.7861V67.0288H176.338Z" fill="#343434"/>
|
||||
<path d="M182.575 77.3183C182.575 71.4385 187.13 66.6367 193.466 66.6367C197.328 66.6367 200.694 68.5966 202.476 71.7325L200.595 72.8104C199.308 70.3606 196.536 68.6946 193.466 68.6946C188.219 68.6946 184.654 72.5165 184.654 77.3183C184.654 82.12 188.219 85.9419 193.466 85.9419C196.536 85.9419 199.308 84.2759 200.595 81.8261L202.476 82.806C200.793 85.8439 197.328 87.9018 193.466 87.9018C187.031 87.9018 182.575 83.198 182.575 77.3183Z" fill="#343434"/>
|
||||
<path d="M220.694 85.648V87.6079H208.516V67.0288H220.595V68.9887H210.694V76.2404H219.902V78.2003H210.694V85.648H220.694Z" fill="#343434"/>
|
||||
</svg>
|
After Width: | Height: | Size: 6.0 KiB |
3
src/assets/delete_ico.svg
Normal file
3
src/assets/delete_ico.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="33" height="33" viewBox="0 0 33 33" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M31.3571 2.24695C31.9857 2.24695 32.5 2.69695 32.5 3.24695V5.24695C32.5 5.79695 31.9857 6.24695 31.3571 6.24695H1.64286C1.01429 6.24695 0.5 5.79695 0.5 5.24695V3.24695C0.5 2.69695 1.01429 2.24695 1.64286 2.24695H10.2143L10.8857 1.0782C11.1429 0.621948 11.8214 0.246948 12.4071 0.246948H12.4143H20.5786C21.1643 0.246948 21.85 0.621948 22.1143 1.0782L22.7857 2.24695H31.3571ZM4.3 29.4344L2.78571 8.24695H30.2143L28.7 29.4344C28.5857 30.9844 27.0571 32.2469 25.2786 32.2469H7.72143C5.94286 32.2469 4.41429 30.9844 4.3 29.4344Z" fill="#343434"/>
|
||||
</svg>
|
After Width: | Height: | Size: 654 B |
3
src/assets/remove_ico.svg
Normal file
3
src/assets/remove_ico.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="33" height="33" viewBox="0 0 33 33" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.39729 27.0172C0.82276 27.5917 0.5 28.3709 0.5 29.1834C0.5 29.9959 0.82276 30.7751 1.39729 31.3497C1.97181 31.9242 2.75104 32.2469 3.56354 32.2469C4.37604 32.2469 5.15526 31.9242 5.72978 31.3497C9.33002 27.7494 12.8998 24.1492 16.5 20.61L27.2702 31.3497C27.5547 31.6341 27.8924 31.8598 28.2641 32.0138C28.6358 32.1677 29.0342 32.2469 29.4365 32.2469C29.8388 32.2469 30.2371 32.1677 30.6088 32.0138C30.9805 31.8598 31.3182 31.6341 31.6027 31.3497C31.8872 31.0652 32.1128 30.7275 32.2668 30.3558C32.4208 29.9841 32.5 29.5857 32.5 29.1834C32.5 28.7811 32.4208 28.3827 32.2668 28.011C32.1128 27.6394 31.8872 27.3016 31.6027 27.0172C28.0025 23.4779 24.4022 19.8472 20.863 16.2469C23.3649 13.6841 25.8667 11.2127 28.3686 8.71085C29.467 7.61247 30.5043 6.51409 31.6027 5.47673C32.1772 4.9022 32.5 4.12298 32.5 3.31048C32.5 2.49798 32.1772 1.71876 31.6027 1.14423C31.0282 0.569709 30.249 0.246948 29.4365 0.246948C28.624 0.246948 27.8447 0.569709 27.2702 1.14423L16.5 11.9328L5.72978 1.14423C5.15526 0.569709 4.37604 0.246948 3.56354 0.246948C2.75104 0.246948 1.97181 0.569709 1.39729 1.14423C0.82276 1.71876 0.5 2.49798 0.5 3.31048C0.5 4.12298 0.82276 4.9022 1.39729 5.47673L12.1858 16.2469L1.39729 27.0172Z" fill="#343434"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
@ -1,122 +0,0 @@
|
||||
import React from 'react';
|
||||
import {Container, FlexColumn, FlexRow, Grid} from '../../utils/containers';
|
||||
import {Body, H3} from '../../utils/fonts';
|
||||
import styled from 'styled-components';
|
||||
import IconLabel from '../generic/IconLabel';
|
||||
import {Link} from 'react-router-dom';
|
||||
import {CHALLENGE_PAGE, MINI_DESCRIPTION_RENDER} from '../../utils/globals';
|
||||
import theme from '../../utils/theme';
|
||||
import PropsTypes from 'prop-types';
|
||||
|
||||
const ChallengeStyle = styled(FlexColumn)`
|
||||
padding: 12px;
|
||||
border: 1px solid ${({theme}) => theme.colors.dark05};
|
||||
cursor: pointer;
|
||||
transition: transform 0.3s ease-in-out;
|
||||
position: relative;
|
||||
max-width: 420px;
|
||||
|
||||
* {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
article {
|
||||
width: 100%;
|
||||
align-items: flex-start;
|
||||
|
||||
p {
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: ${({theme}) => theme.overMobile}) {
|
||||
width: 360px;
|
||||
padding: 20px;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
`;
|
||||
|
||||
const IconsGrid = styled(Grid)`
|
||||
width: 100%;
|
||||
grid-gap: 14px;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
|
||||
@media (min-width: 500px) {
|
||||
grid-template-columns: auto auto auto;
|
||||
}
|
||||
`;
|
||||
|
||||
const MiniChallenge = (props) => {
|
||||
const deadlineRender = () => {
|
||||
if (props.deadline) {
|
||||
return (
|
||||
<IconLabel size='24px' gap='8px' type='deadline' time={props.deadline}>
|
||||
{props.deadline.slice(0, 10)}
|
||||
</IconLabel>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ChallengeStyle as={Link} to={`${CHALLENGE_PAGE}/${props.name}`}>
|
||||
<FlexColumn as='article'>
|
||||
<FlexRow margin='0 0 14px 0' gap='12px' width='100%' alignmentX='space-between'>
|
||||
<H3 as='h3' width='85%'>
|
||||
{props.title}
|
||||
</H3>
|
||||
{props.type ? <IconLabel type={props.type} size='30px'/> : 'xxx'}
|
||||
</FlexRow>
|
||||
<Container margin='0 0 14px 0' width='85%' height='1px' backgroundColor={theme.colors.dark05}/>
|
||||
<Body as='p' margin='0 0 14px 0'>
|
||||
{props.description ? MINI_DESCRIPTION_RENDER(props.description) : 'xxx'}
|
||||
</Body>
|
||||
<IconsGrid>
|
||||
<IconLabel size='24px' gap='8px' type='metric'>
|
||||
{props.metric ? props.metric : 'xxx'}
|
||||
</IconLabel>
|
||||
<IconLabel size='24px' gap='8px' type='bestScore'>
|
||||
{props.bestScore ? props.bestScore : 'xxx'}
|
||||
</IconLabel>
|
||||
{deadlineRender()}
|
||||
<IconLabel size='24px' gap='8px' type='baseline'>
|
||||
{props.baseline ? props.baseline : 'xxx'}
|
||||
</IconLabel>
|
||||
{props.prize ? <IconLabel size='24px' gap='8px' type='prize'>
|
||||
{props.prize}
|
||||
</IconLabel> : ''}
|
||||
</IconsGrid>
|
||||
</FlexColumn>
|
||||
</ChallengeStyle>
|
||||
);
|
||||
};
|
||||
|
||||
MiniChallenge.propTypes = {
|
||||
name: PropsTypes.string,
|
||||
title: PropsTypes.string,
|
||||
type: PropsTypes.string,
|
||||
description: PropsTypes.string,
|
||||
metric: PropsTypes.string,
|
||||
bestScore: PropsTypes.string,
|
||||
deadline: PropsTypes.string,
|
||||
baseline: PropsTypes.string,
|
||||
prize: PropsTypes.string
|
||||
};
|
||||
|
||||
MiniChallenge.defaultProps = {
|
||||
name: 'xxx',
|
||||
title: 'xxx',
|
||||
type: 'xxx',
|
||||
description: 'xxx',
|
||||
metric: 'xxx',
|
||||
bestScore: 'xxx',
|
||||
deadline: 'xxx',
|
||||
baseline: 'xxx',
|
||||
prize: 'xxx'
|
||||
};
|
||||
|
||||
export default MiniChallenge;
|
@ -1,105 +0,0 @@
|
||||
import React from 'react';
|
||||
import {FlexColumn, FlexRow, ImageBackground} from '../../utils/containers';
|
||||
import {Body, H2, Medium} from '../../utils/fonts';
|
||||
import CircleNumber from '../generic/CircleNumber';
|
||||
import Media from 'react-media';
|
||||
import theme from '../../utils/theme';
|
||||
import commercialImage from '../../assets/commercial-image.svg';
|
||||
|
||||
const Commercial = () => {
|
||||
const listItemsContent = [
|
||||
'A company comes to CSI with a business need',
|
||||
'CSI determines the need with an appropriate challenge on Gonito',
|
||||
'The challenge is solved by willing users',
|
||||
'The company appropriately rewards users who have contributed to the required outcome',
|
||||
];
|
||||
|
||||
const mobileRender = () => {
|
||||
return (
|
||||
<FlexColumn as='section' alignmentX='flex-start'>
|
||||
<H2 as='h2' margin='0 0 24px 0'>
|
||||
Commercial challenges
|
||||
</H2>
|
||||
<FlexColumn gap='20px'>
|
||||
<Body as='p'>
|
||||
The artificial intelligence center works with companies by accepting
|
||||
machine learning challenges from them that are available to solve on Gonito.
|
||||
Each commercial challenge is properly scored which translates into an award
|
||||
for solving it according to the client's requirements.
|
||||
</Body>
|
||||
<FlexColumn as='ul' gap='16px' alignmentX='flex-start'>
|
||||
{
|
||||
listItemsContent.map((item, index) => {
|
||||
return (
|
||||
<FlexRow key={`commercial-item-${index}`} width='100%' gap='8px'>
|
||||
<CircleNumber number={String(index + 1)}/>
|
||||
<Medium width='80%' as='li'>
|
||||
{item}
|
||||
</Medium>
|
||||
</FlexRow>
|
||||
);
|
||||
})
|
||||
}
|
||||
</FlexColumn>
|
||||
<Body as='p'>
|
||||
Open challenges can allow you to find the right people to work with.
|
||||
Find a challenge for your team and take it on!
|
||||
</Body>
|
||||
</FlexColumn>
|
||||
</FlexColumn>
|
||||
);
|
||||
};
|
||||
|
||||
const desktopRender = () => {
|
||||
return (
|
||||
<FlexRow gap='46px'>
|
||||
<FlexColumn as='section' alignmentX='flex-start' maxWidth='490px'>
|
||||
<H2 as='h2' margin='0 0 48px 0'>
|
||||
Commercial challenges
|
||||
</H2>
|
||||
<FlexColumn gap='32px'>
|
||||
<Body as='p'>
|
||||
The artificial intelligence center works with companies by accepting
|
||||
machine learning challenges from them that are available to solve on Gonito.
|
||||
Each commercial challenge is properly scored which translates into an award
|
||||
for solving it according to the client's requirements.
|
||||
</Body>
|
||||
<FlexColumn as='ul' gap='24px' alignmentX='flex-start'>
|
||||
{
|
||||
listItemsContent.map((item, index) => {
|
||||
return (
|
||||
<FlexRow key={`commercial-item-${index}`} width='100%' gap='16px'>
|
||||
<CircleNumber number={String(index + 1)}/>
|
||||
<Medium width='80%' as='li'>
|
||||
{item}
|
||||
</Medium>
|
||||
</FlexRow>
|
||||
);
|
||||
})
|
||||
}
|
||||
</FlexColumn>
|
||||
<Body as='p'>
|
||||
Open challenges can allow you to find the right people to work with.
|
||||
Find a challenge for your team and take it on!
|
||||
</Body>
|
||||
</FlexColumn>
|
||||
</FlexColumn>
|
||||
<ImageBackground image={commercialImage} width='200px' height='284px' size='contain'/>
|
||||
</FlexRow>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Media query={theme.mobile}>
|
||||
{mobileRender()}
|
||||
</Media>
|
||||
<Media query={theme.desktop}>
|
||||
{desktopRender()}
|
||||
</Media>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Commercial;
|
@ -1,45 +0,0 @@
|
||||
import React from 'react';
|
||||
import {Container, FlexRow} from '../../utils/containers';
|
||||
import styled from 'styled-components';
|
||||
import {Medium} from '../../utils/fonts';
|
||||
import {CSI_LINK} from '../../utils/globals';
|
||||
|
||||
const FooterStyle = styled(FlexRow)`
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: ${({theme}) => theme.colors.green};
|
||||
z-index: 1000;
|
||||
position: relative;
|
||||
|
||||
p, a {
|
||||
color: ${({theme}) => theme.colors.white}
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media (min-width: ${({theme}) => theme.overMobile}) {
|
||||
height: 72px;
|
||||
}
|
||||
`;
|
||||
|
||||
const Footer = () => {
|
||||
return (
|
||||
<FooterStyle as='footer'>
|
||||
<Medium as='p'>
|
||||
Read more about
|
||||
<Container as='a' display='inline' target='_blank'
|
||||
href={CSI_LINK}>
|
||||
CSI
|
||||
</Container>
|
||||
</Medium>
|
||||
</FooterStyle>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
@ -1,93 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Body, H1, Medium } from '../../utils/fonts';
|
||||
import { Container, FlexColumn, FlexRow, Svg } from '../../utils/containers';
|
||||
import theme from '../../utils/theme';
|
||||
import ButtonLink from '../generic/ButtonLink';
|
||||
import Media from 'react-media';
|
||||
import codepenIco from '../../assets/codepen_ico.svg';
|
||||
import styled from 'styled-components';
|
||||
import KeyCloakService from '../../services/KeyCloakService';
|
||||
|
||||
const TitleParagraph = styled(Medium)`
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
`;
|
||||
|
||||
const Hero = (props) => {
|
||||
const mobileRender = () => {
|
||||
return (
|
||||
<FlexColumn alignmentX="flex-start" gap="24px" maxWidth="452px">
|
||||
<H1 as="h1">
|
||||
Welcome to
|
||||
<Container display="inline" color={theme.colors.green}>
|
||||
Gonito!
|
||||
</Container>
|
||||
</H1>
|
||||
<Body as="p">
|
||||
A data challenge platform for machine learning research, competition,
|
||||
cooperation and reproducibility.
|
||||
</Body>
|
||||
<ButtonLink
|
||||
as="button"
|
||||
onClick={() =>
|
||||
props.popUpMessageHandler(
|
||||
'Reminder',
|
||||
'Remember to check your spam mailbox to confirm your account.',
|
||||
() => KeyCloakService.doRegister
|
||||
)
|
||||
}
|
||||
>
|
||||
Join us!
|
||||
</ButtonLink>
|
||||
</FlexColumn>
|
||||
);
|
||||
};
|
||||
|
||||
const desktopRender = () => {
|
||||
return (
|
||||
<FlexColumn alignmentX="flex-start" gap="24px">
|
||||
<H1 as="h1">
|
||||
Welcome to
|
||||
<Container display="inline" color={theme.colors.green}>
|
||||
Gonito!
|
||||
</Container>
|
||||
</H1>
|
||||
<FlexRow gap="20px">
|
||||
<Container>
|
||||
<TitleParagraph as="p" maxWidth="286px" margin="0 0 20px 0">
|
||||
A data challenge platform for machine learning research,
|
||||
competition, cooperation and reproducibility.
|
||||
</TitleParagraph>
|
||||
<ButtonLink
|
||||
as="button"
|
||||
onClick={() =>
|
||||
props.popUpMessageHandler(
|
||||
'Reminder',
|
||||
'Remember to check your spam mailbox to confirm your account.',
|
||||
() => KeyCloakService.doRegister
|
||||
)
|
||||
}
|
||||
>
|
||||
Join us!
|
||||
</ButtonLink>
|
||||
</Container>
|
||||
<Svg
|
||||
src={codepenIco}
|
||||
width="180px"
|
||||
height="150px"
|
||||
size="contain"
|
||||
backgroundColor={theme.colors.green}
|
||||
/>
|
||||
</FlexRow>
|
||||
</FlexColumn>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Media query={theme.mobile}>{mobileRender()}</Media>
|
||||
<Media query={theme.desktop}>{desktopRender()}</Media>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Hero;
|
@ -1,82 +0,0 @@
|
||||
import React from 'react';
|
||||
import {FlexColumn, FlexRow, ImageBackground, Svg} from '../../utils/containers';
|
||||
import {Body, H2} from '../../utils/fonts';
|
||||
import cubeIcon from '../../assets/cube_ico.svg';
|
||||
import theme from '../../utils/theme';
|
||||
import Media from 'react-media';
|
||||
import ellipse from '../../assets/ellipse.svg';
|
||||
|
||||
const Motivation = () => {
|
||||
const content = [
|
||||
'Explore interesting solutions to problems using AI',
|
||||
'Train by solving our challenges',
|
||||
'Participate in competitions with commercial challenges'
|
||||
];
|
||||
|
||||
const mobileRender = () => {
|
||||
return (
|
||||
<FlexColumn as='section' alignmentX='flex-start' gap='24px' width='100%'>
|
||||
<H2 as='h2'>
|
||||
Motivation
|
||||
</H2>
|
||||
|
||||
<FlexColumn as='ul' gap='16px' alignmentX='flex-start'>
|
||||
{
|
||||
content.map((paragraph, index) => {
|
||||
return (
|
||||
<FlexRow key={`motivation-${index}`} as='li' gap='12px' alignmentX='flex-start'
|
||||
alignmentY='flex-start'>
|
||||
<Svg src={cubeIcon} width='14px' height='14px' margin='4px 0 0 0'
|
||||
backgroundColor={theme.colors.green}/>
|
||||
<Body as='p' width='90%'>
|
||||
{paragraph}
|
||||
</Body>
|
||||
</FlexRow>
|
||||
);
|
||||
})
|
||||
}
|
||||
</FlexColumn>
|
||||
</FlexColumn>
|
||||
);
|
||||
};
|
||||
|
||||
const desktopRender = () => {
|
||||
return (
|
||||
<ImageBackground as='section' image={ellipse}
|
||||
gap='48px' width='612px' height='458px'>
|
||||
<H2 as='h2'>
|
||||
Motivation
|
||||
</H2>
|
||||
|
||||
<FlexColumn as='ul' gap='22px' alignmentX='flex-start'>
|
||||
{
|
||||
content.map((paragraph, index) => {
|
||||
return (
|
||||
<FlexRow key={`motivation-${index}`} as='li' gap='16px' alignmentY='flex-start'>
|
||||
<Svg src={cubeIcon} width='20px' height='20px' size='cover' margin='2px 0 0 0'
|
||||
backgroundColor={theme.colors.green}/>
|
||||
<Body as='p' maxWidth='380px'>
|
||||
{paragraph}
|
||||
</Body>
|
||||
</FlexRow>
|
||||
);
|
||||
})
|
||||
}
|
||||
</FlexColumn>
|
||||
</ImageBackground>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Media query={theme.mobile}>
|
||||
{mobileRender()}
|
||||
</Media>
|
||||
<Media query={theme.desktop}>
|
||||
{desktopRender()}
|
||||
</Media>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Motivation;
|
@ -1,48 +0,0 @@
|
||||
import React from 'react';
|
||||
import {FlexColumn, Grid} from '../../utils/containers';
|
||||
import {H2} from '../../utils/fonts';
|
||||
import Placeholder from '../generic/Placeholder';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const PartnershipsStyle = styled(FlexColumn)`
|
||||
justify-content: flex-start;
|
||||
gap: 32px;
|
||||
|
||||
@media (min-width: ${({theme}) => theme.overMobile}) {
|
||||
gap: 64px;
|
||||
|
||||
.grid {
|
||||
grid-template-rows: 1fr 1fr;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-gap: 64px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const Partnerships = () => {
|
||||
return (
|
||||
<PartnershipsStyle as='section'>
|
||||
<H2 as='h2'>
|
||||
Our partnerships
|
||||
</H2>
|
||||
<FlexColumn width='100%'>
|
||||
<Grid className='grid' gridGap='32px 0'>
|
||||
<Placeholder>
|
||||
1
|
||||
</Placeholder>
|
||||
<Placeholder>
|
||||
2
|
||||
</Placeholder>
|
||||
<Placeholder>
|
||||
3
|
||||
</Placeholder>
|
||||
<Placeholder>
|
||||
4
|
||||
</Placeholder>
|
||||
</Grid>
|
||||
</FlexColumn>
|
||||
</PartnershipsStyle>
|
||||
);
|
||||
};
|
||||
|
||||
export default Partnerships;
|
@ -33,6 +33,7 @@ const Button = (props) => {
|
||||
onClick={() => props.handler()}
|
||||
width={props.width}
|
||||
height={props.height}
|
||||
margin={props.margin}
|
||||
color={props.color}
|
||||
backgroundColor={props.backgroundColor}
|
||||
to={props.to}
|
||||
|
@ -1,14 +1,14 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import {Label} from '../../utils/fonts';
|
||||
import { Label } from '../../utils/fonts';
|
||||
import PropsTypes from 'prop-types';
|
||||
|
||||
const ButtonLinkStyle = styled(Label)`
|
||||
background-color: ${({theme}) => theme.colors.green};
|
||||
color: ${({theme}) => theme.colors.white};
|
||||
background-color: ${({ theme }) => theme.colors.green};
|
||||
color: ${({ theme }) => theme.colors.white};
|
||||
border-radius: 12px;
|
||||
text-align: center;
|
||||
width: 122px;
|
||||
width: 144px;
|
||||
padding: 4px 0;
|
||||
cursor: pointer;
|
||||
transition: transform 0.3s ease-in-out;
|
||||
@ -18,32 +18,32 @@ const ButtonLinkStyle = styled(Label)`
|
||||
transform: scale(1.15);
|
||||
}
|
||||
|
||||
@media (min-width: ${({theme}) => theme.overMobile}) {
|
||||
@media (min-width: ${({ theme }) => theme.overMobile}) {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 44px;
|
||||
height: 48px;
|
||||
}
|
||||
`;
|
||||
|
||||
const ButtonLink = (props) => {
|
||||
return (
|
||||
<ButtonLinkStyle as={props.as} onClick={props.onClick}>
|
||||
{props.children}
|
||||
</ButtonLinkStyle>
|
||||
);
|
||||
return (
|
||||
<ButtonLinkStyle as={props.as} onClick={props.onClick}>
|
||||
{props.children}
|
||||
</ButtonLinkStyle>
|
||||
);
|
||||
};
|
||||
|
||||
ButtonLink.propTypes = {
|
||||
as: PropsTypes.string,
|
||||
onClick: PropsTypes.func,
|
||||
children: PropsTypes.node,
|
||||
as: PropsTypes.string,
|
||||
onClick: PropsTypes.func,
|
||||
children: PropsTypes.node,
|
||||
};
|
||||
|
||||
ButtonLink.defaultProps = {
|
||||
children: '',
|
||||
as: 'div',
|
||||
onClick: null,
|
||||
children: '',
|
||||
as: 'div',
|
||||
onClick: null,
|
||||
};
|
||||
|
||||
export default ButtonLink;
|
@ -1,60 +0,0 @@
|
||||
import React from 'react';
|
||||
import { FlexColumn, FlexRow, Grid } from '../../utils/containers';
|
||||
import { Medium } from '../../utils/fonts';
|
||||
import theme from '../../utils/theme';
|
||||
import ImageButton from './ImageButton';
|
||||
import pencilIco from '../../assets/pencil_ico.svg';
|
||||
import styled from 'styled-components';
|
||||
import PopUp from './PopUp';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
const DropdownWithPopupStyle = styled(FlexColumn)`
|
||||
cursor: pointer;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
align-items: flex-start;
|
||||
* {
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
const DropdownWithPopup = (props) => {
|
||||
const [tagsPopUp, setTagsPopUp] = React.useState(false);
|
||||
|
||||
return (
|
||||
<DropdownWithPopupStyle
|
||||
onClick={() => {
|
||||
if (!tagsPopUp) setTagsPopUp(true);
|
||||
}}
|
||||
>
|
||||
<Medium as="label" htmlFor={props.label}>
|
||||
{props.label}
|
||||
</Medium>
|
||||
<Grid
|
||||
borderRadius="4px"
|
||||
width="100%"
|
||||
height="100px"
|
||||
border={`1px solid ${theme.colors.dark}`}
|
||||
shadow={theme.shadow}
|
||||
onChange={(e) => props.handler(e.target.value)}
|
||||
padding="12px"
|
||||
gridTemplateColumns="1fr auto"
|
||||
>
|
||||
<FlexRow height="100%" alignmentX="flex-start" alignmentY="flex-start">
|
||||
dsa dsa dsa dsa dsa dsa dsa dsa dsa dsa dsa dsa dsa dsa dsa dsa dsa
|
||||
dsa dsa dsa dsa dsa dsa dsa dsa dsa dsa dsa dsa dsa dsa dsa dsa dsa
|
||||
dsa dsa dsa dsa dsa dsa dsa dsa dsa dsa dsa dsa dsa dsa dsa dsa dsa
|
||||
dsa dsa dsa dsa dsa dsa dsa dsa dsa dsa dsa dsa dsa
|
||||
</FlexRow>
|
||||
<ImageButton src={pencilIco} width="20px" height="20px" />
|
||||
</Grid>
|
||||
{tagsPopUp &&
|
||||
createPortal(
|
||||
<PopUp closeHandler={() => setTagsPopUp(false)}></PopUp>,
|
||||
document.body
|
||||
)}
|
||||
</DropdownWithPopupStyle>
|
||||
);
|
||||
};
|
||||
|
||||
export default DropdownWithPopup;
|
@ -0,0 +1,14 @@
|
||||
import styled from 'styled-components';
|
||||
import { FlexColumn } from '../../../utils/containers';
|
||||
|
||||
const EntireScreenLoadingStyle = styled(FlexColumn)`
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
z-index: 1000;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: ${({ theme }) => theme.colors.white};
|
||||
`;
|
||||
|
||||
export default EntireScreenLoadingStyle;
|
@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
import EntireScreenLoadingStyle from './EntireScreenLoadingStyle';
|
||||
import Loading from '../Loading';
|
||||
|
||||
const EntireScreenLoading = () => {
|
||||
return (
|
||||
<EntireScreenLoadingStyle>
|
||||
<Loading />
|
||||
</EntireScreenLoadingStyle>
|
||||
);
|
||||
};
|
||||
|
||||
export default EntireScreenLoading;
|
0
src/components/generic/EntireScreenLoading/index.js
Normal file
0
src/components/generic/EntireScreenLoading/index.js
Normal file
@ -1,24 +1,29 @@
|
||||
import React from 'react';
|
||||
import {H1} from '../../utils/fonts';
|
||||
import { H1 } from '../../utils/fonts';
|
||||
import theme from '../../utils/theme';
|
||||
import {Link} from 'react-router-dom';
|
||||
import { Link } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const LogoStyle = styled(H1)`
|
||||
font-size: 24px;
|
||||
|
||||
@media (min-width: ${({theme}) => theme.overMobile}) {
|
||||
@media (min-width: ${({ theme }) => theme.overMobile}) {
|
||||
font-size: 32px;
|
||||
line-height: 32px;
|
||||
}
|
||||
`;
|
||||
|
||||
const Logo = () => {
|
||||
return (
|
||||
<LogoStyle as={Link} cursor='pointer' to='/' color={theme.colors.green}>
|
||||
Gonito
|
||||
</LogoStyle>
|
||||
);
|
||||
const Logo = (props) => {
|
||||
return (
|
||||
<LogoStyle
|
||||
as={props.navOptions ? Link : 'span'}
|
||||
cursor="pointer"
|
||||
to="/"
|
||||
color={theme.colors.green}
|
||||
>
|
||||
Gonito
|
||||
</LogoStyle>
|
||||
);
|
||||
};
|
||||
|
||||
export default Logo;
|
@ -63,7 +63,7 @@ const Pager = (props) => {
|
||||
as="a"
|
||||
href="#start"
|
||||
src={polygon}
|
||||
onClick={() => PREVIOUS_PAGE(props.pageNr, props.setPage)}
|
||||
onClick={() => PREVIOUS_PAGE(props.pageNr, props.setPageNr)}
|
||||
size="cover"
|
||||
backgroundColor={leftArrowVisible()}
|
||||
/>
|
||||
@ -76,7 +76,7 @@ const Pager = (props) => {
|
||||
as="a"
|
||||
href="#start"
|
||||
src={polygon}
|
||||
onClick={() => NEXT_PAGE(props.elements, props.pageNr, props.setPage)}
|
||||
onClick={() => NEXT_PAGE(props.elements, props.pageNr, props.setPageNr)}
|
||||
size="cover"
|
||||
backgroundColor={rightArrowVisible()}
|
||||
/>
|
||||
|
@ -9,14 +9,17 @@ const PopUpStyle = styled(FlexColumn)`
|
||||
z-index: 100;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background-color: ${({ theme }) => theme.colors.dark01};
|
||||
background-color: ${({ theme, backgroundColor }) =>
|
||||
backgroundColor ? backgroundColor : theme.colors.dark01};
|
||||
|
||||
.PopUpStyle__body {
|
||||
width: ${({ width }) => (width ? width : '60%')};
|
||||
min-height: ${({ minHeight }) => (minHeight ? minHeight : '50%')};
|
||||
height: ${({ height }) => (height ? height : '50%')};
|
||||
padding: ${({ padding }) => (padding ? padding : '48px')};
|
||||
margin: ${({ margin }) => (margin ? margin : '0')};
|
||||
border-radius: 12px;
|
||||
background-color: ${({ theme }) => theme.colors.white};
|
||||
justify-content: flex-start;
|
||||
}
|
||||
`;
|
||||
|
||||
@ -29,9 +32,12 @@ const PopUp = (props) => {
|
||||
|
||||
return (
|
||||
<PopUpStyle
|
||||
backgroundColor={props.backgroundColor}
|
||||
padding={props.padding}
|
||||
width={props.width}
|
||||
height={props.height}
|
||||
minHeight={props.minHeight}
|
||||
margin={props.margin}
|
||||
onClick={closePopUp}
|
||||
>
|
||||
<FlexColumn
|
||||
|
@ -28,13 +28,13 @@ const PopupMessage = (props) => {
|
||||
borderRadius="12px"
|
||||
backgroundColor={theme.colors.white}
|
||||
padding="56px"
|
||||
border={`4px solid ${theme.colors.green}`}
|
||||
border={`4px solid ${props.borderColor ? props.borderColor : theme.colors.green}`}
|
||||
>
|
||||
<FlexColumn gap="48px" margin="0 0 48px 0">
|
||||
<H3>{props.header}</H3>
|
||||
<Body>{props.message}</Body>
|
||||
</FlexColumn>
|
||||
<Button handler={confirmPopUp}>Ok</Button>
|
||||
<Button backgroundColor={props.borderColor ? props.borderColor : theme.colors.green} handler={confirmPopUp}>Ok</Button>
|
||||
</FlexColumn>
|
||||
</FlexColumn>
|
||||
);
|
||||
|
@ -18,6 +18,7 @@ const SubmitInput = (props) => {
|
||||
height="36px"
|
||||
border={`1px solid ${theme.colors.dark}`}
|
||||
shadow={theme.shadow}
|
||||
defaultValue={props.defaultValue}
|
||||
onChange={(e) => props.handler(e.target.value)}
|
||||
padding="4px"
|
||||
/>
|
||||
|
81
src/components/generic/Table/Table.js
Normal file
81
src/components/generic/Table/Table.js
Normal file
@ -0,0 +1,81 @@
|
||||
import React from 'react';
|
||||
import Media from 'react-media';
|
||||
import theme from '../../../utils/theme';
|
||||
import MobileTable from './components/MobileTable';
|
||||
import DesktopTable from './components/DesktopTable';
|
||||
import EditPopUp from './components/EditPopUp';
|
||||
import DeletePopUp from './components/DeletePopUp';
|
||||
|
||||
const Table = ({
|
||||
items,
|
||||
orderedKeys,
|
||||
popUpMessageHandler,
|
||||
sortByUpdate,
|
||||
profileInfo,
|
||||
rowFooter = true,
|
||||
}) => {
|
||||
const [, updateState] = React.useState();
|
||||
const tableUpdate = React.useCallback(() => updateState({}), []);
|
||||
const [deletedItems, setDeletedItems] = React.useState([]);
|
||||
const [deletePopUp, setDeletePopUp] = React.useState(false);
|
||||
const [editPopUp, setEditPopUp] = React.useState(false);
|
||||
const [itemToHandle, setItemToHandle] = React.useState(null);
|
||||
const itemsToRender = items.filter((item) => !deletedItems.includes(item));
|
||||
|
||||
return (
|
||||
<>
|
||||
<DeletePopUp
|
||||
item={itemToHandle}
|
||||
setDeletePopUp={setDeletePopUp}
|
||||
deletePopUp={deletePopUp}
|
||||
setDeletedItems={setDeletedItems}
|
||||
deletedItems={deletedItems}
|
||||
popUpMessageHandler={popUpMessageHandler}
|
||||
/>
|
||||
<EditPopUp
|
||||
item={itemToHandle}
|
||||
setEditPopUp={setEditPopUp}
|
||||
editPopUp={editPopUp}
|
||||
popUpMessageHandler={popUpMessageHandler}
|
||||
/>
|
||||
<Media query={theme.mobile}>
|
||||
<MobileTable
|
||||
elements={itemsToRender}
|
||||
deletePopUp={deletePopUp}
|
||||
orderedKeys={orderedKeys}
|
||||
rowFooter={rowFooter}
|
||||
deletedItems={deletedItems}
|
||||
popUpMessageHandler={popUpMessageHandler}
|
||||
itemToHandle={itemToHandle}
|
||||
sortByUpdate={sortByUpdate}
|
||||
profileInfo={profileInfo}
|
||||
tableUpdate={tableUpdate}
|
||||
setItemToHandle={setItemToHandle}
|
||||
setEditPopUp={setEditPopUp}
|
||||
setDeletePopUp={setDeletePopUp}
|
||||
setDeletedItems={setDeletedItems}
|
||||
/>
|
||||
</Media>
|
||||
<Media query={theme.desktop}>
|
||||
<DesktopTable
|
||||
elements={itemsToRender}
|
||||
deletePopUp={deletePopUp}
|
||||
orderedKeys={orderedKeys}
|
||||
rowFooter={rowFooter}
|
||||
deletedItems={deletedItems}
|
||||
popUpMessageHandler={popUpMessageHandler}
|
||||
itemToHandle={itemToHandle}
|
||||
sortByUpdate={sortByUpdate}
|
||||
profileInfo={profileInfo}
|
||||
tableUpdate={tableUpdate}
|
||||
setItemToHandle={setItemToHandle}
|
||||
setEditPopUp={setEditPopUp}
|
||||
setDeletePopUp={setDeletePopUp}
|
||||
setDeletedItems={setDeletedItems}
|
||||
/>
|
||||
</Media>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Table;
|
@ -0,0 +1,74 @@
|
||||
import { createPortal } from 'react-dom';
|
||||
import PopUp from '../../../PopUp';
|
||||
import Button from '../../../Button';
|
||||
import { Medium, H3 } from '../../../../../utils/fonts';
|
||||
import { FlexColumn, FlexRow } from '../../../../../utils/containers';
|
||||
import theme from '../../../../../utils/theme';
|
||||
import deleteSubmission from '../../../../../api/deleteSubmission';
|
||||
|
||||
const deleteItem = async (
|
||||
item,
|
||||
setDeletePopUp,
|
||||
deletedItems,
|
||||
setDeletedItems,
|
||||
popUpMessageHandler
|
||||
) => {
|
||||
setDeletePopUp(false);
|
||||
await deleteSubmission(
|
||||
item,
|
||||
deletedItems,
|
||||
setDeletedItems,
|
||||
popUpMessageHandler
|
||||
);
|
||||
};
|
||||
|
||||
const DeletePopUp = ({
|
||||
deletePopUp,
|
||||
setDeletePopUp,
|
||||
item,
|
||||
deletedItems,
|
||||
setDeletedItems,
|
||||
popUpMessageHandler,
|
||||
}) => {
|
||||
if (deletePopUp) {
|
||||
return createPortal(
|
||||
<PopUp
|
||||
width="30%"
|
||||
height="30vh"
|
||||
padding="32px"
|
||||
backgroundColor={theme.colors.dark003}
|
||||
closeHandler={() => setDeletePopUp(false)}
|
||||
>
|
||||
<FlexColumn width="100%" height="100%" gap="48px">
|
||||
<H3>Warning</H3>
|
||||
<Medium>
|
||||
Are you sure want to delete submission with id: {item.id}?
|
||||
</Medium>
|
||||
<FlexRow gap="48px">
|
||||
<Button
|
||||
handler={() =>
|
||||
deleteItem(
|
||||
item,
|
||||
deletedItems,
|
||||
setDeletedItems,
|
||||
popUpMessageHandler
|
||||
)
|
||||
}
|
||||
>
|
||||
Yes
|
||||
</Button>
|
||||
<Button
|
||||
handler={() => setDeletePopUp(false)}
|
||||
backgroundColor={theme.colors.dark}
|
||||
>
|
||||
No
|
||||
</Button>
|
||||
</FlexRow>
|
||||
</FlexColumn>
|
||||
</PopUp>,
|
||||
document.body
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default DeletePopUp;
|
@ -0,0 +1 @@
|
||||
export { default } from './DeletePopUp';
|
@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import TableStyle from '../../styles/TableStyle';
|
||||
import TableHeader from '../TableHeader/TableHeader';
|
||||
import TableRowItems from '../TableRowItems/TableRowItems';
|
||||
import TableRowFooter from '../TableRowFooter/TableRowFooter';
|
||||
import RowsBackgroundStyle from '../../styles/RowsBackgroundStyle';
|
||||
|
||||
const DesktopTable = (props) => {
|
||||
return (
|
||||
<TableStyle rowFooter={props.rowFooter}>
|
||||
<tbody>
|
||||
<TableHeader
|
||||
orderedKeys={props.orderedKeys}
|
||||
sortByUpdate={props.sortByUpdate}
|
||||
tableUpdate={props.tableUpdate}
|
||||
/>
|
||||
{props.elements.map((item, i) => {
|
||||
return (
|
||||
<tr key={`table-row-${i}`} className="TableStyle__tr">
|
||||
<TableRowItems
|
||||
orderedKeys={props.orderedKeys}
|
||||
item={item}
|
||||
i={i}
|
||||
/>
|
||||
<TableRowFooter
|
||||
deleteItem={() => {
|
||||
props.setItemToHandle(item);
|
||||
props.setDeletePopUp(true);
|
||||
}}
|
||||
editItem={() => {
|
||||
props.setItemToHandle(item);
|
||||
props.setEditPopUp(true);
|
||||
}}
|
||||
rowFooter={props.rowFooter}
|
||||
profileInfo={props.profileInfo}
|
||||
item={item}
|
||||
i={i}
|
||||
/>
|
||||
<RowsBackgroundStyle i={i} as="td" />
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</TableStyle>
|
||||
);
|
||||
};
|
||||
|
||||
export default DesktopTable;
|
@ -0,0 +1 @@
|
||||
export { default } from './DesktopTable';
|
105
src/components/generic/Table/components/EditPopUp/EditPopUp.js
Normal file
105
src/components/generic/Table/components/EditPopUp/EditPopUp.js
Normal file
@ -0,0 +1,105 @@
|
||||
import { createPortal } from 'react-dom';
|
||||
import PopUp from '../../../PopUp';
|
||||
import Button from '../../../Button';
|
||||
import { H3 } from '../../../../../utils/fonts';
|
||||
import { FlexColumn, FlexRow } from '../../../../../utils/containers';
|
||||
import theme from '../../../../../utils/theme';
|
||||
import SubmitInput from '../../../SubmitInput';
|
||||
import TagsChoose from '../../../../../pages/Submit/components/TagsChoose/TagsChoose';
|
||||
import editSubmission from '../../../../../api/editSubmission';
|
||||
import getTags from '../../../../../api/getTags';
|
||||
import React from 'react';
|
||||
|
||||
const editSubmissionHandler = async (
|
||||
item,
|
||||
setEditPopUp,
|
||||
tagsToEdit,
|
||||
description,
|
||||
popUpMessageHandler
|
||||
) => {
|
||||
setEditPopUp(false);
|
||||
let tags = '';
|
||||
if (tagsToEdit) {
|
||||
tags = tagsToEdit.join(',');
|
||||
} else {
|
||||
if (item?.tags) {
|
||||
tags = item.tags.map((tag) => tag.name).join(',');
|
||||
}
|
||||
}
|
||||
await editSubmission(item.id, tags, description, popUpMessageHandler);
|
||||
};
|
||||
|
||||
const EditPopUp = ({ editPopUp, setEditPopUp, item, popUpMessageHandler }) => {
|
||||
const [tags, setTags] = React.useState([]);
|
||||
const [tagsToEdit, setTagsToEdit] = React.useState(item?.tags?.slice());
|
||||
const [description, setDescription] = React.useState(
|
||||
item?.description?.slice()
|
||||
);
|
||||
|
||||
React.useMemo(() => {
|
||||
getTags(setTags);
|
||||
}, []);
|
||||
|
||||
if (editPopUp) {
|
||||
return createPortal(
|
||||
<PopUp
|
||||
width="30%"
|
||||
height="50vh"
|
||||
padding="32px"
|
||||
backgroundColor={theme.colors.dark003}
|
||||
closeHandler={() => setEditPopUp(false)}
|
||||
>
|
||||
<FlexColumn width="100%" height="100%" gap="48px">
|
||||
<H3>Editing submission</H3>
|
||||
<SubmitInput
|
||||
label="Description"
|
||||
defaultValue={item.description}
|
||||
handler={(value) => {
|
||||
setDescription(value);
|
||||
}}
|
||||
/>
|
||||
<TagsChoose
|
||||
label="Submission tags"
|
||||
updateTags={(submissionTags, globalTags) => {
|
||||
setTagsToEdit(submissionTags);
|
||||
setTags(globalTags);
|
||||
}}
|
||||
tags={tags ? tags : []}
|
||||
submissionTags={tagsToEdit?.length ? tagsToEdit : item.tags}
|
||||
/>
|
||||
<FlexRow gap="48px">
|
||||
<Button
|
||||
width="100px"
|
||||
height="32px"
|
||||
handler={() =>
|
||||
editSubmissionHandler(
|
||||
item,
|
||||
setEditPopUp,
|
||||
tagsToEdit,
|
||||
description,
|
||||
popUpMessageHandler
|
||||
)
|
||||
}
|
||||
>
|
||||
Confirm
|
||||
</Button>
|
||||
<Button
|
||||
width="100px"
|
||||
height="32px"
|
||||
handler={() => {
|
||||
setTagsToEdit([]);
|
||||
setEditPopUp(false);
|
||||
}}
|
||||
backgroundColor={theme.colors.dark}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</FlexRow>
|
||||
</FlexColumn>
|
||||
</PopUp>,
|
||||
document.body
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default EditPopUp;
|
@ -0,0 +1 @@
|
||||
export { default } from './EditPopUp';
|
@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import TableRowItems from '../TableRowItems/TableRowItems';
|
||||
import TableRowFooter from '../TableRowFooter/TableRowFooter';
|
||||
import MobileTableStyle from './MobileTableStyle';
|
||||
|
||||
const MobileTable = (props) => {
|
||||
return (
|
||||
<MobileTableStyle as="table">
|
||||
{props.elements.map((item, i) => {
|
||||
return (
|
||||
<tr key={`table-row-${i}`} className="TableStyle__tr">
|
||||
<TableRowItems orderedKeys={props.orderedKeys} item={item} i={i} />
|
||||
<TableRowFooter
|
||||
deleteItem={() => {
|
||||
props.setItemToHandle(item);
|
||||
props.setDeletePopUp(true);
|
||||
}}
|
||||
editItem={() => {
|
||||
props.setItemToHandle(item);
|
||||
props.setEditPopUp(true);
|
||||
}}
|
||||
rowFooter={props.rowFooter}
|
||||
item={item}
|
||||
i={i}
|
||||
/>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</MobileTableStyle>
|
||||
);
|
||||
};
|
||||
|
||||
export default MobileTable;
|
@ -0,0 +1,66 @@
|
||||
import styled from 'styled-components';
|
||||
import { Container } from '../../../../../utils/containers';
|
||||
|
||||
const MobileTableStyle = styled(Container)`
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 32px 0;
|
||||
tr:nth-of-type(odd) {
|
||||
background: ${({ theme }) => theme.colors.dark03};
|
||||
}
|
||||
th {
|
||||
background: ${({ theme }) => theme.colors.dark05};
|
||||
color: ${({ theme }) => theme.colors.white};
|
||||
}
|
||||
td,
|
||||
th {
|
||||
padding: 6px;
|
||||
border: 1px solid ${({ theme }) => theme.colors.white};
|
||||
text-align: left;
|
||||
}
|
||||
thead,
|
||||
tbody,
|
||||
th,
|
||||
tr,
|
||||
td {
|
||||
display: block;
|
||||
}
|
||||
thead tr {
|
||||
position: absolute;
|
||||
top: -9999px;
|
||||
left: -9999px;
|
||||
}
|
||||
td {
|
||||
border: none;
|
||||
border-bottom: 1px solid ${({ theme }) => theme.colors.dark01};
|
||||
position: relative;
|
||||
padding-left: 50%;
|
||||
}
|
||||
.mobile-table-header {
|
||||
font-weight: 500;
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: 6px;
|
||||
width: 45%;
|
||||
padding-right: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.TableStyle__row-footer {
|
||||
padding: 0 8px;
|
||||
min-height: 36px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.TableStyle__tag {
|
||||
color: ${({ theme }) => theme.colors.white};
|
||||
background-color: ${({ theme }) => theme.colors.green08};
|
||||
padding: 4px;
|
||||
border-radius: 2px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.TableStyle__tags-container {
|
||||
gap: 4px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default MobileTableStyle;
|
@ -0,0 +1 @@
|
||||
export { default } from './MobileTable';
|
@ -0,0 +1,50 @@
|
||||
import React from 'react';
|
||||
import { FlexRow } from '../../../../../utils/containers';
|
||||
import ColumnFilterIcon from '../../../ColumnFilterIcon';
|
||||
|
||||
const TableHeader = (props) => {
|
||||
const [activeIcon, setActiveIcon] = React.useState(null);
|
||||
const [rotateActiveIcon, setRotateActiveIcon] = React.useState(false);
|
||||
|
||||
return (
|
||||
<tr className="TableStyle__tr-header">
|
||||
{props.orderedKeys.map((keyValue, i) => {
|
||||
return (
|
||||
<th
|
||||
key={`table-header-${i}`}
|
||||
className="TableStyle__th"
|
||||
onClick={() => {
|
||||
if (activeIcon === i) {
|
||||
let newRotateActiveIcon = !rotateActiveIcon;
|
||||
setRotateActiveIcon(newRotateActiveIcon);
|
||||
} else {
|
||||
setRotateActiveIcon(false);
|
||||
}
|
||||
setActiveIcon(i);
|
||||
props.sortByUpdate(keyValue);
|
||||
props.tableUpdate();
|
||||
}}
|
||||
>
|
||||
<FlexRow as="span" alignmentX="flex-start" gap="8px" width="100%">
|
||||
{keyValue}
|
||||
<FlexRow
|
||||
as="span"
|
||||
className="TableStyle__sort-button"
|
||||
column={keyValue}
|
||||
>
|
||||
<ColumnFilterIcon
|
||||
index={i}
|
||||
active={activeIcon}
|
||||
rotateIcon={rotateActiveIcon}
|
||||
/>
|
||||
</FlexRow>
|
||||
</FlexRow>
|
||||
</th>
|
||||
);
|
||||
})}
|
||||
<FlexRow className="TableStyle__line" as="td" />
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
export default TableHeader;
|
@ -0,0 +1 @@
|
||||
export { default } from './TableHeader';
|
@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import { FlexRow, Svg } from '../../../../../utils/containers';
|
||||
import theme from '../../../../../utils/theme';
|
||||
|
||||
const TableRowButtons = ({ buttons, i, active, buttonAccessMessage }) => {
|
||||
const getButtonTitle = (defaultTitle) => {
|
||||
if (buttonAccessMessage === 'default') {
|
||||
return defaultTitle;
|
||||
} else return buttonAccessMessage;
|
||||
};
|
||||
|
||||
return (
|
||||
<FlexRow gap="12px" position='relative'>
|
||||
{buttons.map((button, j) => {
|
||||
return (
|
||||
<Svg
|
||||
title={getButtonTitle(button.title)}
|
||||
key={`table-item-button-${i}-${j}`}
|
||||
onClick={active ? button.handler : null}
|
||||
src={button.icon}
|
||||
backgroundColor={active ? theme.colors.dark : theme.colors.dark05}
|
||||
cursor={active ? 'pointer' : 'auto'}
|
||||
size="cover"
|
||||
width="16px"
|
||||
height="16px"
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</FlexRow>
|
||||
);
|
||||
};
|
||||
|
||||
export default TableRowButtons;
|
@ -0,0 +1 @@
|
||||
export { default } from './TableRowButtons';
|
@ -0,0 +1,46 @@
|
||||
import React from 'react';
|
||||
import { FlexRow } from '../../../../../utils/containers';
|
||||
import TableRowTags from '../TableRowTags/TableRowTags';
|
||||
import TableRowButtons from '../TableRowButtons/TableRowButtons';
|
||||
import pensilIco from '../../../../../assets/pencil_ico.svg';
|
||||
import deleteIco from '../../../../../assets/delete_ico.svg';
|
||||
import KeyCloakService from '../../../../../services/KeyCloakService';
|
||||
|
||||
const TableRowFooter = ({ rowFooter, item, i, deleteItem, editItem, profileInfo }) => {
|
||||
const buttonsActive = () => {
|
||||
if (!KeyCloakService.isLoggedIn()) return false;
|
||||
else if (
|
||||
profileInfo?.preferred_username !== item.submitter &&
|
||||
profileInfo?.name !== item.submitter
|
||||
) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
const getButtonAccessMessage = () => {
|
||||
if (!KeyCloakService.isLoggedIn()) {
|
||||
return "You must be logged in to use this option.";
|
||||
} else if (profileInfo?.preferred_username !== item.submitter &&
|
||||
profileInfo?.name !== item.submitter) {
|
||||
return "You don't have permission to use this option.";
|
||||
} return "default";
|
||||
};
|
||||
|
||||
if (rowFooter) {
|
||||
return (
|
||||
<FlexRow className="TableStyle__row-footer">
|
||||
<TableRowTags item={item} i={i} />
|
||||
<TableRowButtons
|
||||
buttons={[
|
||||
{ title: "edit", icon: pensilIco, handler: () => editItem() },
|
||||
{ title: "delete", icon: deleteIco, handler: () => deleteItem() },
|
||||
]}
|
||||
active={buttonsActive()}
|
||||
buttonAccessMessage={getButtonAccessMessage()}
|
||||
i={i}
|
||||
/>
|
||||
</FlexRow>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default TableRowFooter;
|
@ -0,0 +1 @@
|
||||
export { default } from './TableRowFooter';
|
@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
RENDER_WHEN,
|
||||
RENDER_METRIC_VALUE,
|
||||
IS_MOBILE,
|
||||
} from '../../../../../utils/globals';
|
||||
import { Container } from '../../../../../utils/containers';
|
||||
|
||||
const TableRowItems = ({ orderedKeys, item, i }) => {
|
||||
const renderValue = (keyValue) => {
|
||||
if (keyValue === 'when') {
|
||||
return RENDER_WHEN(item[keyValue]);
|
||||
} else {
|
||||
return RENDER_METRIC_VALUE(item[keyValue]);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{orderedKeys.map((keyValue, j) => {
|
||||
return (
|
||||
<td key={`table-item-${i}-${j}`} className="TableStyle__td">
|
||||
{IS_MOBILE() && (
|
||||
<Container as="span" className="mobile-table-header">
|
||||
{keyValue}
|
||||
</Container>
|
||||
)}
|
||||
{renderValue(keyValue)}
|
||||
{keyValue === '#' && i + 1}
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TableRowItems;
|
@ -0,0 +1 @@
|
||||
export { default } from './TableRowItems';
|
@ -0,0 +1,12 @@
|
||||
import renderTags from './renderTags';
|
||||
import { FlexRow } from '../../../../../utils/containers';
|
||||
|
||||
const TableRowTags = ({ item, i }) => {
|
||||
return (
|
||||
<FlexRow as="span" className="TableStyle__tags-container">
|
||||
{renderTags(item.tags)}
|
||||
</FlexRow>
|
||||
);
|
||||
};
|
||||
|
||||
export default TableRowTags;
|
@ -0,0 +1 @@
|
||||
export { default } from './TableRowTags';
|
@ -0,0 +1,15 @@
|
||||
import { FlexRow } from '../../../../../utils/containers';
|
||||
|
||||
const renderTags = (tags, i) => {
|
||||
if (tags && tags.length > 0) {
|
||||
return tags.map((tag, j) => {
|
||||
return (
|
||||
<FlexRow className="TableStyle__tag" key={`submissionTag-${i}-${j}`}>
|
||||
{tag.name}
|
||||
</FlexRow>
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default renderTags;
|
1
src/components/generic/Table/index.js
Normal file
1
src/components/generic/Table/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './Table';
|
14
src/components/generic/Table/styles/RowsBackgroundStyle.js
Normal file
14
src/components/generic/Table/styles/RowsBackgroundStyle.js
Normal file
@ -0,0 +1,14 @@
|
||||
import styled from 'styled-components';
|
||||
import { FlexRow } from '../../../../utils/containers';
|
||||
|
||||
const RowsBackgroundStyle = styled(FlexRow)`
|
||||
width: calc(100% + 12px);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -6px;
|
||||
height: 100%;
|
||||
background-color: ${({ theme, i }) =>
|
||||
i % 2 === 0 ? theme.colors.dark01 : 'transparent'};
|
||||
`;
|
||||
|
||||
export default RowsBackgroundStyle;
|
66
src/components/generic/Table/styles/TableStyle.js
Normal file
66
src/components/generic/Table/styles/TableStyle.js
Normal file
@ -0,0 +1,66 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const TableStyle = styled.table`
|
||||
border-collapse: separate;
|
||||
border-spacing: 12px 0;
|
||||
width: 100%;
|
||||
|
||||
.TableStyle__th {
|
||||
cursor: pointer;
|
||||
* {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.TableStyle__tr-header {
|
||||
height: 48px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.TableStyle__tr {
|
||||
position: relative;
|
||||
height: ${({ rowFooter }) => (rowFooter ? '72px' : 'auto')};
|
||||
}
|
||||
|
||||
.TableStyle__td {
|
||||
padding: ${({ rowFooter }) => (rowFooter ? '4px 0 32px 0' : '12px 0')};
|
||||
margin: 0 0 0 2px;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.TableStyle_line {
|
||||
position: absolute;
|
||||
top: 94%;
|
||||
bottom: ${({ bottom }) => (bottom ? bottom : 'auto')};
|
||||
left: -6px;
|
||||
width: calc(100% + 12px);
|
||||
background-color: ${({ theme }) => theme.colors.dark04};
|
||||
height: 3px;
|
||||
box-shadow: ${({ theme }) => theme.shadow};
|
||||
}
|
||||
|
||||
.TableStyle__tag {
|
||||
color: ${({ theme }) => theme.colors.white};
|
||||
background-color: ${({ theme }) => theme.colors.green08};
|
||||
padding: 4px;
|
||||
border-radius: 2px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.TableStyle__row-footer {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
position: absolute;
|
||||
top: 55%;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.TableStyle__tags-container {
|
||||
gap: 4px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default TableStyle;
|
@ -1,6 +1,12 @@
|
||||
import React from 'react';
|
||||
import {Container, FlexColumn, FlexRow, Svg, TransBack} from '../../utils/containers';
|
||||
import {Body, Medium} from '../../utils/fonts';
|
||||
import {
|
||||
Container,
|
||||
FlexColumn,
|
||||
FlexRow,
|
||||
Svg,
|
||||
TransBack,
|
||||
} from '../../utils/containers';
|
||||
import { Body, Medium } from '../../utils/fonts';
|
||||
import theme from '../../utils/theme';
|
||||
import userIco from '../../assets/user_ico.svg';
|
||||
import KeyCloakService from '../../services/KeyCloakService';
|
||||
@ -15,8 +21,8 @@ const LoggedBarStyle = styled(FlexColumn)`
|
||||
right: 0;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
background-color: ${({theme}) => theme.colors.white};
|
||||
box-shadow: ${({theme}) => theme.shadow};
|
||||
background-color: ${({ theme }) => theme.colors.white};
|
||||
box-shadow: ${({ theme }) => theme.shadow};
|
||||
z-index: 3;
|
||||
|
||||
button {
|
||||
@ -32,11 +38,11 @@ const LoggedBarStyle = styled(FlexColumn)`
|
||||
|
||||
&:hover {
|
||||
li {
|
||||
color: ${({theme}) => theme.colors.green};
|
||||
color: ${({ theme }) => theme.colors.green};
|
||||
}
|
||||
|
||||
div {
|
||||
background-color: ${({theme}) => theme.colors.green};
|
||||
background-color: ${({ theme }) => theme.colors.green};
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,37 +53,61 @@ const LoggedBarStyle = styled(FlexColumn)`
|
||||
`;
|
||||
|
||||
const LoggedBar = (props) => {
|
||||
return (
|
||||
<TransBack transition='transform' translateX={props.visible}
|
||||
onClick={props.loggedBarVisibleHandler} animTime='0.2s'>
|
||||
<LoggedBarStyle onMouseEnter={props.loggedBarHoverTrue}
|
||||
onMouseLeave={props.loggedBarHoverFalse}>
|
||||
<FlexRow alignmentX='flex-start' alignmentY='flex-end'
|
||||
gap='16px' width='100%' padding='12px 16px'>
|
||||
<Svg src={userIco} width='32px' height='32px' backgroundColor={theme.colors.dark} size='cover'/>
|
||||
<Medium as='p'>
|
||||
{props.username}
|
||||
</Medium>
|
||||
</FlexRow>
|
||||
<Container width='90%' backgroundColor={theme.colors.dark05} height='1px'/>
|
||||
<FlexColumn as='ul' onClick={() => console.log('profile click')}
|
||||
gap='24px' padding='32px 24px' alignmentX='flex-start'>
|
||||
<FlexRow as='button' gap='16px'>
|
||||
<Svg width='16px' height='16px' src={userIco} size='cover'/>
|
||||
<Body as='li'>
|
||||
Profile
|
||||
</Body>
|
||||
</FlexRow>
|
||||
<FlexRow as='button' onClick={props.visible === '0' ? KeyCloakService.doLogout : null} gap='16px'>
|
||||
<Svg width='16px' height='16px' src={loginIco} rotate='180deg'/>
|
||||
<Body as='li'>
|
||||
Sign out
|
||||
</Body>
|
||||
</FlexRow>
|
||||
</FlexColumn>
|
||||
</LoggedBarStyle>
|
||||
</TransBack>
|
||||
);
|
||||
return (
|
||||
<TransBack
|
||||
transition="transform"
|
||||
translateX={props.visible}
|
||||
onClick={props.loggedBarVisibleHandler}
|
||||
animTime="0.2s"
|
||||
>
|
||||
<LoggedBarStyle
|
||||
onMouseEnter={props.loggedBarHoverTrue}
|
||||
onMouseLeave={props.loggedBarHoverFalse}
|
||||
>
|
||||
<FlexRow
|
||||
alignmentX="flex-start"
|
||||
alignmentY="flex-end"
|
||||
gap="16px"
|
||||
width="100%"
|
||||
padding="12px 16px"
|
||||
>
|
||||
<Svg
|
||||
src={userIco}
|
||||
width="32px"
|
||||
height="32px"
|
||||
backgroundColor={theme.colors.dark}
|
||||
size="cover"
|
||||
/>
|
||||
<Medium as="p">{props.username}</Medium>
|
||||
</FlexRow>
|
||||
<Container
|
||||
width="90%"
|
||||
backgroundColor={theme.colors.dark05}
|
||||
height="1px"
|
||||
/>
|
||||
<FlexColumn
|
||||
as="ul"
|
||||
onClick={KeyCloakService.goToProfile}
|
||||
gap="24px"
|
||||
padding="32px 24px"
|
||||
alignmentX="flex-start"
|
||||
>
|
||||
<FlexRow as="button" gap="16px">
|
||||
<Svg width="16px" height="16px" src={userIco} size="cover" />
|
||||
<Body as="li">Profile</Body>
|
||||
</FlexRow>
|
||||
<FlexRow
|
||||
as="button"
|
||||
onClick={props.visible === '0' ? KeyCloakService.doLogout : null}
|
||||
gap="16px"
|
||||
>
|
||||
<Svg width="16px" height="16px" src={loginIco} rotate="180deg" />
|
||||
<Body as="li">Sign out</Body>
|
||||
</FlexRow>
|
||||
</FlexColumn>
|
||||
</LoggedBarStyle>
|
||||
</TransBack>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoggedBar;
|
@ -43,6 +43,7 @@ const NavBar = (props) => {
|
||||
const mobileMenuHoverFalse = () => {
|
||||
setMobileMenuHover(false);
|
||||
};
|
||||
|
||||
const toggleNavMenu = () => {
|
||||
if (navMenuTranslateY === 'calc(-100vh - 42px)') setNavMenuTranslateY('0');
|
||||
else if (!mobileMenuHover) setNavMenuTranslateY('calc(-100vh - 42px)');
|
||||
@ -51,51 +52,57 @@ const NavBar = (props) => {
|
||||
return (
|
||||
<NavBarStyle as="header">
|
||||
<FlexRow height="100%" alignmentX="space-between" as="nav">
|
||||
<Logo />
|
||||
<MenuButton as="button" onClick={toggleNavMenu} />
|
||||
<FlexRow as="ul" className="ul-desktop" gap="32px">
|
||||
<FlexRow as={Link} to={CHALLENGES_PAGE} gap="16px">
|
||||
<Svg width="16px" height="16px" src={cupIco} />
|
||||
<Menu as="li">Challenges</Menu>
|
||||
</FlexRow>
|
||||
<FlexRow as={Link} to={POLICY_PRIVACY_PAGE} gap="12px">
|
||||
<Svg size="cover" width="16px" height="16px" src={policyIco} />
|
||||
<Menu as="li">Privacy policy</Menu>
|
||||
</FlexRow>
|
||||
{!KeyCloakService.isLoggedIn() ? (
|
||||
<FlexRow
|
||||
as="button"
|
||||
onClick={() =>
|
||||
props.popUpMessageHandler(
|
||||
'Reminder',
|
||||
'Remember to check your spam mailbox to confirm your account.',
|
||||
() => KeyCloakService.doRegister
|
||||
)
|
||||
}
|
||||
gap="16px"
|
||||
>
|
||||
<Svg width="16px" height="16px" src={registerIco} />
|
||||
<Menu as="li">Register</Menu>
|
||||
<Logo navOptions={props.navOptions} />
|
||||
{props.navOptions && (
|
||||
<>
|
||||
<MenuButton as="button" onClick={toggleNavMenu} />
|
||||
<FlexRow as="ul" className="ul-desktop" gap="32px">
|
||||
<FlexRow as={Link} to={CHALLENGES_PAGE} gap="16px">
|
||||
<Svg width="16px" height="16px" src={cupIco} />
|
||||
<Menu as="li">Challenges</Menu>
|
||||
</FlexRow>
|
||||
<FlexRow as={Link} to={POLICY_PRIVACY_PAGE} gap="12px">
|
||||
<Svg size="cover" width="16px" height="16px" src={policyIco} />
|
||||
<Menu as="li">Privacy policy</Menu>
|
||||
</FlexRow>
|
||||
{!KeyCloakService.isLoggedIn() && (
|
||||
<FlexRow
|
||||
as="button"
|
||||
onClick={() =>
|
||||
props.popUpMessageHandler(
|
||||
'Reminder',
|
||||
'Remember to check your spam mailbox to confirm your account.',
|
||||
() => KeyCloakService.doRegister
|
||||
)
|
||||
}
|
||||
gap="16px"
|
||||
>
|
||||
<Svg width="16px" height="16px" src={registerIco} />
|
||||
<Menu as="li">Register</Menu>
|
||||
</FlexRow>
|
||||
)}
|
||||
{KeyCloakService.isLoggedIn() ? (
|
||||
<Svg
|
||||
as="button"
|
||||
onClick={props.loggedBarVisibleHandler}
|
||||
width="32px"
|
||||
height="32px"
|
||||
src={userIco}
|
||||
margin="0 16px 0 0"
|
||||
/>
|
||||
) : (
|
||||
<FlexRow
|
||||
as="button"
|
||||
onClick={KeyCloakService.doLogin}
|
||||
gap="16px"
|
||||
>
|
||||
<Svg width="16px" height="16px" src={loginIco} />
|
||||
<Menu as="li">Sign in</Menu>
|
||||
</FlexRow>
|
||||
)}
|
||||
</FlexRow>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
{KeyCloakService.isLoggedIn() ? (
|
||||
<Svg
|
||||
as="button"
|
||||
onClick={props.loggedBarVisibleHandler}
|
||||
width="32px"
|
||||
height="32px"
|
||||
src={userIco}
|
||||
margin="0 16px 0 0"
|
||||
/>
|
||||
) : (
|
||||
<FlexRow as="button" onClick={KeyCloakService.doLogin} gap="16px">
|
||||
<Svg width="16px" height="16px" src={loginIco} />
|
||||
<Menu as="li">Sign in</Menu>
|
||||
</FlexRow>
|
||||
)}
|
||||
</FlexRow>
|
||||
</>
|
||||
)}
|
||||
</FlexRow>
|
||||
<MobileNavMenu
|
||||
mobileMenuHoverTrue={mobileMenuHoverTrue}
|
||||
|
@ -1,244 +0,0 @@
|
||||
import React from 'react';
|
||||
import theme from '../../../utils/theme';
|
||||
import Media from 'react-media';
|
||||
import { FlexColumn } from '../../../utils/containers';
|
||||
import { H2 } from '../../../utils/fonts';
|
||||
import {
|
||||
CALC_PAGES,
|
||||
EVALUATIONS_FORMAT,
|
||||
RENDER_WHEN,
|
||||
IS_MOBILE,
|
||||
} from '../../../utils/globals';
|
||||
import Loading from '../../generic/Loading';
|
||||
import Pager from '../../generic/Pager';
|
||||
import Table from '../Table';
|
||||
import Search from '../../generic/Search';
|
||||
import allEntriesSearchQueryHandler from './allEntriesSearchQueryHandler';
|
||||
import getAllEntries from '../../../api/getAllEntries';
|
||||
|
||||
const AllEntries = (props) => {
|
||||
const [entriesFromApi, setEntriesFromApi] = React.useState([]);
|
||||
const [entriesAll, setEntriesAll] = React.useState([]);
|
||||
const [entries, setEntries] = React.useState([]);
|
||||
const [pageNr, setPageNr] = React.useState(1);
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [scoresSorted, setScoresSorted] = React.useState([]);
|
||||
const [submitterSorted, setSubmitterSorted] = React.useState(false);
|
||||
const [whenSorted, setWhenSorted] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (props.challengeName) challengeDataRequest(props.challengeName);
|
||||
}, [props.challengeName]);
|
||||
|
||||
const challengeDataRequest = (challengeName) => {
|
||||
getAllEntries(challengeName, setEntriesFromApi, setEntriesAll);
|
||||
getAllEntries(
|
||||
challengeName,
|
||||
undefined,
|
||||
setEntries,
|
||||
setLoading,
|
||||
setScoresSorted
|
||||
);
|
||||
};
|
||||
|
||||
const getPossibleMetrics = () => {
|
||||
let metrics = [];
|
||||
if (entriesFromApi.tests) {
|
||||
for (let test of entriesFromApi.tests) {
|
||||
let myEval = `${test.metric}.${test.name}`;
|
||||
if (myEval && !metrics.includes(myEval)) {
|
||||
metrics.push(myEval);
|
||||
}
|
||||
}
|
||||
}
|
||||
return metrics;
|
||||
};
|
||||
|
||||
const getAllEntriesHeader = () => {
|
||||
let header = ['#', 'submitter'];
|
||||
if (IS_MOBILE()) header.push('when');
|
||||
for (let metric of getPossibleMetrics()) {
|
||||
header.push(metric);
|
||||
}
|
||||
if (!IS_MOBILE()) header.push('when');
|
||||
return header;
|
||||
};
|
||||
|
||||
const searchQueryHandler = (event) => {
|
||||
allEntriesSearchQueryHandler(event, entriesAll, setPageNr, setEntries);
|
||||
};
|
||||
|
||||
const sortByUpdate = (elem, i) => {
|
||||
let newEntries = entries;
|
||||
switch (elem) {
|
||||
case '#':
|
||||
break;
|
||||
case 'submitter':
|
||||
if (submitterSorted) {
|
||||
setSubmitterSorted(false);
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.submitter.toLowerCase() < b.submitter.toLowerCase()
|
||||
? 1
|
||||
: b.submitter.toLowerCase() < a.submitter.toLowerCase()
|
||||
? -1
|
||||
: 0
|
||||
);
|
||||
} else {
|
||||
setSubmitterSorted(true);
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.submitter.toLowerCase() > b.submitter.toLowerCase()
|
||||
? 1
|
||||
: b.submitter.toLowerCase() > a.submitter.toLowerCase()
|
||||
? -1
|
||||
: 0
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'when':
|
||||
if (whenSorted) {
|
||||
setWhenSorted(false);
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.when < b.when ? 1 : b.when < a.when ? -1 : 0
|
||||
);
|
||||
} else {
|
||||
setWhenSorted(true);
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.when > b.when ? 1 : b.when > a.when ? -1 : 0
|
||||
);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
let metricIndex = getPossibleMetrics().indexOf(elem);
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
let newScoresSorted = scoresSorted;
|
||||
if (scoresSorted[metricIndex]) {
|
||||
newEntries = newEntries.sort(
|
||||
(a, b) =>
|
||||
(b.evaluations ? b.evaluations[elem] : -1) -
|
||||
(a.evaluations ? a.evaluations[elem] : -1)
|
||||
);
|
||||
newScoresSorted[metricIndex] = false;
|
||||
setScoresSorted(newScoresSorted);
|
||||
} else {
|
||||
newEntries = newEntries.sort(
|
||||
(a, b) =>
|
||||
(a.evaluations ? a.evaluations[elem] : -1) -
|
||||
(b.evaluations ? b.evaluations[elem] : -1)
|
||||
);
|
||||
newScoresSorted[metricIndex] = true;
|
||||
setScoresSorted(newScoresSorted);
|
||||
}
|
||||
break;
|
||||
}
|
||||
setEntries(newEntries);
|
||||
};
|
||||
|
||||
const mobileRender = () => {
|
||||
return (
|
||||
<FlexColumn padding="24px 12px" width="70%" as="section" id="start">
|
||||
<H2 as="h2" margin="0 0 12px 0">
|
||||
All Entries
|
||||
</H2>
|
||||
{!loading ? (
|
||||
<>
|
||||
<Search searchQueryHandler={searchQueryHandler} />
|
||||
<Table
|
||||
challengeName={props.challengeName}
|
||||
headerElements={getAllEntriesHeader()}
|
||||
possibleMetrics={getPossibleMetrics()}
|
||||
tableType="allEntries"
|
||||
gridTemplateColumns={
|
||||
'1fr ' + '4fr '.repeat(getAllEntriesHeader().length - 1)
|
||||
}
|
||||
staticColumnElements={[
|
||||
{ name: 'id', format: null, order: 1, align: 'left' },
|
||||
{ name: 'submitter', format: null, order: 2, align: 'left' },
|
||||
{ name: 'when', format: RENDER_WHEN, order: 5, align: 'right' },
|
||||
]}
|
||||
iterableColumnElement={{
|
||||
name: 'evaluations',
|
||||
format: EVALUATIONS_FORMAT,
|
||||
order: 3,
|
||||
align: 'left',
|
||||
}}
|
||||
pageNr={pageNr}
|
||||
elements={entries}
|
||||
sortByUpdate={sortByUpdate}
|
||||
/>
|
||||
<Pager
|
||||
pageNr={pageNr}
|
||||
elements={entries}
|
||||
setPageNr={setPageNr}
|
||||
width="48px"
|
||||
borderRadius="64px"
|
||||
pages={CALC_PAGES(entries)}
|
||||
number={`${pageNr} / ${CALC_PAGES(entries)}`}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Loading />
|
||||
)}
|
||||
</FlexColumn>
|
||||
);
|
||||
};
|
||||
|
||||
const desktopRender = () => {
|
||||
return (
|
||||
<FlexColumn padding="24px" as="section" width="100%" maxWidth="1600px">
|
||||
<H2 as="h2" margin="0 0 32px 0">
|
||||
All Entries
|
||||
</H2>
|
||||
{!loading ? (
|
||||
<>
|
||||
<Search searchQueryHandler={searchQueryHandler} />
|
||||
<Table
|
||||
challengeName={props.challengeName}
|
||||
headerElements={getAllEntriesHeader()}
|
||||
possibleMetrics={getPossibleMetrics()}
|
||||
gridTemplateColumns={
|
||||
'1fr 3fr ' + '3fr '.repeat(getPossibleMetrics().length) + ' 3fr'
|
||||
}
|
||||
user={props.user}
|
||||
staticColumnElements={[
|
||||
{ name: 'id', format: null, order: 1, align: 'left' },
|
||||
{ name: 'submitter', format: null, order: 2, align: 'left' },
|
||||
{ name: 'when', format: RENDER_WHEN, order: 5, align: 'right' },
|
||||
]}
|
||||
metrics={getPossibleMetrics()}
|
||||
iterableColumnElement={{
|
||||
name: 'evaluations',
|
||||
format: EVALUATIONS_FORMAT,
|
||||
order: 3,
|
||||
align: 'left',
|
||||
}}
|
||||
pageNr={pageNr}
|
||||
elements={entries}
|
||||
setPageNr={setPageNr}
|
||||
sortByUpdate={sortByUpdate}
|
||||
/>
|
||||
<Pager
|
||||
pageNr={pageNr}
|
||||
elements={entries}
|
||||
setPageNr={setPageNr}
|
||||
width="72px"
|
||||
borderRadius="64px"
|
||||
pages={CALC_PAGES(entries, 2)}
|
||||
number={`${pageNr} / ${CALC_PAGES(entries, 2)}`}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Loading />
|
||||
)}
|
||||
</FlexColumn>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Media query={theme.mobile}>{mobileRender()}</Media>
|
||||
<Media query={theme.desktop}>{desktopRender()}</Media>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AllEntries;
|
@ -1,32 +0,0 @@
|
||||
const allEntriesSearchQueryHandler = (
|
||||
event,
|
||||
entriesFromApi,
|
||||
setPageNr,
|
||||
setEntries
|
||||
) => {
|
||||
let searchQuery = event.target.value;
|
||||
let submissionsToRender = [];
|
||||
setPageNr(1);
|
||||
if (searchQuery === '') setEntries(entriesFromApi);
|
||||
else {
|
||||
for (let entry of entriesFromApi) {
|
||||
const { id, when, submitter } = entry;
|
||||
console.log(entry);
|
||||
let evaluations = '';
|
||||
if (entry.evaluations) {
|
||||
for (let evaluation of Object.values(entry.evaluations)) {
|
||||
evaluations += ` ${evaluation}`;
|
||||
}
|
||||
}
|
||||
const str = `${id} ${submitter} ${when.slice(11, 16)} ${when.slice(
|
||||
0,
|
||||
10
|
||||
)} ${evaluations}`;
|
||||
if (str.toLowerCase().includes(searchQuery.toLowerCase()))
|
||||
submissionsToRender.push(entry);
|
||||
}
|
||||
setEntries(submissionsToRender);
|
||||
}
|
||||
};
|
||||
|
||||
export default allEntriesSearchQueryHandler;
|
@ -1,42 +0,0 @@
|
||||
import React from 'react';
|
||||
import getFullUser from '../../../api/getFullUserInfo';
|
||||
import KeyCloakService from '../../../services/KeyCloakService';
|
||||
import { FlexColumn } from '../../../utils/containers';
|
||||
import { IS_MOBILE } from '../../../utils/globals';
|
||||
import HowToContent from './sections/HowToContent';
|
||||
|
||||
const HowTo = (props) => {
|
||||
const [userFullInfo, setUserFullInfo] = React.useState(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
getFullUser(setUserFullInfo);
|
||||
|
||||
if (!KeyCloakService.isLoggedIn()) {
|
||||
props.popUpMessageHandler(
|
||||
'Please log in',
|
||||
'To see everything you must log in',
|
||||
() => KeyCloakService.doLogin
|
||||
);
|
||||
}
|
||||
}, [props]);
|
||||
|
||||
return (
|
||||
<FlexColumn
|
||||
margin={IS_MOBILE() ? null : '64px 0 0 0'}
|
||||
padding={IS_MOBILE() ? '12px 20px' : null}
|
||||
gap={IS_MOBILE() ? '24px' : '48px'}
|
||||
alignmentX={IS_MOBILE() ? 'flex-start' : 'center'}
|
||||
maxWidth={IS_MOBILE() ? '668px' : 'none'}
|
||||
>
|
||||
<FlexColumn maxWidth="680px" alignmentX="flex-start" gap="48px">
|
||||
<HowToContent
|
||||
userFullInfo={userFullInfo}
|
||||
user={props.user ? props.user : 'yourID'}
|
||||
challengeName={props.challengeName}
|
||||
/>
|
||||
</FlexColumn>
|
||||
</FlexColumn>
|
||||
);
|
||||
};
|
||||
|
||||
export default HowTo;
|
@ -1,307 +0,0 @@
|
||||
import React from 'react';
|
||||
import Media from 'react-media';
|
||||
import theme from '../../../utils/theme';
|
||||
import { FlexColumn } from '../../../utils/containers';
|
||||
import { H2 } from '../../../utils/fonts';
|
||||
import Table from '../Table';
|
||||
import PropsTypes from 'prop-types';
|
||||
import getChallengeLeaderboard from '../../../api/getChallengeLeaderboard';
|
||||
import leaderboardSearchQueryHandler from './leaderboardSearchQueryHandler';
|
||||
import {
|
||||
CALC_PAGES,
|
||||
EVALUATIONS_FORMAT,
|
||||
RENDER_WHEN,
|
||||
} from '../../../utils/globals';
|
||||
import Search from '../../generic/Search';
|
||||
import Pager from '../../generic/Pager';
|
||||
import Loading from '../../generic/Loading';
|
||||
|
||||
const Leaderboard = (props) => {
|
||||
const [entriesFromApi, setEntriesFromApi] = React.useState([]);
|
||||
const [entries, setEntries] = React.useState([]);
|
||||
const [pageNr, setPageNr] = React.useState(1);
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [submitterSorted, setSubmitterSorted] = React.useState(false);
|
||||
const [descriptionSorted, setDescriptionSorted] = React.useState(false);
|
||||
const [entriesSorted, setEntriesSorted] = React.useState(false);
|
||||
const [whenSorted, setWhenSorted] = React.useState(false);
|
||||
const [scoresSorted, setScoresSorted] = React.useState([]);
|
||||
|
||||
React.useEffect(() => {
|
||||
challengeDataRequest(props.challengeName);
|
||||
}, [props.challengeName]);
|
||||
|
||||
const challengeDataRequest = (challengeName) => {
|
||||
getChallengeLeaderboard(setEntriesFromApi, challengeName);
|
||||
getChallengeLeaderboard(setEntries, challengeName, setLoading);
|
||||
};
|
||||
|
||||
const getMetricIndex = (metricName) => {
|
||||
let i = 0;
|
||||
for (let evaluation of entriesFromApi[0].evaluations) {
|
||||
if (`${evaluation.test.metric}.${evaluation.test.name}` === metricName) {
|
||||
return i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
};
|
||||
|
||||
const searchQueryHandler = (event) => {
|
||||
leaderboardSearchQueryHandler(event, entriesFromApi, setPageNr, setEntries);
|
||||
};
|
||||
|
||||
const getPossibleMetrics = () => {
|
||||
let metrics = [];
|
||||
for (let entry of entriesFromApi) {
|
||||
for (let evaluation of entry.evaluations) {
|
||||
let metric = evaluation.test.metric;
|
||||
let name = evaluation.test.name;
|
||||
if (metric && !metrics.includes(`${metric}.${name}`)) {
|
||||
metrics.push(`${metric}.${name}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
return metrics;
|
||||
};
|
||||
|
||||
const getLeaderboardHeader = () => {
|
||||
let header = ['#', 'submitter', 'description'];
|
||||
for (let metric of getPossibleMetrics()) {
|
||||
header.push(metric);
|
||||
}
|
||||
header.push('entries');
|
||||
header.push('when');
|
||||
return header;
|
||||
};
|
||||
|
||||
const getLeaderboardHeaderMobile = () => {
|
||||
let header = ['#', 'submitter', 'description', 'entries', 'when'];
|
||||
for (let metric of getPossibleMetrics()) {
|
||||
header.push(metric);
|
||||
}
|
||||
return header;
|
||||
};
|
||||
|
||||
const sortByUpdate = (elem) => {
|
||||
let metricIndex = 0;
|
||||
let newEntries = entries;
|
||||
switch (elem) {
|
||||
case 'submitter':
|
||||
if (submitterSorted) {
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.submitter.toLowerCase() < b.submitter.toLowerCase()
|
||||
? 1
|
||||
: b.submitter.toLowerCase() < a.submitter.toLowerCase()
|
||||
? -1
|
||||
: 0
|
||||
);
|
||||
setSubmitterSorted(false);
|
||||
} else {
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.submitter.toLowerCase() > b.submitter.toLowerCase()
|
||||
? 1
|
||||
: b.submitter.toLowerCase() > a.submitter.toLowerCase()
|
||||
? -1
|
||||
: 0
|
||||
);
|
||||
setSubmitterSorted(true);
|
||||
}
|
||||
break;
|
||||
case 'description':
|
||||
if (descriptionSorted) {
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.description.toLowerCase() < b.description.toLowerCase()
|
||||
? 1
|
||||
: b.description.toLowerCase() < a.description.toLowerCase()
|
||||
? -1
|
||||
: 0
|
||||
);
|
||||
setDescriptionSorted(false);
|
||||
} else {
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.description.toLowerCase() > b.description.toLowerCase()
|
||||
? 1
|
||||
: b.description.toLowerCase() > a.description.toLowerCase()
|
||||
? -1
|
||||
: 0
|
||||
);
|
||||
setDescriptionSorted(true);
|
||||
}
|
||||
break;
|
||||
case 'entries':
|
||||
if (entriesSorted) {
|
||||
newEntries = newEntries.sort((a, b) => b.times - a.times);
|
||||
setEntriesSorted(false);
|
||||
} else {
|
||||
newEntries = newEntries.sort((a, b) => a.times - b.times);
|
||||
setEntriesSorted(true);
|
||||
}
|
||||
break;
|
||||
case 'when':
|
||||
if (whenSorted) {
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.when < b.when ? 1 : b.when < a.when ? -1 : 0
|
||||
);
|
||||
setWhenSorted(false);
|
||||
} else {
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.when > b.when ? 1 : b.when > a.when ? -1 : 0
|
||||
);
|
||||
setWhenSorted(true);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
metricIndex = getMetricIndex(elem);
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
let newScoresSorted = scoresSorted;
|
||||
if (scoresSorted[metricIndex]) {
|
||||
newEntries = newEntries.sort(
|
||||
(a, b) =>
|
||||
b.evaluations[metricIndex].score -
|
||||
a.evaluations[metricIndex].score
|
||||
);
|
||||
newScoresSorted[metricIndex] = false;
|
||||
setScoresSorted(newScoresSorted);
|
||||
} else {
|
||||
newEntries = newEntries.sort(
|
||||
(a, b) =>
|
||||
a.evaluations[metricIndex].score -
|
||||
b.evaluations[metricIndex].score
|
||||
);
|
||||
newScoresSorted[metricIndex] = true;
|
||||
setScoresSorted(newScoresSorted);
|
||||
}
|
||||
break;
|
||||
}
|
||||
setEntries(newEntries);
|
||||
};
|
||||
|
||||
const mobileRender = () => {
|
||||
return (
|
||||
<FlexColumn padding="24px 12px" width="70%" as="section" id="start">
|
||||
<H2 as="h2" margin="0 0 12px 0">
|
||||
Leaderboard
|
||||
</H2>
|
||||
{!loading ? (
|
||||
<>
|
||||
<Search searchQueryHandler={searchQueryHandler} />
|
||||
<Table
|
||||
challengeName={props.challengeName}
|
||||
headerElements={getLeaderboardHeaderMobile()}
|
||||
tableType="leaderboard"
|
||||
gridTemplateColumns={
|
||||
entries[0]
|
||||
? '1fr 2fr 3fr ' +
|
||||
'2fr '.repeat(entries[0].evaluations.length) +
|
||||
'1fr 2fr'
|
||||
: ''
|
||||
}
|
||||
user={props.user}
|
||||
staticColumnElements={[
|
||||
{ name: 'id', format: null, order: 1, align: 'left' },
|
||||
{ name: 'submitter', format: null, order: 2, align: 'left' },
|
||||
{ name: 'description', format: null, order: 3, align: 'left' },
|
||||
{ name: 'times', format: null, order: 4, align: 'left' },
|
||||
{ name: 'when', format: RENDER_WHEN, order: 5, align: 'right' },
|
||||
]}
|
||||
metrics={getPossibleMetrics()}
|
||||
iterableColumnElement={{
|
||||
name: 'evaluations',
|
||||
format: EVALUATIONS_FORMAT,
|
||||
order: 3,
|
||||
align: 'left',
|
||||
}}
|
||||
pageNr={pageNr}
|
||||
elements={entries}
|
||||
sortByUpdate={sortByUpdate}
|
||||
/>
|
||||
<Pager
|
||||
pageNr={pageNr}
|
||||
elements={entries}
|
||||
setPageNr={setPageNr}
|
||||
width="48px"
|
||||
borderRadius="64px"
|
||||
pages={CALC_PAGES(entries)}
|
||||
number={`${pageNr} / ${CALC_PAGES(entries)}`}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Loading />
|
||||
)}
|
||||
</FlexColumn>
|
||||
);
|
||||
};
|
||||
|
||||
const desktopRender = () => {
|
||||
return (
|
||||
<FlexColumn padding="24px" as="section" width="100%" maxWidth="1600px">
|
||||
<H2 as="h2" margin="0 0 32px 0">
|
||||
Leaderboard
|
||||
</H2>
|
||||
{!loading ? (
|
||||
<>
|
||||
<Search searchQueryHandler={searchQueryHandler} />
|
||||
<Table
|
||||
challengeName={props.challengeName}
|
||||
headerElements={getLeaderboardHeader()}
|
||||
gridTemplateColumns={
|
||||
entries[0]
|
||||
? '1fr 2fr 3fr ' +
|
||||
'2fr '.repeat(entries[0].evaluations.length) +
|
||||
'1fr 2fr'
|
||||
: ''
|
||||
}
|
||||
user={props.user}
|
||||
staticColumnElements={[
|
||||
{ name: 'id', format: null, order: 1, align: 'left' },
|
||||
{ name: 'submitter', format: null, order: 2, align: 'left' },
|
||||
{ name: 'description', format: null, order: 3, align: 'left' },
|
||||
{ name: 'times', format: null, order: 4, align: 'left' },
|
||||
{ name: 'when', format: RENDER_WHEN, order: 5, align: 'right' },
|
||||
]}
|
||||
metrics={getPossibleMetrics()}
|
||||
iterableColumnElement={{
|
||||
name: 'evaluations',
|
||||
format: EVALUATIONS_FORMAT,
|
||||
order: 3,
|
||||
align: 'left',
|
||||
}}
|
||||
pageNr={pageNr}
|
||||
elements={entries}
|
||||
setPageNr={setPageNr}
|
||||
sortByUpdate={sortByUpdate}
|
||||
/>
|
||||
<Pager
|
||||
pageNr={pageNr}
|
||||
elements={entries}
|
||||
setPageNr={setPageNr}
|
||||
width="72px"
|
||||
borderRadius="64px"
|
||||
pages={CALC_PAGES(entries, 2)}
|
||||
number={`${pageNr} / ${CALC_PAGES(entries, 2)}`}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Loading />
|
||||
)}
|
||||
</FlexColumn>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Media query={theme.mobile}>{mobileRender()}</Media>
|
||||
<Media query={theme.desktop}>{desktopRender()}</Media>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Leaderboard.propsTypes = {
|
||||
challengeName: PropsTypes.string,
|
||||
};
|
||||
|
||||
Leaderboard.defaultProps = {
|
||||
challengeName: '',
|
||||
};
|
||||
|
||||
export default Leaderboard;
|
@ -1,220 +0,0 @@
|
||||
import React from 'react';
|
||||
import { FlexColumn } from '../../../utils/containers';
|
||||
import { H2 } from '../../../utils/fonts';
|
||||
import getMyEntries from '../../../api/getMyEntries';
|
||||
import Pager from '../../generic/Pager';
|
||||
import {
|
||||
CALC_PAGES,
|
||||
EVALUATIONS_FORMAT,
|
||||
IS_MOBILE,
|
||||
RENDER_WHEN,
|
||||
} from '../../../utils/globals';
|
||||
import Media from 'react-media';
|
||||
import theme from '../../../utils/theme';
|
||||
import Loading from '../../generic/Loading';
|
||||
import Table from '../Table';
|
||||
import myEntriesSearchQueryHandler from './myEntriesSearchQueryHandler';
|
||||
import Search from '../../generic/Search';
|
||||
|
||||
const MyEntries = (props) => {
|
||||
const [myEntriesFromAPI, setMyEntriesFromAPI] = React.useState({});
|
||||
const [myEntriesAll, setMyEntriesAll] = React.useState({});
|
||||
const [myEntries, setMyEntries] = React.useState({});
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [pageNr, setPageNr] = React.useState(1);
|
||||
const [whenSorted, setWhenSorted] = React.useState(false);
|
||||
const [scoresSorted, setScoresSorted] = React.useState([]);
|
||||
|
||||
React.useEffect(() => {
|
||||
challengesRequest();
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
const searchQueryHandler = (event) => {
|
||||
myEntriesSearchQueryHandler(event, myEntriesAll, setPageNr, setMyEntries);
|
||||
};
|
||||
|
||||
const getPossibleMetrics = () => {
|
||||
let metrics = [];
|
||||
for (let test of myEntriesFromAPI.tests) {
|
||||
let myEval = `${test.metric}.${test.name}`;
|
||||
if (myEval && !metrics.includes(myEval)) {
|
||||
metrics.push(myEval);
|
||||
}
|
||||
}
|
||||
return metrics;
|
||||
};
|
||||
|
||||
const getMyEntriesHeader = () => {
|
||||
let header = ['#'];
|
||||
if (IS_MOBILE()) header.push('when');
|
||||
for (let myEval of getPossibleMetrics()) {
|
||||
header.push(myEval);
|
||||
}
|
||||
if (!IS_MOBILE()) header.push('when');
|
||||
return header;
|
||||
};
|
||||
|
||||
const challengesRequest = () => {
|
||||
getMyEntries(
|
||||
props.challengeName,
|
||||
setMyEntriesFromAPI,
|
||||
setMyEntriesAll,
|
||||
setMyEntries,
|
||||
setLoading,
|
||||
setScoresSorted
|
||||
);
|
||||
};
|
||||
|
||||
const sortByUpdate = (elem, i) => {
|
||||
let newEntries = myEntries;
|
||||
switch (elem) {
|
||||
case '#':
|
||||
break;
|
||||
case 'when':
|
||||
if (whenSorted) {
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.when < b.when ? 1 : b.when < a.when ? -1 : 0
|
||||
);
|
||||
setWhenSorted(false);
|
||||
} else {
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.when > b.when ? 1 : b.when > a.when ? -1 : 0
|
||||
);
|
||||
setWhenSorted(true);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
let metricIndex = getPossibleMetrics().indexOf(elem);
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
let newScoresSorted = scoresSorted;
|
||||
if (scoresSorted[metricIndex]) {
|
||||
newEntries = newEntries.sort(
|
||||
(a, b) =>
|
||||
(b.evaluations ? b.evaluations[elem] : -1) -
|
||||
(a.evaluations ? a.evaluations[elem] : -1)
|
||||
);
|
||||
newScoresSorted[metricIndex] = false;
|
||||
setScoresSorted(newScoresSorted);
|
||||
} else {
|
||||
newEntries = newEntries.sort(
|
||||
(a, b) =>
|
||||
(a.evaluations ? a.evaluations[elem] : -1) -
|
||||
(b.evaluations ? b.evaluations[elem] : -1)
|
||||
);
|
||||
newScoresSorted[metricIndex] = true;
|
||||
setScoresSorted(newScoresSorted);
|
||||
}
|
||||
break;
|
||||
}
|
||||
setMyEntries(newEntries);
|
||||
};
|
||||
|
||||
const mobileRender = () => {
|
||||
return (
|
||||
<FlexColumn padding="24px 12px" width="70%" as="section" id="start">
|
||||
<H2 as="h2" margin="0 0 12px 0">
|
||||
My Entries
|
||||
</H2>
|
||||
{!loading ? (
|
||||
<>
|
||||
<Search searchQueryHandler={searchQueryHandler} />
|
||||
<Table
|
||||
challengeName={props.challengeName}
|
||||
headerElements={getMyEntriesHeader()}
|
||||
possibleMetrics={getPossibleMetrics()}
|
||||
tableType="myEntries"
|
||||
gridTemplateColumns={
|
||||
'1fr ' + '4fr '.repeat(getMyEntriesHeader().length - 1)
|
||||
}
|
||||
staticColumnElements={[
|
||||
{ name: 'id', format: null, order: 1, align: 'left' },
|
||||
{ name: 'when', format: RENDER_WHEN, order: 3, align: 'right' },
|
||||
]}
|
||||
iterableColumnElement={{
|
||||
name: 'evaluations',
|
||||
format: EVALUATIONS_FORMAT,
|
||||
order: 2,
|
||||
align: 'left',
|
||||
}}
|
||||
pageNr={pageNr}
|
||||
elements={myEntries}
|
||||
sortByUpdate={sortByUpdate}
|
||||
/>
|
||||
<Pager
|
||||
pageNr={pageNr}
|
||||
elements={myEntries}
|
||||
setPageNr={setPageNr}
|
||||
width="48px"
|
||||
borderRadius="64px"
|
||||
pages={CALC_PAGES(myEntries)}
|
||||
number={`${pageNr} / ${CALC_PAGES(myEntries)}`}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Loading />
|
||||
)}
|
||||
</FlexColumn>
|
||||
);
|
||||
};
|
||||
|
||||
const desktopRender = () => {
|
||||
return (
|
||||
<FlexColumn padding="24px" as="section" width="100%" maxWidth="1600px">
|
||||
<FlexColumn padding="24px 12px" width="70%" as="section" id="start">
|
||||
<H2 as="h2" margin="0 0 32px 0">
|
||||
My Entries
|
||||
</H2>
|
||||
</FlexColumn>
|
||||
{myEntries && !loading ? (
|
||||
<>
|
||||
<Search searchQueryHandler={searchQueryHandler} />
|
||||
<Table
|
||||
challengeName={props.challengeName}
|
||||
headerElements={getMyEntriesHeader()}
|
||||
possibleMetrics={getPossibleMetrics()}
|
||||
gridTemplateColumns={
|
||||
'1fr ' + '3fr '.repeat(getMyEntriesHeader().length - 2) + ' 4fr'
|
||||
}
|
||||
staticColumnElements={[
|
||||
{ name: 'id', format: null, order: 1, align: 'left' },
|
||||
{ name: 'when', format: RENDER_WHEN, order: 3, align: 'right' },
|
||||
]}
|
||||
iterableColumnElement={{
|
||||
name: 'evaluations',
|
||||
format: EVALUATIONS_FORMAT,
|
||||
order: 2,
|
||||
align: 'left',
|
||||
}}
|
||||
pageNr={pageNr}
|
||||
elements={myEntries}
|
||||
sortByUpdate={sortByUpdate}
|
||||
/>
|
||||
<Pager
|
||||
pageNr={pageNr}
|
||||
elements={myEntries}
|
||||
setPageNr={setPageNr}
|
||||
width="72px"
|
||||
mobileRender
|
||||
borderRadius="64px"
|
||||
pages={CALC_PAGES(myEntries, 2)}
|
||||
number={`${pageNr} / ${CALC_PAGES(myEntries, 2)}`}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Loading />
|
||||
)}
|
||||
</FlexColumn>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Media query={theme.mobile}>{mobileRender()}</Media>
|
||||
<Media query={theme.desktop}>{desktopRender()}</Media>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MyEntries;
|
@ -1,33 +0,0 @@
|
||||
const myEntriesSearchQueryHandler = (
|
||||
event,
|
||||
entriesFromApi,
|
||||
setPageNr,
|
||||
setEntries
|
||||
) => {
|
||||
let searchQuery = event.target.value;
|
||||
let submissionsToRender = [];
|
||||
setPageNr(1);
|
||||
if (searchQuery === '') setEntries(entriesFromApi);
|
||||
else {
|
||||
for (let entry of entriesFromApi) {
|
||||
const { id, when } = entry;
|
||||
let evaluations = '';
|
||||
if (entry.evaluations) {
|
||||
for (let evaluation of Object.values(entry.evaluations)) {
|
||||
evaluations += ` ${evaluation}`;
|
||||
}
|
||||
}
|
||||
const str = `${id} ${when.slice(11, 16)} ${when.slice(
|
||||
0,
|
||||
10
|
||||
)} ${evaluations}`;
|
||||
console.log(entry);
|
||||
console.log(str);
|
||||
if (str.toLowerCase().includes(searchQuery.toLowerCase()))
|
||||
submissionsToRender.push(entry);
|
||||
}
|
||||
setEntries(submissionsToRender);
|
||||
}
|
||||
};
|
||||
|
||||
export default myEntriesSearchQueryHandler;
|
@ -1,88 +0,0 @@
|
||||
import React from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { FlexColumn } from '../../utils/containers';
|
||||
import { H2, Menu } from '../../utils/fonts';
|
||||
import SubmitInput from '../generic/SubmitInput';
|
||||
import Button from '../generic/Button';
|
||||
import theme from '../../utils/theme';
|
||||
import challengeSubmission from '../../api/challengeSubmissionPost';
|
||||
import Loading from '../generic/Loading';
|
||||
import getTags from '../../api/getTags';
|
||||
import DropdownWithPopup from '../generic/DropdownWithPopup';
|
||||
|
||||
const Submit = (props) => {
|
||||
const [description, setDescription] = React.useState('');
|
||||
const [repoUrl, setRepoUrl] = React.useState('');
|
||||
const [repoBranch, setRepoBranch] = React.useState('');
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [tags, setTags] = React.useState([]);
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [submissionTags, setSubmissionTags] = React.useState([]);
|
||||
|
||||
React.useMemo(() => {
|
||||
getTags(setTags);
|
||||
}, []);
|
||||
|
||||
const challengeSubmissionSubmit = () => {
|
||||
setLoading(true);
|
||||
challengeSubmission(
|
||||
props.challengeName,
|
||||
repoUrl,
|
||||
repoBranch,
|
||||
description,
|
||||
setLoading
|
||||
);
|
||||
};
|
||||
|
||||
if (!loading) {
|
||||
console.log(tags);
|
||||
return (
|
||||
<FlexColumn
|
||||
margin="40px 0 0 0"
|
||||
padding="24px"
|
||||
as="section"
|
||||
gap="64px"
|
||||
maxWidth="624px"
|
||||
width="100%"
|
||||
alignmentX="flex-start"
|
||||
>
|
||||
<H2 as="h2" width="100%" textAlign="center">
|
||||
Submit a solution to the challenge
|
||||
</H2>
|
||||
<FlexColumn width="100%" gap="32px">
|
||||
<SubmitInput
|
||||
label="Submission description"
|
||||
handler={setDescription}
|
||||
/>
|
||||
<SubmitInput label="Submission repo URL" handler={setRepoUrl} />
|
||||
<SubmitInput label="Submission repo branch" handler={setRepoBranch} />
|
||||
<DropdownWithPopup
|
||||
label="Submission tags"
|
||||
handler={setSubmissionTags}
|
||||
/>
|
||||
</FlexColumn>
|
||||
<Button width="122px" height="44px" handler={challengeSubmissionSubmit}>
|
||||
<Menu color={theme.colors.white}>Submit</Menu>
|
||||
</Button>
|
||||
</FlexColumn>
|
||||
);
|
||||
} else {
|
||||
return createPortal(
|
||||
<FlexColumn
|
||||
position="fixed"
|
||||
top="0"
|
||||
left="0"
|
||||
width="100%"
|
||||
height="100vh"
|
||||
zIndex="100"
|
||||
backgroundColor={theme.colors.white}
|
||||
>
|
||||
<H2 as="h1">Submission processing...</H2>
|
||||
<Loading />
|
||||
</FlexColumn>,
|
||||
document.body
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default Submit;
|
@ -1,283 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Container, FlexColumn, FlexRow, Grid } from '../../utils/containers';
|
||||
import Media from 'react-media';
|
||||
import theme from '../../utils/theme';
|
||||
import { ELEMENTS_PER_PAGE, IS_MOBILE } from '../../utils/globals';
|
||||
import { Body, Medium } from '../../utils/fonts';
|
||||
import styled from 'styled-components';
|
||||
import ColumnFilterIcon from './ColumnFilterIcon';
|
||||
|
||||
const TableStyle = styled(FlexColumn)`
|
||||
overflow-x: ${({metrics}) => metrics > 10 ? 'scroll' : 'auto'};
|
||||
`;
|
||||
|
||||
const Line = styled(FlexRow)`
|
||||
position: absolute;
|
||||
top: ${({ top }) => (top ? top : 'auto')};
|
||||
bottom: ${({ bottom }) => (bottom ? bottom : 'auto')};
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background-color: ${({ theme }) => theme.colors.dark04};
|
||||
height: ${({ height }) => (height ? height : '1px')};
|
||||
`;
|
||||
|
||||
const MobileTableStyle = styled(Container)`
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 32px 0;
|
||||
|
||||
tr:nth-of-type(odd) {
|
||||
background: ${({ theme }) => theme.colors.dark03};
|
||||
}
|
||||
|
||||
th {
|
||||
background: ${({ theme }) => theme.colors.dark05};
|
||||
color: ${({ theme }) => theme.colors.white};
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 6px;
|
||||
border: 1px solid ${({ theme }) => theme.colors.white};
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
display: block;
|
||||
|
||||
thead,
|
||||
tbody,
|
||||
th,
|
||||
td {
|
||||
display: block;
|
||||
}
|
||||
|
||||
thead tr {
|
||||
position: absolute;
|
||||
top: -9999px;
|
||||
left: -9999px;
|
||||
}
|
||||
|
||||
td {
|
||||
border: none;
|
||||
border-bottom: 1px solid ${({ theme }) => theme.colors.dark01};
|
||||
position: relative;
|
||||
padding-left: 50%;
|
||||
}
|
||||
|
||||
.mobile-table-header {
|
||||
font-weight: 400;
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: 6px;
|
||||
width: 45%;
|
||||
padding-right: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
`;
|
||||
|
||||
const Table = (props) => {
|
||||
const [, updateState] = React.useState();
|
||||
const forceUpdate = React.useCallback(() => updateState({}), []);
|
||||
const [activeIcon, setActiveIcon] = React.useState(null);
|
||||
const [rotateActiveIcon, setRotateActiveIcon] = React.useState(false);
|
||||
|
||||
const metricsRender = (elem) => {
|
||||
if (!props.iterableColumnElement) return <></>;
|
||||
if (Array.isArray(elem[props.iterableColumnElement.name]))
|
||||
elem = elem[props.iterableColumnElement.name];
|
||||
else {
|
||||
let newElem = [];
|
||||
for (let metric of props.possibleMetrics) {
|
||||
if (Object.hasOwn(elem, props.iterableColumnElement.name)) {
|
||||
if (elem[props.iterableColumnElement.name][metric] === '-1')
|
||||
newElem.push('N/A');
|
||||
else newElem.push(elem[props.iterableColumnElement.name][metric]);
|
||||
} else {
|
||||
newElem.push('N/A');
|
||||
}
|
||||
}
|
||||
elem = newElem;
|
||||
}
|
||||
let indexModificator = 2;
|
||||
if (props.tableType === 'leaderboard') indexModificator = 4;
|
||||
if (props.tableType === 'allEntries') indexModificator = 3;
|
||||
|
||||
return elem.map((iterableElem, i) => {
|
||||
return (
|
||||
<Body
|
||||
key={`metric-result-${i}`}
|
||||
as="td"
|
||||
order={props.iterableColumnElement.order}
|
||||
textAlign={props.iterableColumnElement.align}
|
||||
minWidth="88px"
|
||||
margin="auto 0"
|
||||
overflowWrap="anywhere"
|
||||
>
|
||||
{IS_MOBILE() && (
|
||||
<Container className="mobile-table-header">
|
||||
{props.headerElements[indexModificator + i]}
|
||||
</Container>
|
||||
)}
|
||||
{props.iterableColumnElement.format
|
||||
? props.iterableColumnElement.format(iterableElem)
|
||||
: iterableElem}
|
||||
</Body>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const rowRender = (elem) => {
|
||||
let RowStyle = Body;
|
||||
if (elem.submitter === props.user) RowStyle = Medium;
|
||||
return props.staticColumnElements.map((elemName, i) => {
|
||||
return (
|
||||
<RowStyle
|
||||
key={`leaderboard-static-elemName-${i}-${elem[elemName.name]}`}
|
||||
as="td"
|
||||
order={elemName.order}
|
||||
textAlign={elemName.align}
|
||||
margin="auto 0"
|
||||
minWidth="88px"
|
||||
overflowWrap="anywhere"
|
||||
>
|
||||
{IS_MOBILE() && (
|
||||
<Container className="mobile-table-header">
|
||||
{props.headerElements[i]}
|
||||
</Container>
|
||||
)}
|
||||
{elemName.format
|
||||
? elemName.format(elem[elemName.name])
|
||||
: elem[elemName.name]}
|
||||
</RowStyle>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const desktopRender = () => {
|
||||
const n = (props.pageNr - 1) * (ELEMENTS_PER_PAGE * 2);
|
||||
let elementsToMap = props.elements.slice(n, n + ELEMENTS_PER_PAGE * 2);
|
||||
if (elementsToMap.length > 0) {
|
||||
return (
|
||||
<TableStyle as="table" margin="32px 0 72px 0" width="100%">
|
||||
<FlexColumn as="tbody" width="100%">
|
||||
<Grid
|
||||
as="tr"
|
||||
gridGap="20px"
|
||||
position="relative"
|
||||
width="100%"
|
||||
padding="0 6px"
|
||||
minHeight="44px"
|
||||
margin="0 0 6px 0"
|
||||
gridTemplateColumns={props.gridTemplateColumns}
|
||||
>
|
||||
{props.headerElements.map((elem, i) => {
|
||||
return (
|
||||
<FlexRow
|
||||
key={`table-header-${i}`}
|
||||
alignmentX="flex-start"
|
||||
as="td"
|
||||
cursor="pointer"
|
||||
onClick={() => {
|
||||
if (activeIcon === i) {
|
||||
let newRotateActiveIcon = !rotateActiveIcon;
|
||||
setRotateActiveIcon(newRotateActiveIcon);
|
||||
} else {
|
||||
setRotateActiveIcon(false);
|
||||
}
|
||||
setActiveIcon(i);
|
||||
props.sortByUpdate(elem, i);
|
||||
forceUpdate();
|
||||
}}
|
||||
>
|
||||
<Medium
|
||||
cursor={elem !== '#' ? 'pointer' : ''}
|
||||
textAlign={elem === 'when' ? 'right' : 'left'}
|
||||
width={elem === 'when' ? '100%' : 'auto'}
|
||||
padding="0 4px 0 0"
|
||||
overflowWrap="anywhere"
|
||||
minWidth="72px"
|
||||
|
||||
// minWidth={elem === 'result' ? '72px' : 'none'}
|
||||
>
|
||||
{elem.replace('.', ' ')}
|
||||
</Medium>
|
||||
{elem !== '#' && (
|
||||
<ColumnFilterIcon
|
||||
cursor="pointer"
|
||||
index={i}
|
||||
active={activeIcon}
|
||||
rotateIcon={rotateActiveIcon}
|
||||
/>
|
||||
)}
|
||||
</FlexRow>
|
||||
);
|
||||
})}
|
||||
<Line
|
||||
height="2px"
|
||||
top="calc(100% + 2px)"
|
||||
as="td"
|
||||
shadow={theme.shadow}
|
||||
/>
|
||||
</Grid>
|
||||
{elementsToMap.map((elem, index) => {
|
||||
return (
|
||||
<Grid
|
||||
as="tr"
|
||||
key={`leaderboard-row-${index}`}
|
||||
backgroundColor={
|
||||
index % 2 === 1 ? theme.colors.dark01 : 'transparent'
|
||||
}
|
||||
gridTemplateColumns={props.gridTemplateColumns}
|
||||
gridGap="20px"
|
||||
position="relative"
|
||||
width="100%"
|
||||
padding="4px"
|
||||
minHeight="48px"
|
||||
>
|
||||
{rowRender(elem)}
|
||||
{props.headerElements ? metricsRender(elem) : ''}
|
||||
</Grid>
|
||||
);
|
||||
})}
|
||||
</FlexColumn>
|
||||
</TableStyle>
|
||||
);
|
||||
}
|
||||
return <Medium margin="72px 0">No results ;c</Medium>;
|
||||
};
|
||||
|
||||
const mobileRender = () => {
|
||||
const n = (props.pageNr - 1) * ELEMENTS_PER_PAGE;
|
||||
let elementsToMap = props.elements.slice(n, n + ELEMENTS_PER_PAGE);
|
||||
if (elementsToMap.length > 0) {
|
||||
return (
|
||||
<MobileTableStyle
|
||||
as="table"
|
||||
staticColumnElements={props.staticColumnElements}
|
||||
headerElements={props.headerElements}
|
||||
>
|
||||
<Container as="tbody">
|
||||
{elementsToMap.map((elem, index) => {
|
||||
return (
|
||||
<Grid as="tr" key={`leaderboard-row-${index}`}>
|
||||
{rowRender(elem)}
|
||||
{props.headerElements ? metricsRender(elem) : ''}
|
||||
</Grid>
|
||||
);
|
||||
})}
|
||||
</Container>
|
||||
</MobileTableStyle>
|
||||
);
|
||||
}
|
||||
return <Medium margin="72px 0">No results ;c</Medium>;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Media query={theme.mobile}>{mobileRender()}</Media>
|
||||
<Media query={theme.desktop}>{desktopRender()}</Media>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Table;
|
@ -8,11 +8,7 @@ import HttpService from './services/HttpService';
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
|
||||
const renderApp = () => root.render(
|
||||
<React.StrictMode>
|
||||
<App/>
|
||||
</React.StrictMode>
|
||||
);
|
||||
const renderApp = () => root.render(<App />);
|
||||
|
||||
KeyCloakService.initKeycloak(renderApp);
|
||||
HttpService.configure();
|
||||
|
170
src/pages/AllEntries/AllEntries.js
Normal file
170
src/pages/AllEntries/AllEntries.js
Normal file
@ -0,0 +1,170 @@
|
||||
import React from 'react';
|
||||
import { FlexColumn } from '../../utils/containers';
|
||||
import { H2 } from '../../utils/fonts';
|
||||
import Pager from '../../components/generic/Pager';
|
||||
import Search from '../../components/generic/Search';
|
||||
import getEntries from '../../api/getEntries';
|
||||
import Table from '../../components/generic/Table';
|
||||
import Loading from '../../components/generic/Loading';
|
||||
import { CALC_PAGES, ELEMENTS_PER_PAGE } from '../../utils/globals';
|
||||
import searchQueryHandler from './searchHandler';
|
||||
import orderKeys from './orderKeys';
|
||||
import KeyCloakService from '../../services/KeyCloakService';
|
||||
|
||||
const AllEntries = (props) => {
|
||||
const [entriesAll, setEntriesAll] = React.useState([]);
|
||||
const [entries, setEntries] = React.useState([]);
|
||||
const [pageNr, setPageNr] = React.useState(1);
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [idSorted, setIdSorted] = React.useState([]);
|
||||
const [scoresSorted, setScoresSorted] = React.useState([]);
|
||||
const [submitterSorted, setSubmitterSorted] = React.useState(false);
|
||||
const [whenSorted, setWhenSorted] = React.useState(false);
|
||||
const [profileInfo, setProfileInfo] = React.useState(null);
|
||||
|
||||
const getProfileInfo = () => {
|
||||
if (KeyCloakService.isLoggedIn()) {
|
||||
KeyCloakService.getProfileInfo(setProfileInfo);
|
||||
} else {
|
||||
setProfileInfo(false);
|
||||
}
|
||||
};
|
||||
|
||||
React.useMemo(() => {
|
||||
if (props.challengeName) {
|
||||
getEntries(
|
||||
'challenge-all-submissions',
|
||||
props.challengeName,
|
||||
[setEntries, setEntriesAll],
|
||||
setLoading,
|
||||
setScoresSorted
|
||||
);
|
||||
}
|
||||
getProfileInfo();
|
||||
}, [props.challengeName]);
|
||||
|
||||
const sortByUpdate = React.useCallback(
|
||||
(elem) => {
|
||||
let newEntries = entries.slice();
|
||||
const possibleMetrics = orderKeys(entries[0]).filter(
|
||||
(key) => !['id', 'submitter', 'when'].includes(key)
|
||||
);
|
||||
let metricIndex = possibleMetrics.indexOf(elem);
|
||||
let newScoresSorted = scoresSorted.slice();
|
||||
switch (elem) {
|
||||
case 'id':
|
||||
if (idSorted) {
|
||||
setIdSorted(false);
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.id > b.id ? 1 : b.id > a.id ? -1 : 0
|
||||
);
|
||||
} else {
|
||||
setIdSorted(true);
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.id < b.id ? 1 : b.id < a.id ? -1 : 0
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'submitter':
|
||||
if (submitterSorted) {
|
||||
setSubmitterSorted(false);
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.submitter.toLowerCase() < b.submitter.toLowerCase()
|
||||
? 1
|
||||
: b.submitter.toLowerCase() < a.submitter.toLowerCase()
|
||||
? -1
|
||||
: 0
|
||||
);
|
||||
} else {
|
||||
setSubmitterSorted(true);
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.submitter.toLowerCase() > b.submitter.toLowerCase()
|
||||
? 1
|
||||
: b.submitter.toLowerCase() > a.submitter.toLowerCase()
|
||||
? -1
|
||||
: 0
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'when':
|
||||
if (whenSorted) {
|
||||
setWhenSorted(false);
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.when < b.when ? 1 : b.when < a.when ? -1 : 0
|
||||
);
|
||||
} else {
|
||||
setWhenSorted(true);
|
||||
newEntries = newEntries.sort((a, b) =>
|
||||
a.when > b.when ? 1 : b.when > a.when ? -1 : 0
|
||||
);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (scoresSorted[metricIndex]) {
|
||||
newEntries = newEntries.sort(
|
||||
(a, b) => (b ? b[elem] : -1) - (a ? a[elem] : -1)
|
||||
);
|
||||
newScoresSorted[metricIndex] = false;
|
||||
setScoresSorted(newScoresSorted);
|
||||
} else {
|
||||
newEntries = newEntries.sort(
|
||||
(a, b) => (a ? a[elem] : -1) - (b ? b[elem] : -1)
|
||||
);
|
||||
newScoresSorted[metricIndex] = true;
|
||||
setScoresSorted(newScoresSorted);
|
||||
}
|
||||
break;
|
||||
}
|
||||
setEntries(newEntries);
|
||||
},
|
||||
[entries, idSorted, scoresSorted, submitterSorted, whenSorted]
|
||||
);
|
||||
|
||||
const n = (pageNr - 1) * (ELEMENTS_PER_PAGE * 2);
|
||||
const elements = entries.slice(n, n + ELEMENTS_PER_PAGE * 2);
|
||||
|
||||
return (
|
||||
<FlexColumn
|
||||
as="section"
|
||||
padding="24px"
|
||||
gap="32px"
|
||||
width="100%"
|
||||
maxWidth="1600px"
|
||||
>
|
||||
<H2 as="h2">All Entries</H2>
|
||||
{!loading && (profileInfo !== null) ? (
|
||||
<>
|
||||
<Search
|
||||
searchQueryHandler={(event) =>
|
||||
searchQueryHandler(event, entriesAll, setPageNr, setEntries)
|
||||
}
|
||||
/>
|
||||
{elements.length > 0 && entries[0] && (
|
||||
<div style={{ width: '100%', overflowX: 'auto' }}>
|
||||
<Table
|
||||
items={elements}
|
||||
orderedKeys={orderKeys(entries[0])}
|
||||
sortByUpdate={sortByUpdate}
|
||||
popUpMessageHandler={props.popUpMessageHandler}
|
||||
profileInfo={profileInfo}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Pager
|
||||
pageNr={pageNr}
|
||||
elements={entries}
|
||||
setPageNr={setPageNr}
|
||||
width="72px"
|
||||
borderRadius="64px"
|
||||
pages={CALC_PAGES(entries, 2)}
|
||||
number={`${pageNr} / ${CALC_PAGES(entries, 2)}`}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Loading />
|
||||
)}
|
||||
</FlexColumn>
|
||||
);
|
||||
};
|
||||
|
||||
export default AllEntries;
|
27
src/pages/AllEntries/orderKeys.js
Normal file
27
src/pages/AllEntries/orderKeys.js
Normal file
@ -0,0 +1,27 @@
|
||||
const orderKeys = (elem) => {
|
||||
if (elem) {
|
||||
let result = ['id', 'submitter', 'description'];
|
||||
const elemKeys = Object.keys(elem);
|
||||
const dev0keys = elemKeys
|
||||
.filter((key) => key.split('.')[1] === 'dev-0')
|
||||
.sort();
|
||||
const dev1keys = elemKeys
|
||||
.filter((key) => key.split('.')[1] === 'dev-1')
|
||||
.sort();
|
||||
const testAkeys = elemKeys
|
||||
.filter((key) => key.split('.')[1] === 'test-A')
|
||||
.sort();
|
||||
const testBkeys = elemKeys
|
||||
.filter((key) => key.split('.')[1] === 'test-B')
|
||||
.sort();
|
||||
result = result.concat(dev0keys);
|
||||
result = result.concat(dev1keys);
|
||||
result = result.concat(testAkeys);
|
||||
result = result.concat(testBkeys);
|
||||
result.push('when');
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export default orderKeys;
|
28
src/pages/AllEntries/searchHandler.js
Normal file
28
src/pages/AllEntries/searchHandler.js
Normal file
@ -0,0 +1,28 @@
|
||||
const searchQueryHandler = (event, entriesAll, setPageNr, setEntries) => {
|
||||
let searchQuery = event.target.value;
|
||||
let submissionsToRender = [];
|
||||
setPageNr(1);
|
||||
if (searchQuery === '') setEntries(entriesAll);
|
||||
else {
|
||||
for (let entry of entriesAll) {
|
||||
let { when, tags } = entry;
|
||||
tags = Object.values(tags)
|
||||
.map((tag) => tag.name)
|
||||
.join(' ');
|
||||
const otherKeys = Object.values(entry)
|
||||
.join(' ')
|
||||
.replaceAll(-999999999, 'N/A')
|
||||
.replaceAll('[object Object]', '')
|
||||
.replaceAll(',', '');
|
||||
const str = `${when.slice(11, 16)} ${when.slice(
|
||||
0,
|
||||
10
|
||||
)} ${otherKeys} ${tags}`;
|
||||
if (str.toLowerCase().includes(searchQuery.toLowerCase()))
|
||||
submissionsToRender.push(entry);
|
||||
}
|
||||
setEntries(submissionsToRender);
|
||||
}
|
||||
};
|
||||
|
||||
export default searchQueryHandler;
|
@ -3,13 +3,13 @@ import Media from 'react-media';
|
||||
import theme from '../../utils/theme';
|
||||
import getChallenges from '../../api/getChallenges';
|
||||
import { CHALLENGES_STATUS_FILTER } from '../../utils/globals';
|
||||
import FiltersMenu from '../../components/challenges_list/FiltersMenu';
|
||||
import FiltersMenu from './components/FiltersMenu';
|
||||
import statusFilterHandle from './functions/statusFilterHandle';
|
||||
import ChallengesMobile from './components/ChallengesMobile';
|
||||
import ChallengesDesktop from './components/ChallengesDesktop';
|
||||
import challengeSearchQueryHandler from './functions/challengeSearchQueryHandler';
|
||||
import ChallengesReducer from './model/ChallengesReducer';
|
||||
import CHALLENGES_ACTION from './model/ChallengesActionEnum';
|
||||
import CHALLENGES_ACTION from './model/ChallengesActions';
|
||||
|
||||
const Challenges = () => {
|
||||
const [state, dispatch] = React.useReducer(ChallengesReducer, {
|
||||
@ -33,7 +33,7 @@ const Challenges = () => {
|
||||
statusFilterHandle(state.statusFilter, state.challenges, dispatch);
|
||||
}, [state.statusFilter, state.challenges]);
|
||||
|
||||
const setPage = React.useCallback((value) => {
|
||||
const setPageNr = React.useCallback((value) => {
|
||||
dispatch({ type: CHALLENGES_ACTION.SET_PAGE, payload: value });
|
||||
}, []);
|
||||
|
||||
@ -73,7 +73,7 @@ const Challenges = () => {
|
||||
dispatch={dispatch}
|
||||
filtersMenuRender={filtersMenuRender}
|
||||
searchQueryHandler={searchQueryHandler}
|
||||
setPage={setPage}
|
||||
setPageNr={setPageNr}
|
||||
filtersMenu={state.filtersMenu}
|
||||
loading={state.loading}
|
||||
pageNr={state.pageNr}
|
||||
@ -85,7 +85,7 @@ const Challenges = () => {
|
||||
dispatch={dispatch}
|
||||
filtersMenuRender={filtersMenuRender}
|
||||
searchQueryHandler={searchQueryHandler}
|
||||
setPage={setPage}
|
||||
setPageNr={setPageNr}
|
||||
filtersMenu={state.filtersMenu}
|
||||
loading={state.loading}
|
||||
pageNr={state.pageNr}
|
||||
|
@ -35,7 +35,7 @@ const ChallengesDesktop = (props) => {
|
||||
{!props.loading && (
|
||||
<Pager
|
||||
pageNr={props.pageNr}
|
||||
setPage={props.setPage}
|
||||
setPageNr={props.setPageNr}
|
||||
elements={props.challengesFiltered}
|
||||
pages={CALC_PAGES(props.challengesFiltered)}
|
||||
width="72px"
|
||||
|
@ -7,7 +7,7 @@ import Search from '../../../components/generic/Search';
|
||||
import { CALC_PAGES } from '../../../utils/globals';
|
||||
import renderChallenges from '../functions/renderChallenges';
|
||||
import Loading from '../../../components/generic/Loading';
|
||||
import CHALLENGES_ACTION from '../model/ChallengesActionEnum';
|
||||
import CHALLENGES_ACTION from '../model/ChallengesActions';
|
||||
|
||||
const ChallengesMobile = (props) => {
|
||||
return (
|
||||
|
@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import { FlexColumn, Grid, Svg } from '../../utils/containers';
|
||||
import Filter from '../generic/Filter';
|
||||
import { Body, H3, Medium } from '../../utils/fonts';
|
||||
import arrow from '../../assets/arrow.svg';
|
||||
import { FlexColumn, Grid, Svg } from '../../../utils/containers';
|
||||
import Filter from '../../../components/generic/Filter';
|
||||
import { Body, H3, Medium } from '../../../utils/fonts';
|
||||
import arrow from '../../../assets/arrow.svg';
|
||||
import Media from 'react-media';
|
||||
import theme from '../../utils/theme';
|
||||
import theme from '../../../utils/theme';
|
||||
|
||||
const FilterBy = (props) => {
|
||||
const renderFilterOptions = () => {
|
@ -1,12 +1,12 @@
|
||||
import React from 'react';
|
||||
import { FlexColumn, FlexRow, TransBack } from '../../../utils/containers';
|
||||
import Button from '../../generic/Button';
|
||||
import theme from '../../../utils/theme';
|
||||
import { FlexColumn, FlexRow, TransBack } from '../../../../utils/containers';
|
||||
import Button from '../../../../components/generic/Button';
|
||||
import theme from '../../../../utils/theme';
|
||||
import styled from 'styled-components';
|
||||
import FilterBy from '../FilterBy';
|
||||
import filterOptions from './filterOptions';
|
||||
import Media from 'react-media';
|
||||
import CHALLENGES_ACTION from '../../../pages/Challanges/model/ChallengesActionEnum';
|
||||
import CHALLENGES_ACTION from '../../model/ChallengesActions';
|
||||
|
||||
const FiltersMenuStyle = styled(FlexColumn)`
|
||||
position: fixed;
|
129
src/pages/Challanges/components/MiniChallenge.js
Normal file
129
src/pages/Challanges/components/MiniChallenge.js
Normal file
@ -0,0 +1,129 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Container,
|
||||
FlexColumn,
|
||||
FlexRow,
|
||||
Grid,
|
||||
} from '../../../utils/containers';
|
||||
import { Body, H3 } from '../../../utils/fonts';
|
||||
import styled from 'styled-components';
|
||||
import IconLabel from '../../../components/generic/IconLabel';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {
|
||||
CHALLENGE_PAGE,
|
||||
MINI_DESCRIPTION_RENDER,
|
||||
} from '../../../utils/globals';
|
||||
import theme from '../../../utils/theme';
|
||||
|
||||
const ChallengeStyle = styled(FlexColumn)`
|
||||
padding: 12px;
|
||||
border: 1px solid ${({ theme }) => theme.colors.dark05};
|
||||
cursor: pointer;
|
||||
transition: transform 0.3s ease-in-out;
|
||||
position: relative;
|
||||
max-width: 420px;
|
||||
|
||||
* {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
article {
|
||||
width: 100%;
|
||||
align-items: flex-start;
|
||||
|
||||
p {
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: ${({ theme }) => theme.overMobile}) {
|
||||
width: 360px;
|
||||
padding: 20px;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
`;
|
||||
|
||||
const IconsGrid = styled(Grid)`
|
||||
width: 100%;
|
||||
grid-gap: 14px;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
|
||||
@media (min-width: 500px) {
|
||||
grid-template-columns: auto auto auto;
|
||||
}
|
||||
`;
|
||||
|
||||
const MiniChallenge = (props) => {
|
||||
const deadlineRender = () => {
|
||||
if (props.deadline) {
|
||||
return (
|
||||
<IconLabel size="24px" gap="8px" type="deadline" time={props.deadline}>
|
||||
{props.deadline.slice(0, 10)}
|
||||
</IconLabel>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const baselineRender = () => {
|
||||
if (props.baseline) {
|
||||
return (
|
||||
<IconLabel size="24px" gap="8px" type="baseline">
|
||||
{props.baseline}
|
||||
</IconLabel>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ChallengeStyle as={Link} to={`${CHALLENGE_PAGE}/${props.name}`}>
|
||||
<FlexColumn as="article">
|
||||
<FlexRow
|
||||
margin="0 0 14px 0"
|
||||
gap="12px"
|
||||
width="100%"
|
||||
alignmentX="space-between"
|
||||
>
|
||||
<H3 as="h3" width="85%">
|
||||
{props.title}
|
||||
</H3>
|
||||
{props.type ? <IconLabel type={props.type} size="30px" /> : 'xxx'}
|
||||
</FlexRow>
|
||||
<Container
|
||||
margin="0 0 14px 0"
|
||||
width="85%"
|
||||
height="1px"
|
||||
backgroundColor={theme.colors.dark05}
|
||||
/>
|
||||
<Body as="p" margin="0 0 14px 0">
|
||||
{props.description
|
||||
? MINI_DESCRIPTION_RENDER(props.description)
|
||||
: 'xxx'}
|
||||
</Body>
|
||||
<IconsGrid>
|
||||
<IconLabel size="24px" gap="8px" type="metric">
|
||||
{props.metric ? props.metric : 'xxx'}
|
||||
</IconLabel>
|
||||
<IconLabel size="24px" gap="8px" type="bestScore">
|
||||
{props.bestScore ? props.bestScore.slice(0, 6) : 'xxx'}
|
||||
</IconLabel>
|
||||
{deadlineRender()}
|
||||
{baselineRender()}
|
||||
{props.prize ? (
|
||||
<IconLabel size="24px" gap="8px" type="prize">
|
||||
{props.prize}
|
||||
</IconLabel>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</IconsGrid>
|
||||
</FlexColumn>
|
||||
</ChallengeStyle>
|
||||
);
|
||||
};
|
||||
|
||||
export default MiniChallenge;
|
@ -1,4 +1,4 @@
|
||||
import CHALLENGES_ACTION from '../model/ChallengesActionEnum';
|
||||
import CHALLENGES_ACTION from '../model/ChallengesActions';
|
||||
|
||||
const challengeSearchQueryHandler = (event, challengesFromAPI, dispatch) => {
|
||||
let searchQuery = event.target.value;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ELEMENTS_PER_PAGE } from '../../../utils/globals';
|
||||
import MiniChallenge from '../../../components/challenges_list/MiniChallenge';
|
||||
import MiniChallenge from '../components/MiniChallenge';
|
||||
import { Grid } from '../../../utils/containers';
|
||||
import styled from 'styled-components';
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { CHALLENGES_STATUS_FILTER } from '../../../utils/globals';
|
||||
import CHALLENGES_ACTION from '../model/ChallengesActionEnum';
|
||||
import CHALLENGES_ACTION from '../model/ChallengesActions';
|
||||
|
||||
const dateIsOlder = (newerDate, olderDate) => {
|
||||
if (newerDate.year > olderDate.year) return true;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import CHALLENGES_ACTION from './ChallengesActionEnum';
|
||||
import CHALLENGES_ACTION from './ChallengesActions';
|
||||
|
||||
const ChallengesReducer = (state, action) => {
|
||||
switch (action.type) {
|
||||
|
@ -3,30 +3,27 @@ import { Container, FlexColumn, FlexRow, Svg } from '../../utils/containers';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { H1, Medium } from '../../utils/fonts';
|
||||
import theme from '../../utils/theme';
|
||||
import MobileChallengeMenu from './MobileChallengeMenu';
|
||||
import Leaderboard from './Leaderboard/Leaderboard';
|
||||
import Readme from './Readme';
|
||||
import HowTo from './HowTo/HowTo';
|
||||
import MyEntries from './MyEntries/MyEntries';
|
||||
import Submit from './Submit';
|
||||
import MobileChallengeMenu from './components/MobileChallengeMenu';
|
||||
import Leaderboard from '../Leaderboard';
|
||||
import Readme from '../Readme';
|
||||
import HowTo from '../HowTo';
|
||||
import MyEntries from '../MyEntries';
|
||||
import Submit from '../Submit';
|
||||
import Media from 'react-media';
|
||||
import DesktopChallengeMenu from './DesktopChallengeMenu';
|
||||
import DesktopChallengeMenu from './components/DesktopChallengeMenu';
|
||||
import { CHALLENGE_SECTIONS, RENDER_ICO } from '../../utils/globals';
|
||||
import textIco from '../../assets/text_ico.svg';
|
||||
import getChallengeInfo from '../../api/getChallengeInfo';
|
||||
import Loading from '../generic/Loading';
|
||||
import getUser from '../../api/getUser';
|
||||
import AllEntries from './AllEntries/AllEntries';
|
||||
import Loading from '../../components/generic/Loading';
|
||||
import AllEntries from '../AllEntries';
|
||||
|
||||
const Challenge = (props) => {
|
||||
const challengeName = useParams().challengeId;
|
||||
const [challenge, setChallenge] = React.useState([]);
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [user, setUser] = React.useState('');
|
||||
|
||||
React.useEffect(() => {
|
||||
getChallengeInfo(setChallenge, setLoading, challengeName);
|
||||
getUser(setUser);
|
||||
}, [challengeName]);
|
||||
|
||||
const sectionRender = () => {
|
||||
@ -36,12 +33,15 @@ const Challenge = (props) => {
|
||||
<Leaderboard
|
||||
challengeName={challengeName}
|
||||
mainMetric={challenge.mainMetric}
|
||||
user={user}
|
||||
/>
|
||||
);
|
||||
case CHALLENGE_SECTIONS.ALL_ENTRIES:
|
||||
return (
|
||||
<AllEntries challengeName={challengeName} setLoading={setLoading} />
|
||||
<AllEntries
|
||||
challengeName={challengeName}
|
||||
setLoading={setLoading}
|
||||
popUpMessageHandler={props.popUpMessageHandler}
|
||||
/>
|
||||
);
|
||||
case CHALLENGE_SECTIONS.README:
|
||||
return (
|
||||
@ -57,11 +57,15 @@ const Challenge = (props) => {
|
||||
<HowTo
|
||||
popUpMessageHandler={props.popUpMessageHandler}
|
||||
challengeName={challengeName}
|
||||
user={user}
|
||||
/>
|
||||
);
|
||||
case CHALLENGE_SECTIONS.MY_ENTRIES:
|
||||
return <MyEntries challengeName={challengeName} />;
|
||||
return (
|
||||
<MyEntries
|
||||
challengeName={challengeName}
|
||||
popUpMessageHandler={props.popUpMessageHandler}
|
||||
/>
|
||||
);
|
||||
case CHALLENGE_SECTIONS.SUBMIT:
|
||||
return <Submit challengeName={challengeName} setLoading={setLoading} />;
|
||||
default:
|
@ -1,15 +1,15 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { FlexColumn } from '../../utils/containers';
|
||||
import { H3 } from '../../utils/fonts';
|
||||
import { FlexColumn } from '../../../utils/containers';
|
||||
import { H3 } from '../../../utils/fonts';
|
||||
import PropsTypes from 'prop-types';
|
||||
import KeyCloakService from '../../services/KeyCloakService';
|
||||
import KeyCloakService from '../../../services/KeyCloakService';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {
|
||||
MENU_CHALLENGE_SECTIONS_WITH_LOGIN,
|
||||
MENU_CHALLENGE_SECTIONS_NO_LOGIN,
|
||||
IS_MOBILE,
|
||||
} from '../../utils/globals';
|
||||
} from '../../../utils/globals';
|
||||
|
||||
const DesktopChallengeMenuStyle = styled(FlexColumn)`
|
||||
justify-content: flex-start;
|
@ -1,14 +1,14 @@
|
||||
import React from 'react';
|
||||
import { FlexRow } from '../../utils/containers';
|
||||
import { FlexRow } from '../../../utils/containers';
|
||||
import styled from 'styled-components';
|
||||
import { Medium } from '../../utils/fonts';
|
||||
import { Medium } from '../../../utils/fonts';
|
||||
import PropsTypes from 'prop-types';
|
||||
import KeyCloakService from '../../services/KeyCloakService';
|
||||
import KeyCloakService from '../../../services/KeyCloakService';
|
||||
import {
|
||||
CHALLENGE_SECTIONS,
|
||||
MENU_CHALLENGE_SECTIONS_WITH_LOGIN,
|
||||
MENU_CHALLENGE_SECTIONS_NO_LOGIN,
|
||||
} from '../../utils/globals';
|
||||
} from '../../../utils/globals';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const MenuOption = styled(Medium)`
|
1
src/pages/Challenge/index.js
Normal file
1
src/pages/Challenge/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './Challenge';
|
44
src/pages/HowTo/HowTo.js
Normal file
44
src/pages/HowTo/HowTo.js
Normal file
@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import getFullUser from '../../api/getFullUserInfo';
|
||||
import KeyCloakService from '../../services/KeyCloakService';
|
||||
import { FlexColumn } from '../../utils/containers';
|
||||
import { IS_MOBILE } from '../../utils/globals';
|
||||
import HowToContent from './components/HowToContent';
|
||||
|
||||
const HowTo = (props) => {
|
||||
const [userFullInfo, setUserFullInfo] = React.useState(null);
|
||||
const username = KeyCloakService.getUsername();
|
||||
|
||||
React.useMemo(async () => {
|
||||
await getFullUser(setUserFullInfo);
|
||||
setTimeout(() => {
|
||||
if (!KeyCloakService.isLoggedIn()) {
|
||||
props.popUpMessageHandler(
|
||||
'Please log in',
|
||||
'To see everything you must log in',
|
||||
() => KeyCloakService.doLogin
|
||||
);
|
||||
}
|
||||
}, 1000);
|
||||
}, [props]);
|
||||
|
||||
return (
|
||||
<FlexColumn
|
||||
margin={IS_MOBILE() ? null : '64px 0 0 0'}
|
||||
padding={IS_MOBILE() ? '12px 20px' : null}
|
||||
gap={IS_MOBILE() ? '24px' : '48px'}
|
||||
alignmentX={IS_MOBILE() ? 'flex-start' : 'center'}
|
||||
maxWidth={IS_MOBILE() ? '668px' : 'none'}
|
||||
>
|
||||
<FlexColumn maxWidth="680px" alignmentX="flex-start" gap="48px">
|
||||
<HowToContent
|
||||
userFullInfo={userFullInfo}
|
||||
user={username ? username : 'yourID'}
|
||||
challengeName={props.challengeName}
|
||||
/>
|
||||
</FlexColumn>
|
||||
</FlexColumn>
|
||||
);
|
||||
};
|
||||
|
||||
export default HowTo;
|
@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
import { IS_MOBILE } from '../../../../utils/globals';
|
||||
import { Body, H2, Medium } from '../../../../utils/fonts';
|
||||
import { FlexColumn, Grid } from '../../../../utils/containers';
|
||||
import CircleNumber from '../../../generic/CircleNumber';
|
||||
import CodeShell from '../../../generic/CodeShell';
|
||||
import { IS_MOBILE } from '../../../utils/globals';
|
||||
import { Body, H2, Medium } from '../../../utils/fonts';
|
||||
import { FlexColumn, Grid } from '../../../utils/containers';
|
||||
import CircleNumber from '../../../components/generic/CircleNumber';
|
||||
import CodeShell from '../../../components/generic/CodeShell';
|
||||
|
||||
const HowToContent = (props) => {
|
||||
const pullCodeLineRender = () => {
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user