From 7c207f9ebb78a9664b8af0f9ff8b843acb3b48e8 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Thu, 14 Nov 2024 21:43:57 +0200 Subject: [PATCH] Feat/552 rs app courses integration (#633) * feat: 552 - add env with api url * feat: 552 - implement to fetch course data * fix: 552 - test issues * refactor: 552 - change all courses default date to TBD * chore: 552 - add env secret to CI build step * chore: 552 - add env secret to CI build step * refactor: 552 - remove unnecessary file extensions * refactor: 552 - remove console log * feat: 552 - add error handling for course fetching * fix: 552 - to include all course aliases * feat: 552 - implement to also sync with registration date * refactor: 552 - add consistency to test cases * chore: 552 - add warning comment --- .env.example | 1 + .github/workflows/main.yml | 2 + .github/workflows/preview-create.yml | 2 + .github/workflows/production.yml | 2 + .gitignore | 3 + dev-data/courses.data.ts | 37 ++++-- env.d.ts | 5 + package-lock.json | 7 +- package.json | 4 +- src/app/courses/angular/page.tsx | 8 +- src/app/courses/aws-cloud-developer/page.tsx | 8 +- src/app/courses/aws-devops/page.tsx | 8 +- src/app/courses/aws-fundamentals/page.tsx | 8 +- .../courses/javascript-preschool-ru/page.tsx | 8 +- src/app/courses/javascript-ru/page.tsx | 8 +- src/app/courses/javascript/page.tsx | 8 +- src/app/courses/nodejs/page.tsx | 8 +- src/app/courses/page.tsx | 3 +- src/app/courses/reactjs/page.tsx | 8 +- src/core/base-layout/base-layout.test.tsx | 6 +- src/core/base-layout/base-layout.tsx | 7 +- .../components/footer/desktop-view.tsx | 9 +- .../components/footer/footer.test.tsx | 21 ++- .../base-layout/components/footer/footer.tsx | 7 +- .../components/header/header.test.tsx | 5 +- .../base-layout/components/header/header.tsx | 28 ++-- src/entities/course/api/course-api.ts | 21 +++ src/entities/course/constants.ts | 2 + .../course/helpers/sync-with-api-data.ts | 27 ++++ src/entities/course/types.ts | 26 +++- .../ui/course-card/course-card.test.tsx | 5 +- .../ui/course-item/course-item.test.tsx | 5 +- src/shared/__tests__/constants.ts | 122 +++++++++++++++++- src/shared/__tests__/setup-tests.tsx | 5 +- src/shared/constants.ts | 14 ++ src/shared/helpers/get-course-title.ts | 10 +- src/shared/helpers/getActualData.test.ts | 22 +++- src/shared/helpers/getActualData.ts | 13 +- src/shared/helpers/getCourseDate.test.ts | 5 +- src/shared/helpers/getCourseDate.ts | 3 +- .../use-course-by-title/utils/is-course.ts | 7 - .../utils/select-course.ts | 18 +-- src/views/courses.tsx | 8 +- .../ui/about-course/about-course.test.tsx | 10 +- src/widgets/courses/ui/courses.test.tsx | 52 ++------ src/widgets/courses/ui/courses.tsx | 13 +- .../hero-course/ui/hero-course.test.tsx | 3 + .../member-activity/ui/stages/stages.test.tsx | 14 +- src/widgets/mobile-view/ui/mobile-view.tsx | 12 +- src/widgets/school-menu/school-menu.test.tsx | 83 +++--------- src/widgets/school-menu/ui/school-menu.tsx | 10 +- .../ui/training-program.test.tsx | 16 ++- .../ui/upcoming-courses.test.tsx | 122 +----------------- .../upcoming-courses/ui/upcoming-courses.tsx | 5 +- tsconfig.json | 3 +- 55 files changed, 485 insertions(+), 392 deletions(-) create mode 100644 .env.example create mode 100644 env.d.ts create mode 100644 src/entities/course/api/course-api.ts create mode 100644 src/entities/course/constants.ts create mode 100644 src/entities/course/helpers/sync-with-api-data.ts delete mode 100644 src/shared/hooks/use-course-by-title/utils/is-course.ts diff --git a/.env.example b/.env.example new file mode 100644 index 000000000..717171d2e --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +API_URL=URL diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c97b93293..3e5316702 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -36,3 +36,5 @@ jobs: - name: Build run: npm run build + env: + API_URL: ${{ secrets.API_URL }} diff --git a/.github/workflows/preview-create.yml b/.github/workflows/preview-create.yml index 86ec1ef91..f300ac330 100644 --- a/.github/workflows/preview-create.yml +++ b/.github/workflows/preview-create.yml @@ -34,6 +34,8 @@ jobs: run: | npm ci npm run build + env: + API_URL: ${{ secrets.API_URL }} - uses: jakejarvis/s3-sync-action@master name: Sync to S3 diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml index e63a1696d..9847c9e6c 100644 --- a/.github/workflows/production.yml +++ b/.github/workflows/production.yml @@ -29,6 +29,8 @@ jobs: - name: Build run: npm run build + env: + API_URL: ${{ secrets.API_URL }} # Copy the static files to the S3 bucket from _next folder (js, css, images) # Set the cache-control header to 30 days diff --git a/.gitignore b/.gitignore index c854f3ef3..ff0063bc8 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ src/shared/__tests__/test-results src/shared/__tests__/report /blob-report/ /playwright/.cache/ + +# env +.env diff --git a/dev-data/courses.data.ts b/dev-data/courses.data.ts index 799dc2102..754288e28 100644 --- a/dev-data/courses.data.ts +++ b/dev-data/courses.data.ts @@ -17,17 +17,20 @@ import nodejs from '@/shared/assets/icons/node.svg'; import nodejsSecondary from '@/shared/assets/icons/nodejs-secondary.webp'; import reactSecondary from '@/shared/assets/icons/react-secondary.webp'; import react from '@/shared/assets/icons/react.svg'; +import { COURSE_ALIASES, TO_BE_DETERMINED } from '@/shared/constants'; import { COURSE_TITLES } from 'data'; export const courses: Course[] = [ { id: '1', title: COURSE_TITLES.JS_PRESCHOOL_RU, + alias: COURSE_ALIASES.JS_PRESCHOOL_RU, altTitle: 'JavaScript / Front-end', iconSrc: javascript, iconSmall: htmlSmall, secondaryIcon: jsSecondary, - startDate: 'Jun 24, 2024', + startDate: TO_BE_DETERMINED, + registrationEndDate: TO_BE_DETERMINED, language: ['ru'], mode: 'online', detailsUrl: `/${ROUTES.COURSES}/${ROUTES.JS_PRESCHOOL_RU}`, @@ -40,11 +43,13 @@ export const courses: Course[] = [ { id: '2', title: COURSE_TITLES.JS_EN, + alias: COURSE_ALIASES.JS_EN, altTitle: 'JavaScript / Front-end', iconSrc: javascript, iconSmall: jsSmall, secondaryIcon: jsSecondary, - startDate: 'Oct 28, 2024', + startDate: TO_BE_DETERMINED, + registrationEndDate: TO_BE_DETERMINED, language: ['en'], mode: 'online', detailsUrl: `/${ROUTES.COURSES}/${ROUTES.JS}`, @@ -57,11 +62,13 @@ export const courses: Course[] = [ { id: '3', title: COURSE_TITLES.JS_RU, + alias: COURSE_ALIASES.JS_RU, altTitle: 'JavaScript / Front-end', iconSrc: javascript, iconSmall: jsSmall, secondaryIcon: jsSecondary, - startDate: 'Oct 27, 2024', + startDate: TO_BE_DETERMINED, + registrationEndDate: TO_BE_DETERMINED, language: ['ru'], mode: 'online', detailsUrl: `/${ROUTES.COURSES}/${ROUTES.JS_RU}`, @@ -74,10 +81,12 @@ export const courses: Course[] = [ { id: '4', title: COURSE_TITLES.REACT, + alias: COURSE_ALIASES.REACT, iconSrc: react, iconSmall: reactSmall, secondaryIcon: reactSecondary, - startDate: 'Jul 1, 2024', + startDate: TO_BE_DETERMINED, + registrationEndDate: TO_BE_DETERMINED, language: ['en'], mode: 'online', detailsUrl: `/${ROUTES.COURSES}/${ROUTES.REACT}`, @@ -90,10 +99,12 @@ export const courses: Course[] = [ { id: '5', title: COURSE_TITLES.ANGULAR, + alias: COURSE_ALIASES.ANGULAR, iconSrc: angular, iconSmall: angularSmall, secondaryIcon: angularSecondary, - startDate: 'Jul 1, 2024', + startDate: TO_BE_DETERMINED, + registrationEndDate: TO_BE_DETERMINED, language: ['en'], mode: 'online', detailsUrl: `/${ROUTES.COURSES}/${ROUTES.ANGULAR}`, @@ -106,10 +117,12 @@ export const courses: Course[] = [ { id: '6', title: COURSE_TITLES.NODE, + alias: COURSE_ALIASES.NODE, iconSrc: nodejs, iconSmall: nodejsSmall, secondaryIcon: nodejsSecondary, - startDate: 'Sep 30, 2024', + startDate: TO_BE_DETERMINED, + registrationEndDate: TO_BE_DETERMINED, language: ['en'], mode: 'online', detailsUrl: `/${ROUTES.COURSES}/${ROUTES.NODE_JS}`, @@ -122,10 +135,12 @@ export const courses: Course[] = [ { id: '7', title: COURSE_TITLES.AWS_FUNDAMENTALS, + alias: COURSE_ALIASES.AWS_FUNDAMENTALS, iconSrc: aws, iconSmall: awsFundSmall, secondaryIcon: awsSecondary, - startDate: 'Apr 15, 2024', + startDate: TO_BE_DETERMINED, + registrationEndDate: TO_BE_DETERMINED, language: ['en'], mode: 'online', detailsUrl: `/${ROUTES.COURSES}/${ROUTES.AWS_FUNDAMENTALS}`, @@ -139,10 +154,12 @@ export const courses: Course[] = [ { id: '8', title: COURSE_TITLES.AWS_CLOUD_DEVELOPER, + alias: COURSE_ALIASES.AWS_CLOUD_DEVELOPER, iconSrc: aws, iconSmall: awsDevSmall, secondaryIcon: awsSecondary, - startDate: 'May 28, 2024', + startDate: TO_BE_DETERMINED, + registrationEndDate: TO_BE_DETERMINED, language: ['en'], mode: 'online', detailsUrl: `/${ROUTES.COURSES}/${ROUTES.AWS_DEVELOPER}`, @@ -155,10 +172,12 @@ export const courses: Course[] = [ { id: '9', title: COURSE_TITLES.AWS_DEVOPS, + alias: COURSE_ALIASES.AWS_DEVOPS, iconSrc: aws, iconSmall: awsDevSmall, secondaryIcon: awsSecondary, - startDate: 'Sep 23, 2024', + startDate: TO_BE_DETERMINED, + registrationEndDate: TO_BE_DETERMINED, language: ['en'], mode: 'online', detailsUrl: `/${ROUTES.COURSES}/${ROUTES.AWS_DEVOPS}`, diff --git a/env.d.ts b/env.d.ts new file mode 100644 index 000000000..b4d224638 --- /dev/null +++ b/env.d.ts @@ -0,0 +1,5 @@ +declare namespace NodeJS { + interface ProcessEnv { + API_URL: string; + } +} diff --git a/package-lock.json b/package-lock.json index e4ef11ade..6c345b708 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3297,10 +3297,11 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.8.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.7.tgz", - "integrity": "sha512-LidcG+2UeYIWcMuMUpBKOnryBWG/rnmOHQR5apjn8myTQcx3rinFRn7DcIFhMnS0PPFSC6OafdIKEad0lj6U0Q==", + "version": "22.8.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.6.tgz", + "integrity": "sha512-tosuJYKrIqjQIlVCM4PEGxOmyg3FCPa/fViuJChnGeEIhjA46oy8FMVoF9su1/v8PNs2a8Q0iFNyOx0uOF91nw==", "dev": true, + "license": "MIT", "dependencies": { "undici-types": "~6.19.8" } diff --git a/package.json b/package.json index e949d40a1..8a13d9aa6 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,9 @@ "build": "tsc && next build", "stylelint": "npx stylelint \"**/*.scss\"", "stylelint:fix": "npx stylelint \"**/*.scss\" --fix", - "lint": "npx eslint . --report-unused-disable-directives --max-warnings 400", + "lint": "npx eslint . --report-unused-disable-directives --max-warnings 500", "lint:err": "npx eslint . --quiet", - "lint:fix": "npx eslint . --fix --report-unused-disable-directives --max-warnings 400", + "lint:fix": "npx eslint . --fix --report-unused-disable-directives --max-warnings 500", "preview": "vite preview", "test": "vitest --run", "test:playwright": "npx playwright test", diff --git a/src/app/courses/angular/page.tsx b/src/app/courses/angular/page.tsx index 6964554dc..8ccb8dee9 100644 --- a/src/app/courses/angular/page.tsx +++ b/src/app/courses/angular/page.tsx @@ -2,16 +2,16 @@ import { Metadata } from 'next'; import { getCourseTitle } from '@/shared/helpers/get-course-title'; import { selectCourse } from '@/shared/hooks/use-course-by-title/utils/select-course'; import { Angular } from '@/views/angular'; -import { COURSE_TITLES, courses } from 'data'; +import { COURSE_TITLES } from 'data'; const courseName = COURSE_TITLES.ANGULAR; export async function generateMetadata(): Promise { - return { title: getCourseTitle(courseName) }; + return { title: await getCourseTitle(courseName) }; } -export default function AngularRoute() { - const course = selectCourse(courses, courseName); +export default async function AngularRoute() { + const course = await selectCourse(courseName); return ; } diff --git a/src/app/courses/aws-cloud-developer/page.tsx b/src/app/courses/aws-cloud-developer/page.tsx index 881696b0a..180f433ad 100644 --- a/src/app/courses/aws-cloud-developer/page.tsx +++ b/src/app/courses/aws-cloud-developer/page.tsx @@ -2,16 +2,16 @@ import { Metadata } from 'next'; import { getCourseTitle } from '@/shared/helpers/get-course-title'; import { selectCourse } from '@/shared/hooks/use-course-by-title/utils/select-course'; import { AwsDeveloper } from '@/views/aws-developer'; -import { COURSE_TITLES, courses } from 'data'; +import { COURSE_TITLES } from 'data'; const courseName = COURSE_TITLES.AWS_CLOUD_DEVELOPER; export async function generateMetadata(): Promise { - return { title: getCourseTitle(courseName) }; + return { title: await getCourseTitle(courseName) }; } -export default function AwsDeveloperRoute() { - const course = selectCourse(courses, courseName); +export default async function AwsDeveloperRoute() { + const course = await selectCourse(courseName); return ; } diff --git a/src/app/courses/aws-devops/page.tsx b/src/app/courses/aws-devops/page.tsx index 1bc9ad797..60fd1ee84 100644 --- a/src/app/courses/aws-devops/page.tsx +++ b/src/app/courses/aws-devops/page.tsx @@ -2,16 +2,16 @@ import { Metadata } from 'next'; import { getCourseTitle } from '@/shared/helpers/get-course-title'; import { selectCourse } from '@/shared/hooks/use-course-by-title/utils/select-course'; import { AwsDevOps } from '@/views/aws-devops'; -import { COURSE_TITLES, courses } from 'data'; +import { COURSE_TITLES } from 'data'; const courseName = COURSE_TITLES.AWS_DEVOPS; export async function generateMetadata(): Promise { - return { title: getCourseTitle(courseName) }; + return { title: await getCourseTitle(courseName) }; } -export default function AwsDeveloperRoute() { - const course = selectCourse(courses, courseName); +export default async function AwsDeveloperRoute() { + const course = await selectCourse(courseName); return ; } diff --git a/src/app/courses/aws-fundamentals/page.tsx b/src/app/courses/aws-fundamentals/page.tsx index 125b49269..0302c4e79 100644 --- a/src/app/courses/aws-fundamentals/page.tsx +++ b/src/app/courses/aws-fundamentals/page.tsx @@ -2,16 +2,16 @@ import { Metadata } from 'next'; import { getCourseTitle } from '@/shared/helpers/get-course-title'; import { selectCourse } from '@/shared/hooks/use-course-by-title/utils/select-course'; import { AwsFundamentals } from '@/views/aws-fundamentals'; -import { COURSE_TITLES, courses } from 'data'; +import { COURSE_TITLES } from 'data'; const courseName = COURSE_TITLES.AWS_FUNDAMENTALS; export async function generateMetadata(): Promise { - return { title: getCourseTitle(courseName) }; + return { title: await getCourseTitle(courseName) }; } -export default function AwsFundRoute() { - const course = selectCourse(courses, courseName); +export default async function AwsFundRoute() { + const course = await selectCourse(courseName); return ; } diff --git a/src/app/courses/javascript-preschool-ru/page.tsx b/src/app/courses/javascript-preschool-ru/page.tsx index b617d9272..15fa0667a 100644 --- a/src/app/courses/javascript-preschool-ru/page.tsx +++ b/src/app/courses/javascript-preschool-ru/page.tsx @@ -2,16 +2,16 @@ import { Metadata } from 'next'; import { getCourseTitle } from '@/shared/helpers/get-course-title'; import { selectCourse } from '@/shared/hooks/use-course-by-title/utils/select-course'; import { JavaScriptPreSchoolRu } from '@/views/javascript-preschool-ru'; -import { COURSE_TITLES, courses } from 'data'; +import { COURSE_TITLES } from 'data'; const courseName = COURSE_TITLES.JS_PRESCHOOL_RU; export async function generateMetadata(): Promise { - return { title: getCourseTitle(courseName) }; + return { title: await getCourseTitle(courseName) }; } -export default function JsPreRoute() { - const course = selectCourse(courses, courseName); +export default async function JsPreRoute() { + const course = await selectCourse(courseName); return ( diff --git a/src/app/courses/javascript-ru/page.tsx b/src/app/courses/javascript-ru/page.tsx index f49c29c98..ccb8da1ac 100644 --- a/src/app/courses/javascript-ru/page.tsx +++ b/src/app/courses/javascript-ru/page.tsx @@ -2,16 +2,16 @@ import { Metadata } from 'next'; import { getCourseTitle } from '@/shared/helpers/get-course-title'; import { selectCourse } from '@/shared/hooks/use-course-by-title/utils/select-course'; import { JavaScriptRu } from '@/views/javascript-ru'; -import { COURSE_TITLES, courses } from 'data'; +import { COURSE_TITLES } from 'data'; const courseName = COURSE_TITLES.JS_RU; export async function generateMetadata(): Promise { - return { title: getCourseTitle(courseName) }; + return { title: await getCourseTitle(courseName) }; } -export default function JsRuRoute() { - const course = selectCourse(courses, courseName); +export default async function JsRuRoute() { + const course = await selectCourse(courseName); return ; } diff --git a/src/app/courses/javascript/page.tsx b/src/app/courses/javascript/page.tsx index 5a1ce2bf0..2dd98e56c 100644 --- a/src/app/courses/javascript/page.tsx +++ b/src/app/courses/javascript/page.tsx @@ -2,16 +2,16 @@ import { Metadata } from 'next'; import { getCourseTitle } from '@/shared/helpers/get-course-title'; import { selectCourse } from '@/shared/hooks/use-course-by-title/utils/select-course'; import { JavaScriptEn } from '@/views/javascript-en'; -import { COURSE_TITLES, courses } from 'data'; +import { COURSE_TITLES } from 'data'; const courseName = COURSE_TITLES.JS_EN; export async function generateMetadata(): Promise { - return { title: getCourseTitle(courseName) }; + return { title: await getCourseTitle(courseName) }; } -export default function JsEnRoute() { - const course = selectCourse(courses, courseName); +export default async function JsEnRoute() { + const course = await selectCourse(courseName); return ; } diff --git a/src/app/courses/nodejs/page.tsx b/src/app/courses/nodejs/page.tsx index cfbd1f145..510130704 100644 --- a/src/app/courses/nodejs/page.tsx +++ b/src/app/courses/nodejs/page.tsx @@ -2,16 +2,16 @@ import { Metadata } from 'next'; import { getCourseTitle } from '@/shared/helpers/get-course-title'; import { selectCourse } from '@/shared/hooks/use-course-by-title/utils/select-course'; import { Nodejs } from '@/views/nodejs'; -import { COURSE_TITLES, courses } from 'data'; +import { COURSE_TITLES } from 'data'; const courseName = COURSE_TITLES.NODE; export async function generateMetadata(): Promise { - return { title: getCourseTitle(courseName) }; + return { title: await getCourseTitle(courseName) }; } -export default function NodeRoute() { - const course = selectCourse(courses, courseName); +export default async function NodeRoute() { + const course = await selectCourse(courseName); return ; } diff --git a/src/app/courses/page.tsx b/src/app/courses/page.tsx index cb2626fb8..c0fbd3d99 100644 --- a/src/app/courses/page.tsx +++ b/src/app/courses/page.tsx @@ -1,6 +1,5 @@ import { Metadata } from 'next'; import { Courses } from '@/views/courses'; -import { courses } from 'data'; export async function generateMetadata(): Promise { const title = 'Courses · The Rolling Scopes School'; @@ -9,5 +8,5 @@ export async function generateMetadata(): Promise { } export default function CoursesRoute() { - return ; + return ; } diff --git a/src/app/courses/reactjs/page.tsx b/src/app/courses/reactjs/page.tsx index bde94108a..efb6b4d7e 100644 --- a/src/app/courses/reactjs/page.tsx +++ b/src/app/courses/reactjs/page.tsx @@ -2,16 +2,16 @@ import { Metadata } from 'next'; import { getCourseTitle } from '@/shared/helpers/get-course-title'; import { selectCourse } from '@/shared/hooks/use-course-by-title/utils/select-course'; import { React } from '@/views/react'; -import { COURSE_TITLES, courses } from 'data'; +import { COURSE_TITLES } from 'data'; const courseName = COURSE_TITLES.REACT; export async function generateMetadata(): Promise { - return { title: getCourseTitle(courseName) }; + return { title: await getCourseTitle(courseName) }; } -export default function ReactRoute() { - const course = selectCourse(courses, courseName); +export default async function ReactRoute() { + const course = await selectCourse(courseName); return ; } diff --git a/src/core/base-layout/base-layout.test.tsx b/src/core/base-layout/base-layout.test.tsx index 25de07ba1..089d36882 100644 --- a/src/core/base-layout/base-layout.test.tsx +++ b/src/core/base-layout/base-layout.test.tsx @@ -18,9 +18,11 @@ vi.mock('next/navigation', () => ({ })); describe('BaseLayout', () => { - beforeEach(() => { + beforeEach(async () => { mockUsePathname.mockImplementation(() => ROUTES.HOME); - render({null}); + const baseLayout = await BaseLayout({ children: null }); + + render(baseLayout); }); it('renders Header component', () => { diff --git a/src/core/base-layout/base-layout.tsx b/src/core/base-layout/base-layout.tsx index 0898778f0..02e0fae7b 100644 --- a/src/core/base-layout/base-layout.tsx +++ b/src/core/base-layout/base-layout.tsx @@ -1,10 +1,13 @@ import { PropsWithChildren } from 'react'; import { Footer, Header, Partnered } from './components'; +import { getCourses } from '@/entities/course/api/course-api'; + +export const BaseLayout = async ({ children }: PropsWithChildren) => { + const courses = await getCourses(); -export const BaseLayout = ({ children }: PropsWithChildren) => { return ( <> -
+
{children}