diff --git a/.github/workflows/CD-Pipeline.yml b/.github/workflows/CD-Pipeline.yml new file mode 100644 index 0000000..95a55f5 --- /dev/null +++ b/.github/workflows/CD-Pipeline.yml @@ -0,0 +1,13 @@ +name: Continuous Deployment Pipeline + +on: + push: + branches: [main] + +jobs: + Web_Deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Trigger Deployment + run: curl https://api.render.com/deploy/srv-${{ secrets.RENDER_SERVICE_ID }}?key=${{ secrets.RENDER_API_KEY }} \ No newline at end of file diff --git a/.github/workflows/Deployment-Pipeline.yml b/.github/workflows/CI-Pipeline.yml similarity index 83% rename from .github/workflows/Deployment-Pipeline.yml rename to .github/workflows/CI-Pipeline.yml index 44bb76b..8d022a8 100644 --- a/.github/workflows/Deployment-Pipeline.yml +++ b/.github/workflows/CI-Pipeline.yml @@ -1,10 +1,6 @@ -name: FullStack Deployment Pipeline +name: Continuous Integration Pipeline -on: - push: - branches: - - main - +on: pull_request: branches: [main] types: [opened, synchronize] @@ -28,6 +24,9 @@ jobs: - name: Check style run: npm run lint + - name: Check Security + run: npx eslint . + Backend-Pipeline: runs-on: ubuntu-latest defaults: @@ -46,6 +45,9 @@ jobs: - name: Check style run: npm run lint + - name: Check security + run: npx eslint . + - name: Run tests run: npm run test @@ -100,12 +102,4 @@ jobs: with: name: playwright-report path: playwright-report/ - retention-days: 30 - - Web_Deploy: - runs-on: ubuntu-latest - needs: [Frontend-Pipeline,Backend-Pipeline,Playwright-E2E-Tests] - steps: - - uses: actions/checkout@v4 - - name: Trigger Deployment - run: curl https://api.render.com/deploy/srv-${{ secrets.RENDER_SERVICE_ID }}?key=${{ secrets.RENDER_API_KEY }} + retention-days: 30 \ No newline at end of file diff --git a/.github/workflows/hello.yml b/.github/workflows/hello.yml deleted file mode 100644 index 02f96d2..0000000 --- a/.github/workflows/hello.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Hello World! - -on: - push: - branches: - - main - - pull_request: - branches: [main] - types: [opened, synchronize] - -jobs: - hello_world_job: - runs-on: ubuntu-latest - steps: - - name: Say Hello - run: | - echo "Hello World!" - echo "Current date and time:" - date diff --git a/back/eslint.config.mjs b/back/eslint.config.mjs index a2838ab..30ad90e 100644 --- a/back/eslint.config.mjs +++ b/back/eslint.config.mjs @@ -1,16 +1,12 @@ // @ts-check import eslint from '@eslint/js'; -import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; import globals from 'globals'; import tseslint from 'typescript-eslint'; +import pluginSecurity from 'eslint-plugin-security'; export default tseslint.config( - { - ignores: ['eslint.config.mjs'], - }, eslint.configs.recommended, ...tseslint.configs.recommendedTypeChecked, - eslintPluginPrettierRecommended, { languageOptions: { globals: { @@ -23,12 +19,17 @@ export default tseslint.config( tsconfigRootDir: import.meta.dirname, }, }, + plugins: { + security: pluginSecurity, + } }, { rules: { '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-floating-promises': 'warn', - '@typescript-eslint/no-unsafe-argument': 'warn' + '@typescript-eslint/no-unsafe-argument': 'warn', + ...pluginSecurity.configs.recommended.rules, }, }, + { ignores: ['eslint.config.mjs', "node_modules", "dist", "bonsai", "coverage", "public"], }, ); \ No newline at end of file diff --git a/back/package-lock.json b/back/package-lock.json index 06de57e..71c211b 100644 --- a/back/package-lock.json +++ b/back/package-lock.json @@ -51,6 +51,7 @@ "eslint": "^9.39.3", "eslint-config-prettier": "^10.0.1", "eslint-plugin-prettier": "^5.2.2", + "eslint-plugin-security": "^4.0.0", "globals": "^16.0.0", "jest": "^29.7.0", "prettier": "^3.4.2", @@ -3042,12 +3043,12 @@ } }, "node_modules/@nestjs/common": { - "version": "11.1.14", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.14.tgz", - "integrity": "sha512-IN/tlqd7Nl9gl6f0jsWEuOrQDaCI9vHzxv0fisHysfBQzfQIkqlv5A7w4Qge02BUQyczXT9HHPgHtWHCxhjRng==", + "version": "11.1.17", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.17.tgz", + "integrity": "sha512-hLODw5Abp8OQgA+mUO4tHou4krKgDtUcM9j5Ihxncst9XeyxYBTt2bwZm4e4EQr5E352S4Fyy6V3iFx9ggxKAg==", "license": "MIT", "dependencies": { - "file-type": "21.3.0", + "file-type": "21.3.2", "iterare": "1.2.1", "load-esm": "1.0.3", "tslib": "2.8.1", @@ -3217,14 +3218,14 @@ } }, "node_modules/@nestjs/platform-express": { - "version": "11.1.15", - "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.15.tgz", - "integrity": "sha512-sR42dQ5fPy4NGbnhT4mRIJLL0h2eNY4KpLkcfd27vg5XdTjQQ07wkQORrw6rAkWkBvMRr0dmhAcUBnXpe7ro5Q==", + "version": "11.1.17", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.17.tgz", + "integrity": "sha512-mAf4eOsSBsTOn/VbrUO1gsjW6dVh91qqXPMXun4dN8SnNjf7PTQagM9o8d6ab8ZBpNe6UdZftdrZoDetU+n4Qg==", "license": "MIT", "dependencies": { "cors": "2.8.6", "express": "5.2.1", - "multer": "2.1.0", + "multer": "2.1.1", "path-to-regexp": "8.3.0", "tslib": "2.8.1" }, @@ -7234,6 +7235,22 @@ } } }, + "node_modules/eslint-plugin-security": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-4.0.0.tgz", + "integrity": "sha512-tfuQT8K/Li1ZxhFzyD8wPIKtlzZxqBcPr9q0jFMQ77wWAbKBVEhaMPVQRTMTvCMUDhwBe5vPVqQPwAGk/ASfxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-regex": "^2.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/eslint-scope": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", @@ -7718,9 +7735,9 @@ } }, "node_modules/file-type": { - "version": "21.3.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.0.tgz", - "integrity": "sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA==", + "version": "21.3.2", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.2.tgz", + "integrity": "sha512-DLkUvGwep3poOV2wpzbHCOnSKGk1LzyXTv+aHFgN2VFl96wnp8YA9YjO2qPzg5PuL8q/SW9Pdi6WTkYOIh995w==", "license": "MIT", "dependencies": { "@tokenizer/inflate": "^0.4.1", @@ -7845,9 +7862,9 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, @@ -10848,9 +10865,9 @@ "license": "MIT" }, "node_modules/multer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/multer/-/multer-2.1.0.tgz", - "integrity": "sha512-TBm6j41rxNohqawsxlsWsNNh/VdV4QFXcBvRcPhXaA05EZ79z0qJ2bQFpync6JBoHTeNY5Q1JpG7AlTjdlfAEA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.1.1.tgz", + "integrity": "sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==", "license": "MIT", "dependencies": { "append-field": "^1.0.0", @@ -11858,6 +11875,16 @@ "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", "license": "Apache-2.0" }, + "node_modules/regexp-tree": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", + "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==", + "dev": true, + "license": "MIT", + "bin": { + "regexp-tree": "bin/regexp-tree" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -12077,6 +12104,16 @@ ], "license": "MIT" }, + "node_modules/safe-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz", + "integrity": "sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "regexp-tree": "~0.1.1" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -12200,9 +12237,9 @@ } }, "node_modules/sequelize": { - "version": "6.37.7", - "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.37.7.tgz", - "integrity": "sha512-mCnh83zuz7kQxxJirtFD7q6Huy6liPanI67BSlbzSYgVNl5eXVdE2CN1FuAeZwG1SNpGsNRCV+bJAVVnykZAFA==", + "version": "6.37.8", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.37.8.tgz", + "integrity": "sha512-HJ0IQFqcTsTiqbEgiuioYFMSD00TP6Cz7zoTti+zVVBwVe9fEhev9cH6WnM3XU31+ABS356durAb99ZuOthnKw==", "funding": [ { "type": "opencollective", @@ -14363,9 +14400,9 @@ } }, "node_modules/yauzl": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.2.0.tgz", - "integrity": "sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.2.1.tgz", + "integrity": "sha512-k1isifdbpNSFEHFJ1ZY4YDewv0IH9FR61lDetaRMD3j2ae3bIXGV+7c+LHCqtQGofSd8PIyV4X6+dHMAnSr60A==", "dev": true, "license": "MIT", "dependencies": { diff --git a/back/package.json b/back/package.json index b3eb14d..69a9912 100644 --- a/back/package.json +++ b/back/package.json @@ -62,6 +62,7 @@ "eslint": "^9.39.3", "eslint-config-prettier": "^10.0.1", "eslint-plugin-prettier": "^5.2.2", + "eslint-plugin-security": "^4.0.0", "globals": "^16.0.0", "jest": "^29.7.0", "prettier": "^3.4.2", diff --git a/back/src/types/verify.ts b/back/src/types/verify.ts index b679407..6ee50c4 100644 --- a/back/src/types/verify.ts +++ b/back/src/types/verify.ts @@ -18,8 +18,8 @@ export const isString = (text: unknown): text is string => { }; export const isNumber = (value: unknown): value is number => { - if (typeof value === 'string') - return value.trim() !== '' && /^-?\d+(\.\d+)?$/.test(value.trim()); + if (typeof value === 'string') + return value.trim() !== '' && !isNaN(Number(value.trim())); return ( value !== null && value !== undefined && diff --git a/front/eslint.config.js b/front/eslint.config.js index 092408a..86a20cb 100644 --- a/front/eslint.config.js +++ b/front/eslint.config.js @@ -3,11 +3,12 @@ import globals from 'globals' import reactHooks from 'eslint-plugin-react-hooks' import reactRefresh from 'eslint-plugin-react-refresh' import tseslint from 'typescript-eslint' +import pluginSecurity from 'eslint-plugin-security'; export default tseslint.config( - { ignores: ['dist'] }, + js.configs.recommended, + ...tseslint.configs.recommended, { - extends: [js.configs.recommended, ...tseslint.configs.recommended], files: ['**/*.{ts,tsx}'], languageOptions: { ecmaVersion: 2020, @@ -16,13 +17,16 @@ export default tseslint.config( plugins: { 'react-hooks': reactHooks, 'react-refresh': reactRefresh, + 'security': pluginSecurity }, rules: { ...reactHooks.configs.recommended.rules, + ...pluginSecurity.configs.recommended.rules, 'react-refresh/only-export-components': [ 'warn', { allowConstantExport: true }, ], }, }, + { ignores: ["node_modules", "build", 'dist'] }, ) diff --git a/front/package-lock.json b/front/package-lock.json index 96043c0..6b0f7dd 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -45,6 +45,7 @@ "eslint": "^9.21.0", "eslint-plugin-react-hooks": "^5.1.0", "eslint-plugin-react-refresh": "^0.4.19", + "eslint-plugin-security": "^4.0.0", "globals": "^15.15.0", "typescript": "~5.7.2", "typescript-eslint": "^8.24.1", @@ -4259,6 +4260,22 @@ "eslint": ">=8.40" } }, + "node_modules/eslint-plugin-security": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-4.0.0.tgz", + "integrity": "sha512-tfuQT8K/Li1ZxhFzyD8wPIKtlzZxqBcPr9q0jFMQ77wWAbKBVEhaMPVQRTMTvCMUDhwBe5vPVqQPwAGk/ASfxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-regex": "^2.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/eslint-scope": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", @@ -4449,9 +4466,9 @@ } }, "node_modules/flatted": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.4.tgz", - "integrity": "sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, @@ -5742,6 +5759,16 @@ "redux": "^5.0.0" } }, + "node_modules/regexp-tree": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", + "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==", + "dev": true, + "license": "MIT", + "bin": { + "regexp-tree": "bin/regexp-tree" + } + }, "node_modules/rehackt": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/rehackt/-/rehackt-0.1.0.tgz", @@ -5860,6 +5887,16 @@ ], "license": "MIT" }, + "node_modules/safe-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz", + "integrity": "sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "regexp-tree": "~0.1.1" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -6291,9 +6328,9 @@ } }, "node_modules/undici": { - "version": "7.22.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz", - "integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.4.tgz", + "integrity": "sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w==", "license": "MIT", "engines": { "node": ">=20.18.1" diff --git a/front/package.json b/front/package.json index 816e845..e43d70b 100644 --- a/front/package.json +++ b/front/package.json @@ -47,6 +47,7 @@ "eslint": "^9.21.0", "eslint-plugin-react-hooks": "^5.1.0", "eslint-plugin-react-refresh": "^0.4.19", + "eslint-plugin-security": "^4.0.0", "globals": "^15.15.0", "typescript": "~5.7.2", "typescript-eslint": "^8.24.1", diff --git a/front/src/apolloClient.ts b/front/src/apolloClient.ts index 2c5d49a..d064a0c 100644 --- a/front/src/apolloClient.ts +++ b/front/src/apolloClient.ts @@ -1,4 +1,7 @@ import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client"; +import { SongResponse } from "./types/songTypes"; +import { ArtistResponse } from "./types/artistTypes"; +import { AlbumResponse } from "./types/albumTypes"; export const client = new ApolloClient({ cache: new InMemoryCache({ @@ -8,40 +11,33 @@ export const client = new ApolloClient({ getAllGenreSongs: { keyArgs: ["genre"], merge(existing, incoming, { args }) { - const page = args?.page ?? 1; - const limit = args?.limit ?? 2; + const page: number = args?.page ?? 1; + const limit: number = args?.limit ?? 2; const offset = (page - 1) * limit; - const merged = existing ? existing.slice(0) : []; - - for (let i = 0; i < incoming.length; i++) { - merged[offset + i] = incoming[i]; - }; + const merged: SongResponse[] = existing ? existing.slice(0) : []; + merged.splice(offset, incoming.length, ...incoming); return merged; } }, getAllArtists: { keyArgs: false, merge(existing, incoming, { args }) { - const page = args?.page ?? 1; - const limit = args?.limit ?? 20; + const page: number = args?.page ?? 1; + const limit: number = args?.limit ?? 20; const offset = (page - 1) * limit; - const merged = existing ? existing.slice(0) : []; - for (let i = 0; i < incoming.length; i++) { - merged[offset + i] = incoming[i] - } + const merged: ArtistResponse[] = existing ? existing.slice(0) : []; + merged.splice(offset, incoming.length, ...incoming); return merged; } }, getAlbums: { keyArgs: false, merge(existing, incoming, {args}) { - const page = args?.page ?? 1; - const limit = args?.limit ?? 20; + const page: number = args?.page ?? 1; + const limit: number = args?.limit ?? 20; const offset = (page - 1) * limit; - const merged = existing ? existing.slice(0) : []; - for (let i=0; i < incoming.length; i++) { - merged[offset + i] = incoming[i] - } + const merged: AlbumResponse[] = existing ? existing.slice(0) : []; + merged.splice(offset, incoming.length, ...incoming); return merged; } } diff --git a/front/src/components/MainLayout/TopBar/LaraRecommendModal/ModalComponents/Sliders/SliderTags.tsx b/front/src/components/MainLayout/TopBar/LaraRecommendModal/ModalComponents/Sliders/SliderTags.tsx index fd232a2..56ba80c 100644 --- a/front/src/components/MainLayout/TopBar/LaraRecommendModal/ModalComponents/Sliders/SliderTags.tsx +++ b/front/src/components/MainLayout/TopBar/LaraRecommendModal/ModalComponents/Sliders/SliderTags.tsx @@ -1,5 +1,5 @@ import { SLIDER_TAGS_GAP_SIZES, SLIDER_TAGS_PX_SIZES } from "@/components/constants/TopBarC"; -import { TagSliderStyles } from "@/dynamicCSS/TagsSliderStyles"; +import { MapSliderTagStyles } from "@/dynamicCSS/TagsSliderStyles"; import { SliderLabels } from "@/types/RecDataTypes"; import { Flex, For, Tag } from "@chakra-ui/react"; @@ -7,13 +7,23 @@ const SliderTags = ({ labels } : { labels: SliderLabels[] }) => { return( - {(label) => ( + {(label) => { + let labelStyle = MapSliderTagStyles.get(label); + if (labelStyle === undefined) { + labelStyle = { + bg: "gray.500", + border: "0 0 0 2px var(--chakra-colors-gray-500), 0 0 8px var(--chakra-colors-gray-500)" + } + }; + return ( + bg={labelStyle.bg} py={1} fontWeight="800" + boxShadow={labelStyle.border} letterSpacing="wide" + px={SLIDER_TAGS_PX_SIZES} fontSize="'Barlow', sans-serif"> {label} - )} + ) + }} ); diff --git a/front/src/components/MainLayout/TopBar/LaraRecommendModal/ModalComponents/Switch/LeftSwitchTag.tsx b/front/src/components/MainLayout/TopBar/LaraRecommendModal/ModalComponents/Switch/LeftSwitchTag.tsx index 69cc61e..3ee2a5f 100644 --- a/front/src/components/MainLayout/TopBar/LaraRecommendModal/ModalComponents/Switch/LeftSwitchTag.tsx +++ b/front/src/components/MainLayout/TopBar/LaraRecommendModal/ModalComponents/Switch/LeftSwitchTag.tsx @@ -1,14 +1,15 @@ import { Tag } from "@chakra-ui/react"; import { Tooltip } from "@/components/ui/tooltip"; import { SwitchLabelType } from "@/types/RecDataTypes"; -import { SwitchStyles } from "@/dynamicCSS/SwitchStyles"; +import { defaultSwitchStyle, MapSwitchStyles } from "@/dynamicCSS/SwitchStyles"; import { LEFT_SWITCH_PX, tagLBG } from "@/components/constants/TopBarC"; const LeftSwitchTag = ({ label, description } : { label: SwitchLabelType, description: string }) => { + const leftSwitchStyle = MapSwitchStyles.get(label) || defaultSwitchStyle; return( - {label} diff --git a/front/src/components/MainLayout/TopBar/LaraRecommendModal/ModalComponents/Switch/RightSwitchTag.tsx b/front/src/components/MainLayout/TopBar/LaraRecommendModal/ModalComponents/Switch/RightSwitchTag.tsx index 875573d..d6fa321 100644 --- a/front/src/components/MainLayout/TopBar/LaraRecommendModal/ModalComponents/Switch/RightSwitchTag.tsx +++ b/front/src/components/MainLayout/TopBar/LaraRecommendModal/ModalComponents/Switch/RightSwitchTag.tsx @@ -1,16 +1,17 @@ import { Tag } from "@chakra-ui/react"; import { Tooltip } from "@/components/ui/tooltip"; import { SwitchLabelType } from "@/types/RecDataTypes"; -import { SwitchStyles } from "@/dynamicCSS/SwitchStyles"; +import { defaultSwitchStyle, MapSwitchStyles } from "@/dynamicCSS/SwitchStyles"; import { RIGHT_SWITCH_PX, tagRBG } from "@/components/constants/TopBarC"; const RightSwitchTag = ({ label, description } : { label: SwitchLabelType, description: string }) => { + const switchStyle = MapSwitchStyles.get(label) || defaultSwitchStyle; return( + fontWeight="600" px={RIGHT_SWITCH_PX} py={2} color="white" bg={switchStyle.tagBg} + borderRadius="full" boxShadow={switchStyle.border}> {label} diff --git a/front/src/components/MainLayout/TopBar/LaraRecommendModal/ModalComponents/Switch/SwitchButton.tsx b/front/src/components/MainLayout/TopBar/LaraRecommendModal/ModalComponents/Switch/SwitchButton.tsx index 95bcb86..b59dfdd 100644 --- a/front/src/components/MainLayout/TopBar/LaraRecommendModal/ModalComponents/Switch/SwitchButton.tsx +++ b/front/src/components/MainLayout/TopBar/LaraRecommendModal/ModalComponents/Switch/SwitchButton.tsx @@ -1,17 +1,21 @@ import { Box, Heading, Flex, Switch } from "@chakra-ui/react"; import { useAppDispatch } from "@/components/Utils/redux-hooks"; -import { SwitchStyles } from "@/dynamicCSS/SwitchStyles"; import { SwitchOptionsType, SwitchLabelType } from "@/types/RecDataTypes"; import LeftSwitchTag from "./LeftSwitchTag"; import RightSwitchTag from "./RightSwitchTag"; import ControlSwitchStyle from "./ControlSwitchStyle"; import useSwitchValue from "@/components/Utils/hooks/useSwitchValue"; import { BLUE_SHADOW_MODAL, LARA_OPT_SIZES, SWITCH_BTN_TRANSITION } from "@/components/constants/TopBarC"; +import { defaultSwitchStyle, MapSwitchStyles } from "@/dynamicCSS/SwitchStyles"; const SwitchButton = ({ params }:{ params: SwitchOptionsType }) => { const { switchTagValue, toggleSwitchValue } = useSwitchValue(params.title, params.labels[0].label, params.labels[1].label, params.setValue); + + const leftSwitchStyle = MapSwitchStyles.get(params.labels[0].label) || defaultSwitchStyle; + const rightSwitchStyle = MapSwitchStyles.get(params.labels[1].label) || defaultSwitchStyle; const dispatch = useAppDispatch(); + return( ({ params }:{ params: dispatch(toggleSwitchValue())}> - + diff --git a/front/src/dynamicCSS/SliderStyles.ts b/front/src/dynamicCSS/SliderStyles.ts index b6857ab..50b601d 100644 --- a/front/src/dynamicCSS/SliderStyles.ts +++ b/front/src/dynamicCSS/SliderStyles.ts @@ -5,19 +5,19 @@ import { FaWind, FaLeaf } from "react-icons/fa"; import { FaHeartCircleBolt, FaCloud } from "react-icons/fa6"; import { AiFillThunderbolt } from "react-icons/ai"; import { GiGuitarHead, GiHeartBeats, GiLotus } from "react-icons/gi"; -import { SliderStylesOptions } from "@/types/modalCssTypes"; +import { SliderStylesProps, SliderType } from "@/types/modalCssTypes"; -export const SliderStyles: SliderStylesOptions = { - Energy: { +export const MapSliderStyles = new Map ([ + ["Energy", { threshold: [0.30, 0.70], SliderBg: ["green.500", "yellow.600", "red.600"], Icon: [FaCloud, BsFire, AiFillThunderbolt], IconColor: ["green.400", "red.500", "yellow.500"], IconBorder: ["cyan.600", "orange.600", "yellow.600"], shadow: ["0 0 8px var(--chakra-colors-green-500)", "0 0 8px var(--chakra-colors-yellow-600)", - "0 0 8px var(--chakra-colors-red-600)" ] - }, - Danceability: { + "0 0 8px var(--chakra-colors-red-600)" ], + }], + ["Danceability", { threshold: [0.30, 0.70], SliderBg: ["blue.500", "teal.500", "red.600"], Icon: [GiLotus, GiHeartBeats, RiFlashlightFill], @@ -25,8 +25,8 @@ export const SliderStyles: SliderStylesOptions = { IconBorder: ["cyan.600", "cyan.500", "yellow.600"], shadow: ["0 0 8px var(--chakra-colors-blue-500)", "0 0 8px var(--chakra-colors-teal-500)", "0 0 8px var(--chakra-colors-red-600)" ] - }, - Tempo: { + }], + ["Tempo", { threshold: [75, 160], SliderBg: ["yellow.600", "green.500", "purple.500"], Icon: [FaWind, GiGuitarHead, FaHeartCircleBolt], @@ -34,8 +34,8 @@ export const SliderStyles: SliderStylesOptions = { IconBorder: ["orange.500", "teal.500", "purple.600"], shadow: ["0 0 8px var(--chakra-colors-yellow-600)", "0 0 8px var(--chakra-colors-green-500)", "0 0 8px var(--chakra-colors-purple-500)"] - }, - Sentiment: { + }], + ["Sentiment", { threshold: [0.30, 0.70], SliderBg: ["cyan.600", "orange.600", "pink.600"], Icon: [FaLeaf, RiEmotionHappyFill, BiSolidHappyBeaming], @@ -43,5 +43,15 @@ export const SliderStyles: SliderStylesOptions = { IconBorder: ["cyan.500", "orange.600", "red.500"], shadow: ["0 0 8px var(--chakra-colors-cyan-600)", "0 0 8px var(--chakra-colors-orange-600)", "0 0 8px var(--chakra-colors-pink-600)" ] - } -}; \ No newline at end of file + }] +]); + +export const defaultSliderStyle = { + threshold: [0.30, 0.70], + SliderBg: ["green.500", "yellow.600", "red.600"], + Icon: [FaCloud, BsFire, AiFillThunderbolt], + IconColor: ["green.400", "red.500", "yellow.500"], + IconBorder: ["cyan.600", "orange.600", "yellow.600"], + shadow: ["0 0 8px var(--chakra-colors-green-500)", "0 0 8px var(--chakra-colors-yellow-600)", + "0 0 8px var(--chakra-colors-red-600)" ] +} \ No newline at end of file diff --git a/front/src/dynamicCSS/SwitchStyles.ts b/front/src/dynamicCSS/SwitchStyles.ts index 551ddd5..d8b6b48 100644 --- a/front/src/dynamicCSS/SwitchStyles.ts +++ b/front/src/dynamicCSS/SwitchStyles.ts @@ -1,8 +1,8 @@ -import { SwitchTagsStyles } from "@/types/modalCssTypes"; +import { SwitchStyle, SwitchType } from "@/types/modalCssTypes"; import { FaMicrophoneAlt, FaGuitar, FaSmile, FaSadTear, FaMicrophone, FaHeadphones } from "react-icons/fa"; -export const SwitchStyles: SwitchTagsStyles = { - Vocals: { +export const MapSwitchStyles = new Map([ + ["Vocals", { tagBg: "pink.400", ThumbBg: "pink.200", ThumbBackgroundBg: "pink.400", @@ -11,8 +11,8 @@ export const SwitchStyles: SwitchTagsStyles = { ThumbBackgroundIcon: FaGuitar, BackgroundIconColor: "cyan.500", border: "0 0 0 2px var(--chakra-colors-pink-500), 0 0 8px var(--chakra-colors-pink-500)", - }, - Instrumental: { + }], + ["Instrumental", { tagBg: "cyan.500", ThumbBg: "cyan.200", ThumbBackgroundBg: "cyan.400", @@ -21,8 +21,8 @@ export const SwitchStyles: SwitchTagsStyles = { ThumbBackgroundIcon: FaMicrophone, BackgroundIconColor: "pink.400", border: "0 0 0 2px var(--chakra-colors-cyan-600), 0 0 8px var(--chakra-colors-cyan-600)" - }, - Happy: { + }], + ["Happy", { tagBg: "purple.400", ThumbBg: "purple.200", ThumbBackgroundBg: "purple.400", @@ -31,8 +31,8 @@ export const SwitchStyles: SwitchTagsStyles = { ThumbBackgroundIcon: FaSadTear, BackgroundIconColor: "blue.500", border: "0 0 0 2px var(--chakra-colors-purple-500), 0 0 8px var(--chakra-colors-purple-500)" - }, - Sad: { + }], + ["Sad", { tagBg: "blue.500", ThumbBg: "blue.500", ThumbBackgroundBg: "blue.400", @@ -41,8 +41,8 @@ export const SwitchStyles: SwitchTagsStyles = { ThumbBackgroundIcon: FaSmile, BackgroundIconColor: "yellow.500", border: "0 0 0 2px var(--chakra-colors-blue-600), 0 0 8px var(--chakra-colors-blue-600)" - }, - Acoustic: { + }], + ["Acoustic", { tagBg: "green.500", ThumbBg: "green.200", ThumbBackgroundBg: "green.500", @@ -51,8 +51,8 @@ export const SwitchStyles: SwitchTagsStyles = { ThumbBackgroundIcon: FaHeadphones, BackgroundIconColor: "orange.300", border: "0 0 0 2px var(--chakra-colors-green-600), 0 0 8px var(--chakra-colors-green-600)" - }, - Electronic: { + }], + ["Electronic", { tagBg: "orange.400", ThumbBg: "orange.200", ThumbBackgroundBg: "orange.400", @@ -61,5 +61,16 @@ export const SwitchStyles: SwitchTagsStyles = { ThumbBackgroundIcon: FaGuitar, BackgroundIconColor: "green.600", border: "0 0 0 2px var(--chakra-colors-orange-500), 0 0 8px var(--chakra-colors-orange-500)" - }, -}; \ No newline at end of file + }] +]); + +export const defaultSwitchStyle = { + tagBg: "gray.500", + ThumbBg: "gray.200", + ThumbBackgroundBg: "gray.400", + ThumbIcon: FaMicrophoneAlt, + IconColor: "gray.500", + ThumbBackgroundIcon: FaGuitar, + BackgroundIconColor: "cyan.500", + border: "0 0 0 2px var(--chakra-colors-gray-500), 0 0 8px var(--chakra-colors-gray-500)" +} \ No newline at end of file diff --git a/front/src/dynamicCSS/TagsSliderStyles.ts b/front/src/dynamicCSS/TagsSliderStyles.ts index df8aef8..49afdc8 100644 --- a/front/src/dynamicCSS/TagsSliderStyles.ts +++ b/front/src/dynamicCSS/TagsSliderStyles.ts @@ -1,52 +1,49 @@ -import { SliderTagsOptions } from "@/types/modalCssTypes"; +import { SliderTagsProps } from "@/types/modalCssTypes"; +import { SliderLabels } from "@/types/RecDataTypes"; -export const TagSliderStyles: SliderTagsOptions = { - Relaxed: { +export const MapSliderTagStyles = new Map ([ + ["Relaxed", { bg: "green.600", border: "0 0 0 2px var(--chakra-colors-cyan-600), 0 0 8px var(--chakra-colors-cyan-600)", - }, - Active: { + }], + ["Active", { bg: "yellow.600", border: "0 0 0 2px var(--chakra-colors-orange-500), 0 0 8px var(--chakra-colors-orange-500)" - }, - Intense: { + }], + ["Intense", { bg: "red.600", - border: "0 0 0 2px var(--chakra-colors-orange-600), 0 0 8px var(--chakra-colors-orange-600)" - }, - Calm: { + border: "0 0 0 2px var(--chakra-colors-orange-600), 0 0 8px var(--chakra-colors-orange-600)", + }], + ["Calm", { bg: "blue.500", border: "0 0 0 2px var(--chakra-colors-blue-600), 0 0 8px var(--chakra-colors-blue-600)" - }, - Rhythmic: { + }], + ["Rhythmic", { bg: "teal.500", border: "0 0 0 2px var(--chakra-colors-green-600), 0 0 8px var(--chakra-colors-green-600)" - }, - Energetic: { - bg: "red.600", - border: "0 0 0 2px var(--chakra-colors-orange-600), 0 0 8px var(--chakra-colors-orange-600)" - }, - "70 BPM": { + }], + ["70 BPM", { bg: "orange.500", border: "0 0 0 2px var(--chakra-colors-yellow-600), 0 0 8px var(--chakra-colors-yellow-600)" - }, - "120 BPM": { + }], + ["120 BPM", { bg: "green.500", border: "0 0 0 2px var(--chakra-colors-cyan-600), 0 0 8px var(--chakra-colors-cyan-600)" - }, - "230 BPM": { + }], + ["230 BPM", { bg: "purple.500", border: "0 0 0 2px var(--chakra-colors-purple-600), 0 0 8px var(--chakra-colors-purple-600)" - }, - "😌": { + }], + ["😌", { bg: "cyan.600", border: "0 0 0 2px var(--chakra-colors-blue-600), 0 0 8px var(--chakra-colors-blue-600)" - }, - "🙂": { + }], + ["🙂", { bg: "orange.600", border: "0 0 0 2px var(--chakra-colors-yellow-600), 0 0 8px var(--chakra-colors-yellow-600)" - }, - "😝": { + }], + ["😝", { bg: "pink.500", border: "0 0 0 2px var(--chakra-colors-orange-500), 0 0 8px var(--chakra-colors-orange-500)" - } -}; \ No newline at end of file + }], +]); \ No newline at end of file diff --git a/front/src/dynamicCSS/VolumeSliderStyle.tsx b/front/src/dynamicCSS/VolumeSliderStyle.tsx index dfd1ecf..eed72a5 100644 --- a/front/src/dynamicCSS/VolumeSliderStyle.tsx +++ b/front/src/dynamicCSS/VolumeSliderStyle.tsx @@ -1,11 +1,13 @@ +import { IconType } from "react-icons/lib"; import { PiSpeakerHighBold, PiSpeakerSlashBold, PiSpeakerLowBold } from "react-icons/pi"; -const VolumeStyle = { - threshold: 0.5, - Icon: [PiSpeakerSlashBold, PiSpeakerLowBold, PiSpeakerHighBold ] -}; +const MapVolumeStyle = new Map ([ + [0, PiSpeakerSlashBold], + [1, PiSpeakerLowBold], + [2, PiSpeakerHighBold] +]); export const getVolumeIconStyle = (value: number) => { - const idx = value === 0 ? "0" : value < 0.5 ? "1" : "2"; - return VolumeStyle.Icon[idx] + const idx = value === 0 ? 0 : value < 0.5 ? 1 : 2; + return MapVolumeStyle.get(idx) ?? PiSpeakerHighBold; }; \ No newline at end of file diff --git a/front/src/dynamicCSS/getDynamicSlider.tsx b/front/src/dynamicCSS/getDynamicSlider.tsx index c8420a7..b054982 100644 --- a/front/src/dynamicCSS/getDynamicSlider.tsx +++ b/front/src/dynamicCSS/getDynamicSlider.tsx @@ -1,15 +1,39 @@ import { SliderTitles } from "@/types/RecDataTypes"; -import { SliderStyles } from "./SliderStyles"; +import { defaultSliderStyle, MapSliderStyles } from "./SliderStyles"; import { Icon } from "@chakra-ui/react"; export const getSliderStyle = (title: SliderTitles, value: number) => { - const style = SliderStyles[title]; - const idx = value < style.threshold[0] ? 0 : value < style.threshold[1] ? 1 : 2; + const style = MapSliderStyles.get(title) || defaultSliderStyle; + const [lowBg, midBg, HighBg] = style.SliderBg; + const [lowIcon, midIcon, HighIcon] = style.Icon; + const [lowIconCol, midIconCol, HighIconCol] = style.IconColor; + const [lowBorder, midBorder, HighBorder] = style.IconBorder; + const [lowShadow, midShadow, HighShadow] = style.shadow; - return { - Bg: style.SliderBg[idx], - Icon: , - IconBorder: style.IconBorder[idx], - Shadow: style.shadow[idx] - }; + switch (true) { + case value < style.threshold[0]: { + return { + Bg: lowBg, + Icon: , + IconBorder: lowBorder, + Shadow: lowShadow, + } + } + case value < style.threshold[1]: { + return { + Bg: midBg, + Icon: , + IconBorder: midBorder, + Shadow: midShadow, + } + } + default: { + return { + Bg: HighBg, + Icon: , + IconBorder: HighBorder, + Shadow: HighShadow, + } + } + } }; \ No newline at end of file diff --git a/front/src/types/modalCssTypes.ts b/front/src/types/modalCssTypes.ts index 10c8722..9dd96f5 100644 --- a/front/src/types/modalCssTypes.ts +++ b/front/src/types/modalCssTypes.ts @@ -11,21 +11,14 @@ export interface SwitchStyle { border: string; }; -export interface SwitchTagsStyles { - Vocals: SwitchStyle; - Instrumental: SwitchStyle; - Happy: SwitchStyle; - Sad: SwitchStyle; - Acoustic: SwitchStyle; - Electronic: SwitchStyle; -}; +export type SwitchType = "Vocals" | "Instrumental" | "Happy" | "Sad" | "Acoustic" | "Electronic"; export interface SliderTagsProps { bg: string; border: string; }; -interface SliderStylesProps { +export interface SliderStylesProps { threshold: number[]; SliderBg: string[]; Icon: IconType[]; @@ -34,24 +27,4 @@ interface SliderStylesProps { shadow: string[]; }; -export interface SliderStylesOptions { - Energy: SliderStylesProps; - Danceability: SliderStylesProps; - Tempo: SliderStylesProps; - Sentiment: SliderStylesProps; -}; - -export interface SliderTagsOptions { - Relaxed: SliderTagsProps; - Active: SliderTagsProps; - Intense: SliderTagsProps; - Calm: SliderTagsProps; - Rhythmic: SliderTagsProps; - Energetic: SliderTagsProps; - "70 BPM": SliderTagsProps; - "120 BPM": SliderTagsProps; - "230 BPM": SliderTagsProps; - "😌": SliderTagsProps; - "🙂": SliderTagsProps; - "😝": SliderTagsProps; -}; \ No newline at end of file +export type SliderType = "Energy" | "Danceability" | "Tempo" | "Sentiment"; \ No newline at end of file