diff --git a/client/src/components/daily-coding-challenge/calendar-day.tsx b/client/src/components/daily-coding-challenge/calendar-day.tsx index 91d5968a1b5..ef6038d6808 100644 --- a/client/src/components/daily-coding-challenge/calendar-day.tsx +++ b/client/src/components/daily-coding-challenge/calendar-day.tsx @@ -1,26 +1,50 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; +import { Spacer } from '@freecodecamp/ui'; import { Link } from '../helpers'; import GreenPass from '../../assets/icons/green-pass'; import GreenNotCompleted from '../../assets/icons/green-not-completed'; +import JavaScriptIcon from '../../assets/icons/javascript'; +import PythonIcon from '../../assets/icons/python'; import { formatDisplayDate } from './helpers'; interface CalendarDayProps { dayNumber: number; date?: string; - isCompleted?: boolean; + challengeNumber?: number; + completedLanguages?: string[]; isAvailable?: boolean; + title?: string; } -// Todo: Change this to render checkmarks for JS and Python +function Checkmark({ completed }: { completed: boolean }): JSX.Element { + return completed ? ( + + + + ) : ( + + + + ); +} function DailyCodingChallengeCalendarDay({ dayNumber, date, - isCompleted = false, - isAvailable = false + isAvailable = false, + title, + completedLanguages = [], + challengeNumber }: CalendarDayProps): JSX.Element { const { t } = useTranslation(); + // dayNumber = 0 -> render nothing if (dayNumber === 0) return
; @@ -50,21 +74,41 @@ function DailyCodingChallengeCalendarDay({ {dayNumber} - {isCompleted ? ( - - - - ) : ( - - - - )} +
#{challengeNumber}
+ +
+
{title}
+ + {completedLanguages.length === 2 ? ( + + + + + + + ) : ( +
+
+
+
+ +
+
JavaScript
+ +
+ +
+
+ +
+
Python
+ +
+
+ )} +
); } diff --git a/client/src/components/daily-coding-challenge/calendar.css b/client/src/components/daily-coding-challenge/calendar.css index e78dc3795da..e673c92f904 100644 --- a/client/src/components/daily-coding-challenge/calendar.css +++ b/client/src/components/daily-coding-challenge/calendar.css @@ -2,6 +2,9 @@ display: grid; grid-template-columns: repeat(7, 1fr); text-align: center; + margin: 0 auto; + max-width: 1500px; + padding: 0 20px; } .calendar-head { @@ -18,18 +21,85 @@ display: grid; grid-template-columns: repeat(7, 1fr); /* 7 columns for days of the week */ gap: 4px; + margin: 0 auto; + max-width: 1500px; + padding: 0 20px; } .calendar-day { - display: flex; - justify-content: center; - align-items: center; position: relative; - padding: 10px; - min-height: 100px; + padding: 10px 10px 20px; + min-height: 200px; border: 1px solid var(--tertiary-color); - text-align: center; background-color: var(--primary-background); + text-decoration: none; +} + +.calendar-day-number { + position: absolute; + top: 0; + left: 5px; +} + +.dc-number { + font-style: italic; + color: var(--gray-45); + text-align: center; +} + +.dc-info { + display: flex; + flex-direction: column; + height: 100%; +} + +.dc-title { + width: 100%; + text-align: center; + align-self: center; + margin-top: 5px; + height: 50px; + margin-bottom: -5px; +} + +.dc-languages { + padding: 0 15px; +} + +.dc-language { + font-size: 0.8rem; + display: flex; + justify-content: space-between; + align-items: center; + gap: 10px; +} + +.dc-language-icon { + fill: var(--primary-color); +} + +.dc-language-icon svg { + width: 25px; + height: 25px; +} + +.dc-small-checkmark { + position: relative; + /* top: -5px; */ +} + +.dc-small-checkmark svg { + width: 20px; + height: 20px; +} + +.dc-big-checkmark { + margin: 0 auto; +} + +.dc-big-checkmark svg { + width: 50px; + height: 50px; } .not-available:hover, @@ -38,22 +108,24 @@ background-color: var(--primary-background); } -.not-available:hover .calendar-day-number, -.not-available:active .calendar-day-number { - color: var(--primary-color); -} - .available:hover, .available:active { cursor: pointer; background-color: var(--primary-color); } +.not-available:hover .calendar-day-number, +.not-available:active .calendar-day-number { + color: var(--primary-color); +} + .available:hover .calendar-day-number, .available:active .calendar-day-number { color: var(--primary-background); } +.available:hover .dc-language-icon svg path, +.available:active .dc-language-icon svg path, .available:hover .completed svg circle, .available:active .completed svg circle { fill: var(--primary-background); @@ -72,27 +144,16 @@ stroke: var(--primary-color); } -.calendar-day-number { - position: absolute; - top: 0; - left: 5px; +.available:hover .dc-title, +.available:active .dc-title, +.available:hover .dc-language-name, +.available:active .dc-language-name { + color: var(--primary-background); } -.calendar-day svg, -.empty-cirle { - width: calc(10px + 2vw); - height: calc(10px + 2vw); - max-width: 40px; - max-height: 40px; -} - -.empty-cirle { - width: calc(10px + 2vw); - height: calc(10px + 2vw); - max-width: 40px; - max-height: 40px; - border-radius: 50%; - border: 2px solid var(--primary-color); +.available:hover .dc-number, +.available:active .dc-number { + color: var(--quaternary-background); } @media (max-width: 500px) { @@ -100,3 +161,75 @@ min-height: 75px; } } + +@media (max-width: 1345px) { + .calendar-day-number, + .dc-number, + .dc-title { + font-size: 0.8rem; + } + + .dc-info hr { + margin: 10px 0; + } + + .calendar-day { + min-height: 170px; + } + + .dc-language { + justify-content: center; + } + + .dc-language-name { + display: none; + } +} + +@media (max-width: 1115px) { + .calendar-grid { + max-width: unset; + } + + .calendar-day { + min-height: 125px; + padding: 10px; + } + + .dc-title, + .dc-info hr { + display: none; + } + + .dc-info { + display: flex; + justify-content: center; + align-items: center; + height: 80%; + } + + .dc-spacer { + display: none; + } +} + +@media (max-width: 815px) { + .calendar-day { + min-height: 100px; + padding: 0; + } + + .dc-number { + text-align: right; + padding-right: 5px; + } + + .dc-languages { + padding: 0; + } + + .dc-big-checkmark svg { + width: 40px; + height: 40px; + } +} diff --git a/client/src/components/daily-coding-challenge/calendar.tsx b/client/src/components/daily-coding-challenge/calendar.tsx index 231013ef26a..41420235de8 100644 --- a/client/src/components/daily-coding-challenge/calendar.tsx +++ b/client/src/components/daily-coding-challenge/calendar.tsx @@ -1,12 +1,15 @@ import React, { useEffect, useState } from 'react'; import { connect } from 'react-redux'; import { useTranslation } from 'react-i18next'; -import { Button, Callout, Col, Spacer } from '@freecodecamp/ui'; +import { Button, Callout, Container, Col, Row, Spacer } from '@freecodecamp/ui'; import { completedDailyCodingChallengesSelector, isSignedInSelector } from '../../redux/selectors'; -import { CompletedDailyCodingChallenge } from '../../redux/prop-types'; +import { + CompletedDailyCodingChallenge, + DailyCodingChallengeLanguages +} from '../../redux/prop-types'; import { Loader } from '../helpers'; import envData from '../../../config/env.json'; import Login from '../Header/components/login'; @@ -40,9 +43,9 @@ interface AllDailyChallengeFromDb { interface DailyChallengeMap { id: string; date: string; - isCompleted: boolean; challengeNumber: number; title: string; + completedLanguages: DailyCodingChallengeLanguages[]; } type DailyChallengesMap = Map; @@ -85,16 +88,20 @@ const getMonthInfo = ( }); const challengeData = dailyChallengesMap.get(formattedDate); - const isCompleted = challengeData?.isCompleted || false; + const completedLanguages = challengeData?.completedLanguages || []; + const title = challengeData?.title || ''; const isAvailable = challengeData !== undefined; + const challengeNumber = challengeData?.challengeNumber; days.push( ); } @@ -118,10 +125,6 @@ function DailyCodingChallengeCalendar({ const todayUsCentral = getTodayUsCentral(); - const completedDailyCodingChallengeIds = completedDailyCodingChallenges.map( - c => c.id - ); - const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(false); const [monthInfo, setMonthInfo] = useState(null); @@ -145,7 +148,9 @@ function DailyCodingChallengeCalendar({ newDailyChallengesMap.set(date, { ...c, date, - isCompleted: completedDailyCodingChallengeIds.includes(c.id) + completedLanguages: + completedDailyCodingChallenges.find(ch => ch.id === c.id) + ?.languages ?? [] }); }); @@ -231,18 +236,22 @@ function DailyCodingChallengeCalendar({ return ( <> - - - {t('daily-coding-challenges.release-note')} - + + + + + {t('daily-coding-challenges.release-note')} + - - + + + + diff --git a/client/src/pages/learn/daily-coding-challenge/archive.tsx b/client/src/pages/learn/daily-coding-challenge/archive.tsx index 3c98fa0995c..a4e15e34803 100644 --- a/client/src/pages/learn/daily-coding-challenge/archive.tsx +++ b/client/src/pages/learn/daily-coding-challenge/archive.tsx @@ -9,25 +9,26 @@ function Archive(): JSX.Element { const { t } = useTranslation(); return ( - - - - -

- {t('daily-coding-challenges.title')} -

- - - - - - - - - - -
-
+ <> + +

+ {t('daily-coding-challenges.title')} +

+ + + + + + + + + + + + + + + ); } diff --git a/client/src/redux/prop-types.ts b/client/src/redux/prop-types.ts index 78f6af19aa3..b895d084fab 100644 --- a/client/src/redux/prop-types.ts +++ b/client/src/redux/prop-types.ts @@ -331,7 +331,7 @@ export type DailyCodingChallengeLanguages = 'javascript' | 'python'; export interface CompletedDailyCodingChallenge { id: string; completedDate: number; - completedLanguages: DailyCodingChallengeLanguages[]; + languages: DailyCodingChallengeLanguages[]; } type Quiz = { diff --git a/e2e/daily-coding-challenge.spec.ts b/e2e/daily-coding-challenge.spec.ts index 1a16eb8203c..b0586353a3c 100644 --- a/e2e/daily-coding-challenge.spec.ts +++ b/e2e/daily-coding-challenge.spec.ts @@ -251,6 +251,6 @@ test.describe('Daily Coding Challenge Archive', () => { await expect(page.getByTestId('calendar-day-completed')).toHaveCount(1); - await expect(page.getByTestId('calendar-day-not-completed')).toHaveCount(1); + await expect(page.getByTestId('calendar-day-not-completed')).toHaveCount(3); }); });