Compare commits
12 Commits
a8841982ef
...
10789d7e39
Author | SHA1 | Date | |
---|---|---|---|
|
10789d7e39 | ||
55316d5509 | |||
9458061f29 | |||
ce92499528 | |||
ea33a44326 | |||
6270a6f194 | |||
1464bc084a | |||
9e9cd81a54 | |||
5d15928f5a | |||
bf6fc83ef7 | |||
bab709a7eb | |||
894e9c9b7c |
@ -21,7 +21,7 @@ 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';
|
||||
import Challenge from './pages/Challenge';
|
||||
|
||||
const App = () => {
|
||||
const [loggedBarVisible, setLoggedBarVisible] = React.useState('100vw');
|
||||
|
@ -1,15 +1,18 @@
|
||||
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 details = {
|
||||
f1: description,
|
||||
f2: submissionTags,
|
||||
f3: repoUrl,
|
||||
f4: repoBranch,
|
||||
};
|
||||
@ -30,7 +33,7 @@ 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`);
|
||||
// console.log(data);
|
||||
|
@ -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,10 +1,11 @@
|
||||
import SUBMIT_ACTION from '../pages/Submit/model/SubmitActionEnum';
|
||||
import { API } from '../utils/globals';
|
||||
|
||||
const getTags = (setTags) => {
|
||||
const getTags = (dispatch) => {
|
||||
fetch(`${API}/list-tags`)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
setTags(data);
|
||||
dispatch({ type: SUBMIT_ACTION.LOAD_TAGS, payload: data });
|
||||
});
|
||||
};
|
||||
|
||||
|
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;
|
@ -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,36 +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';
|
||||
|
||||
const DropdownWithPopup = (props) => {
|
||||
return (
|
||||
<FlexColumn gap="8px" width="100%" alignmentX="flex-start">
|
||||
<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>
|
||||
</FlexColumn>
|
||||
);
|
||||
};
|
||||
|
||||
export default DropdownWithPopup;
|
@ -5,6 +5,9 @@ const ImageButtonStyle = styled(FlexRow)`
|
||||
cursor: pointer;
|
||||
transition: transform ease-in-out 0.3s;
|
||||
transform: rotate(${({ rotate }) => (rotate ? rotate : '0')});
|
||||
* {
|
||||
cursor: pointer;
|
||||
}
|
||||
&:hover {
|
||||
transform: rotate(${({ rotate }) => (rotate ? rotate : '0')}), scale(1.2);
|
||||
}
|
||||
|
53
src/components/generic/PopUp.js
Normal file
53
src/components/generic/PopUp.js
Normal file
@ -0,0 +1,53 @@
|
||||
import React from 'react';
|
||||
import { FlexColumn } from '../../utils/containers';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const PopUpStyle = styled(FlexColumn)`
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background-color: ${({ theme }) => theme.colors.dark01};
|
||||
|
||||
.PopUpStyle__body {
|
||||
width: ${({ width }) => (width ? width : '60%')};
|
||||
height: ${({ height }) => (height ? height : '50%')};
|
||||
min-height: ${({ minHeight }) => (minHeight ? minHeight : '50%')};
|
||||
padding: ${({ padding }) => (padding ? padding : '48px')};
|
||||
margin: ${({ margin }) => (margin ? margin : '0')};
|
||||
border-radius: 12px;
|
||||
background-color: ${({ theme }) => theme.colors.white};
|
||||
justify-content: flex-start;
|
||||
}
|
||||
`;
|
||||
|
||||
const PopUp = (props) => {
|
||||
const [onHover, setOnHover] = React.useState(false);
|
||||
|
||||
const closePopUp = () => {
|
||||
if (!onHover) props.closeHandler();
|
||||
};
|
||||
|
||||
return (
|
||||
<PopUpStyle
|
||||
padding={props.padding}
|
||||
width={props.width}
|
||||
height={props.height}
|
||||
minHeight={props.minHeight}
|
||||
margin={props.margin}
|
||||
onClick={closePopUp}
|
||||
>
|
||||
<FlexColumn
|
||||
onMouseEnter={() => setOnHover(true)}
|
||||
onMouseLeave={() => setOnHover(false)}
|
||||
className="PopUpStyle__body"
|
||||
>
|
||||
{props.children}
|
||||
</FlexColumn>
|
||||
</PopUpStyle>
|
||||
);
|
||||
};
|
||||
|
||||
export default PopUp;
|
@ -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;
|
@ -8,13 +8,9 @@ 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();
|
||||
|
||||
renderApp();
|
||||
renderApp();
|
||||
|
@ -1,20 +1,20 @@
|
||||
import React from 'react';
|
||||
import theme from '../../../utils/theme';
|
||||
import theme from '../../utils/theme';
|
||||
import Media from 'react-media';
|
||||
import { FlexColumn } from '../../../utils/containers';
|
||||
import { H2 } from '../../../utils/fonts';
|
||||
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';
|
||||
} from '../../utils/globals';
|
||||
import Loading from '../../components/generic/Loading';
|
||||
import Pager from '../../components/generic/Pager';
|
||||
import Table from '../../components/generic/Table';
|
||||
import Search from '../../components/generic/Search';
|
||||
import allEntriesSearchQueryHandler from './allEntriesSearchQueryHandler';
|
||||
import getAllEntries from '../../../api/getAllEntries';
|
||||
import getAllEntries from '../../api/getAllEntries';
|
||||
|
||||
const AllEntries = (props) => {
|
||||
const [entriesFromApi, setEntriesFromApi] = React.useState([]);
|
@ -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, {
|
||||
|
@ -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;
|
121
src/pages/Challanges/components/MiniChallenge.js
Normal file
121
src/pages/Challanges/components/MiniChallenge.js
Normal file
@ -0,0 +1,121 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
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,20 +3,20 @@ 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/Leaderboard';
|
||||
import Readme from '../Readme';
|
||||
import HowTo from '../HowTo/HowTo';
|
||||
import MyEntries from '../MyEntries/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 Loading from '../../components/generic/Loading';
|
||||
import getUser from '../../api/getUser';
|
||||
import AllEntries from './AllEntries/AllEntries';
|
||||
import AllEntries from '../AllEntries/AllEntries';
|
||||
|
||||
const Challenge = (props) => {
|
||||
const challengeName = useParams().challengeId;
|
@ -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';
|
@ -1,9 +1,9 @@
|
||||
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';
|
||||
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);
|
@ -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 = () => {
|
@ -1,21 +1,20 @@
|
||||
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 theme from '../../utils/theme';
|
||||
import { FlexColumn } from '../../utils/containers';
|
||||
import { H2 } from '../../utils/fonts';
|
||||
import Table from '../../components/generic/Table';
|
||||
import PropsTypes from 'prop-types';
|
||||
import getChallengeLeaderboard from '../../../api/getChallengeLeaderboard';
|
||||
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';
|
||||
} from '../../utils/globals';
|
||||
import Search from '../../components/generic/Search';
|
||||
import Pager from '../../components/generic/Pager';
|
||||
import Loading from '../../components/generic/Loading';
|
||||
|
||||
const Leaderboard = (props) => {
|
||||
const [entriesFromApi, setEntriesFromApi] = React.useState([]);
|
||||
@ -23,6 +22,7 @@ const Leaderboard = (props) => {
|
||||
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([]);
|
||||
@ -65,7 +65,7 @@ const Leaderboard = (props) => {
|
||||
};
|
||||
|
||||
const getLeaderboardHeader = () => {
|
||||
let header = ['#', 'submitter'];
|
||||
let header = ['#', 'submitter', 'description'];
|
||||
for (let metric of getPossibleMetrics()) {
|
||||
header.push(metric);
|
||||
}
|
||||
@ -75,7 +75,7 @@ const Leaderboard = (props) => {
|
||||
};
|
||||
|
||||
const getLeaderboardHeaderMobile = () => {
|
||||
let header = ['#', 'submitter', 'entries', 'when'];
|
||||
let header = ['#', 'submitter', 'description', 'entries', 'when'];
|
||||
for (let metric of getPossibleMetrics()) {
|
||||
header.push(metric);
|
||||
}
|
||||
@ -107,6 +107,27 @@ const Leaderboard = (props) => {
|
||||
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);
|
||||
@ -170,7 +191,7 @@ const Leaderboard = (props) => {
|
||||
tableType="leaderboard"
|
||||
gridTemplateColumns={
|
||||
entries[0]
|
||||
? '1fr 3fr ' +
|
||||
? '1fr 2fr 3fr ' +
|
||||
'2fr '.repeat(entries[0].evaluations.length) +
|
||||
'1fr 2fr'
|
||||
: ''
|
||||
@ -179,6 +200,7 @@ const Leaderboard = (props) => {
|
||||
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' },
|
||||
]}
|
||||
@ -224,7 +246,7 @@ const Leaderboard = (props) => {
|
||||
headerElements={getLeaderboardHeader()}
|
||||
gridTemplateColumns={
|
||||
entries[0]
|
||||
? '1fr 3fr ' +
|
||||
? '1fr 2fr 3fr ' +
|
||||
'2fr '.repeat(entries[0].evaluations.length) +
|
||||
'1fr 2fr'
|
||||
: ''
|
||||
@ -233,6 +255,7 @@ const Leaderboard = (props) => {
|
||||
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' },
|
||||
]}
|
||||
@ -255,7 +278,6 @@ const Leaderboard = (props) => {
|
||||
width="72px"
|
||||
borderRadius="64px"
|
||||
pages={CALC_PAGES(entries, 2)}
|
||||
|
||||
number={`${pageNr} / ${CALC_PAGES(entries, 2)}`}
|
||||
/>
|
||||
</>
|
@ -1,20 +1,20 @@
|
||||
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 { FlexColumn } from '../../utils/containers';
|
||||
import { H2 } from '../../utils/fonts';
|
||||
import getMyEntries from '../../api/getMyEntries';
|
||||
import Pager from '../../components/generic/Pager';
|
||||
import {
|
||||
CALC_PAGES,
|
||||
EVALUATIONS_FORMAT,
|
||||
IS_MOBILE,
|
||||
RENDER_WHEN,
|
||||
} from '../../../utils/globals';
|
||||
} from '../../utils/globals';
|
||||
import Media from 'react-media';
|
||||
import theme from '../../../utils/theme';
|
||||
import Loading from '../../generic/Loading';
|
||||
import Table from '../Table';
|
||||
import theme from '../../utils/theme';
|
||||
import Loading from '../../components/generic/Loading';
|
||||
import Table from '../../components/generic/Table';
|
||||
import myEntriesSearchQueryHandler from './myEntriesSearchQueryHandler';
|
||||
import Search from '../../generic/Search';
|
||||
import Search from '../../components/generic/Search';
|
||||
|
||||
const MyEntries = (props) => {
|
||||
const [myEntriesFromAPI, setMyEntriesFromAPI] = React.useState({});
|
@ -1,14 +1,12 @@
|
||||
import React from 'react';
|
||||
import { FlexColumn } from '../../utils/containers';
|
||||
import { Body, H2 } from '../../utils/fonts';
|
||||
import { FlexColumn } from '../utils/containers';
|
||||
import { Body, H2 } from '../utils/fonts';
|
||||
import Media from 'react-media';
|
||||
import theme from '../../utils/theme';
|
||||
import getChallengeFullDescription from '../../api/getChallengeFullDescription';
|
||||
import theme from '../utils/theme';
|
||||
import getChallengeFullDescription from '../api/getChallengeFullDescription';
|
||||
import styled from 'styled-components';
|
||||
import InfoList from '../generic/InfoList';
|
||||
import Loading from '../generic/Loading';
|
||||
import PropsTypes from 'prop-types';
|
||||
import MiniChallenge from '../challenges_list/MiniChallenge';
|
||||
import InfoList from '../components/generic/InfoList';
|
||||
import Loading from '../components/generic/Loading';
|
||||
import { marked } from 'marked';
|
||||
|
||||
const ReadmeStyle = styled(Body)`
|
||||
@ -154,14 +152,4 @@ const Readme = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
MiniChallenge.propTypes = {
|
||||
challengeName: PropsTypes.string,
|
||||
description: PropsTypes.string,
|
||||
};
|
||||
|
||||
MiniChallenge.defaultProps = {
|
||||
challengeName: '',
|
||||
description: '',
|
||||
};
|
||||
|
||||
export default Readme;
|
114
src/pages/Submit/Submit.js
Normal file
114
src/pages/Submit/Submit.js
Normal file
@ -0,0 +1,114 @@
|
||||
import React from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { FlexColumn } from '../../utils/containers';
|
||||
import { H2, Menu } from '../../utils/fonts';
|
||||
import SubmitInput from '../../components/generic/SubmitInput';
|
||||
import Button from '../../components/generic/Button';
|
||||
import theme from '../../utils/theme';
|
||||
import challengeSubmission from '../../api/challengeSubmissionPost';
|
||||
import Loading from '../../components/generic/Loading';
|
||||
import getTags from '../../api/getTags';
|
||||
import TagsChoose from './components/TagsChoose';
|
||||
import SubmitReducer from './model/SubmitReducer';
|
||||
import SUBMIT_ACTION from './model/SubmitActionEnum';
|
||||
import SubmitStyle from './SubmitStyle';
|
||||
|
||||
const Submit = (props) => {
|
||||
const [state, dispatch] = React.useReducer(SubmitReducer, {
|
||||
description: '',
|
||||
repoUrl: '',
|
||||
repoBranch: '',
|
||||
submissionLoading: false,
|
||||
tags: [],
|
||||
submissionTags: [],
|
||||
});
|
||||
|
||||
React.useMemo(() => {
|
||||
getTags(dispatch);
|
||||
}, []);
|
||||
|
||||
const challengeSubmissionSubmit = () => {
|
||||
dispatch({ type: SUBMIT_ACTION.TOGGLE_SUBMISSION_LOADING });
|
||||
challengeSubmission(
|
||||
props.challengeName,
|
||||
state.repoUrl,
|
||||
state.repoBranch,
|
||||
state.description,
|
||||
state.submissionTags,
|
||||
dispatch
|
||||
);
|
||||
};
|
||||
|
||||
const setDescription = (value) => {
|
||||
dispatch({ type: SUBMIT_ACTION.SET_DESCRIPTION, payload: value });
|
||||
};
|
||||
|
||||
const setRepoUrl = (value) => {
|
||||
dispatch({ type: SUBMIT_ACTION.SET_REPO_URL, payload: value });
|
||||
};
|
||||
|
||||
const setRepoBranch = (value) => {
|
||||
dispatch({ type: SUBMIT_ACTION.SET_REPO_BRANCH, payload: value });
|
||||
};
|
||||
|
||||
const toggleSubmissionTag = React.useCallback(
|
||||
(tag) => {
|
||||
let actionType = '';
|
||||
if (state.submissionTags.includes(tag))
|
||||
actionType = SUBMIT_ACTION.REMOVE_SUBMISSION_TAG;
|
||||
else actionType = SUBMIT_ACTION.ADD_SUBMISSION_TAG;
|
||||
dispatch({ type: actionType, payload: tag });
|
||||
},
|
||||
[state.submissionTags]
|
||||
);
|
||||
|
||||
const clearSubmissionTags = () => {
|
||||
dispatch({ type: SUBMIT_ACTION.CLEAR_SUBMISSION_TAGS });
|
||||
};
|
||||
|
||||
if (!state.submissionLoading) {
|
||||
return (
|
||||
<SubmitStyle as="section">
|
||||
<H2 as="h2" className="SubmitStyle__header">
|
||||
Submit a solution to the challenge
|
||||
</H2>
|
||||
<FlexColumn className="SubmitStyle__form">
|
||||
<SubmitInput
|
||||
label="Submission description"
|
||||
handler={setDescription}
|
||||
/>
|
||||
<SubmitInput label="Submission repo URL" handler={setRepoUrl} />
|
||||
<SubmitInput label="Submission repo branch" handler={setRepoBranch} />
|
||||
<TagsChoose
|
||||
label="Submission tags"
|
||||
toggleSubmissionTag={toggleSubmissionTag}
|
||||
tags={state.tags}
|
||||
submissionTags={state.submissionTags}
|
||||
clearSubmissionTags={clearSubmissionTags}
|
||||
/>
|
||||
</FlexColumn>
|
||||
<Button width="122px" height="44px" handler={challengeSubmissionSubmit}>
|
||||
<Menu color={theme.colors.white}>Submit</Menu>
|
||||
</Button>
|
||||
</SubmitStyle>
|
||||
);
|
||||
} 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;
|
23
src/pages/Submit/SubmitStyle.js
Normal file
23
src/pages/Submit/SubmitStyle.js
Normal file
@ -0,0 +1,23 @@
|
||||
import styled from 'styled-components';
|
||||
import { FlexColumn } from '../../utils/containers';
|
||||
|
||||
const SubmitStyle = styled(FlexColumn)`
|
||||
margin: 40px 0 0 0;
|
||||
padding: 24px;
|
||||
gap: 64px;
|
||||
max-width: 624px;
|
||||
width: 100%;
|
||||
align-items: flex-start;
|
||||
|
||||
.SubmitStyle__header {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.SubmitStyle__form {
|
||||
width: 100%;
|
||||
gap: 32px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default SubmitStyle;
|
53
src/pages/Submit/components/TagsChoose/TagsChoose.js
Normal file
53
src/pages/Submit/components/TagsChoose/TagsChoose.js
Normal file
@ -0,0 +1,53 @@
|
||||
import React from 'react';
|
||||
import { FlexRow, Grid } from '../../../../utils/containers';
|
||||
import { Medium } from '../../../../utils/fonts';
|
||||
import ImageButton from '../../../../components/generic/ImageButton';
|
||||
import pencilIco from '../../../../assets/pencil_ico.svg';
|
||||
import TagsChoosePopUp from '../TagsChoosePopup/TagsChoosePopUp';
|
||||
import { createPortal } from 'react-dom';
|
||||
import TagsChooseStyle from './TagsChooseStyle';
|
||||
|
||||
const TagsChoose = (props) => {
|
||||
const [tagsPopUp, setTagsPopUp] = React.useState(false);
|
||||
|
||||
return (
|
||||
<TagsChooseStyle
|
||||
onClick={() => {
|
||||
if (!tagsPopUp) setTagsPopUp(true);
|
||||
}}
|
||||
>
|
||||
<Medium as="label" htmlFor={props.label}>
|
||||
{props.label}
|
||||
</Medium>
|
||||
<Grid
|
||||
className="TagsChooseStyle__grid"
|
||||
onChange={(e) => props.handler(e.target.value)}
|
||||
>
|
||||
<FlexRow
|
||||
width="100%"
|
||||
height="100%"
|
||||
className="TagsChooseStyle__tags-container"
|
||||
gap="16px"
|
||||
>
|
||||
{props.submissionTags.map((tag, i) =>
|
||||
i === 0 ? tag.name : `, ${tag.name}`
|
||||
)}
|
||||
</FlexRow>
|
||||
<ImageButton src={pencilIco} width="20px" height="20px" />
|
||||
</Grid>
|
||||
{tagsPopUp &&
|
||||
createPortal(
|
||||
<TagsChoosePopUp
|
||||
tags={props.tags}
|
||||
submissionTags={props.submissionTags}
|
||||
toggleSubmissionTag={props.toggleSubmissionTag}
|
||||
setTagsPopUp={setTagsPopUp}
|
||||
clearSubmissionTags={props.clearSubmissionTags}
|
||||
/>,
|
||||
document.body
|
||||
)}
|
||||
</TagsChooseStyle>
|
||||
);
|
||||
};
|
||||
|
||||
export default TagsChoose;
|
31
src/pages/Submit/components/TagsChoose/TagsChooseStyle.js
Normal file
31
src/pages/Submit/components/TagsChoose/TagsChooseStyle.js
Normal file
@ -0,0 +1,31 @@
|
||||
import styled from 'styled-components';
|
||||
import { FlexColumn } from '../../../../utils/containers';
|
||||
|
||||
const TagsChooseStyle = styled(FlexColumn)`
|
||||
cursor: pointer;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
align-items: flex-start;
|
||||
* {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.TagsChooseStyle__grid {
|
||||
border-radius: 4px;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
border: 1px solid ${({ theme }) => theme.colors.dark};
|
||||
box-shadow: 1px 2px 4px rgba(52, 52, 52, 0.25);
|
||||
padding: 12px;
|
||||
grid-template-columns: 1fr auto;
|
||||
}
|
||||
|
||||
.TagsChooseStyle__tags-container {
|
||||
height: 100%;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
`;
|
||||
|
||||
export default TagsChooseStyle;
|
1
src/pages/Submit/components/TagsChoose/index.js
Normal file
1
src/pages/Submit/components/TagsChoose/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './TagsChoose';
|
@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
import PopUp from '../../../../components/generic/PopUp';
|
||||
import { FlexColumn, FlexRow } from '../../../../utils/containers';
|
||||
import Search from '../../../../components/generic/Search';
|
||||
import theme from '../../../../utils/theme';
|
||||
import Button from '../../../../components/generic/Button';
|
||||
import TagsChoosePopUpStyle from './TagsChoosePopUpStyle';
|
||||
import renderTagItems from './functions/renderTagItems';
|
||||
|
||||
const TagsChoosePopUp = (props) => {
|
||||
return (
|
||||
<PopUp
|
||||
width="50%"
|
||||
height="80vh"
|
||||
padding="36px 32px 0"
|
||||
closeHandler={() => props.setTagsPopUp(false)}
|
||||
>
|
||||
<TagsChoosePopUpStyle>
|
||||
<Search />
|
||||
<FlexColumn as="ul" className="TagsChoosePopUpStyle__tags-list">
|
||||
{renderTagItems(
|
||||
props.submissionTags,
|
||||
props.toggleSubmissionTag,
|
||||
`1px dotted ${theme.colors.green}`,
|
||||
theme.colors.green03,
|
||||
true
|
||||
)}
|
||||
{renderTagItems(props.tags, props.toggleSubmissionTag, 'none')}
|
||||
</FlexColumn>
|
||||
<FlexRow width="100%" gap="20px" alignmentX="flex-start">
|
||||
<Button height="32px" width="76px">
|
||||
Done
|
||||
</Button>
|
||||
<Button
|
||||
height="32px"
|
||||
width="76px"
|
||||
backgroundColor={theme.colors.dark08}
|
||||
handler={() => props.clearSubmissionTags()}
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
<Button
|
||||
height="32px"
|
||||
width="76px"
|
||||
backgroundColor={theme.colors.dark}
|
||||
margin="0 0 0 auto"
|
||||
handler={() => props.setTagsPopUp(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</FlexRow>
|
||||
</TagsChoosePopUpStyle>
|
||||
</PopUp>
|
||||
);
|
||||
};
|
||||
|
||||
export default TagsChoosePopUp;
|
@ -0,0 +1,31 @@
|
||||
import styled from 'styled-components';
|
||||
import { FlexColumn } from '../../../../utils/containers';
|
||||
|
||||
const TagsChoosePopUpStyle = styled(FlexColumn)`
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
height: 100%;
|
||||
gap: 24px;
|
||||
|
||||
.TagsChoosePopUpStyle__tags-list {
|
||||
height: 80%;
|
||||
width: 100%;
|
||||
overflow-y: scroll;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.TagsChoosePopUpStyle__tag-item {
|
||||
height: 42px;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
padding: 12px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => theme.colors.green03};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default TagsChoosePopUpStyle;
|
@ -0,0 +1,42 @@
|
||||
import theme from '../../../../../utils/theme';
|
||||
import tagBackgroundColorHandle from './tagBackgroundColorHandle';
|
||||
import { FlexRow } from '../../../../../utils/containers';
|
||||
import removeIco from '../../../../../assets/remove_ico.svg';
|
||||
import { Svg } from '../../../../../utils/containers';
|
||||
|
||||
const renderTagItems = (
|
||||
tags,
|
||||
toggleTag,
|
||||
border,
|
||||
backgroundColor,
|
||||
submissionTags = false
|
||||
) => {
|
||||
return tags.map((tag, index) => {
|
||||
return (
|
||||
<FlexRow
|
||||
key={`tag-${index}`}
|
||||
onClick={() => toggleTag(tag)}
|
||||
className="TagsChoosePopUpStyle__tag-item"
|
||||
backgroundColor={
|
||||
backgroundColor
|
||||
? backgroundColor
|
||||
: tagBackgroundColorHandle(theme, index)
|
||||
}
|
||||
border={border}
|
||||
>
|
||||
{tag.name}
|
||||
{submissionTags && (
|
||||
<Svg
|
||||
src={removeIco}
|
||||
backgroundColor={theme.colors.dark}
|
||||
width="10px"
|
||||
size="cover"
|
||||
height="10px"
|
||||
/>
|
||||
)}
|
||||
</FlexRow>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export default renderTagItems;
|
@ -0,0 +1,6 @@
|
||||
const tagBackgroundColorHandle = (theme, index) => {
|
||||
if (index % 2 === 0) return theme.colors.white;
|
||||
return theme.colors.dark01;
|
||||
};
|
||||
|
||||
export default tagBackgroundColorHandle;
|
1
src/pages/Submit/components/TagsChoosePopup/index.js
Normal file
1
src/pages/Submit/components/TagsChoosePopup/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './TagsChoosePopUp';
|
1
src/pages/Submit/index.js
Normal file
1
src/pages/Submit/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './Submit';
|
12
src/pages/Submit/model/SubmitActionEnum.js
Normal file
12
src/pages/Submit/model/SubmitActionEnum.js
Normal file
@ -0,0 +1,12 @@
|
||||
const SUBMIT_ACTION = {
|
||||
SET_DESCRIPTION: 'set_description',
|
||||
SET_REPO_URL: 'set_repo_url',
|
||||
SET_REPO_BRANCH: 'set_repo_branch',
|
||||
TOGGLE_SUBMISSION_LOADING: 'toggle_submission_loading',
|
||||
LOAD_TAGS: 'load_tags',
|
||||
ADD_SUBMISSION_TAG: 'add_submission_tag',
|
||||
REMOVE_SUBMISSION_TAG: 'remove_submission_tag',
|
||||
CLEAR_SUBMISSION_TAGS: 'clear_submission_tags',
|
||||
};
|
||||
|
||||
export default SUBMIT_ACTION;
|
42
src/pages/Submit/model/SubmitReducer.js
Normal file
42
src/pages/Submit/model/SubmitReducer.js
Normal file
@ -0,0 +1,42 @@
|
||||
import SUBMIT_ACTION from './SubmitActionEnum';
|
||||
|
||||
const SubmitReducer = (state, action) => {
|
||||
console.log(`SubmitReducer: ${action.type}`);
|
||||
let newSubmissionTags = state.submissionTags;
|
||||
let newTags = state.tags;
|
||||
switch (action.type) {
|
||||
case SUBMIT_ACTION.SET_DESCRIPTION:
|
||||
return { ...state, description: action.payload };
|
||||
case SUBMIT_ACTION.SET_REPO_BRANCH:
|
||||
return { ...state, repoBranch: action.payload };
|
||||
case SUBMIT_ACTION.SET_REPO_URL:
|
||||
return { ...state, repoUrl: action.payload };
|
||||
case SUBMIT_ACTION.TOGGLE_SUBMISSION_LOADING:
|
||||
return { ...state, submissionLoading: !state.submissionLoading };
|
||||
case SUBMIT_ACTION.LOAD_TAGS:
|
||||
if (state.tags.length === 0) newTags = action.payload;
|
||||
return { ...state, tags: newTags };
|
||||
case SUBMIT_ACTION.ADD_SUBMISSION_TAG:
|
||||
if (!newSubmissionTags.includes(action.payload)) {
|
||||
newTags = state.tags;
|
||||
newSubmissionTags.push(action.payload);
|
||||
newTags = newTags.filter((tag) => tag.name !== action.payload.name);
|
||||
}
|
||||
return { ...state, submissionTags: newSubmissionTags, tags: newTags };
|
||||
case SUBMIT_ACTION.REMOVE_SUBMISSION_TAG:
|
||||
if (newSubmissionTags.includes(action.payload)) {
|
||||
newSubmissionTags = newSubmissionTags.filter(
|
||||
(tag) => tag.name !== action.payload.name
|
||||
);
|
||||
newTags.push(action.payload);
|
||||
newTags = newTags.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
return { ...state, submissionTags: newSubmissionTags, tags: newTags };
|
||||
case SUBMIT_ACTION.CLEAR_SUBMISSION_TAGS:
|
||||
return { ...state, submissionTags: [] };
|
||||
default:
|
||||
throw new Error('Undefined action in SubmitReducer!');
|
||||
}
|
||||
};
|
||||
|
||||
export default SubmitReducer;
|
@ -1,15 +1,16 @@
|
||||
const colors = {
|
||||
white: '#FCFCFC',
|
||||
green: '#1B998B',
|
||||
green03: 'rgba(27, 153, 139, 0.3)',
|
||||
green05: 'rgba(27, 153, 139, 0.5)',
|
||||
dark: '#343434',
|
||||
dark01: 'rgba(52, 52, 52, 0.1)',
|
||||
dark03: 'rgba(52, 52, 52, 0.3)',
|
||||
dark04: 'rgba(52, 52, 52, 0.4)',
|
||||
dark05: 'rgba(52, 52, 52, 0.5)',
|
||||
dark07: 'rgba(52, 52, 52, 0.7)',
|
||||
dark08: 'rgba(52, 52, 52, 0.8)',
|
||||
white: '#FCFCFC',
|
||||
green: '#1B998B',
|
||||
blue: '#4B8FF0',
|
||||
green03: 'rgba(27, 153, 139, 0.3)',
|
||||
green05: 'rgba(27, 153, 139, 0.5)',
|
||||
dark: '#343434',
|
||||
dark01: 'rgba(52, 52, 52, 0.1)',
|
||||
dark03: 'rgba(52, 52, 52, 0.3)',
|
||||
dark04: 'rgba(52, 52, 52, 0.4)',
|
||||
dark05: 'rgba(52, 52, 52, 0.5)',
|
||||
dark07: 'rgba(52, 52, 52, 0.7)',
|
||||
dark08: 'rgba(52, 52, 52, 0.8)',
|
||||
};
|
||||
|
||||
export default colors;
|
||||
export default colors;
|
||||
|
@ -37,6 +37,7 @@ const Container = styled.div`
|
||||
list-style: ${({ listStyle }) => (listStyle ? listStyle : 'none')};
|
||||
overflow-wrap: ${({ overflowWrap }) =>
|
||||
overflowWrap ? overflowWrap : 'normal'};
|
||||
overflow-y: ${({ overflowY }) => (overflowY ? overflowY : 'none')};
|
||||
`;
|
||||
|
||||
const FlexRow = styled(Container)`
|
||||
|
Loading…
Reference in New Issue
Block a user