diff --git a/src/components/card/BaseCard.tsx b/src/components/card/base/BaseCard.tsx similarity index 100% rename from src/components/card/BaseCard.tsx rename to src/components/card/base/BaseCard.tsx diff --git a/src/components/card/card.scss b/src/components/card/base/baseCard.scss similarity index 100% rename from src/components/card/card.scss rename to src/components/card/base/baseCard.scss diff --git a/src/components/card/phase/PhaseCard.tsx b/src/components/card/phase/PhaseCard.tsx new file mode 100644 index 00000000..55d0594f --- /dev/null +++ b/src/components/card/phase/PhaseCard.tsx @@ -0,0 +1,85 @@ +import classNames from 'classnames' +import { useCallback, useEffect, useRef, useState } from 'react' +import { useMediaType } from '../../../hook/useMediaType' +import type { TaskDTO, Duration, PhaseStatus } from '../../../data/phase/dto.type' +import { PhaseDropDown } from '../../dropDown/phase/PhaseDropDown' + +export type PhaseCardProps = Readonly<{ + number: number + phaseName: string + phaseDescription: string + tasks: TaskDTO[] + phaseDuration?: Duration + status: PhaseStatus +}> + +export const PhaseCard = ({ + number, + phaseName, + phaseDescription, + tasks, + phaseDuration, + status +}: PhaseCardProps): JSX.Element => { + const [isDropDownOpen, setIsDropDownOpen] = useState(false) + const dropDownContainerRef = useRef(null) + const isMobileScreen = useMediaType('(max-width: 580px)') + + const toggleDropDown = useCallback(() => { + setIsDropDownOpen(!isDropDownOpen) + }, [isDropDownOpen]) + + useEffect(() => { + if (isDropDownOpen) { + dropDownContainerRef.current?.scrollIntoView({ behavior: 'smooth', block: 'nearest' }) + } + }, [isDropDownOpen]) + + const mask = ( +
+
+

Coming Soon

+
+
+ ) + + const buttonChallenges = ( +
+ Challenges & Rewards +
+ ) + + return ( +
+
+
+ {status === 'coming' && mask} +
+
+

Phase {number}

+

{phaseName}

+
+

{phaseDescription}

+ {!isMobileScreen && status !== 'coming' && buttonChallenges} +
+
+
+ {isMobileScreen && status !== 'coming' && buttonChallenges} + {isDropDownOpen && phaseDuration && ( + + )} +
+ ) +} diff --git a/src/components/card/phase/phaseCard.scss b/src/components/card/phase/phaseCard.scss new file mode 100644 index 00000000..1c2c6b89 --- /dev/null +++ b/src/components/card/phase/phaseCard.scss @@ -0,0 +1,268 @@ +.okp4-nemeton-web-phase-card-main { + display: flex; + flex-direction: column; + + .okp4-nemeton-web-phase-card-content-button-container { + display: flex; + flex-direction: column; + gap: 10px; + margin: 20px 0 0 80px; + + > p { + font-family: 'Gotham light', sans-serif; + align-self: center; + } + + @media screen and (max-width: 900px) { + margin-left: unset; + } + + .okp4-nemeton-web-phase-card-button { + max-width: 350px; + border: 2px solid white; + background-color: transparent; + color: white; + cursor: pointer; + font-family: 'Six Caps', sans-serif; + font-size: 42px; + letter-spacing: 2px; + padding: 0 28px; + + &.right { + margin: 20px 80px 0 0; + @media screen and (max-width: 900px) { + margin-right: unset; + } + } + + &.disabled { + pointer-events: none; + } + + @media screen and (max-width: 580px) { + white-space: nowrap; + max-width: unset; + font-size: 38px; + margin-top: 26px; + align-self: center; + margin-left: unset; + } + } + } + + .okp4-nemeton-web-phase-card-container { + display: flex; + width: 100%; + justify-content: center; + + @media screen and (min-width: 2125px) { + background-image: none; + justify-content: center; + } + + @media screen and (max-width: 1440px) { + background-size: 802px 602px; + } + + @media screen and (max-width: 580px) { + background-repeat: no-repeat; + background-position: center top 5%; + background-size: contain; + } + + &.imbolc { + @media screen and (max-width: 580px) { + background-image: url('/image/imbolc-mobile.webp'); + } + } + + &.sidh { + @media screen and (max-width: 580px) { + background-image: url('/image/sidh-mobile.webp'); + } + } + + &.beltaine { + @media screen and (max-width: 580px) { + background-image: url('/image/beltaine-mobile.webp'); + } + } + + .okp4-nemeton-web-phase-card-content-container { + max-width: 1312px; + width: 100%; + min-width: 1170px; + height: 519px; + border: 1px solid white; + border-radius: 20px; + margin: 0 177px; + position: relative; + background-repeat: no-repeat; + + &.no-border { + border: unset; + } + + &.imbolc { + background-image: url('/image/imbolc-large.webp'); + background-position: left; + background-size: contain; + @media screen and (min-width: 581px) and (max-width: 1920px) { + background-image: url('/image/imbolc-medium.webp'); + } + @media screen and (max-width: 900px) { + background-size: cover; + } + } + + &.beltaine { + background-image: url('/image/beltaine-large.webp'); + background-position: right; + background-size: auto; + @media screen and (min-width: 581px) and (max-width: 1920px) { + background-image: url('/image/beltaine-medium.webp'); + } + @media screen and (max-width: 900px) { + background-size: cover; + } + } + + &.sidh { + background-image: url('/image/sidh-large.webp'); + background-position: right; + background-size: contain; + @media screen and (min-width: 581px) and (max-width: 1920px) { + background-image: url('/image/sidh-medium.webp'); + } + @media screen and (max-width: 900px) { + background-size: cover; + } + } + + @media screen and (min-width: 2125px) { + background-image: url('/image/sidh.webp'); + background-position: right; + background-repeat: no-repeat; + } + + @media screen and (max-width: 1440px) { + max-width: 1197px; + height: 470px; + margin: 0 122px; + min-width: unset; + } + + @media screen and (max-width: 580px) { + background-image: none !important; + width: 100%; + height: 475px; + margin: 50px 26px 0; + min-width: unset; + } + + .okp4-nemeton-web-phase-card-mask-container { + position: absolute; + background-color: #2e534f; + width: 100%; + height: 519px; + min-width: unset; + border-radius: 20px; + opacity: 0.95; + display: flex; + align-items: center; + justify-content: center; + + @media screen and (max-width: 580px) { + height: 475px; + } + + @media screen and (min-width: 581px) and (max-width: 1440px) { + height: 470px; + } + + > h2:first-of-type { + font-size: 80px; + padding: 0 40px; + @media screen and (max-width: 900px) { + font-size: 40px; + } + } + + .okp4-nemeton-web-phase-card-mask-divider { + width: 100px; + border-top: 3px solid white; + @media screen and (max-width: 900px) { + width: 40px; + } + } + } + + .okp4-nemeton-web-phase-card-content-details { + padding: 20px 0 0 45px; + display: flex; + flex-direction: column; + align-items: baseline; + gap: 15px; + height: 100%; + + @media screen and (max-width: 900px) { + padding: 0 20px; + align-items: center; + } + + &.imbolc { + @media screen and (min-width: 901px) { + align-items: flex-end; + padding: 20px 45px 0 0; + } + } + @media screen and (max-width: 580px) { + padding: 0 20px; + gap: 0; + display: flex; + flex-direction: column; + justify-content: flex-end; + max-height: inherit; + } + + > p:first-of-type { + max-height: 201px; + max-width: 420px; + font-size: 20px; + font-family: 'Gotham light', sans-serif; + @media screen and (max-width: 580px) { + font-size: 18px; + height: unset; + padding-bottom: 20px; + } + } + } + + .okp4-nemeton-web-phase-card-content-title { + display: flex; + align-items: baseline; + @media screen and (max-width: 580px) { + justify-content: center; + } + + > h1:first-of-type { + font-size: 140px; + line-height: 1em; + word-break: keep-all; + @media screen and (max-width: 580px) { + font-size: 100px; + } + } + + > h2:first-of-type { + padding-right: 22px; + font-size: 40px; + white-space: nowrap; + @media screen and (max-width: 580px) { + font-size: 30px; + } + } + } + } + } +} diff --git a/src/components/dropDown/phase/PhaseDropDown.tsx b/src/components/dropDown/phase/PhaseDropDown.tsx new file mode 100644 index 00000000..8cab831e --- /dev/null +++ b/src/components/dropDown/phase/PhaseDropDown.tsx @@ -0,0 +1,58 @@ +import ClearIcon from '@mui/icons-material/Clear' +import LoupeIcon from '@mui/icons-material/Loupe' +import moment from 'moment' +import Link from 'next/link' +import type { Duration, TaskDTO } from '../../../data/phase/dto.type' + +type DropDownProps = Readonly<{ + refObj: React.RefObject | null + onClose: () => void + phaseDuration: Duration + phaseName: string + tasks: TaskDTO[] +}> + +export const PhaseDropDown: React.FC = ({ + tasks, + phaseName, + onClose, + refObj, + phaseDuration +}): JSX.Element => { + const from = moment(phaseDuration.from).utc().format('MMM. Do') + const to = moment(phaseDuration.to).utc().format('MMM. Do') + + return ( +
+
+ +
+

+ Duration: {moment(phaseDuration.to).diff(moment(phaseDuration.from), 'weeks')} weeks +

+

{`From ${from} to ${to}`}

+
+ +
+ {tasks.map(({ group, taskName, points }, index, array) => { + const previous = index > 0 ? array[index - 1] : null + const mustDisplayGroup = !previous || previous.group !== group + return ( +
+ {mustDisplayGroup &&

{group}

} +
+

{taskName}

+

{points.toLocaleString()} Pts

+
+
+ ) + })} +
+ + See detailled tasks +
+
+
+
+ ) +} diff --git a/src/components/phaseCard/challenges.scss b/src/components/dropDown/phase/phaseDropDown.scss similarity index 80% rename from src/components/phaseCard/challenges.scss rename to src/components/dropDown/phase/phaseDropDown.scss index 9608e44f..ebec934a 100644 --- a/src/components/phaseCard/challenges.scss +++ b/src/components/dropDown/phase/phaseDropDown.scss @@ -1,4 +1,4 @@ -.okp4-nemeton-web-challenges-container { +.okp4-nemeton-web-phase-dropdown-main { display: flex; width: 100%; height: 100%; @@ -11,7 +11,7 @@ margin-top: 40px; } - .okp4-nemeton-web-challenges { + .okp4-nemeton-web-phase-dropdown-container { display: flex; justify-content: space-between; width: 100%; @@ -23,7 +23,18 @@ padding: 20px; } - .okp4-nemeton-web-challenges-duration { + .okp4-nemeton-web-phase-dropdown-clear-icon { + font-size: 32px !important; + cursor: pointer; + order: 4; + + @media screen and (max-width: 1100px) { + align-self: flex-end; + order: 0; + } + } + + .okp4-nemeton-web-phase-dropdown-duration { display: flex; height: 105px; background-color: #1f403c; @@ -49,7 +60,7 @@ } } - .okp4-nemeton-web-challenges-task { + .okp4-nemeton-web-phase-dropdown-tasks { display: flex; flex-direction: column; @@ -58,7 +69,7 @@ height: unset; } - .okp4-nemeton-web-challenge-details-container { + .okp4-nemeton-web-phase-dropdown-details-container { align-self: flex-end; margin-top: 40px; display: flex; @@ -76,11 +87,11 @@ } } - .okp4-nemeton-web-challenge-subject { + .okp4-nemeton-web-phase-dropdown-task { > h3 { font-size: 50px; letter-spacing: 2px; - padding: 20px 0 40px 0; + padding: 20px 0 40px; @media screen and (max-width: 1440px) { font-size: 40px; @@ -93,20 +104,16 @@ } } - .okp4-nemeton-web-challenges-rewards { + .okp4-nemeton-web-phase-dropdown-reward { + display: flex; + justify-content: space-between; + gap: 20px; padding-left: 40px; line-height: 32px; @media screen and (max-width: 580px) { padding: unset; } - } - - .okp4-nemeton-web-challenge-reward { - display: flex; - justify-content: space-between; - gap: 20px; - padding: 0; > p { font-family: 'Gotham light', sans-serif; diff --git a/src/components/phaseCard/Challenges.tsx b/src/components/phaseCard/Challenges.tsx deleted file mode 100644 index b107fb2c..00000000 --- a/src/components/phaseCard/Challenges.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import ClearIcon from '@mui/icons-material/Clear' -import LoupeIcon from '@mui/icons-material/Loupe' -import moment from 'moment' -import Link from 'next/link' -import React from 'react' -import type { Duration, Task } from '../../data/phase/dto.type' - -type ChallengesProps = { - tasks: Task[] - refObj: React.RefObject | null - onClose: () => void - phaseName: string - phaseDuration: Duration -} - -export const Challenges: React.FC = ({ - tasks, - phaseName, - onClose, - refObj, - phaseDuration -}): JSX.Element => { - const from = moment(phaseDuration.from).utc().format('MMM. Do') - const to = moment(phaseDuration.to).utc().format('MMM. Do') - - return ( -
-
- -
-

- Duration: {moment(phaseDuration.to).diff(moment(phaseDuration.from), 'weeks')} weeks -

-

{`From ${from} to ${to}`}

-
- -
- {tasks.map(({ group: subject, taskName, points }, index: number, array) => { - const previous: Task | null = index > 0 ? array[index - 1] : null - const mustDisplaySubject = !previous || previous.group !== subject - return ( -
- {mustDisplaySubject &&

{subject}

} -
-
-

{taskName}

-

{points.toLocaleString()} Pts

-
-
-
- ) - })} -
- - See detailled tasks -
-
-
-
- ) -} diff --git a/src/components/phaseCard/PhaseCard.tsx b/src/components/phaseCard/PhaseCard.tsx deleted file mode 100644 index 15d12684..00000000 --- a/src/components/phaseCard/PhaseCard.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import classNames from 'classnames' -import { useCallback, useEffect, useRef, useState } from 'react' -import { useMediaType } from '../../hook/useMediaType' -import { Challenges } from './Challenges' -import type { PhaseDTO } from '../../data/phase/dto.type' - -export type PhaseCardProps = PhaseDTO - -export const PhaseCard = ({ - number, - phaseName, - phaseDescription, - tasks, - phaseDuration, - status -}: PhaseCardProps): JSX.Element => { - const [isChallengesOpen, setIsChallengesOpen] = useState(false) - const challengeContainerRef = useRef(null) - const isMobileScreen = useMediaType('(max-width: 580px)') - - const toggleChallenges = useCallback(() => { - status !== 'coming' && setIsChallengesOpen(!isChallengesOpen) - }, [isChallengesOpen, status]) - - useEffect(() => { - if (isChallengesOpen) { - challengeContainerRef.current?.scrollIntoView({ behavior: 'smooth', block: 'nearest' }) - } - }, [isChallengesOpen]) - - const buttonChallenges = ( -
- Challenges & Rewards -
- ) - - return ( -
-
-
- {status === 'coming' && ( -
-
-

Coming Soon

-
-
- )} -
-
-

Phase {number}

-

{phaseName}

-
-

{phaseDescription}

- {!isMobileScreen && status !== 'coming' && buttonChallenges} -
-
-
- {isMobileScreen && status !== 'coming' && buttonChallenges} - {isChallengesOpen && phaseDuration && ( - - )} -
- ) -} diff --git a/src/data/phase/dto.type.ts b/src/data/phase/dto.type.ts index ac36f1cf..ed5608dd 100644 --- a/src/data/phase/dto.type.ts +++ b/src/data/phase/dto.type.ts @@ -4,14 +4,15 @@ export type Duration = Readonly<{ }> export type TaskContentId = 'rewards' | 'criteria' | 'submit' | 'description' +export type PhaseStatus = 'coming' | 'active' | 'closed' export type TaskContent = Readonly<{ id: TaskContentId - name: string - content: JSX.Element + title: string + contentDescription: JSX.Element }> -export type Task = Readonly<{ +export type TaskDTO = Readonly<{ group: string taskName: string taskContent: TaskContent[] @@ -23,7 +24,7 @@ export type PhaseDTO = Readonly<{ number: number phaseName: string phaseDescription: string - status: 'coming' | 'active' | 'closed' + status: PhaseStatus phaseDuration?: Duration - tasks: Task[] + tasks: TaskDTO[] }> diff --git a/src/data/phase/imbolc.tsx b/src/data/phase/imbolc.tsx index 5e63c6c0..a7743e74 100644 --- a/src/data/phase/imbolc.tsx +++ b/src/data/phase/imbolc.tsx @@ -8,6 +8,7 @@ export const imbolc = ({ phaseName: 'imbolc', phaseDescription: "The second phase is focused on testing Druids' performance and uptime. Maintenance tasks and upgrades will be performed to test different kinds of state migrations.", + status: 'active', phaseDuration: { from: '2023-01-02T00:00:00Z', to: '2023-01-31T23:59:59Z' @@ -19,18 +20,18 @@ export const imbolc = ({ taskContent: [ { id: 'description', - name: 'Description', - content:

Provide a public RPC endpoint.

+ title: 'Description', + contentDescription:

Provide a public RPC endpoint.

}, { id: 'rewards', - name: 'Rewards', - content:

1 500 points.

+ title: 'Rewards', + contentDescription:

1 500 points.

}, { id: 'criteria', - name: 'Judging Criteria', - content: ( + title: 'Judging Criteria', + contentDescription: (

You will receive the points once the OKP4 team has checked your RPC endpoint availability. @@ -39,8 +40,8 @@ export const imbolc = ({ }, { id: 'submit', - name: 'How to Submit', - content: ( + title: 'How to Submit', + contentDescription: (

Share the RPC endpoint on{' '} @@ -63,18 +64,18 @@ export const imbolc = ({ taskContent: [ { id: 'description', - name: 'Description', - content:

Provide network snapshots.

+ title: 'Description', + contentDescription:

Provide network snapshots.

}, { id: 'rewards', - name: 'Rewards', - content:

2 000 points.

+ title: 'Rewards', + contentDescription:

2 000 points.

}, { id: 'criteria', - name: 'Judging Criteria', - content: ( + title: 'Judging Criteria', + contentDescription: (

You will receive the points once the OKP4 team has checked your snapshots availability. @@ -83,8 +84,8 @@ export const imbolc = ({ }, { id: 'submit', - name: 'How to Submit', - content: ( + title: 'How to Submit', + contentDescription: (

Share the link to your snapshots on{' '} @@ -107,8 +108,8 @@ export const imbolc = ({ taskContent: [ { id: 'description', - name: 'Description', - content: ( + title: 'Description', + contentDescription: (

Observability is a key element for monitoring network behavior and usage and detect possible anomalies, and as a validator you have access to a lot of information and @@ -118,13 +119,13 @@ export const imbolc = ({ }, { id: 'rewards', - name: 'Rewards', - content:

2 000 points.

+ title: 'Rewards', + contentDescription:

2 000 points.

}, { id: 'criteria', - name: 'Judging Criteria', - content: ( + title: 'Judging Criteria', + contentDescription: ( <>

OKP4 team will judge if any submission deserves points or not based on the @@ -141,8 +142,8 @@ export const imbolc = ({ }, { id: 'submit', - name: 'How to Submit', - content: ( + title: 'How to Submit', + contentDescription: (

Share the link to your dashboard on{' '} @@ -165,8 +166,8 @@ export const imbolc = ({ taskContent: [ { id: 'description', - name: 'Description', - content: ( + title: 'Description', + contentDescription: (

Publish a tweet about the Uptime challenge while including the @OKP4_PROTOCOL tag using your validator Twitter account. Feel free to share your excitement! @@ -175,18 +176,20 @@ export const imbolc = ({ }, { id: 'rewards', - name: 'Rewards', - content:

500 points.

+ title: 'Rewards', + contentDescription:

500 points.

}, { id: 'criteria', - name: 'Judging Criteria', - content:

You will receive the points once the OKP4 team has reviewed your tweet.

+ title: 'Judging Criteria', + contentDescription: ( +

You will receive the points once the OKP4 team has reviewed your tweet.

+ ) }, { id: 'submit', - name: 'How to Submit', - content:

Tweets are automatically tracked.

+ title: 'How to Submit', + contentDescription:

Tweets are automatically tracked.

} ], taskDuration: { @@ -201,8 +204,8 @@ export const imbolc = ({ taskContent: [ { id: 'description', - name: 'Description', - content: ( + title: 'Description', + contentDescription: ( <>

Maintain the best uptime with your validator. This is war, we may intentionally @@ -214,8 +217,8 @@ export const imbolc = ({ }, { id: 'rewards', - name: 'Rewards', - content: ( + title: 'Rewards', + contentDescription: ( <>

Up to 15 000 points with the following formula: 15 001 ^0,01x - 1 with x = %uptime. @@ -226,13 +229,13 @@ export const imbolc = ({ }, { id: 'criteria', - name: 'Judging Criteria', - content:

The less blocks your validator miss, the more points you get.

+ title: 'Judging Criteria', + contentDescription:

The less blocks your validator miss, the more points you get.

}, { id: 'submit', - name: 'How to Submit', - content:

Missed blocks are automatically tracked.

+ title: 'How to Submit', + contentDescription:

Missed blocks are automatically tracked.

} ], taskDuration: { @@ -241,6 +244,5 @@ export const imbolc = ({ }, points: 15000 } - ], - status: 'active' + ] }) diff --git a/src/data/phase/sidh.tsx b/src/data/phase/sidh.tsx index 580094a3..79a00d50 100644 --- a/src/data/phase/sidh.tsx +++ b/src/data/phase/sidh.tsx @@ -6,6 +6,7 @@ export const sidh = ({ sidh: { originalContentUrl } }: TasksUrls): PhaseDTO => ( phaseName: 'sidh', phaseDescription: " This first phase is pretty basic, it is dedicated to setting up Druids' validator environment, participating in the genesis, and getting familiar with the OKP4 testnet.", + status: 'closed', phaseDuration: { from: '2022-12-01T00:00:00Z', to: '2023-01-01T23:59:00Z' @@ -17,8 +18,8 @@ export const sidh = ({ sidh: { originalContentUrl } }: TasksUrls): PhaseDTO => ( taskContent: [ { id: 'description', - name: 'Description', - content: ( + title: 'Description', + contentDescription: (

Before starting the network, we must to register your validator in the genesis.json.{' '}
@@ -42,13 +43,13 @@ export const sidh = ({ sidh: { originalContentUrl } }: TasksUrls): PhaseDTO => ( }, { id: 'rewards', - name: 'Rewards', - content:

1 000 points.

+ title: 'Rewards', + contentDescription:

1 000 points.

}, { id: 'criteria', - name: 'Judging Criteria', - content: ( + title: 'Judging Criteria', + contentDescription: (

You will receive the points once the OKP4 team has integrated your gentx in the genesis. @@ -57,8 +58,10 @@ export const sidh = ({ sidh: { originalContentUrl } }: TasksUrls): PhaseDTO => ( }, { id: 'submit', - name: 'How to Submit', - content:

Send the issue number in a private message to Anik#9282 on Discord.

+ title: 'How to Submit', + contentDescription: ( +

Send the issue number in a private message to Anik#9282 on Discord.

+ ) } ], taskDuration: { @@ -73,8 +76,8 @@ export const sidh = ({ sidh: { originalContentUrl } }: TasksUrls): PhaseDTO => ( taskContent: [ { id: 'description', - name: 'Description', - content: ( + title: 'Description', + contentDescription: (

It is time to make the okp4-nemeton-1 network alive; you have to set up your node and join the network. The technical documentation regarding node setup and network join @@ -91,18 +94,18 @@ export const sidh = ({ sidh: { originalContentUrl } }: TasksUrls): PhaseDTO => ( }, { id: 'rewards', - name: 'Rewards', - content:

2 000 points.

+ title: 'Rewards', + contentDescription:

2 000 points.

}, { id: 'criteria', - name: 'Judging Criteria', - content:

Your validator is up and running.

+ title: 'Judging Criteria', + contentDescription:

Your validator is up and running.

}, { id: 'submit', - name: 'How to Submit', - content: ( + title: 'How to Submit', + contentDescription: (

The validator's presence in the consensus will be automatically checked.

) } @@ -119,8 +122,8 @@ export const sidh = ({ sidh: { originalContentUrl } }: TasksUrls): PhaseDTO => ( taskContent: [ { id: 'description', - name: 'Description', - content: ( + title: 'Description', + contentDescription: (

Publish a tweet about the Nemeton testnet while including the @okp4_protocol tag using your validator Twitter account. Feel free to share your excitement! @@ -129,18 +132,20 @@ export const sidh = ({ sidh: { originalContentUrl } }: TasksUrls): PhaseDTO => ( }, { id: 'rewards', - name: 'Rewards', - content:

500 points.

+ title: 'Rewards', + contentDescription:

500 points.

}, { id: 'criteria', - name: 'Judging Criteria', - content:

You will receive the points once the OKP4 team has reviewed your tweet.

+ title: 'Judging Criteria', + contentDescription: ( +

You will receive the points once the OKP4 team has reviewed your tweet.

+ ) }, { id: 'submit', - name: 'How to Submit', - content:

Tweets are automatically tracked.

+ title: 'How to Submit', + contentDescription:

Tweets are automatically tracked.

} ], taskDuration: { @@ -155,25 +160,27 @@ export const sidh = ({ sidh: { originalContentUrl } }: TasksUrls): PhaseDTO => ( taskContent: [ { id: 'description', - name: 'Description', - content:

Maintain the best uptime with your validator.

+ title: 'Description', + contentDescription:

Maintain the best uptime with your validator.

}, { id: 'rewards', - name: 'Rewards', - content: ( + title: 'Rewards', + contentDescription: (

Up to 2 500 points with the following formula: 2 501 ^0,01x - 1 with x = %uptime.

) }, { id: 'criteria', - name: 'Judging Criteria', - content:

You will receive the points once the OKP4 team has reviewed your tweet.

+ title: 'Judging Criteria', + contentDescription: ( +

You will receive the points once the OKP4 team has reviewed your tweet.

+ ) }, { id: 'submit', - name: 'How to Submit', - content:

Missed blocks are automatically tracked.

+ title: 'How to Submit', + contentDescription:

Missed blocks are automatically tracked.

} ], taskDuration: { @@ -188,8 +195,8 @@ export const sidh = ({ sidh: { originalContentUrl } }: TasksUrls): PhaseDTO => ( taskContent: [ { id: 'description', - name: 'Description', - content: ( + title: 'Description', + contentDescription: (

Based on your experience as a validator, write an original article, twitter thread or video content providing value to other validators and the community in general. @@ -214,8 +221,8 @@ export const sidh = ({ sidh: { originalContentUrl } }: TasksUrls): PhaseDTO => ( }, { id: 'rewards', - name: 'Rewards', - content: ( + title: 'Rewards', + contentDescription: (

Up to 10 000 points per druid will be attributed, capped at 150 000 points in total.

@@ -223,8 +230,8 @@ export const sidh = ({ sidh: { originalContentUrl } }: TasksUrls): PhaseDTO => ( }, { id: 'criteria', - name: 'Judging Criteria', - content: ( + title: 'Judging Criteria', + contentDescription: ( <>

OKP4 team will judge if any submission deserves points or not based on the @@ -244,8 +251,8 @@ export const sidh = ({ sidh: { originalContentUrl } }: TasksUrls): PhaseDTO => ( }, { id: 'submit', - name: 'How to Submit', - content: ( + title: 'How to Submit', + contentDescription: (

Share the content links on{' '} @@ -262,6 +269,5 @@ export const sidh = ({ sidh: { originalContentUrl } }: TasksUrls): PhaseDTO => ( }, points: 10000 } - ], - status: 'closed' + ] }) diff --git a/src/hook/useAccordion.ts b/src/hook/useAccordion.ts index 8c833610..2f6dac3f 100644 --- a/src/hook/useAccordion.ts +++ b/src/hook/useAccordion.ts @@ -4,6 +4,6 @@ export type AccordionState = string | number | null export type UseAccordionTuple = [AccordionState, (value: AccordionState) => void] export const useAccordion = (): UseAccordionTuple => { - const [activeIndex, setActiveIndex] = useState(null) - return [activeIndex, setActiveIndex] + const [accordionState, setAccordionState] = useState(null) + return [accordionState, setAccordionState] } diff --git a/src/pages/faq.tsx b/src/pages/faq.tsx index 1d578c78..c75684a5 100644 --- a/src/pages/faq.tsx +++ b/src/pages/faq.tsx @@ -353,7 +353,7 @@ const Faq: NextPage = props => {

{faqs({ discordUrl }).map(({ part, question, answer }, index, array) => { - const previous: FAQ | null = index > 0 ? array[index - 1] : null + const previous = index > 0 ? array[index - 1] : null const active = activeIndex === index const mustDisplayPart = !previous || previous.part !== part diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 0c1572a8..20acdbc9 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -4,7 +4,7 @@ import { Header } from '../components/layout/header/Header' import { Footer } from '../components/layout/footer/Footer' import type { Config } from '../types/config.type' import { config } from '../lib/config' -import { PhaseCard } from '../components/phaseCard/PhaseCard' +import { PhaseCard } from '../components/card/phase/PhaseCard' import Phases from '../data/phase/index' import type { PhaseDTO } from '../data/phase/dto.type' @@ -18,7 +18,7 @@ const Home: NextPage = props => {
-
+
{Object.values(Phases(urls.tasksUrls)).map( ({ tasks, phaseDescription, phaseName, status, number, phaseDuration }: PhaseDTO) => (
diff --git a/src/pages/leaderboard.tsx b/src/pages/leaderboard.tsx index a3826411..8ecdef18 100644 --- a/src/pages/leaderboard.tsx +++ b/src/pages/leaderboard.tsx @@ -1,7 +1,7 @@ import type { GetServerSideProps, NextPage } from 'next' import InfiniteScroll from 'react-infinite-scroll-component' -import type { BaseCardProps } from '../components/card/BaseCard' -import { BaseCard } from '../components/card/BaseCard' +import type { BaseCardProps } from '../components/card/base/BaseCard' +import { BaseCard } from '../components/card/base/BaseCard' import { Head } from '../components/head/Head' import { Footer } from '../components/layout/footer/Footer' import { Header } from '../components/layout/header/Header' diff --git a/src/pages/tasks.tsx b/src/pages/tasks.tsx index 983067fe..662bf670 100644 --- a/src/pages/tasks.tsx +++ b/src/pages/tasks.tsx @@ -13,7 +13,13 @@ import { useAccordion } from '../hook/useAccordion' import type { AccordionState } from '../hook/useAccordion' import { Accordion } from '../components/accordion/Accordion' import Phases from '../data/phase/index' -import type { PhaseDTO, Task, TaskContent, TaskContentId } from '../data/phase/dto.type' +import type { + PhaseDTO, + PhaseStatus, + TaskDTO, + TaskContent, + TaskContentId +} from '../data/phase/dto.type' import { useRouter } from 'next/router' import moment from 'moment' @@ -22,15 +28,15 @@ export type TasksProps = Pick type PhaseAccordionProps = Readonly<{ activeAccordion: AccordionState - name: string - onClick: (challenge: string) => () => void - status: 'coming' | 'active' | 'closed' - tasks: Task[] + onClick: (taskName: string) => () => void + phaseName: string + status: PhaseStatus + tasks: TaskDTO[] }> const ContentBlock: React.FC = ({ title, description, icon }): JSX.Element => ( @@ -60,57 +66,56 @@ const formatString = (text: string): string => text.trim().toLowerCase() const PhaseAccordions: React.FC = ({ activeAccordion, - name, + phaseName, status, tasks, onClick -}): JSX.Element => { - return ( -
-

- {name} - {status === 'closed' && (closed)} -

- {tasks.map(({ taskName, taskContent, taskDuration }, index) => { - const { from, to } = taskDuration - const title = ( -
-

{taskName}

-

{`${moment(from).utc().format('MMM. Do, H:mm ')} UTC - ${moment(to) - .utc() - .format('MMM. Do, H:mm ')} UTC`}

-
- ) - const accordionId = formatString(`${name}-${taskName}`) - const active = activeAccordion === accordionId - return ( -
- - {taskContent.map( - ({ id, name, content }: TaskContent): JSX.Element => ( -
- -
- ) - )} - - } - isExpanded={active} - onToggle={onClick(accordionId)} - title={title} - /> -
- ) - })} -
- ) -} +}): JSX.Element => ( +
+

+ {phaseName} + {status === 'closed' && (closed)} +

+ {tasks.map(({ taskName, taskContent, taskDuration }, index) => { + const { from, to } = taskDuration + const title = ( +
+

{taskName}

+

{`${moment(from).utc().format('MMM. Do, H:mm ')} UTC - ${moment(to) + .utc() + .format('MMM. Do, H:mm ')} UTC`}

+
+ ) + const accordionId = formatString(`${phaseName}-${taskName}`) + const active = activeAccordion === accordionId + + return ( +
+ + {taskContent.map( + ({ id, title, contentDescription }: TaskContent): JSX.Element => ( +
+ +
+ ) + )} + + } + isExpanded={active} + onToggle={onClick(accordionId)} + title={title} + /> +
+ ) + })} +
+) const Tasks: NextPage = props => { const { @@ -118,21 +123,21 @@ const Tasks: NextPage = props => { urls: { tasksUrls } } = props const { query } = useRouter() - const [activeChallenge, setActiveChallenge] = useAccordion() + const [activeTask, setActiveTask] = useAccordion() const handleClick = useCallback( - (challenge: string) => () => { - activeChallenge === challenge ? setActiveChallenge(null) : setActiveChallenge(challenge) + (taskName: string) => () => { + activeTask === taskName ? setActiveTask(null) : setActiveTask(taskName) }, - [activeChallenge, setActiveChallenge] + [activeTask, setActiveTask] ) useEffect(() => { const { phase, task } = query if (typeof task === 'string' && typeof phase === 'string') { - setActiveChallenge(formatString(decodeURI(`${phase}-${task}`))) + setActiveTask(formatString(decodeURI(`${phase}-${task}`))) } - }, [query, setActiveChallenge]) + }, [query, setActiveTask]) return (
@@ -141,14 +146,14 @@ const Tasks: NextPage = props => {

Tasks

- {Object.values(Phases(tasksUrls)).map( + {Phases(tasksUrls).map( ({ phaseName, tasks, status }: PhaseDTO, index) => status !== 'coming' && (
diff --git a/src/styles.scss b/src/styles.scss index 2d721791..6fd18e24 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -4,8 +4,9 @@ @import './mixins.scss'; @import 'src/components/layout/header/header.scss'; @import 'src/components/layout/footer/footer.scss'; -@import 'src/components/phaseCard/challenges.scss'; -@import 'src/components/card/card.scss'; +@import 'src/components/card/phase/phaseCard.scss'; +@import 'src/components/dropDown/phase/phaseDropDown.scss'; +@import 'src/components/card/base/baseCard.scss'; @import 'src/components/podium/podium.scss'; @import 'src/components/search/search.scss'; @import 'src/components/table/table.scss'; @@ -46,17 +47,6 @@ main { color: $primary-white; } -.okp4-nemeton-web-challenge-clear-icon { - font-size: 32px !important; - cursor: pointer; - order: 4; - - @media screen and (max-width: 1100px) { - align-self: flex-end; - order: 0; - } -} - .okp4-nemeton-web-icon { cursor: pointer; @@ -241,7 +231,7 @@ main { } } -.okp4-nemeton-web-phases-main { +.okp4-nemeton-web-home-main { display: flex; flex-direction: column; gap: 90px; @@ -250,275 +240,6 @@ main { gap: 30px; margin-top: 20px; } - - .okp4-nemeton-web-phase-main { - display: flex; - flex-direction: column; - - .okp4-nemeton-web-phase-content-button-container { - display: flex; - flex-direction: column; - gap: 10px; - margin: 20px 0 0 80px; - - > p { - font-family: 'Gotham light', sans-serif; - align-self: center; - } - - @media screen and (max-width: 900px) { - margin-left: unset; - } - } - - .okp4-nemeton-web-phase-container { - display: flex; - width: 100%; - justify-content: center; - - @media screen and (min-width: 2125px) { - background-image: none; - justify-content: center; - } - - @media screen and (max-width: 1440px) { - background-size: 802px 602px; - } - - @media screen and (max-width: 580px) { - background-repeat: no-repeat; - background-position: center top 5%; - background-size: contain; - } - - &.imbolc { - @media screen and (max-width: 580px) { - background-image: url('/image/imbolc-mobile.webp'); - } - } - - &.sidh { - @media screen and (max-width: 580px) { - background-image: url('/image/sidh-mobile.webp'); - } - } - - &.beltaine { - @media screen and (max-width: 580px) { - background-image: url('/image/beltaine-mobile.webp'); - } - } - - .okp4-nemeton-web-phase-content-container { - max-width: 1312px; - width: 100%; - min-width: 1170px; - height: 519px; - border: 1px solid white; - border-radius: 20px; - margin: 0 177px; - position: relative; - background-repeat: no-repeat; - - &.no-border { - border: unset; - } - - &.imbolc { - background-image: url('/image/imbolc-large.webp'); - background-position: left; - background-size: contain; - @media screen and (min-width: 581px) and (max-width: 1920px) { - background-image: url('/image/imbolc-medium.webp'); - } - @media screen and (max-width: 900px) { - background-size: cover; - } - } - - &.beltaine { - background-image: url('/image/beltaine-large.webp'); - background-position: right; - background-size: auto; - @media screen and (min-width: 581px) and (max-width: 1920px) { - background-image: url('/image/beltaine-medium.webp'); - } - @media screen and (max-width: 900px) { - background-size: cover; - } - } - - &.sidh { - background-image: url('/image/sidh-large.webp'); - background-position: right; - background-size: contain; - @media screen and (min-width: 581px) and (max-width: 1920px) { - background-image: url('/image/sidh-medium.webp'); - } - @media screen and (max-width: 900px) { - background-size: cover; - } - } - - @media screen and (min-width: 2125px) { - background-image: url('/image/sidh.webp'); - background-position: right; - background-repeat: no-repeat; - } - - @media screen and (max-width: 1440px) { - max-width: 1197px; - height: 470px; - margin: 0 122px; - min-width: unset; - } - - @media screen and (max-width: 580px) { - background-image: none !important; - width: 100%; - height: 475px; - margin: 50px 26px 0; - min-width: unset; - } - - .okp4-nemeton-web-phase-content-details { - padding: 20px 0 0 45px; - display: flex; - flex-direction: column; - align-items: baseline; - gap: 15px; - height: 100%; - - @media screen and (max-width: 900px) { - padding: 0 20px; - align-items: center; - } - - &.imbolc { - @media screen and (min-width: 901px) { - align-items: flex-end; - padding: 20px 45px 0 0; - } - } - @media screen and (max-width: 580px) { - padding: 0 20px; - gap: 0; - display: flex; - flex-direction: column; - justify-content: flex-end; - max-height: inherit; - } - - > p:first-of-type { - max-height: 201px; - max-width: 420px; - font-size: 20px; - font-family: 'Gotham light', sans-serif; - @media screen and (max-width: 580px) { - font-size: 18px; - height: unset; - padding-bottom: 20px; - } - } - } - - .okp4-nemeton-web-phase-content-title { - display: flex; - align-items: baseline; - @media screen and (max-width: 580px) { - justify-content: center; - } - - > h1:first-of-type { - font-size: 140px; - line-height: 1em; - word-break: keep-all; - @media screen and (max-width: 580px) { - font-size: 100px; - } - } - - > h2:first-of-type { - padding-right: 22px; - font-size: 40px; - white-space: nowrap; - @media screen and (max-width: 580px) { - font-size: 30px; - } - } - } - } - } - - .okp4-nemeton-web-challenge-button { - max-width: 350px; - border: 2px solid white; - background-color: transparent; - color: white; - cursor: pointer; - font-family: 'Six Caps', sans-serif; - font-size: 42px; - letter-spacing: 2px; - padding: 0 28px; - - &.right { - margin: 20px 80px 0 0; - @media screen and (max-width: 900px) { - margin-right: unset; - } - } - - &.disabled { - pointer-events: none; - } - - @media screen and (max-width: 580px) { - white-space: nowrap; - max-width: unset; - font-size: 38px; - margin-top: 26px; - align-self: center; - margin-left: unset; - } - } - } -} - -.okp4-nemeton-web-phase-mask-container { - position: absolute; - background-color: #2e534f; - width: 100%; - height: 519px; - min-width: unset; - border-radius: 20px; - opacity: 0.95; - display: flex; - align-items: center; - justify-content: center; - - @media screen and (max-width: 580px) { - height: 475px; - } - - @media screen and (min-width: 581px) and (max-width: 1440px) { - height: 470px; - } - - > h2:first-of-type { - font-size: 80px; - padding: 0 40px; - @media screen and (max-width: 900px) { - font-size: 40px; - } - } - - .okp4-nemeton-web-phase-mask-divider { - width: 100px; - border-top: 3px solid white; - @media screen and (max-width: 900px) { - width: 40px; - } - } } .okp4-nemeton-web-page-leaderboard-summary-container {