diff --git a/.github/workflows/cd.deploy.stg.yml b/.github/workflows/cd.deploy.stg.yml index 88a614afd..62177af3e 100644 --- a/.github/workflows/cd.deploy.stg.yml +++ b/.github/workflows/cd.deploy.stg.yml @@ -4,6 +4,7 @@ on: push: tags: - "stg-v*" + workflow_dispatch: jobs: install-build-deploy: @@ -11,16 +12,15 @@ jobs: strategy: matrix: - node-version: [18.x] + node-version: [20.x] os: [ubuntu-latest] env: CI: true STAGE: staging - SSH_KNOWN_HOSTS: ${{ secrets.SSH_KNOWN_HOSTS }} SSH_ADDRESS_STG: ${{ secrets.SSH_ADDRESS_STG }} FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }} - EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} + DEPLOY_VERSION: ${{ github.ref_type == 'tag' && github.ref_name || format('stg-0.0.0-{0}-{1}-{2}', github.ref_name, github.run_number, github.run_attempt) }} steps: - name: "Git" @@ -37,11 +37,6 @@ jobs: with: node-version: ${{ matrix.node-version }} cache: "yarn" - - name: "Expo" - uses: expo/expo-github-action@v8 - with: - expo-version: 4.x - token: ${{ secrets.EXPO_TOKEN }} - name: "Install" shell: bash @@ -51,7 +46,7 @@ jobs: - name: "Bundle info" shell: bash run: | - yarn generate:bundle-info ${{ github.ref_name }} stage + yarn generate:bundle-info $DEPLOY_VERSION stage - name: "Build" shell: bash @@ -62,7 +57,7 @@ jobs: - name: "Sentry Release" shell: bash run: | - yarn generate:sentry-release ${{ github.ref_name }} stage ${{ secrets.SENTRY_AUTH_TOKEN }} + yarn generate:sentry-release $DEPLOY_VERSION stage ${{ secrets.SENTRY_AUTH_TOKEN }} - name: "Deploy" shell: bash diff --git a/.github/workflows/cd.deploy.yml b/.github/workflows/cd.deploy.yml index 59207e46c..afab8edde 100644 --- a/.github/workflows/cd.deploy.yml +++ b/.github/workflows/cd.deploy.yml @@ -11,16 +11,15 @@ jobs: strategy: matrix: - node-version: [18.x] + node-version: [20.x] os: [ubuntu-latest] env: CI: true STAGE: production - SSH_KNOWN_HOSTS: ${{ secrets.SSH_KNOWN_HOSTS }} SSH_ADDRESS_PRD: ${{ secrets.SSH_ADDRESS_PRD }} FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }} - EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} + DEPLOY_VERSION: ${{ github.ref_type == 'tag' && github.ref_name || format('0.0.0-{0}-{1}-{2}', github.ref_name, github.run_number, github.run_attempt) }} steps: - name: "Git" @@ -38,11 +37,6 @@ jobs: with: node-version: ${{ matrix.node-version }} cache: "yarn" - - name: "Expo" - uses: expo/expo-github-action@v8 - with: - expo-version: 4.x - token: ${{ secrets.EXPO_TOKEN }} - name: "Install" shell: bash @@ -52,13 +46,13 @@ jobs: - name: "Version" shell: bash run: | - yarn version:apply ${{ github.ref_name }} - yarn version:push ${{ github.ref_name }} + yarn version:apply $DEPLOY_VERSION + yarn version:push $DEPLOY_VERSION - name: "Bundle info" shell: bash run: | - yarn generate:bundle-info ${{ github.ref_name }} production + yarn generate:bundle-info $DEPLOY_VERSION production - name: "Build" shell: bash @@ -69,7 +63,7 @@ jobs: - name: "Sentry Release" shell: bash run: | - yarn generate:sentry-release ${{ github.ref_name }} production ${{ secrets.SENTRY_AUTH_TOKEN }} + yarn generate:sentry-release $DEPLOY_VERSION production ${{ secrets.SENTRY_AUTH_TOKEN }} - name: "Deploy" shell: bash diff --git a/.github/workflows/ci.main.yml b/.github/workflows/ci.main.yml index 72887c4db..24d882597 100644 --- a/.github/workflows/ci.main.yml +++ b/.github/workflows/ci.main.yml @@ -11,7 +11,7 @@ jobs: strategy: matrix: - node-version: [18.x] + node-version: [20.x] os: [macos-latest, ubuntu-latest, windows-latest] fail-fast: false @@ -67,13 +67,6 @@ jobs: name: codecov-data directory: ./data fail_ci_if_error: false # put it back to true once we have tests in ./data - - uses: codecov/codecov-action@v3 - if: ${{ matrix.os == 'ubuntu-latest' }} - with: - flags: mobile - name: codecov-mobile - directory: ./mobile - fail_ci_if_error: true - uses: codecov/codecov-action@v3 if: ${{ matrix.os == 'ubuntu-latest' }} with: @@ -88,19 +81,13 @@ jobs: name: codecov-utils directory: ./packages/utils fail_ci_if_error: true - - uses: codecov/codecov-action@v3 - if: ${{ matrix.os == 'ubuntu-latest' }} - with: - flags: ui - name: codecov-ui - directory: ./packages/ui - fail_ci_if_error: true - name: "Misc" shell: bash run: | yarn lerna run bundle:alone --scope @dzcode.io/web yarn lerna run generate:sitemap --scope @dzcode.io/web + yarn lerna run generate:htmls --scope @dzcode.io/web env: CI: true diff --git a/.github/workflows/ci.pr.e2e.yml b/.github/workflows/ci.pr.e2e.yml index 1404ecd4a..13b74f67f 100644 --- a/.github/workflows/ci.pr.e2e.yml +++ b/.github/workflows/ci.pr.e2e.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: - node-version: [18.x] + node-version: [20.x] os: [ubuntu-latest] browser: [chrome, firefox, edge] fail-fast: false diff --git a/.github/workflows/ci.pr.yml b/.github/workflows/ci.pr.yml index 6a2712ae8..f9d42a839 100644 --- a/.github/workflows/ci.pr.yml +++ b/.github/workflows/ci.pr.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: - node-version: [18.x] + node-version: [20.x] os: [macos-latest, ubuntu-latest, windows-latest] fail-fast: false diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 2a6879f93..08a012fd0 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -32,10 +32,9 @@ jobs: strategy: fail-fast: false matrix: - language: ["javascript"] - node-version: [18.x] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + language: ["javascript", "typescript"] # Learn more about CodeQL language support at https://git.io/codeql-language-support + node-version: [20.x] steps: - name: "Git" diff --git a/README.md b/README.md index bf9edbe7a..dac05084d 100644 --- a/README.md +++ b/README.md @@ -95,10 +95,14 @@ If you use VSCode, please make sure to have a `.vscode/settings.json` file with ```json { + "files.associations": { + "*.css": "tailwindcss" + }, "prettier.configPath": "packages/tooling/.prettierrc", "eslint.options": { "overrideConfigFile": "packages/tooling/.eslintrc.json" }, "editor.codeActionsOnSave": { - "source.fixAll": true + "source.fixAll.eslint": "always", + "source.fixAll.ts": "always" } } ``` diff --git a/api/oracle-cloud/Dockerfile b/api/oracle-cloud/Dockerfile index 7680956bb..18f4ec1cb 100644 --- a/api/oracle-cloud/Dockerfile +++ b/api/oracle-cloud/Dockerfile @@ -1,4 +1,4 @@ -FROM node:16 +FROM node:20 # Create app directory WORKDIR /usr/src/repo diff --git a/api/oracle-cloud/deploy.ts b/api/oracle-cloud/deploy.ts index b68d9d4cf..271c8f602 100644 --- a/api/oracle-cloud/deploy.ts +++ b/api/oracle-cloud/deploy.ts @@ -1,3 +1,6 @@ +// can be ran locally from ./api: +// SSH_ADDRESS_STG="root@x.x.x.x" SSH_PATH="path/to/private/ssh/key" yarn deploy:stg + import { execSync } from "child_process"; import { copySync, existsSync } from "fs-extra"; import { join } from "path"; @@ -82,8 +85,6 @@ logs = execSync( console.log("āœ… New code uploaded."); console.log("\nāš™ļø Starting up the app"); -logs = execSync( - sshPrefix + '"sudo docker-compose -f ' + appPath + '/docker-compose.yml up -d --build"', -); +logs = execSync(sshPrefix + '"cd ' + appPath + ' && docker compose up -d --build"'); console.log(String(logs)); console.log("āœ… Deployment successful."); diff --git a/api/oracle-cloud/docker-compose.yml b/api/oracle-cloud/docker-compose.yml index 69d7435d4..c66ec79cb 100644 --- a/api/oracle-cloud/docker-compose.yml +++ b/api/oracle-cloud/docker-compose.yml @@ -6,6 +6,6 @@ services: ports: - "80:7070" env_file: - - /home/opc/app-env/api.env + - /home/ubuntu/app-env/api.env volumes: - - /home/opc/app-data/api/fetch_cache:/usr/src/repo/api/fetch_cache + - /home/ubuntu/app-data/api/fetch_cache:/usr/src/repo/api/fetch_cache diff --git a/api/package.json b/api/package.json index cfa85f9a4..af602cb2f 100644 --- a/api/package.json +++ b/api/package.json @@ -64,8 +64,8 @@ }, "scripts": { "build": "lerna run build:alone --scope=@dzcode.io/api --include-dependencies --stream", - "build:alone": "tsc", - "build:alone:watch": "tsc --watch --preserveWatchOutput", + "build:alone": "tspc", + "build:alone:watch": "tspc --watch --preserveWatchOutput", "build:watch": "lerna run build:alone:watch --scope=@dzcode.io/api --include-dependencies --parallel", "clean": "lerna run clean:alone --scope=@dzcode.io/api --include-dependencies --stream", "clean:alone": "rimraf dist coverage fetch_cache oracle-cloud/build", @@ -80,7 +80,7 @@ "lint:fix:alone": "yarn lint:eslint --fix . && yarn lint:prettier --write .", "lint:prettier": "prettier --config ../packages/tooling/.prettierrc --ignore-path ../packages/tooling/.prettierignore --loglevel warn", "lint:ts-prune": "ts-node ../packages/tooling/setup-ts-prune.ts && ts-prune --error", - "lint:tsc": "tsc --noEmit", + "lint:tsc": "tspc --noEmit", "start": "node dist/app/index.js", "start:dev": "ts-node ../packages/tooling/nodemon.ts @dzcode.io/api && nodemon dist/app/index.js", "test": "yarn build && yarn test:alone", diff --git a/api/src/contribution/repository.ts b/api/src/contribution/repository.ts index 970eecbb2..2eda63401 100644 --- a/api/src/contribution/repository.ts +++ b/api/src/contribution/repository.ts @@ -81,6 +81,8 @@ export class ContributionRepository { ) ).reduce((pV, cV) => [...pV, ...cV], []); if (filterFn) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore contributions = contributions.filter(filterFn); } contributions = contributions.sort( @@ -93,11 +95,15 @@ export class ContributionRepository { this.pushUniqueOption([{ name: project.slug, label: project.name }], filters[0].options); this.pushUniqueOption( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore languages.map((language) => ({ name: language })), filters[1].options, ); this.pushUniqueOption( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore labels.map((label) => ({ name: label })), filters[2].options, ); diff --git a/api/src/project/controller.ts b/api/src/project/controller.ts index b5403d8f1..c5f3715ac 100644 --- a/api/src/project/controller.ts +++ b/api/src/project/controller.ts @@ -1,4 +1,3 @@ -import { AccountEntity } from "@dzcode.io/models/dist/account"; import { Controller, Get } from "routing-controllers"; import { OpenAPI, ResponseSchema } from "routing-controllers-openapi"; import { DataService } from "src/data/service"; @@ -26,52 +25,8 @@ export class ProjectController { // get projects from /data folder: const projects = await this.dataService.listProjects(); - // fetch info about these projects from github: - const infoRichProjects: GetProjectsResponseDto["projects"] = await Promise.all( - projects.map(async (project) => { - const { repositories } = project; - return { - ...project, - repositories: await Promise.all( - repositories.map(async ({ provider, owner, repository }) => { - let contributionCount = 0; - let contributors: AccountEntity[] = []; - let languages: string[] = []; - try { - [contributionCount, contributors, languages] = await Promise.all([ - (await this.githubService.listRepositoryIssues({ owner, repository })).length, - await Promise.all( - ( - await this.githubService.listRepositoryContributors({ owner, repository }) - ).map(async ({ login }) => { - const githubUser = await this.githubService.getUser({ username: login }); - return this.githubService.githubUserToAccountEntity(githubUser); - }), - ), - await this.githubService.listRepositoryLanguages({ owner, repository }), - ]); - } catch (error) { - this.loggerService.error({ - message: `Failed to fetch rich info for project: ${owner}/${repository}`, - meta: { owner, repository, error }, - }); - } - - return { - provider, - owner, - repository, - stats: { contributionCount, languages }, - contributors, - }; - }), - ), - }; - }), - ); - return { - projects: infoRichProjects, + projects, }; } } diff --git a/api/src/project/types.ts b/api/src/project/types.ts index fc7a42ec8..35f66202b 100644 --- a/api/src/project/types.ts +++ b/api/src/project/types.ts @@ -8,9 +8,5 @@ import { GeneralResponseDto } from "src/app/types"; export class GetProjectsResponseDto extends GeneralResponseDto { @ValidateNested({ each: true }) @Type(() => ProjectEntity) - // @TODO-ZM: find a way to DRY this, eg: - // projects!: Model[] - projects!: Array< - Model & { repositories: Model[] } - >; + projects!: Array & { repositories: Model[] }>; } diff --git a/api/tsconfig.json b/api/tsconfig.json index b0ea4c0e8..390031bc2 100644 --- a/api/tsconfig.json +++ b/api/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "@dzcode.io/tooling/tsconfig.json", + "extends": "../packages/tooling/tsconfig.json", "compilerOptions": { "outDir": "dist", "baseUrl": "." diff --git a/data/package.json b/data/package.json index 34c085b90..a8a199fd3 100644 --- a/data/package.json +++ b/data/package.json @@ -19,7 +19,7 @@ "@types/fs-extra": "^9.0.13", "@types/glob": "^7.1.4", "cpx": "^1.5.0", - "firebase-tools": "^9.1.0" + "firebase-tools": "^13.16.0" }, "engines": { "node": ">=16", diff --git a/lerna.json b/lerna.json index 9ba03279b..8c0baa478 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "packages": ["packages/*", "api", "data", "web", "mobile"], + "packages": ["packages/*", "api", "data", "web"], "version": "independent", "npmClient": "yarn", "useWorkspaces": true diff --git a/package.json b/package.json index de0ec99f4..981b45689 100644 --- a/package.json +++ b/package.json @@ -28,11 +28,11 @@ "semver": "^7.3.5", "syncpack": "^5.8.15", "ts-jest": "^29.0.5", - "ts-node": "^10.9.1", - "ts-patch": "^2.0.2", + "ts-node": "^10.9.2", + "ts-patch": "^3.2.1", "ts-prune": "^0.10.3", - "typescript": "^4.8.2", - "typescript-transform-paths": "^3.3.1" + "typescript": "5.5.3", + "typescript-transform-paths": "^3.5.0" }, "lint-staged": { "**/package.json": [ @@ -43,27 +43,16 @@ ] }, "private": true, - "resolutions": { - "@types/jest": "^29.5.0", - "@types/react": "^18.0.0", - "caniuse-lite": "^1.0.30001359", - "jest": "^29.5.0", - "jest-watch-typeahead": "^0.6.5", - "react": "^18.0.1", - "ts-jest": "^29.0.5", - "typescript": "^4.8.2" - }, "scripts": { "build": "lerna run build:alone --stream", "build:watch": "lerna run build:alone:watch --parallel", "clean": "lerna run clean:alone --stream", "deploy": "lerna run deploy --parallel", "deploy:stg": "lerna run deploy:stg --parallel", - "dev": "echo \"Please run one of these commands:\\n\\nyarn dev:web\\nyarn dev:mobile\\nyarn dev:all\n\"", + "dev": "echo \"Please run one of these commands:\\n\\nyarn dev:web\\nyarn dev:api\\nyarn dev:all\n\"", "dev:all": "npm-run-all \"build --include-dependencies {@}\" --parallel \"build:watch --include-dependencies {@}\" \"start:dev {@}\" --", "dev:api": "yarn dev:all --scope=@dzcode.io/api", - "dev:mobile": "yarn dev:all --scope=@dzcode.io/{mobile,api}", - "dev:web": "yarn dev:all --scope=@dzcode.io/{web,api}", + "dev:web": "yarn dev:all --scope=@dzcode.io/web", "e2e:all": "npm-run-all \"build --include-dependencies {@}\" --parallel \"build:watch --include-dependencies {@}\" \"start:dev {@}\" \"e2e:dev --scope=@dzcode.io/web\" --", "e2e:dev": "lerna run e2e:dev --parallel", "e2e:web": "BROWSER=none yarn e2e:all --scope=@dzcode.io/{web,api}", @@ -87,7 +76,6 @@ "packages/*", "api", "data", - "web", - "mobile" + "web" ] } diff --git a/packages/tooling/.eslintignore b/packages/tooling/.eslintignore index 9b785104a..68e3f9b5c 100644 --- a/packages/tooling/.eslintignore +++ b/packages/tooling/.eslintignore @@ -10,6 +10,7 @@ firebase # web bundle +src/_build *.* !*.ts diff --git a/packages/tooling/.prettierignore b/packages/tooling/.prettierignore index d434014c3..8fcc4f0bc 100644 --- a/packages/tooling/.prettierignore +++ b/packages/tooling/.prettierignore @@ -15,7 +15,9 @@ bundle # mobile .expo + ./.* +.editorconfig .gitignore nodemon.json *.tsbuildinfo diff --git a/packages/tooling/patches/@sentry-internal+tracing+7.46.0.patch b/packages/tooling/patches/@sentry-internal+tracing+7.46.0.patch deleted file mode 100644 index 4a7b6360a..000000000 --- a/packages/tooling/patches/@sentry-internal+tracing+7.46.0.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/node_modules/@sentry-internal/tracing/cjs/extensions.js b/node_modules/@sentry-internal/tracing/cjs/extensions.js -index 8c6cddb..fd60b6b 100644 ---- a/node_modules/@sentry-internal/tracing/cjs/extensions.js -+++ b/node_modules/@sentry-internal/tracing/cjs/extensions.js -@@ -59,7 +59,7 @@ function _autoloadDatabaseIntegrations() { - * This patches the global object and injects the Tracing extensions methods - */ - function addExtensionMethods() { -- core.addTracingExtensions(); -+ if (core.addTracingExtensions) core.addTracingExtensions(); - - // Detect and automatically load specified integrations. - if (utils.isNodeEnv()) { diff --git a/packages/ui-mobile/package.json b/packages/ui-mobile/package.jsonc similarity index 100% rename from packages/ui-mobile/package.json rename to packages/ui-mobile/package.jsonc diff --git a/packages/ui/package.json b/packages/ui/package.json deleted file mode 100644 index abd783abd..000000000 --- a/packages/ui/package.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "name": "@dzcode.io/ui", - "description": "dzcode.io ui library", - "version": "5.3.6", - "author": { - "email": "sohaib.code@gmail.com", - "name": "Souhaib Benbouzid", - "url": "https://sohaibbenbouzid.com/" - }, - "dependencies": { - "@dzcode.io/utils": "*", - "@emotion/react": "^11.7.0", - "@emotion/styled": "^11.6.0", - "@fontsource/roboto": "^4.5.1", - "@loadable/component": "^5.15.2", - "@mui/icons-material": "^5.2.0", - "@mui/lab": "^5.0.0-alpha.95", - "@mui/material": "^5.10.5", - "@sentry/react": "^7.46.0", - "@sentry/tracing": "^7.46.0", - "markdown-to-jsx": "^7.1.0", - "react": "^18.0.1", - "react-router-dom": "^5.2.0", - "react-syntax-highlighter": "^15.4.3", - "stylis": "^4.1.3", - "stylis-plugin-rtl": "2.0.2", - "usehooks-ts": "latest" - }, - "devDependencies": { - "@dzcode.io/models": "*", - "@dzcode.io/tooling": "*", - "@testing-library/jest-dom": "^5.16.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^13.5.0", - "@types/loadable__component": "^5.13.4", - "@types/markdown-to-jsx": "^6.11.3", - "@types/react-router-dom": "^5.1.6", - "@types/react-syntax-highlighter": "^13.5.0", - "@types/stylis": "^4.0.2" - }, - "license": "MIT", - "lint-staged": { - "*.*": [ - "yarn lint:eslint --fix", - "yarn lint:prettier --write" - ] - }, - "main": "dist", - "private": true, - "scripts": { - "build": "lerna run build:alone --scope=@dzcode.io/ui --include-dependencies --stream", - "build:alone": "tsc", - "build:alone:watch": "tsc --watch --preserveWatchOutput", - "build:watch": "yarn build & yarn lerna run build:alone:watch --scope=@dzcode.io/ui --include-dependencies --parallel", - "clean": "lerna run clean:alone --scope=@dzcode.io/ui --include-dependencies --stream", - "clean:alone": "rimraf dist coverage", - "lint": "yarn build && yarn lint:alone", - "lint:alone": "yarn lint:eslint . && yarn lint:prettier --check . && yarn lint:ts-prune", - "lint:eslint": "eslint --config ../tooling/.eslintrc.json --ignore-path ../tooling/.eslintignore --report-unused-disable-directives", - "lint:fix": "yarn build && yarn lint:fix:alone", - "lint:fix:alone": "yarn lint:eslint --fix . && yarn lint:prettier --write .", - "lint:prettier": "prettier --config ../tooling/.prettierrc --ignore-path ../tooling/.prettierignore --loglevel warn", - "lint:ts-prune": "ts-node ../tooling/setup-ts-prune.ts && ts-prune --error || echo \"@TODO-ZM: enable ts-prune on /ui\"", - "test": "yarn build && yarn test:alone", - "test:alone": "jest --config ../tooling/jest.config.ts --rootDir . --env=jsdom", - "test:watch": "npm-run-all build --parallel build:watch \"test:alone --watch {@}\" --" - } -} diff --git a/packages/ui/src/__mocks__/create-contributors.ts b/packages/ui/src/__mocks__/create-contributors.ts deleted file mode 100644 index fb02d9f77..000000000 --- a/packages/ui/src/__mocks__/create-contributors.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { AccountEntity } from "@dzcode.io/models/dist/account"; - -export const createContributors = (number: number) => { - const contributors: AccountEntity[] = []; - for (let i = 0; i < number; i++) { - contributors.push({ - id: `github/${i}`, - username: `mock-user-${i}`, - name: `mock-name-${i}`, - profileUrl: `mock-profile-${i}`, - avatarUrl: `mock-avatar-${i}`, - repositories: [ - { - owner: `user-${i}`, - provider: `github`, - repository: `repository-${i}`, - }, - ], - }); - } - return contributors; -}; diff --git a/packages/ui/src/__mocks__/create-faqs.ts b/packages/ui/src/__mocks__/create-faqs.ts deleted file mode 100644 index fdebcc621..000000000 --- a/packages/ui/src/__mocks__/create-faqs.ts +++ /dev/null @@ -1,27 +0,0 @@ -export interface FaqSection { - title: string; - questions: Array<{ - question: string; - answer: string; - }>; -} - -export const createFaqSections = (number: number) => { - const faqSections: FaqSection[] = []; - for (let i = 0; i < number; i++) { - faqSections.push({ - title: `faq-title-${i}`, - questions: [ - { - question: `faq-question-1`, - answer: `faq-answer-1`, - }, - { - question: `faq-question-2`, - answer: `faq-answer-2`, - }, - ], - }); - } - return faqSections; -}; diff --git a/packages/ui/src/__mocks__/create-projects.ts b/packages/ui/src/__mocks__/create-projects.ts deleted file mode 100644 index e9e1669ff..000000000 --- a/packages/ui/src/__mocks__/create-projects.ts +++ /dev/null @@ -1,17 +0,0 @@ -export const createProjects = (number: number) => { - const projects = []; - for (let i = 0; i < number; i++) { - projects.push({ - slug: `slug-${i}`, - title: `title-${i}`, - authors: ["user1"], - content: `content-${i}`, - projects: ["user1", "user2"], - description: `description-${i}`, - githubURI: `/${i}`, - image: `/${i}`, - views: i, - }); - } - return projects; -}; diff --git a/packages/ui/src/__mocks__/create-repositories.ts b/packages/ui/src/__mocks__/create-repositories.ts deleted file mode 100644 index ba461af46..000000000 --- a/packages/ui/src/__mocks__/create-repositories.ts +++ /dev/null @@ -1,11 +0,0 @@ -export const createRepositories = (number: number) => { - const repositories = []; - for (let i = 0; i < number; i++) { - repositories.push({ - owner: `user-${i}`, - provider: `github`, - repository: `repository-${i}`, - }); - } - return repositories; -}; diff --git a/packages/ui/src/__mocks__/file-mock.ts b/packages/ui/src/__mocks__/file-mock.ts deleted file mode 100644 index dbc304ad9..000000000 --- a/packages/ui/src/__mocks__/file-mock.ts +++ /dev/null @@ -1 +0,0 @@ -export default "test-file-stub"; diff --git a/packages/ui/src/__mocks__/style-mock.ts b/packages/ui/src/__mocks__/style-mock.ts deleted file mode 100644 index ff8b4c563..000000000 --- a/packages/ui/src/__mocks__/style-mock.ts +++ /dev/null @@ -1 +0,0 @@ -export default {}; diff --git a/packages/ui/src/_hooks/use-colors.ts b/packages/ui/src/_hooks/use-colors.ts deleted file mode 100644 index 42fbabeb9..000000000 --- a/packages/ui/src/_hooks/use-colors.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { green } from "@mui/material/colors"; -import grey from "@mui/material/colors/grey"; -import { PaletteOptions, useTheme } from "@mui/material/styles"; - -export type Color = "BACKGROUND_2" | "PRIMARY" | "DIVIDER" | "BACKGROUND_CODE" | "CONTRIBUTION"; - -export const useColors = () => { - const theme = useTheme(); - const modeIndex = (["light", "dark"] as PaletteOptions["mode"][]).indexOf(theme.palette.mode); - - return { - from: (color?: Color) => { - switch (color) { - case "BACKGROUND_2": - return [grey["300"], grey["900"]][modeIndex]; - - case "PRIMARY": - return theme.palette.primary.main; - - case "DIVIDER": - return theme.palette.divider; - - case "BACKGROUND_CODE": - return [grey["300"], grey["800"]][modeIndex]; - - case "CONTRIBUTION": - return [green["300"], green["800"]][modeIndex]; - - default: - return "transparent"; - } - }, - }; -}; diff --git a/packages/ui/src/_hooks/use-theme.ts b/packages/ui/src/_hooks/use-theme.ts deleted file mode 100644 index ac49bb25f..000000000 --- a/packages/ui/src/_hooks/use-theme.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { useTheme as muiUseTheme } from "@mui/material/styles"; -import { BaseUIProps } from "src/_types"; - -export const useTheme = () => { - const theme = muiUseTheme(); - return { - isDarkMode: theme.palette.mode === "dark", - spacing: (amount: number) => theme.spacing(amount), - toCSSMargin: (margin: BaseUIProps["margin"]) => { - switch (typeof margin) { - case "number": - return theme.spacing(margin); - - case "object": - return `${margin.map((value) => theme.spacing(value)).join(" ")} !important`; - - default: - return undefined; - } - }, - }; -}; diff --git a/packages/ui/src/_hooks/use-translation.ts b/packages/ui/src/_hooks/use-translation.ts deleted file mode 100644 index 99bf775cf..000000000 --- a/packages/ui/src/_hooks/use-translation.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { useContext } from "react"; -import { TranslationContext, TranslationContextValue } from "src/translation-factory"; - -export const useTranslation = () => { - // @TODO-ZM: use generic types on Key of t(key: Key), and remove the use of imported t from ./web - const translationContextValue = useContext(TranslationContext); - return translationContextValue; -}; diff --git a/packages/ui/src/_test/pre-test.ts b/packages/ui/src/_test/pre-test.ts deleted file mode 100644 index d0de870dc..000000000 --- a/packages/ui/src/_test/pre-test.ts +++ /dev/null @@ -1 +0,0 @@ -import "@testing-library/jest-dom"; diff --git a/packages/ui/src/_test/setup.ts b/packages/ui/src/_test/setup.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/ui/src/_types/index.ts b/packages/ui/src/_types/index.ts deleted file mode 100644 index 6216cd094..000000000 --- a/packages/ui/src/_types/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ReactNode } from "react"; - -export interface BaseUIProps { - margin?: number | number[]; - padding?: number | number[]; -} - -export interface ChildrenProp { - children?: ReactNode; -} diff --git a/packages/ui/src/accordion/index.tsx b/packages/ui/src/accordion/index.tsx deleted file mode 100644 index 006e4fe55..000000000 --- a/packages/ui/src/accordion/index.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import ExpandMore from "@mui/icons-material/ExpandMore"; -import MUIAccordion from "@mui/material/Accordion"; -import MUIAccordionDetails from "@mui/material/AccordionDetails"; -import MUIAccordionSummary from "@mui/material/AccordionSummary"; -import { FC, ReactNode } from "react"; -import { Stack } from "src/stack"; -import { Text } from "src/text"; - -interface AccordionProps { - items: Array<{ - title: ReactNode; - body: ReactNode; - }>; - margin?: number | number[]; -} - -export const Accordion: FC = ({ items, ...props }) => { - return ( - - {items.map(({ title, body }, index) => ( - - }> - {title} - - {body} - - ))} - - ); -}; diff --git a/packages/ui/src/article/index.tsx b/packages/ui/src/article/index.tsx deleted file mode 100644 index 811b35900..000000000 --- a/packages/ui/src/article/index.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { Model } from "@dzcode.io/models/dist/_base"; -import { AccountEntity } from "@dzcode.io/models/dist/account"; -import { Skeleton } from "@mui/material"; -import { FC } from "react"; -import { Divider } from "src/divider"; -import { Image } from "src/image"; -import { Link } from "src/link"; -import { Markdown } from "src/markdown"; -import { Stack, StackProps } from "src/stack"; -import { Text } from "src/text"; - -export type MinimumAccountInfo = Pick< - Model, - "id" | "name" | "username" | "profileUrl" | "avatarUrl" ->; -export interface ArticleProps extends Pick { - article: { - image: string; - title: string; - content: string; - authors: MinimumAccountInfo[]; - contributors: MinimumAccountInfo[]; - } | null; - authorsText: string; - contributorsText: string; -} - -export const Article: FC = ({ article, authorsText, contributorsText, ...props }) => { - return article ? ( - - - - {article.title} - - - - {(article.authors.length || article.contributors.length) && ( - - )} - {article.authors.length > 0 && ( - <> - - {authorsText} - - - {article.authors.map((author, index) => ( - - - - ))} - - - )} - {article.contributors.length > 0 && ( - <> - - {contributorsText} - - - {article.contributors.map((contributor, index) => ( - - - - ))} - - - )} - - ) : ( - - - - - - - - - - - - ); -}; diff --git a/packages/ui/src/avatar/index.tsx b/packages/ui/src/avatar/index.tsx deleted file mode 100644 index cdd744bcb..000000000 --- a/packages/ui/src/avatar/index.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import MUIAvatar from "@mui/material/Avatar"; -import MUIAvatarGroup from "@mui/material/AvatarGroup"; - -export const Avatar = MUIAvatar; -export const AvatarGroup = MUIAvatarGroup; diff --git a/packages/ui/src/badge/index.tsx b/packages/ui/src/badge/index.tsx deleted file mode 100644 index 08fa22e23..000000000 --- a/packages/ui/src/badge/index.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import MUIBadge from "@mui/material/Badge"; - -export const Badge = MUIBadge; diff --git a/packages/ui/src/button/button.test.tsx b/packages/ui/src/button/button.test.tsx deleted file mode 100644 index 553322162..000000000 --- a/packages/ui/src/button/button.test.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import "@testing-library/jest-dom"; - -import { render, screen } from "@testing-library/react"; - -import { Button } from "./index"; - -test("should render", async () => { - // ARRANGE - render(); - - // ACT - await screen.findByText("v1 button"); - - // ASSERT - expect(screen.getByText("v1 button")).toBeInTheDocument(); -}); diff --git a/packages/ui/src/button/index.tsx b/packages/ui/src/button/index.tsx deleted file mode 100644 index 683a266d0..000000000 --- a/packages/ui/src/button/index.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import MUIButton, { ButtonProps as MUIButtonProps } from "@mui/material/Button"; -import { FC, MouseEvent } from "react"; -import { useTheme } from "src/_hooks/use-theme"; -import { BaseUIProps, ChildrenProp } from "src/_types"; -import { Link } from "src/link"; - -export interface ButtonProps extends BaseUIProps, ChildrenProp { - variant: "v1" | "v2" | "v3" | "v4"; - onClick?: (event: MouseEvent) => void; - href?: string; -} - -const variantToMUIButtonVariant: Record< - Exclude, - MUIButtonProps["variant"] -> = { - v2: "text", - v3: "contained", - v4: "outlined", -}; - -export const Button: FC = ({ children, variant, margin, ...props }) => { - const { toCSSMargin } = useTheme(); - - switch (variant) { - case "v4": - return ( - - {children} - - ); - - case "v1": - return ( - - {children} - - ); - - default: - return ( - - {children} - - ); - } -}; diff --git a/packages/ui/src/card/contribution/index.tsx b/packages/ui/src/card/contribution/index.tsx deleted file mode 100644 index ec7fb660e..000000000 --- a/packages/ui/src/card/contribution/index.tsx +++ /dev/null @@ -1,163 +0,0 @@ -import { Model } from "@dzcode.io/models/dist/_base"; -import { ContributionEntity } from "@dzcode.io/models/dist/contribution"; -import { arrayOf } from "@dzcode.io/utils/dist/array"; -import { getElapsedTime } from "@dzcode.io/utils/dist/date/elapsed-time"; -import { FC, useEffect, useState } from "react"; -import { useTranslation } from "src/_hooks/use-translation"; -import { Badge } from "src/badge"; -import { Chip } from "src/chip"; -import { Divider } from "src/divider"; -import { FilterProps } from "src/filter"; -import { Flex } from "src/flex"; -import { NotesIcon } from "src/icon/notes"; -import { Link } from "src/link"; -import { Markdown } from "src/markdown"; -import { Paper } from "src/paper"; -import { Skeleton } from "src/skeleton"; -import { Stack } from "src/stack"; -import { Text } from "src/text"; - -type ContributionCard = - | { contribution: null; local?: never; onChipClick?: never } - | { - contribution: Model; - local: { - readIssue: string; - reviewChanges: string; - elapsedTime: string; - } & FilterProps["local"]; - onChipClick: (filterName: string, optionName: string) => void; - }; - -const loadingLabels = arrayOf(3); -const loadingLanguages = arrayOf(2); - -export const ContributionCard: FC = ({ contribution, local, onChipClick }) => { - const { t, language } = useTranslation(); - const [elapsedTime, setElapsedTime] = useState(""); - - useEffect(() => { - if (!contribution) return; - - const updateElapsedTime = () => - setElapsedTime(getElapsedTime(contribution.updatedAt, t(local.elapsedTime))); - updateElapsedTime(); - const interval = setInterval(updateElapsedTime, 60000); - return () => clearInterval(interval); - }, [contribution?.updatedAt, language]); - - if (!contribution) - return ( - - - - - - - - - - {loadingLabels.map((index) => ( - } variant="v1" /> - ))} - - - - - - - - - {loadingLanguages.map((index) => ( - } variant="v1" /> - ))} - - - - - - - - - - - - - - - ); - - const localize = (filterName: string, optionName: string): string => { - const keyPrefix = - filterName === "labels" - ? local.contributionLabelKeyPrefix - : filterName === "languages" - ? local.programmingLanguageKeyPrefix - : null; - if (!keyPrefix) return filterName; - return t(`${keyPrefix}-${optionName.toLowerCase()}`, undefined, undefined, optionName); - }; - - return ( - - - - - - {contribution.title} - - - {contribution.labels.length > 0 && ( - - {contribution.labels.map((optionName, index) => ( - onChipClick("labels", optionName)} - /> - ))} - - )} - - - - - {contribution.project.name} - - {contribution.languages.length > 0 && ( - - {contribution.languages.map((optionName, index) => ( - onChipClick("languages", optionName)} - /> - ))} - - )} - - - - - {contribution.type === "issue" ? t(local.readIssue) : t(local.reviewChanges)} - - - {contribution.commentsCount > 0 && ( - - - - )} - - - {elapsedTime} - - - - - ); -}; diff --git a/packages/ui/src/card/contributor/index.tsx b/packages/ui/src/card/contributor/index.tsx deleted file mode 100644 index fed719595..000000000 --- a/packages/ui/src/card/contributor/index.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { Model } from "@dzcode.io/models/dist/_base"; -import { AccountEntity } from "@dzcode.io/models/dist/account"; -import { getRepositoryURL } from "@dzcode.io/models/dist/repository-reference/utils"; -import { FC } from "react"; -import { useTranslation } from "src/_hooks/use-translation"; -import { Image } from "src/image"; -import { Markdown } from "src/markdown"; -import { Paper } from "src/paper"; -import { Skeleton } from "src/skeleton"; -import { Stack } from "src/stack"; -import { Text } from "src/text"; - -type ContributorCard = - | { - contributor: null; - local?: never; - } - | { - contributor: Model; - // @TODO-ZM: make local dynamic based on counts - local: { - repository: string; - }; - }; - -export const ContributorCard: FC = ({ contributor, local }) => { - const { t } = useTranslation(); - - if (!contributor) - return ( - - - - - - - - - - - - - - - - - - - - - - - ); - - const repositoriesInMarkdown = contributor.repositories - .map((repo) => `- [${repo.owner}/${repo.repository}](${getRepositoryURL(repo)})`) - .join("\n"); - - return ( - - - - - - - {contributor.name} - - - {contributor.username} - - - {contributor.repositories.length} {t(local.repository)} - - - - - - {repositoriesInMarkdown} - - - - ); -}; diff --git a/packages/ui/src/card/project/index.tsx b/packages/ui/src/card/project/index.tsx deleted file mode 100644 index 746730748..000000000 --- a/packages/ui/src/card/project/index.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import { Model } from "@dzcode.io/models/dist/_base"; -import { ProjectEntity } from "@dzcode.io/models/dist/project"; -import { RepositoryEntity } from "@dzcode.io/models/dist/repository"; -import { getRepositoryURL } from "@dzcode.io/models/dist/repository-reference/utils"; -import { arrayOf } from "@dzcode.io/utils/dist/array"; -import { FC } from "react"; -import { useColors } from "src/_hooks/use-colors"; -import { useTranslation } from "src/_hooks/use-translation"; -import { Avatar, AvatarGroup } from "src/avatar"; -import { Badge } from "src/badge"; -import { Chip } from "src/chip"; -import { Divider } from "src/divider"; -import { FilterProps } from "src/filter"; -import { Flex } from "src/flex"; -import { ContributionIcon } from "src/icon/contribution"; -import { Link } from "src/link"; -import { Markdown } from "src/markdown"; -import { Paper } from "src/paper"; -import { Skeleton } from "src/skeleton"; -import { Stack } from "src/stack"; -import { Text } from "src/text"; - -type ProjectCard = - | { project: null; local?: never } - | { - // @TODO-ZM: find a way to DRY this, eg: - // project: Model - project: Model & { - repositories: Model[]; - }; - local: FilterProps["local"]; - }; - -const loadingLanguages = arrayOf(3); - -export const ProjectCard: FC = ({ project, local }) => { - const { t } = useTranslation(); - const { from } = useColors(); - - if (!project) - return ( - - - - - - - - - - - - - {loadingLanguages.map((index) => ( - } variant="v1" /> - ))} - - - - - ); - - const localize = (filterName: string, optionName: string): string => { - const keyPrefix = - filterName === "labels" - ? local.contributionLabelKeyPrefix - : filterName === "languages" - ? local.programmingLanguageKeyPrefix - : null; - if (!keyPrefix) return filterName; - return t(`${keyPrefix}-${optionName.toLowerCase()}`, undefined, undefined, optionName); - }; - - return ( - - - - {project.name} - - - - {project.repositories.map((repository, index) => ( - - - {repository.owner}/{repository.repository} - - - - {repository.contributors.map((contributor, index) => ( - - ))} - - {repository.stats.contributionCount > 0 && ( - - {/* @TODO-ZM: use themed colors instead of `sx.color` */} - - - )} - {repository.stats.languages.length > 0 && ( - <> - - - {repository.stats.languages.map((optionName, index) => ( - - ))} - - - )} - - - ))} - - - ); -}; diff --git a/packages/ui/src/checkbox/index.tsx b/packages/ui/src/checkbox/index.tsx deleted file mode 100644 index 58dc915b1..000000000 --- a/packages/ui/src/checkbox/index.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import MUICheckbox, { CheckboxProps as MUICheckboxProps } from "@mui/material/Checkbox"; -import FormControlLabel from "@mui/material/FormControlLabel"; -import { FC } from "react"; -import { useTheme } from "src/_hooks/use-theme"; -import { BaseUIProps } from "src/_types"; - -export interface CheckboxProps - extends BaseUIProps, - Pick, "checked" | "onChange"> { - label: string; -} - -export const Checkbox: FC = ({ margin, checked, onChange, ...props }) => { - const { toCSSMargin } = useTheme(); - - return ( - } - sx={{ margin: toCSSMargin(margin) }} - {...props} - /> - ); -}; diff --git a/packages/ui/src/chip/index.tsx b/packages/ui/src/chip/index.tsx deleted file mode 100644 index ac034cf54..000000000 --- a/packages/ui/src/chip/index.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import MUIChip, { ChipProps as MUIChipProps } from "@mui/material/Chip"; -import { FC, ReactNode } from "react"; -import { useTheme } from "src/_hooks/use-theme"; -import { BaseUIProps } from "src/_types"; - -export interface ChipProps extends BaseUIProps { - label: ReactNode; - variant: "v1"; - onClick?: () => void; -} - -const variantToMUIChipSize: Record = { - v1: "small", -}; - -export const Chip: FC = ({ margin, variant, ...props }) => { - const { toCSSMargin } = useTheme(); - - return ( - - ); -}; diff --git a/packages/ui/src/circular-progress/index.tsx b/packages/ui/src/circular-progress/index.tsx deleted file mode 100644 index 8a57dc811..000000000 --- a/packages/ui/src/circular-progress/index.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import Box from "@mui/material/Box"; -import MUICircularProgress, { CircularProgressProps } from "@mui/material/CircularProgress"; -import Typography from "@mui/material/Typography"; -import type { FC } from "react"; - -export const CircularProgress: FC = ({ - value, - ...props -}) => { - return ( - // @TODO-ZM: cleanup this rushed component - - theme.palette.grey[theme.palette.mode === "light" ? 200 : 800], - position: "absolute", - }} - value={100} - /> - - - {`${Math.round(value * 100)}%`} - - - ); -}; diff --git a/packages/ui/src/divider/index.tsx b/packages/ui/src/divider/index.tsx deleted file mode 100644 index cebaf6750..000000000 --- a/packages/ui/src/divider/index.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import MUIDivider, { DividerProps as MUIDividerProps } from "@mui/material/Divider"; -import { CSSProperties, FC } from "react"; -import { Color, useColors } from "src/_hooks/use-colors"; -import { useTheme } from "src/_hooks/use-theme"; -import { BaseUIProps } from "src/_types"; - -export interface DividerProps extends BaseUIProps, Pick { - orientation: "vertical" | "horizontal"; - width?: CSSProperties["width"]; - thickness?: number; - color?: Color; -} - -export const Divider: FC = ({ - orientation, - margin, - width, - flexItem = true, - thickness = 1, - color = "DIVIDER", -}) => { - const { from } = useColors(); - const { toCSSMargin } = useTheme(); - - return ( - - ); -}; diff --git a/packages/ui/src/dropdown/index.tsx b/packages/ui/src/dropdown/index.tsx deleted file mode 100644 index 5ae68eb4b..000000000 --- a/packages/ui/src/dropdown/index.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import GlobalStyles from "@mui/material/GlobalStyles"; -import Menu from "@mui/material/Menu"; -import MenuItem from "@mui/material/MenuItem"; -import { FC, ReactNode, useState } from "react"; -import { Button } from "src/button"; -import { Stack } from "src/stack"; - -export interface DropdownProps { - items: Array<{ - code: C; - text: ReactNode; - }>; - text: string; - onSelect?: (code: C) => void; -} - -export const Dropdown = ({ - items, - text, - onSelect, -}: DropdownProps): ReturnType>> => { - const [anchorEl, setAnchorEl] = useState(null); - const open = Boolean(anchorEl); - - const handleClose = (code?: C) => { - setAnchorEl(null); - if (code) { - onSelect?.(code); - } - }; - return ( - <> - - - handleClose()}> - - {items.map((item, index) => ( - handleClose(item.code)}> - {item.text} - - ))} - - - - ); -}; diff --git a/packages/ui/src/error-boundary/index.tsx b/packages/ui/src/error-boundary/index.tsx deleted file mode 100644 index b285a38c5..000000000 --- a/packages/ui/src/error-boundary/index.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { ErrorBoundary as SentryErrorBoundary } from "@sentry/react"; -import { FC } from "react"; -import { useTranslation } from "src/_hooks/use-translation"; -import { ChildrenProp } from "src/_types"; -import { Link } from "src/link"; -import { Stack } from "src/stack"; -import { Text } from "src/text"; - -interface ErrorBoundaryProps extends ChildrenProp { - message?: string; - local: { - emailUs: string; - }; -} - -export const ErrorBoundary: FC = ({ - children, - // @TODO-ZM: localize this - message = "Ops, something broke, we're checking on our end...", - local, -}) => { - const { t } = useTranslation(); - - return ( - - - {message} - - - {t(local.emailUs)} - - - } - > - {children} - - ); -}; diff --git a/packages/ui/src/filter/index.tsx b/packages/ui/src/filter/index.tsx deleted file mode 100644 index eaf4f0ee0..000000000 --- a/packages/ui/src/filter/index.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { arrayOf } from "@dzcode.io/utils/dist/array"; -import { FC } from "react"; -import { useTranslation } from "src/_hooks/use-translation"; -import { BaseUIProps } from "src/_types"; -import { Accordion } from "src/accordion"; -import { Checkbox } from "src/checkbox"; -import { Skeleton } from "src/skeleton"; - -const loadingItems = arrayOf(3); - -export interface FilterProps extends BaseUIProps { - items: Array<{ - name: string; - options: Array<{ - label?: string; - name: string; - checked?: boolean; - }>; - }> | null; - local: { - filterLabelKeyPrefix: string; - programmingLanguageKeyPrefix: string; - contributionLabelKeyPrefix: string; - }; - onOptionClick: (filterName: string, optionName: string, checked: boolean) => void; -} - -export const Filter: FC = ({ items, local, onOptionClick, margin }) => { - const { t } = useTranslation(); - const localize = (filterName: string, optionName: string): string => { - const keyPrefix = - filterName === "labels" - ? local.contributionLabelKeyPrefix - : filterName === "languages" - ? local.programmingLanguageKeyPrefix - : null; - if (!keyPrefix) return filterName; - return t(`${keyPrefix}-${optionName.toLowerCase()}`, undefined, undefined, optionName); - }; - return ( - ({ - title: t(`${local.filterLabelKeyPrefix}-${item.name}`), - body: item.options.map((option) => ( - onOptionClick(item.name, option.name, checked)} - /> - )), - })) || loadingItems.map((_, i) => ({ title: , body: null })) - } - /> - ); -}; diff --git a/packages/ui/src/flex/index.tsx b/packages/ui/src/flex/index.tsx deleted file mode 100644 index 1b65a3702..000000000 --- a/packages/ui/src/flex/index.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import type { CSSProperties, FC } from "react"; -import { Color, useColors } from "src/_hooks/use-colors"; -import { ChildrenProp } from "src/_types"; - -// @TODO-ZM: dry this -export const MAX_CONTAINER_WIDTH = 1200; - -export interface FlexProps - extends ChildrenProp, - Pick< - CSSProperties, - | "position" - | "width" - | "height" - | "position" - | "top" - | "bottom" - | "left" - | "right" - | "display" - | "margin" - > { - grow?: number; - max?: Pick; - min?: Pick; - color?: Color; -} - -export const Flex: FC = ({ - children, - grow, - max, - min, - color, - position, - width = "100%", - ...css -}) => { - const { from } = useColors(); - return ( -
- {children} -
- ); -}; diff --git a/packages/ui/src/footer/index.tsx b/packages/ui/src/footer/index.tsx deleted file mode 100644 index 6dea2bfd7..000000000 --- a/packages/ui/src/footer/index.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { FC } from "react"; -import { useTranslation } from "src/_hooks/use-translation"; -import { Divider } from "src/divider"; -import { Flex } from "src/flex"; -import { Link } from "src/link"; -import { Markdown } from "src/markdown"; -import { Stack } from "src/stack"; -import { Text } from "src/text"; - -interface FooterProps { - sections: Array<{ - title: string; - links: Array<{ - text: string; - href: string; - }>; - }>; - bottomText: string; -} - -export const Footer: FC = ({ sections, bottomText }) => { - const { t } = useTranslation(); - - return ( - - - - {sections.map(({ title, links }, sectionIndex) => ( - - - {t(title)} - - {links.map(({ text, href }, linkIndex) => ( - - {t(text)} - - ))} - - ))} - - - - - - ); -}; diff --git a/packages/ui/src/icon/contribution/index.tsx b/packages/ui/src/icon/contribution/index.tsx deleted file mode 100644 index d841941fe..000000000 --- a/packages/ui/src/icon/contribution/index.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import ConstructionIcon from "@mui/icons-material/Construction"; - -export const ContributionIcon = ConstructionIcon; diff --git a/packages/ui/src/icon/notes/index.tsx b/packages/ui/src/icon/notes/index.tsx deleted file mode 100644 index bbc9bd675..000000000 --- a/packages/ui/src/icon/notes/index.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import MUINotesIcon from "@mui/icons-material/Notes"; - -export const NotesIcon = MUINotesIcon; diff --git a/packages/ui/src/image/index.tsx b/packages/ui/src/image/index.tsx deleted file mode 100644 index 28e6ba5b6..000000000 --- a/packages/ui/src/image/index.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { CSSProperties, FC } from "react"; -import { useTheme } from "src/_hooks/use-theme"; -import { BaseUIProps } from "src/_types"; - -export interface ImageProps - extends BaseUIProps, - Pick { - src: string; -} - -// @TODO-ZM: standardize image sizes -export const Image: FC = ({ - src, - margin, - padding, - width = "auto", - height = "auto", - ...cssProps -}) => { - const { toCSSMargin } = useTheme(); - - return ( - - ); -}; diff --git a/packages/ui/src/link/index.tsx b/packages/ui/src/link/index.tsx deleted file mode 100644 index bf7bdc771..000000000 --- a/packages/ui/src/link/index.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import MUILink, { LinkProps as MUILinkProps } from "@mui/material/Link"; -import { AnchorHTMLAttributes, createContext, CSSProperties, FC, useContext } from "react"; -import { Link as ReactRouterLink, Router, RouterProps as RRDRouterProps } from "react-router-dom"; -import { useTheme } from "src/_hooks/use-theme"; -import { BaseUIProps, ChildrenProp } from "src/_types"; - -export type LinkContextValue = { - prefix?: string; -}; - -const LinkContext = createContext({ prefix: "" }); - -export const LinkProvider: FC< - LinkContextValue & { history: RRDRouterProps["history"] } & ChildrenProp -> = ({ children, prefix, history }) => { - return ( - - {children} - - ); -}; - -export type LinkProps = { - // @TODO-ZM: make this required - variant?: "v1" | "v2"; - // @TODO-ZM: to remove these - className?: string; - style?: any; - color?: any; -} & BaseUIProps & - ChildrenProp & - Pick, "target" | "href"> & - Pick; - -const variantToLinkStyle: Record["variant"], CSSProperties> = { - v1: { wordBreak: "break-word" }, - v2: { display: "flex", flexShrink: 0 }, -}; - -// @TODO-ZM: remove default variant -export const Link: FC = ({ - variant = "v1", - margin, - href, - underline = "hover", - ...props -}) => { - const { prefix } = useContext(LinkContext); - - const { toCSSMargin } = useTheme(); - - const style: CSSProperties = { - cursor: "pointer", - margin: toCSSMargin(margin), - flexShrink: 0, - ...variantToLinkStyle[variant], - }; - - if (href?.startsWith("/") || href?.startsWith(location.origin)) { - return ( - - ); - } else { - return ; - } -}; diff --git a/packages/ui/src/loadable-factory/index.tsx b/packages/ui/src/loadable-factory/index.tsx deleted file mode 100644 index 925486acc..000000000 --- a/packages/ui/src/loadable-factory/index.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import loadable from "@loadable/component"; - -export const loadableFactory = loadable; diff --git a/packages/ui/src/loading/index.tsx b/packages/ui/src/loading/index.tsx deleted file mode 100644 index 193947c6e..000000000 --- a/packages/ui/src/loading/index.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import type { FC } from "react"; -import { Image } from "src/image"; -import { Stack } from "src/stack"; - -interface LoadingProps { - logo: string; -} - -export const Loading: FC = ({ logo }) => { - return ( - - - - ); -}; diff --git a/packages/ui/src/markdown/index.tsx b/packages/ui/src/markdown/index.tsx deleted file mode 100644 index 346c3d917..000000000 --- a/packages/ui/src/markdown/index.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import GlobalStyles from "@mui/material/GlobalStyles"; -import Typography from "@mui/material/Typography"; -import ReactMarkdown from "markdown-to-jsx"; -import type { FC } from "react"; -import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; -import prism from "react-syntax-highlighter/dist/cjs/styles/prism/prism"; -import tomorrow from "react-syntax-highlighter/dist/cjs/styles/prism/tomorrow"; -import { useColors } from "src/_hooks/use-colors"; -import { useTheme } from "src/_hooks/use-theme"; -import { ChildrenProp } from "src/_types"; -import { Link } from "src/link"; - -export interface MarkdownProps extends ChildrenProp { - t?: string; -} - -export const Markdown: FC = ({ children, t }) => { - const { from } = useColors(); - const { isDarkMode, spacing } = useTheme(); - return ( - <> - - - ); - }, - }, - }, - }} - /> - - ); -}; diff --git a/packages/ui/src/media-query/index.tsx b/packages/ui/src/media-query/index.tsx deleted file mode 100644 index 6264a7a83..000000000 --- a/packages/ui/src/media-query/index.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { Breakpoint, useTheme } from "@mui/material/styles"; -import type { FC } from "react"; -import { ChildrenProp } from "src/_types"; -import { useMediaQuery } from "usehooks-ts"; - -interface MediaQueryProps extends ChildrenProp { - upTo?: Breakpoint; - downTo?: Breakpoint; -} - -export const MediaQuery: FC = ({ children, upTo, downTo }) => { - const theme = useTheme(); - const minMatches = - typeof upTo !== "undefined" - ? useMediaQuery(theme.breakpoints.down(upTo).replace("@media ", "")) - : true; - const maxMatches = - typeof downTo !== "undefined" - ? useMediaQuery(theme.breakpoints.up(downTo).replace("@media ", "")) - : true; - if (minMatches && maxMatches) { - return <>{children}; - } else { - return null; - } -}; diff --git a/packages/ui/src/milestones/index.tsx b/packages/ui/src/milestones/index.tsx deleted file mode 100644 index c17537bbf..000000000 --- a/packages/ui/src/milestones/index.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import CircleIcon from "@mui/icons-material/Circle"; -import Timeline from "@mui/lab/Timeline"; -import TimelineConnector from "@mui/lab/TimelineConnector"; -import TimelineContent from "@mui/lab/TimelineContent"; -import TimelineDot from "@mui/lab/TimelineDot"; -import TimelineItem from "@mui/lab/TimelineItem"; -import TimelineOppositeContent from "@mui/lab/TimelineOppositeContent"; -import TimelineSeparator from "@mui/lab/TimelineSeparator"; -import Skeleton from "@mui/material/Skeleton"; -import Step from "@mui/material/Step"; -import StepContent from "@mui/material/StepContent"; -import StepIcon from "@mui/material/StepIcon"; -import StepLabel from "@mui/material/StepLabel"; -import Stepper from "@mui/material/Stepper"; -import { useTheme } from "@mui/material/styles"; -import Typography from "@mui/material/Typography"; -import type { FC } from "react"; -import { CircularProgress } from "src/circular-progress"; -import { useMediaQuery } from "usehooks-ts"; - -type MilestoneItem = { - id: string; - title: string; - description?: string; - date?: string; - progress?: number; - state: "open" | "closed" | "in-progress"; -}; - -interface MilestonesProps { - milestones?: MilestoneItem[]; - onClick?: (index: number, item: MilestoneItem) => void; -} - -// @TODO-ZM: update colors -const milestoneColors: Record = { - open: "silver", - closed: "green", - "in-progress": "primary.main", -}; - -export const Milestones: FC = ({ milestones, onClick = () => null }) => { - const large = useMediaQuery("(min-width:600px)"); - const theme = useTheme(); - - return milestones ? ( - large ? ( - - {milestones.map(({ id, title, description, date, state, progress }, milestoneIndex) => ( - - {date ? ( - - {/* @TODO-ZM: localize this */} - {new Date(date).toLocaleDateString()} - - ) : null} - - - {state === "in-progress" || (progress && progress < 1) ? ( - - ) : ( - - )} - - - - onClick(milestoneIndex, milestones[milestoneIndex])} - sx={{ cursor: "pointer" }} - > - {title} - - {description && {description}} - - - ))} - - ) : ( - - {milestones.map(({ id, title, description, date, state, progress }, milestoneIndex) => ( - - - state === "in-progress" || (progress && progress < 1) ? ( - - ) : ( - } /> - ) - } - > - onClick(milestoneIndex, milestones[milestoneIndex])} - sx={{ cursor: "pointer", color: theme.palette.text.primary }} - > - {title} - - - - {date && ( - {new Date(date).toLocaleDateString()} - )} - {description} - - - ))} - - ) - ) : ( - - {[1, 2, 3].map((id) => ( - - - - - - - - - - - - - - - - - - - ))} - - ); -}; diff --git a/packages/ui/src/navbar/index.tsx b/packages/ui/src/navbar/index.tsx deleted file mode 100644 index ca8b7d8e8..000000000 --- a/packages/ui/src/navbar/index.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import { allLanguages, LanguageEntity } from "@dzcode.io/models/dist/language"; -import { FC, Fragment } from "react"; -import { useTranslation } from "src/_hooks/use-translation"; -import { Button } from "src/button"; -import { Divider } from "src/divider"; -import { Dropdown } from "src/dropdown"; -import { Flex, MAX_CONTAINER_WIDTH } from "src/flex"; -import { Image } from "src/image"; -import { Link } from "src/link"; -import { MediaQuery } from "src/media-query"; -import { Stack } from "src/stack"; -import { Text } from "src/text"; -import { useMediaQuery } from "usehooks-ts"; - -interface NavBarProps { - version: string; - selectedLanguageCode: LanguageEntity["code"]; - // @TODO-ZM: dry theme names - themeName: "DARK" | "LIGHT" | "AUTO"; - logo: string; - logoExtended: string; - links: Array<{ text: string; href: string }>; - onLanguageChanged: (languageCode: LanguageEntity["code"]) => void; - onThemeChanged: (themeName: NavBarProps["themeName"]) => void; - fixed: boolean; -} - -// @TODO-ZM: dry theme names -const themNameToText: Record = { - DARK: "šŸŒ™", - LIGHT: "šŸŒž", - AUTO: "šŸŒ—", -}; - -const logoAspectRation = 100 / 17.5; // Width / Height -// @TODO-ZM: 24 is an arbitrary number, use a regulated number -const logoSize = { width: 24 * logoAspectRation, height: 24 }; - -export const Navbar: FC = ({ - version, - selectedLanguageCode, - themeName, - logo, - logoExtended, - links, - onLanguageChanged, - onThemeChanged, - fixed, -}) => { - const { t } = useTranslation(); - - return ( - - - - - - - - - - - - - - - - {version.length > 10 ? `${version.substring(0, 8)}...` : version} - - {version} - - - code === selectedLanguageCode)?.shortLabel || "" - } - items={allLanguages.map((lang) => ({ - code: lang.code, - text: ( - - {lang.label} - - ), - }))} - onSelect={onLanguageChanged} - /> - - {t("ui-theme-DARK")} - - ), - }, - { - code: "LIGHT", - text: ( - - {t("ui-theme-LIGHT")} - - ), - }, - { - code: "AUTO", - text: ( - - {t("ui-theme-AUTO")} - - ), - }, - ]} - onSelect={onThemeChanged} - /> - - - - - - - - - - - - {links.map(({ text, href }, index) => ( - - - {index < links.length - 1 && } - - ))} - - - - - - - - - ); -}; diff --git a/packages/ui/src/paper/index.tsx b/packages/ui/src/paper/index.tsx deleted file mode 100644 index a1b0dbe72..000000000 --- a/packages/ui/src/paper/index.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import MUIPaper from "@mui/material/Paper"; - -export const Paper = MUIPaper; diff --git a/packages/ui/src/skeleton/index.tsx b/packages/ui/src/skeleton/index.tsx deleted file mode 100644 index 5c860cc1a..000000000 --- a/packages/ui/src/skeleton/index.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import MUISkeleton from "@mui/material/Skeleton"; - -export const Skeleton = MUISkeleton; diff --git a/packages/ui/src/stack/index.tsx b/packages/ui/src/stack/index.tsx deleted file mode 100644 index cdfc93c14..000000000 --- a/packages/ui/src/stack/index.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import MUIStack, { StackProps as MUIStackProps } from "@mui/material/Stack"; -import type { CSSProperties, FC } from "react"; -import { useTheme } from "src/_hooks/use-theme"; -import { BaseUIProps, ChildrenProp } from "src/_types"; - -export interface StackProps - extends BaseUIProps, - ChildrenProp, - Pick< - MUIStackProps, - "alignItems" | "justifyContent" | "overflow" | "height" | "width" | "flexWrap" | "gap" - > { - direction: "vertical" | "horizontal"; - grow?: number; - // @TODO-ZM: dry min max - max?: Pick; - min?: Pick; -} - -export const Stack: FC = ({ - children, - direction, - margin, - padding, - max, - min, - grow, - ...props -}) => { - const { toCSSMargin } = useTheme(); - - return ( - - {children} - - ); -}; diff --git a/packages/ui/src/text/index.tsx b/packages/ui/src/text/index.tsx deleted file mode 100644 index f731ea46b..000000000 --- a/packages/ui/src/text/index.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import MUITypography, { TypographyProps } from "@mui/material/Typography"; -import type { CSSProperties, FC } from "react"; -import { useTheme } from "src/_hooks/use-theme"; -import { BaseUIProps, ChildrenProp } from "src/_types"; - -export interface TextProps - extends BaseUIProps, - ChildrenProp, - Pick { - variant: "v1" | "v2" | "v3" | "v4"; -} - -const variantToMUITypographyVariant: Record = { - v1: "caption", - v2: "body1", - v3: "h4", - v4: "h5", -}; - -export const Text: FC = ({ children, variant, margin, ...cssProps }) => { - const { toCSSMargin } = useTheme(); - - return ( - - {children} - - ); -}; diff --git a/packages/ui/src/theme/custom-theme.ts b/packages/ui/src/theme/custom-theme.ts deleted file mode 100644 index c5decfe4e..000000000 --- a/packages/ui/src/theme/custom-theme.ts +++ /dev/null @@ -1,21 +0,0 @@ -import common from "@mui/material/colors/common"; -import grey from "@mui/material/colors/grey"; -import { createTheme, Direction, PaletteOptions } from "@mui/material/styles"; - -export const customTheme = (mode: PaletteOptions["mode"], direction: Direction) => { - const modeIndex = (["light", "dark"] as PaletteOptions["mode"][]).indexOf(mode); - - return createTheme({ - direction, - palette: { - mode, - primary: { - main: "#43a047", - }, - background: { - default: [grey["100"], common.black][modeIndex], - }, - divider: [grey["400"], grey["600"]][modeIndex], - }, - }); -}; diff --git a/packages/ui/src/theme/index.tsx b/packages/ui/src/theme/index.tsx deleted file mode 100644 index adcc7c4bd..000000000 --- a/packages/ui/src/theme/index.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import createCache from "@emotion/cache"; -import { CacheProvider } from "@emotion/react"; -import CssBaseline from "@mui/material/CssBaseline"; -import { Direction, PaletteOptions, ThemeProvider } from "@mui/material/styles"; -import type { FC } from "react"; -import { ChildrenProp } from "src/_types"; -import rtlPlugin from "stylis-plugin-rtl"; -import { useMediaQuery } from "usehooks-ts"; - -import { customTheme } from "./custom-theme"; - -interface ThemeProps extends ChildrenProp { - themeName: "DARK" | "LIGHT" | "AUTO"; - direction: Direction; -} - -const caches = { - ltr: createCache({ - key: "muiltr", - }), - rtl: createCache({ - key: "muirtl", - stylisPlugins: [rtlPlugin as never], - }), -}; - -export const Theme: FC = ({ children, themeName, direction }) => { - const systemPrefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)"); - const mode: PaletteOptions["mode"] = - themeName === "AUTO" - ? systemPrefersDarkMode - ? "dark" - : "light" - : themeName === "DARK" - ? "dark" - : "light"; - document.body.dir = direction; - - return ( - - - - {children} - - - ); -}; diff --git a/packages/ui/src/translation-factory/index.tsx b/packages/ui/src/translation-factory/index.tsx deleted file mode 100644 index 1efc650fd..000000000 --- a/packages/ui/src/translation-factory/index.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { LanguageEntity } from "@dzcode.io/models/dist/language"; -import { createContext, FC } from "react"; -import { ChildrenProp } from "src/_types"; - -type BaseDictionary = Record>; -type TranslationFunction = ReturnType; - -export const translationFactory = - ( - dictionary: T, - getLanguageCode: () => keyof T[keyof T], - fallbackText = "MISSING_TRANSLATION", - ): FC< - Partial> & { - k?: keyof T; - r?: Record; - } - > => - // eslint-disable-next-line react/display-name - ({ k, r = {}, ...props }) => { - const languageCode = getLanguageCode(); - const key = (k as keyof T) || (Object.keys(props)[0] as keyof T); - return <>{replace(dictionary, languageCode, fallbackText, key, r)}; - }; - -export const translationFunctionFactory = - >>( - dictionary: T, - getLanguageCode: () => keyof T[keyof T], - fallbackText?: string, - ): (( - k: keyof T, - r?: Record, - overrideLanguage?: keyof T[keyof T], - individualFallbackText?: string, - ) => string) => - (k, r = {}, overrideLanguage, individualFallbackText = fallbackText) => { - const languageCode = overrideLanguage || getLanguageCode(); - return replace(dictionary, languageCode, individualFallbackText, k, r); - }; - -const replace = ( - dictionary: T, - languageCode: keyof T[keyof T], - fallbackText = "MISSING_TRANSLATION", - k: keyof T, - r: Record = {}, -) => { - const key = k; - let value = dictionary[key]?.[languageCode] || fallbackText; - Object.keys(r).forEach((rKey: keyof typeof r) => { - value = value.replace(RegExp(`${rKey}`), String(r[rKey])); - }); - return value; -}; - -export interface TranslationContextValue { - t: TranslationFunction; - language?: LanguageEntity; -} -export const TranslationContext = createContext({ - t: () => "MISSING_TRANSLATION_CONTEXT", - language: undefined, -}); - -export interface TranslationProviderProps extends ChildrenProp { - language: LanguageEntity; -} - -export const translationProviderFactory = ( - dictionary: T, - getLanguageCode: () => keyof T[keyof T], - fallbackText = "MISSING_TRANSLATION", -): FC => { - const t = translationFunctionFactory(dictionary, getLanguageCode, fallbackText); - - // eslint-disable-next-line react/display-name - return ({ children, language }) => { - return ( - {children} - ); - }; -}; diff --git a/packages/ui/src/treeview/index.tsx b/packages/ui/src/treeview/index.tsx deleted file mode 100644 index ac3e86aca..000000000 --- a/packages/ui/src/treeview/index.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { TreeItem } from "@dzcode.io/utils/dist/ts"; -import Skeleton from "@mui/material/Skeleton"; -import { FC, ReactElement } from "react"; -import { Stack, StackProps } from "src/stack"; - -export interface TreeviewProps> - extends Pick { - items: TreeItem[] | null; - itemRender: (item: TreeItem, info: { isSelected: boolean }) => ReactElement; - shift?: number; - selectedItemId?: TreeItem["id"]; -} - -export const Treeview = >({ - items, - itemRender, - shift = 1, - selectedItemId: selectedItem, - ...props -}: TreeviewProps): ReturnType>> => { - const RecursiveTreeitem: FC<{ item: TreeItem; root?: boolean }> = ({ item, root }) => ( - - {itemRender(item, { isSelected: item.id === selectedItem })} - - {item.children?.map((childItem) => ( - - ))} - - - ); - - return ( - - {items ? ( - items.map((i) => ) - ) : ( - <> - - - - - - - )} - - ); -}; diff --git a/packages/ui/src/try-again/index.tsx b/packages/ui/src/try-again/index.tsx deleted file mode 100644 index 19c6118e5..000000000 --- a/packages/ui/src/try-again/index.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { FC } from "react"; -import { BaseUIProps } from "src/_types"; -import { Button } from "src/button"; -import { Stack } from "src/stack"; -import { Text } from "src/text"; - -export const TryAgain: FC< - { - onClick: () => void; - error: string; - action: string; - } & BaseUIProps -> = ({ error, action, onClick, margin }) => { - return ( - - {error} - - - ); -}; diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json deleted file mode 100644 index b0ea4c0e8..000000000 --- a/packages/ui/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "@dzcode.io/tooling/tsconfig.json", - "compilerOptions": { - "outDir": "dist", - "baseUrl": "." - }, - "include": ["src"] -} diff --git a/packages/ui/typings.d.ts b/packages/ui/typings.d.ts deleted file mode 100644 index 58ade0e34..000000000 --- a/packages/ui/typings.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module "stylis-plugin-rtl" { - const noTypesYet: any; - export default noTypesYet; -} diff --git a/packages/utils/src/ts/index.ts b/packages/utils/src/ts/index.ts index b44235813..a5913d73b 100644 --- a/packages/utils/src/ts/index.ts +++ b/packages/utils/src/ts/index.ts @@ -42,4 +42,9 @@ export type PopSubString = string extends S export type PyramidSplitString< S extends string, D extends string, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore > = S extends `${infer L}-${PopSubString}` ? [L, ...PyramidSplitString] : []; + +export type PartialWithOneRequiredKey }> = Partial & + U[keyof U]; diff --git a/web/.browserslistrc b/web/.browserslistrc deleted file mode 100644 index 53025c930..000000000 --- a/web/.browserslistrc +++ /dev/null @@ -1,9 +0,0 @@ -[production] ->0.2% -not dead -not op_mini all - -[development] -last 1 chrome version -last 1 firefox version -last 1 safari version diff --git a/web/.cracorc.ts b/web/.cracorc.ts deleted file mode 100644 index 2d45acab4..000000000 --- a/web/.cracorc.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { CracoConfig } from "@craco/types"; -import jest from "@dzcode.io/tooling/jest.config"; -import { fsConfig } from "@dzcode.io/utils/dist/config"; -import { Environment, environments } from "@dzcode.io/utils/dist/config/environment"; -import { readFileSync } from "fs-extra"; -import HtmlWebpackPlugin from "html-webpack-plugin"; -import { join, normalize } from "path"; -import { resolve } from "path"; -import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer"; - -import { dynamicPages, PageInfo, staticPages } from "./src/build/pages"; - -const RobotstxtPlugin = require("robotstxt-webpack-plugin"); // eslint-disable-line @typescript-eslint/no-var-requires - -let stage = process.env.STAGE as Environment; -if (!environments.includes(stage)) { - console.log(`āš ļø No STAGE provided, falling back to "development"`); - stage = "development"; -} -const ANALYZE = process.env.ANALYZE === "true"; - -let bundleInfo: { version: string } = { - version: `v${require("./package.json").version as string}`, // eslint-disable-line @typescript-eslint/no-var-requires -}; -try { - bundleInfo = JSON.parse(readFileSync(".bundle-info.json").toString()) as typeof bundleInfo; -} catch (error) { - console.log(`no .bundle-info.json found`); -} - -const distFolder = "./bundle"; -const publicResourcesPath = `w/${bundleInfo.version}`; -const publicPath = "/"; - -type HtmlWebpackPluginTemplate = PageInfo & { - themeColor: string; - stage: Environment; - gaCode: string; -}; - -module.exports = { - devServer: { - port: fsConfig("development").web.port, - }, - webpack: { - alias: { - src: resolve(__dirname, "src/"), - }, - plugins: { - remove: ["HtmlWebpackPlugin"], - add: [ - ...[...staticPages, ...dynamicPages].map( - (pageInfo) => - new HtmlWebpackPlugin({ - filename: normalize( - pageInfo.uri !== "/" ? `${pageInfo.uri}/index.html` : "/index.html", - ).substring(1), - template: `src/_entry/index.html`, - templateParameters: { - ...pageInfo, - stage, - themeColor: "#43a047", - gaCode: "UA-163554556-1", - } as HtmlWebpackPluginTemplate, - favicon: "./src/assets/ico/favicon.ico", - }), - ), - new RobotstxtPlugin({ - policy: [ - { - userAgent: "*", - allow: stage === "production" ? "/" : undefined, - disallow: stage !== "production" ? "/" : undefined, - }, - ], - }), - ANALYZE && [ - // https://github.com/webpack-contrib/webpack-bundle-analyzer - new BundleAnalyzerPlugin({ - analyzerMode: "static", - reportFilename: "../build/analysis.html", - }), - ], - ].filter(Boolean), - }, - configure: (webpackConfig, context) => { - if (context.paths) { - context.paths.appBuild = distFolder; - } - webpackConfig.output = { - ...webpackConfig.output, - chunkFilename: join(publicResourcesPath, "chunk.[contenthash].js"), - filename: join(publicResourcesPath, "bundle.[name].[contenthash].js"), - path: join(__dirname, distFolder, publicPath), - publicPath: publicPath, - }; - return webpackConfig; - }, - }, - eslint: { - enable: false, - }, - babel: { - plugins: [ - ["transform-define", { "window.bundleInfo": bundleInfo }], - // https://www.npmjs.com/package/babel-plugin-typescript-to-proptypes - ["babel-plugin-typescript-to-proptypes", { comments: true }], - ], - }, - jest: { configure: jest }, -} as CracoConfig; diff --git a/web/_global.d.ts b/web/_global.d.ts new file mode 100644 index 000000000..1ea14dfd7 --- /dev/null +++ b/web/_global.d.ts @@ -0,0 +1,17 @@ +/// + +import { Environment } from "@dzcode.io/utils/dist/config/environment"; + +declare global { + interface Window { + bundleInfo: { + version: string; + }; + } + + namespace NodeJS { + interface ProcessEnv { + STAGE: Environment; + } + } +} diff --git a/web/cypress.config.ts b/web/cypress.config.ts index 2d90a9ddf..8b62e1496 100644 --- a/web/cypress.config.ts +++ b/web/cypress.config.ts @@ -4,7 +4,7 @@ import { defineConfig } from "cypress"; export default defineConfig({ e2e: { baseUrl: fsConfig("development").web.url, - supportFile: "src/_e2e-test/support/e2e.ts", + supportFile: false, specPattern: "src/_e2e-test/tests/**/*.spec.{js,jsx,ts,tsx}", downloadsFolder: "src/_e2e-test/downloads", videosFolder: "src/_e2e-test/videos", diff --git a/web/eslint.config.mjs b/web/eslint.config.mjs new file mode 100644 index 000000000..ac2853f7b --- /dev/null +++ b/web/eslint.config.mjs @@ -0,0 +1,31 @@ +import { fixupConfigRules, fixupPluginRules } from "@eslint/compat"; +import js from "@eslint/js"; +import reactHooks from "eslint-plugin-react-hooks"; +import reactJsx from "eslint-plugin-react/configs/jsx-runtime.js"; +import react from "eslint-plugin-react/configs/recommended.js"; +import globals from "globals"; +import ts from "typescript-eslint"; + +export default [ + { languageOptions: { globals: globals.browser } }, + js.configs.recommended, + ...ts.configs.recommended, + ...fixupConfigRules([ + { + ...react, + settings: { + react: { version: "detect" }, + }, + }, + reactJsx, + ]), + { + plugins: { + "react-hooks": fixupPluginRules(reactHooks), + }, + rules: { + ...reactHooks.configs.recommended.rules, + }, + }, + { ignores: ["dist/"] }, +]; diff --git a/web/package.json b/web/package.json index 19781c01c..423204ce1 100644 --- a/web/package.json +++ b/web/package.json @@ -1,67 +1,42 @@ { "name": "@dzcode.io/web", "description": "www.dzcode.io code", - "version": "5.3.6", + "version": "6.0.0", "author": { "email": "contact@zakiii.com", "name": "Zakaria Mansouri", "url": "https://www.zakiii.com" }, "dependencies": { - "@babel/runtime": "^7.16.3", "@dzcode.io/api": "*", "@dzcode.io/data": "*", "@dzcode.io/models": "*", - "@dzcode.io/ui": "*", - "@dzcode.io/utils": "*", - "@n8tb1t/use-scroll-position": "^2.0.3", - "@reduxjs/toolkit": "^1.8.5", - "@sentry/browser": "^7.46.0", - "@sentry/react": "^7.46.0", - "@sentry/tracing": "^7.46.0", - "immutable": "^4.1.0", - "l2t": "^1.1.0", - "markdown-to-jsx": "^7.1.0", - "react": "^18.0.1", - "react-dom": "^18.2.0", - "react-helmet": "^6.1.0", - "react-redux": "^7.2.6", - "react-router-dom": "^5.2.0", - "react-spring": "^8.0.27", - "react-syntax-highlighter": "^15.4.3", - "web-vitals": "^2.1.4" + "@sentry/react": "^8.27.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-helmet-async": "^2.0.5" }, "devDependencies": { - "@craco/craco": "^7.1.0", - "@craco/types": "^7.1.0", - "@dzcode.io/tooling": "*", - "@testing-library/react": "^14.0.0", - "@types/fs-extra": "^9.0.13", - "@types/glob": "^7.1.4", - "@types/markdown-to-jsx": "^6.11.3", - "@types/mini-css-extract-plugin": "^2.0.1", - "@types/react": "^17.0.0", - "@types/react-dom": "^17.0.0", - "@types/react-helmet": "^6.1.5", - "@types/react-redux": "^7.1.20", - "@types/react-router-dom": "^5.1.6", - "@types/react-syntax-highlighter": "^13.5.0", - "@types/redux": "^3.6.0", - "@types/redux-thunk": "^2.1.0", - "@types/webpack-bundle-analyzer": "^4.4.2", - "babel-plugin-typescript-to-proptypes": "^1.4.2", - "cpx": "^1.5.0", - "cypress": "^12.8.1", - "firebase-tools": "^9.1.0", - "fs-extra": "^10.0.0", - "glob": "^7.1.6", - "html-webpack-plugin": "^5.5.0", - "react-scripts": "5.0.1", - "robotstxt-webpack-plugin": "^8.0.0", - "sass": "^1.30.0", + "@loadable/component": "^5.16.4", + "@reduxjs/toolkit": "^2.2.6", + "@rsbuild/core": "1.0.0-alpha.5", + "@rsbuild/plugin-react": "1.0.0-alpha.5", + "@types/loadable__component": "^5.13.9", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "autoprefixer": "^10.4.19", + "cypress": "^13.14.1", + "daisyui": "^4.12.10", + "globals": "^15.8.0", + "postcss": "^8.4.39", + "postcss-loader": "^8.1.1", + "react-markdown": "^9.0.1", + "react-redux": "^9.1.2", + "react-router-dom": "^6.24.1", "sitemap": "^7.1.1", - "wait-on": "^7.0.1", - "webpack-bundle-analyzer": "^4.6.1" + "tailwindcss": "^3.4.4", + "typescript-eslint": "^7.15.0", + "wait-on": "^8.0.0" }, "engines": { "node": ">=16", @@ -83,16 +58,16 @@ "build": "lerna run build:alone --scope=@dzcode.io/web --include-dependencies --stream", "build:watch": "lerna run build:alone:watch --scope=@dzcode.io/web --include-dependencies --parallel", "bundle": "yarn build && yarn bundle:alone", - "bundle:alone": "cross-env TS_NODE_SKIP_PROJECT=true NODE_ENV=production craco build", - "bundle:analyze": "yarn clean && cross-env ANALYZE=true yarn bundle", + "bundle:alone": "rsbuild build", "clean": "lerna run clean:alone --scope=@dzcode.io/web --include-dependencies --stream", "clean:alone": "rimraf dist coverage bundle node_modules/.cache", - "deploy": "yarn generate:sitemap && rimraf ./firebase/public && cpx -u \"./bundle/**/*\" ./firebase/public && cd ./firebase && firebase deploy --only hosting:dzcode.io --token \"$FIREBASE_TOKEN\"", - "deploy:stg": "yarn generate:sitemap && rimraf ./firebase/public && cpx -u \"./bundle/**/*\" ./firebase/public && cd ./firebase && firebase deploy --only hosting:stage.dzcode.io --token \"$FIREBASE_TOKEN\"", + "deploy": "yarn generate:htmls && yarn generate:sitemap && rimraf ./firebase/public && cpx -u \"./bundle/**/*\" ./firebase/public && cd ./firebase && firebase deploy --only hosting:dzcode.io --token \"$FIREBASE_TOKEN\"", + "deploy:stg": "yarn generate:htmls && yarn generate:sitemap && rimraf ./firebase/public && cpx -u \"./bundle/**/*\" ./firebase/public && cd ./firebase && firebase deploy --only hosting:stage.dzcode.io --token \"$FIREBASE_TOKEN\"", "e2e:dev": "yarn wait-on http://localhost:8080/ && yarn cypress open", "generate:bundle-info": "ts-node ../packages/tooling/bundle-info.ts", + "generate:htmls": "cross-env TS_NODE_SKIP_PROJECT=true ts-node --compilerOptions \"{\\\"baseUrl\\\": \\\".\\\"}\" src/_build/gen-multiple-htmls.ts", "generate:sentry-release": "ts-node ../packages/tooling/sentry-release.ts web bundle", - "generate:sitemap": "cross-env TS_NODE_SKIP_PROJECT=true ts-node src/build/sitemap.ts", + "generate:sitemap": "cross-env TS_NODE_SKIP_PROJECT=true ts-node --compilerOptions \"{\\\"baseUrl\\\": \\\".\\\"}\" src/_build/sitemap.ts", "lint": "yarn build && yarn lint:alone", "lint:alone": "yarn lint:eslint . && yarn lint:prettier --check . && yarn lint:tsc && yarn lint:ts-prune", "lint:eslint": "eslint --config ../packages/tooling/.eslintrc.json --ignore-path ../packages/tooling/.eslintignore --report-unused-disable-directives", @@ -101,9 +76,9 @@ "lint:prettier": "prettier --config ../packages/tooling/.prettierrc --ignore-path ../packages/tooling/.prettierignore --loglevel warn", "lint:ts-prune": "ts-node ../packages/tooling/setup-ts-prune.ts && ts-prune --error", "lint:tsc": "tsc --noEmit", - "start:dev": "craco start", + "start:dev": "rsbuild dev --open", "test": "yarn build && yarn test:alone", - "test:alone": "craco test --watchAll=false", + "test:alone": "jest --config ../packages/tooling/jest.config.ts --rootDir .", "test:watch": "npm-run-all build --parallel build:watch \"test:alone --watch {@}\" --" } } diff --git a/web/postcss.config.js b/web/postcss.config.js new file mode 100644 index 000000000..12a703d90 --- /dev/null +++ b/web/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/web/public/index.html b/web/public/index.html deleted file mode 100644 index ebcfa41b0..000000000 --- a/web/public/index.html +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/web/rsbuild.config.ts b/web/rsbuild.config.ts new file mode 100644 index 000000000..4976fe954 --- /dev/null +++ b/web/rsbuild.config.ts @@ -0,0 +1,56 @@ +import { Environment, environments } from "@dzcode.io/utils/dist/config/environment"; +import { defineConfig } from "@rsbuild/core"; +import { pluginReact } from "@rsbuild/plugin-react"; +import { readFileSync } from "fs"; + +let stage = process.env.STAGE; + +if (!environments.includes(stage)) { + console.log(`āš ļø No STAGE provided, falling back to "development"`); + stage = "development"; +} + +let bundleInfo: { version: string } = { + version: `v${require("./package.json").version as string}`, // eslint-disable-line @typescript-eslint/no-var-requires +}; +try { + bundleInfo = JSON.parse(readFileSync(".bundle-info.json").toString()) as typeof bundleInfo; +} catch (error) { + console.log(`no .bundle-info.json found`); +} + +export default defineConfig({ + plugins: [pluginReact()], + source: { + alias: { + src: "./src", + }, + define: { + "window.bundleInfo": bundleInfo, + }, + }, + html: { + template: "./src/_entry/index.html", + favicon: "./src/assets/ico/favicon.ico", + templateParameters: { + stage, + }, + }, + server: { + port: 8080, + }, + output: { + distPath: { + root: "./bundle", + css: `w/${bundleInfo.version}/css`, + cssAsync: `w/${bundleInfo.version}/css/async`, + js: `w/${bundleInfo.version}/js`, + jsAsync: `w/${bundleInfo.version}/js/async`, + image: `w/${bundleInfo.version}/images`, + font: `w/${bundleInfo.version}/fonts`, + media: `w/${bundleInfo.version}/media`, + svg: `w/${bundleInfo.version}/svg`, + wasm: `w/${bundleInfo.version}/wasm`, + }, + }, +}); diff --git a/web/src/_build/gen-multiple-htmls.ts b/web/src/_build/gen-multiple-htmls.ts new file mode 100644 index 000000000..aa05f5879 --- /dev/null +++ b/web/src/_build/gen-multiple-htmls.ts @@ -0,0 +1,65 @@ +// for dev, run: +// yarn clean && yarn bundle && yarn generate:htmls +// yarn rsbuild preview + +import { join } from "path"; +import { readFileSync, writeFileSync, mkdirSync } from "fs"; +import { allPages } from "./pages"; +import { Environment, environments } from "@dzcode.io/utils/dist/config/environment"; + +let stage = process.env.STAGE as Environment; +if (!environments.includes(stage)) { + console.log(`āš ļø No STAGE provided, falling back to "development"`); + stage = "development"; +} + +const distFolder = "./bundle"; + +const indexHtmlPath = join(distFolder, "index.html"); +const indexHtml = readFileSync(indexHtmlPath).toString(); + +const SKIP_ROOT_HTML = process.env.SKIP_ROOT_HTML === "true"; + +console.log(`generating ${allPages.length} html files ...`); + +allPages.forEach((pageInfo) => { + const pathName = pageInfo.uri; + const outputHtmlDir = join(distFolder, pathName); + const outputHtmlPath = join(outputHtmlDir, "index.html"); + if (SKIP_ROOT_HTML && outputHtmlPath === indexHtmlPath) { + console.log(`skipping root html: ${outputHtmlPath}`); + return; + } + + let newHtml = indexHtml; + newHtml = newHtml.replace( + /{{googleAnalytics}}/g, + stage === "development" + ? "" + : ` + + + ` + .replace(/\n/g, "") + .replace(/\s{2,}/g, " "), + ); + newHtml = newHtml.replace(/{{themeColor}}/g, "#43a047"); + newHtml = newHtml.replace(/{{lang}}/g, pageInfo.lang); + newHtml = newHtml.replace(/{{keywords}}/g, pageInfo.keywords); + newHtml = newHtml.replace(/{{title}}/g, pageInfo.title); + newHtml = newHtml.replace(/{{description}}/g, pageInfo.description); + newHtml = newHtml.replace(/{{ogImage}}/g, pageInfo.ogImage); + + mkdirSync(outputHtmlDir, { recursive: true }); + writeFileSync(outputHtmlPath, newHtml); + + console.log(outputHtmlPath, "āœ…"); +}); + +console.log("done"); diff --git a/web/src/build/pages/index.ts b/web/src/_build/pages/index.ts similarity index 70% rename from web/src/build/pages/index.ts rename to web/src/_build/pages/index.ts index 504f8a4d4..8e02e63e4 100644 --- a/web/src/build/pages/index.ts +++ b/web/src/_build/pages/index.ts @@ -1,6 +1,7 @@ import { LanguageEntity } from "@dzcode.io/models/dist/language"; -import { AllDictionaryKeys } from "../../components/t/dictionary"; // eslint-disable-line no-restricted-imports +import { AllDictionaryKeys } from "../../components/locale/dictionary"; +import { staticPages } from "./static-pages"; export interface PageInfo { uri: string; @@ -17,5 +18,4 @@ export type PageInfoWithLocalKeys = Omit "en"); +const localize = (key: string, language: string) => + plainLocalize(dictionary, language, key, "NO-TRANSLATION"); const staticURLs: PageInfoWithLocalKeys[] = [ { @@ -16,7 +17,7 @@ const staticURLs: PageInfoWithLocalKeys[] = [ keywords: "", }, { - uri: "/Contribute", + uri: "/contribute", title: "contribute-title", description: "contribute-description", ogImage: @@ -24,15 +25,7 @@ const staticURLs: PageInfoWithLocalKeys[] = [ keywords: "contribute, open-source, algeria, dzcode", }, { - uri: "/Learn", - title: "learn-title", - description: "learn-description", - ogImage: - "https://images.unsplash.com/photo-1519670107408-15dc1b3ecb1c?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1200&h=627&q=80", - keywords: "learn, open-source, algeria, dzcode", - }, - { - uri: "/Projects", + uri: "/projects", title: "projects-title", description: "projects-description", ogImage: @@ -40,15 +33,7 @@ const staticURLs: PageInfoWithLocalKeys[] = [ keywords: "projects, open-source, algeria, dzcode", }, { - uri: "/Articles", - title: "articles-title", - description: "articles-description", - ogImage: - "https://images.unsplash.com/photo-1585241936939-be4099591252?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1200&h=627&q=80", - keywords: "articles, open-source, algeria, dzcode", - }, - { - uri: "/FAQ", + uri: "/faq", title: "faq-title", description: "faq-description", ogImage: @@ -56,7 +41,7 @@ const staticURLs: PageInfoWithLocalKeys[] = [ keywords: "faq, open-source, algeria, dzcode", }, { - uri: "/Team", + uri: "/team", title: "team-title", description: "team-description", ogImage: @@ -70,8 +55,8 @@ export const staticPages: PageInfo[] = staticURLs.reduce( ...acc, ...allLanguages.map(({ code }) => ({ ...page, - title: t(title, undefined, code), - description: t(description, undefined, code), + title: localize(title, code), + description: localize(description, code), uri: code === "en" ? uri : `/${code}${uri}`, lang: code, })), diff --git a/web/src/build/sitemap.ts b/web/src/_build/sitemap.ts similarity index 53% rename from web/src/build/sitemap.ts rename to web/src/_build/sitemap.ts index 01deaed40..780134269 100644 --- a/web/src/build/sitemap.ts +++ b/web/src/_build/sitemap.ts @@ -3,37 +3,14 @@ import { allLanguages } from "@dzcode.io/models/dist/language"; import { createWriteStream } from "fs"; import { join } from "path"; import { SitemapStream } from "sitemap"; +import { staticPages } from "./pages"; const distFolder = "./bundle"; // Static URLs console.log("Getting Static URLs ..."); -const urls = ["/", "/Contribute", "/Learn", "/Projects", "/Articles", "/FAQ", "/Team"].reduce( - (pV, uri) => [...pV, ...allLanguages.map(({ code }) => (code === "en" ? uri : `/${code}${uri}`))], - [] as string[], -); -console.log("āœ…", urls.length, "URLs Found"); +const urls = staticPages.map(({ uri }) => uri); -// Dynamic URLs -// @TODO-ZM: to localize this -console.log("Getting Dynamic URLs ..."); -[ - { file: "articles", slug: "Articles" }, - { file: "documentation", slug: "Learn" }, - // { file: "projects", slug: "Projects" }, // @TODO-ZM: to put back when we have proper project details page -].forEach((collectionInfo) => { - const collection = getCollection>( - join(__dirname, "../../../data"), - collectionInfo.file, - "ssr.json", - ); - if (!Array.isArray(collection)) { - throw new Error(`Collection is not an array: ${collection}`); - } - collection.forEach((entry) => { - urls.push(`/${collectionInfo.slug}/${entry.slug}`); - }); -}); console.log("āœ…", urls.length, "URLs Found"); // Generate xml file diff --git a/web/src/_e2e-test/support/commands.ts b/web/src/_e2e-test/support/commands.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/src/_e2e-test/support/e2e.ts b/web/src/_e2e-test/support/e2e.ts deleted file mode 100644 index f887c29ae..000000000 --- a/web/src/_e2e-test/support/e2e.ts +++ /dev/null @@ -1 +0,0 @@ -import "./commands"; diff --git a/web/src/_e2e-test/tests/landing.spec.ts b/web/src/_e2e-test/tests/landing.spec.ts index 3d0512471..366696d17 100644 --- a/web/src/_e2e-test/tests/landing.spec.ts +++ b/web/src/_e2e-test/tests/landing.spec.ts @@ -1,15 +1,9 @@ /// describe("Landing Page", () => { - it("should redirect to `/Contribute` when clicking on `Make a contribution` button", () => { + it("should redirect to `/faq` when clicking on `FAQ`", () => { cy.visit("/"); - cy.get('[data-testid="contribute-button"]').click(); - cy.url().should("contain", "/Contribute"); - }); - - it("should redirect to `/FAQ` when clicking on `Do you have a question?` button", () => { - cy.visit("/"); - cy.get('[data-testid="faq-button"]').click(); - cy.url().should("contain", "/FAQ"); + cy.get('[data-testid="top-bar-to:/faq"]').click(); + cy.url().should("contain", "/faq"); }); }); diff --git a/web/src/_entry/app.tsx b/web/src/_entry/app.tsx index 18fd0bd85..5c73b3525 100644 --- a/web/src/_entry/app.tsx +++ b/web/src/_entry/app.tsx @@ -1,125 +1,140 @@ -import "./style.scss"; +import "./style.css"; -import { allLanguages, LanguageEntity } from "@dzcode.io/models/dist/language"; -import { Flex, MAX_CONTAINER_WIDTH } from "@dzcode.io/ui/dist/flex"; -import { Footer } from "@dzcode.io/ui/dist/footer"; -import { Loading } from "@dzcode.io/ui/dist/loading"; -import { Navbar } from "@dzcode.io/ui/dist/navbar"; -import { Stack } from "@dzcode.io/ui/dist/stack"; -import { FC, Suspense, useEffect } from "react"; -import { Helmet } from "react-helmet"; -import { Route, RouteProps, Switch, useLocation, useRouteMatch } from "react-router-dom"; -import logoWide from "src/assets/svg/logo-wide.svg"; -import logo from "src/assets/svg/logo-wide.svg"; -import logoExtended from "src/assets/svg/logo-wide-extended.svg"; -import { L } from "src/components/l"; -import { t } from "src/components/t"; -import { actions } from "src/redux"; -import { useSliceSelector } from "src/redux/selectors"; -import { getEnv } from "src/utils"; -import { urlLanguageRegEx } from "src/utils/language"; +import { HelmetProvider } from "react-helmet-async"; +import { BrowserRouter, Route, RouteProps, Routes } from "react-router-dom"; +import { Footer, FooterProps } from "src/components/footer"; +import { Loadable } from "src/components/loadable"; +import { Languages } from "src/components/locale/languages"; +import { TopBar, TopBarProps } from "src/components/top-bar"; +import { StoreProvider } from "src/redux/store"; +import { getInitialLanguageCode } from "src/utils/website-language"; -interface RouteInterface extends RouteProps { - pageName: string; -} - -const routes: RouteInterface[] = [ +let routes: Array< + RouteProps & { + pageName: string; + } +> = [ { pageName: "landing", path: "/", - exact: true, - }, - { - pageName: "learn", - path: "/Learn/:articleId*", + index: true, }, { pageName: "projects", - path: "/Projects", - }, - { - pageName: "articles", - path: "/Articles/:articleId*", + path: "/projects", }, { pageName: "faq", - path: "/FAQ", + path: "/faq", }, { pageName: "contribute", - path: "/Contribute", + path: "/contribute", }, { pageName: "team", - path: "/Team", + path: "/team", }, { pageName: "not-found", + path: "*", }, ]; -export const App: FC = () => { - const location = useLocation(); - const match = useRouteMatch<{ lang?: LanguageEntity["code"] }>(urlLanguageRegEx); - const landingPageMatch = useRouteMatch<{ lang?: LanguageEntity["code"] }>(`${urlLanguageRegEx}/`); - const { language, themeName } = useSliceSelector("settings"); - const { links } = useSliceSelector("navbarComponent"); - const { sections } = useSliceSelector("footerComponent"); +const initialLanguageCode = getInitialLanguageCode(); +if (initialLanguageCode !== Languages[0].code) { + routes = routes.map((route) => { + return { + ...route, + path: `/${initialLanguageCode}${route.path}`, + }; + }); +} - useEffect(() => { - if (getEnv() !== "development") { - if (window.ga) { - window.ga("set", "page", location.pathname); - window.ga("send", "pageview"); - } - } +const footerSections: FooterProps["sections"] = [ + { + localeKey: "footer-category-title-helpful-links", + links: [ + { localeKey: "footer-category-link-text-home", href: "/" }, + { + localeKey: "footer-category-link-text-projects", + href: "/projects", + }, + { localeKey: "footer-category-link-text-faq", href: "/faq" }, + ], + }, + { + localeKey: "footer-category-title-social-media", + links: [ + { + localeKey: "footer-category-link-text-github", + href: "https://www.github.com/dzcode-io", + }, + { + localeKey: "footer-category-link-text-slack", + href: "https://join.slack.com/t/dzcode/shared_invite/zt-ek9kscb7-m8z_~cBjX79l~uchuABPFQ", + }, + { + localeKey: "footer-category-link-text-facebook", + href: "https://www.facebook.com/dzcode.io", + }, + { + localeKey: "footer-category-link-text-instagram", + href: "https://www.instagram.com/dzcode.io", + }, + { + localeKey: "footer-category-link-text-youTube", + href: "https://www.youtube.com/channel/UC_tLjuQaYotzERtaAo8Y4SQ", + }, + { + localeKey: "footer-category-link-text-twitter", + href: "https://twitter.com/dzcode_io", + }, + { + localeKey: "footer-category-link-text-linkedIn", + href: "https://www.linkedin.com/groups/8924363", + }, + ], + }, +]; - const urlLanguage = - allLanguages.find(({ code }) => code === match?.params.lang) || allLanguages[0]; - if (urlLanguage.code !== language.code) { - actions.settings.set({ language: urlLanguage }); - } - }, [location]); +const topBarLinks: TopBarProps["links"] = [ + { href: "/contribute", localeKey: "navbar-section-contribute" }, + { href: "/team", localeKey: "navbar-section-connect" }, + { href: "/projects", localeKey: "navbar-section-projects" }, + { href: "/faq", localeKey: "navbar-section-faq" }, +]; +const App = () => { return ( <> - - {t("landing-title")} - - - { - actions.settings.set({ - language: allLanguages.find(({ code }) => code === selectedLanguageCode), - }); - }} - onThemeChanged={(selectedThemeName) => { - actions.settings.set({ themeName: selectedThemeName }); - }} - fixed={!!landingPageMatch?.isExact} - /> - - }> - - {routes.map(({ pageName, path, ...route }, index) => ( - } - /> - ))} - - - -