diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..6e87a00 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# Editor configuration, see http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/.env b/.env new file mode 100644 index 0000000..f87942e --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +NX_API_URL = "https://p2pmarket.dev/api/v1" +NX_API_KEY_GOOGLE = "AIzaSyCUMfi4OQnxVWweWBTAkp7-hy5lV203cFY" +NX_API_KEY_ANALYTICS = "G-MVRNNW3DH4" \ No newline at end of file diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +node_modules diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..7c52faa --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,42 @@ +{ + "root": true, + "ignorePatterns": ["**/*"], + "plugins": ["@nrwl/nx"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": { + "@nrwl/nx/enforce-module-boundaries": [ + "error", + { + "enforceBuildableLibDependency": true, + "allow": [], + "depConstraints": [ + { + "sourceTag": "*", + "onlyDependOnLibsWithTags": ["*"] + } + ] + } + ] + } + }, + { + "files": ["*.ts", "*.tsx"], + "extends": ["plugin:@nrwl/nx/typescript"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "extends": ["plugin:@nrwl/nx/javascript"], + "rules": {} + }, + { + "files": ["*.spec.ts", "*.spec.tsx", "*.spec.js", "*.spec.jsx"], + "env": { + "jest": true + }, + "rules": {} + } + ] +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..51b9af5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +dist +tmp +/out-tsc + +# dependencies +node_modules + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +yarn-error.log +testem.log +/typings + +# System Files +.DS_Store +Thumbs.db diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..d0b804d --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +# Add files here to ignore them from prettier formatting + +/dist +/coverage diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..544138b --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "singleQuote": true +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..6a302fe --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "nrwl.angular-console", + "esbenp.prettier-vscode", + "dbaeumer.vscode-eslint", + "firsttris.vscode-jest-runner" + ] +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d0f80a2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,3 @@ +FROM nginx:stable-alpine3.17-slim +COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY dist/apps/ /usr/share/nginx/html \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3ba31ac --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Publicis Sapient + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7902c46 --- /dev/null +++ b/README.md @@ -0,0 +1,89 @@ +# P2P-Exchange-Marketplace-UI + +[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT) + +This is the mono-repo for the P2P-Marketplace UI created using Nx. + +## Table of Contents + +- [Requirements](#requirements) +- [Tools and Technologies](#toolsandtechnologies) +- [Installation](#installation) +- [Usage](#usage) +- [Contributions](#contributions) +- [License](#license) + +## Requirements + +- React >= 18.0.0 +- @nrwl/react >= 15.9.2 +- Nx >= 15.8.1 +- Typescript >= 4.9.5 +- Material-UI >= 5.12.1 +- @nrwl/jest >= 15.9.2 + +## Tools and Technologies + +![](https://img.shields.io/badge/Code-Typescript-informational?style=flat&logo=Typescript&logoColor=white&color=FE414D) +![](https://img.shields.io/badge/Lib-React-informational?style=flat&logo=react&logoColor=white&color=FE414D) +![](https://img.shields.io/badge/Lib-Redux-informational?style=flat&logo=Redux&logoColor=white&color=FE414D) +![](https://img.shields.io/badge/Lib-Thunk-informational?style=flat&logo=redux&logoColor=white&color=FE414D) +![](https://img.shields.io/badge/Lib-MaterialUI-informational?style=flat&logo=mui&logoColor=white&color=FE414D) +![](https://img.shields.io/badge/Framework-Nx-%23AA83C8?style=flat&logo=nx&logoColor=white&color=FE414D) +![](https://img.shields.io/badge/Tool-VsCode-%23AA83C8?style=flat&logo=Visual-Studio-Code&logoColor=white&color=FE414D) + +## Installation + +1. Clone the repository: + + ```bash + git clone https://github.com/PublicisSapient/retailsustainability-pem-storefront.git + ``` + +1. Change the current working directory to the repository: + + ```bash + cd storefront + ``` + +1. Install the dependencies: + + ```bash + npm install or yarn install + ``` + +## Usage + +1. To start the application in development mode, use the following command: + + ```bash + nx serve shell --devRemotes=product-detail,product-list,home + ``` + +2. To start the application in production mode, use the following command: + + ```bash + nx run shell:serve --configuration=production + ``` + +3. To test the application: + + ```bash + nx run shell:test + ``` + +## Contributions + +Contributions to the P2P-Marketplace-UI are welcome! If you find any issues or have suggestions for improvements, please open an issue or submit a pull request. + +1. Fork the repository. +2. Create your feature branch: `git checkout -b feature/my-new-feature`. +3. Commit your changes: `git commit -m 'Add some feature'`. +4. Push to the branch: `git push origin feature/my-new-feature`. +5. Submit a pull request. + +## License + +The P2P-Marketplace-UI is open-source and available under the [MIT License](https://opensource.org/licenses/MIT). + +Feel free to modify and adapt the code to suit your needs. diff --git a/apps/.gitkeep b/apps/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/apps/home-e2e/.eslintrc.json b/apps/home-e2e/.eslintrc.json new file mode 100644 index 0000000..696cb8b --- /dev/null +++ b/apps/home-e2e/.eslintrc.json @@ -0,0 +1,10 @@ +{ + "extends": ["plugin:cypress/recommended", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/apps/home-e2e/cypress.config.ts b/apps/home-e2e/cypress.config.ts new file mode 100644 index 0000000..22f7c84 --- /dev/null +++ b/apps/home-e2e/cypress.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'cypress'; +import { nxE2EPreset } from '@nrwl/cypress/plugins/cypress-preset'; + +export default defineConfig({ + e2e: nxE2EPreset(__dirname), +}); diff --git a/apps/home-e2e/project.json b/apps/home-e2e/project.json new file mode 100644 index 0000000..de51850 --- /dev/null +++ b/apps/home-e2e/project.json @@ -0,0 +1,33 @@ +{ + "name": "home-e2e", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "apps/home-e2e/src", + "projectType": "application", + "targets": { + "e2e": { + "executor": "@nrwl/cypress:cypress", + "options": { + "cypressConfig": "apps/home-e2e/cypress.config.ts", + "devServerTarget": "home:serve:development", + "testingType": "e2e" + }, + "configurations": { + "production": { + "devServerTarget": "home:serve:production" + }, + "ci": { + "devServerTarget": "home:serve-static" + } + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["apps/home-e2e/**/*.{js,ts}"] + } + } + }, + "tags": [], + "implicitDependencies": ["home"] +} diff --git a/apps/home-e2e/src/e2e/app.cy.ts b/apps/home-e2e/src/e2e/app.cy.ts new file mode 100644 index 0000000..887362d --- /dev/null +++ b/apps/home-e2e/src/e2e/app.cy.ts @@ -0,0 +1,13 @@ +import { getGreeting } from '../support/app.po'; + +describe('home', () => { + beforeEach(() => cy.visit('/')); + + it('should display welcome message', () => { + // Custom command example, see `../support/commands.ts` file + cy.login('my-email@something.com', 'myPassword'); + + // Function helper example, see `../support/app.po.ts` file + getGreeting().contains('Welcome home'); + }); +}); diff --git a/apps/home-e2e/src/fixtures/example.json b/apps/home-e2e/src/fixtures/example.json new file mode 100644 index 0000000..294cbed --- /dev/null +++ b/apps/home-e2e/src/fixtures/example.json @@ -0,0 +1,4 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io" +} diff --git a/apps/home-e2e/src/support/app.po.ts b/apps/home-e2e/src/support/app.po.ts new file mode 100644 index 0000000..3293424 --- /dev/null +++ b/apps/home-e2e/src/support/app.po.ts @@ -0,0 +1 @@ +export const getGreeting = () => cy.get('h1'); diff --git a/apps/home-e2e/src/support/commands.ts b/apps/home-e2e/src/support/commands.ts new file mode 100644 index 0000000..310f1fa --- /dev/null +++ b/apps/home-e2e/src/support/commands.ts @@ -0,0 +1,33 @@ +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** + +// eslint-disable-next-line @typescript-eslint/no-namespace +declare namespace Cypress { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + interface Chainable { + login(email: string, password: string): void; + } +} +// +// -- This is a parent command -- +Cypress.Commands.add('login', (email, password) => { + console.log('Custom command example: Login', email, password); +}); +// +// -- This is a child command -- +// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) diff --git a/apps/home-e2e/src/support/e2e.ts b/apps/home-e2e/src/support/e2e.ts new file mode 100644 index 0000000..3d469a6 --- /dev/null +++ b/apps/home-e2e/src/support/e2e.ts @@ -0,0 +1,17 @@ +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands'; diff --git a/apps/home-e2e/tsconfig.json b/apps/home-e2e/tsconfig.json new file mode 100644 index 0000000..cc509a7 --- /dev/null +++ b/apps/home-e2e/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "sourceMap": false, + "outDir": "../../dist/out-tsc", + "allowJs": true, + "types": ["cypress", "node"] + }, + "include": ["src/**/*.ts", "src/**/*.js", "cypress.config.ts"] +} diff --git a/apps/home/.babelrc b/apps/home/.babelrc new file mode 100644 index 0000000..61641ec --- /dev/null +++ b/apps/home/.babelrc @@ -0,0 +1,11 @@ +{ + "presets": [ + [ + "@nrwl/react/babel", + { + "runtime": "automatic" + } + ] + ], + "plugins": [] +} diff --git a/apps/home/.eslintrc.json b/apps/home/.eslintrc.json new file mode 100644 index 0000000..734ddac --- /dev/null +++ b/apps/home/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/apps/home/jest.config.ts b/apps/home/jest.config.ts new file mode 100644 index 0000000..a6c6953 --- /dev/null +++ b/apps/home/jest.config.ts @@ -0,0 +1,11 @@ +/* eslint-disable */ +export default { + displayName: 'home', + preset: '../../jest.preset.js', + transform: { + '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest', + '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nrwl/react/babel'] }], + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../coverage/apps/home', +}; diff --git a/apps/home/module-federation.config.js b/apps/home/module-federation.config.js new file mode 100644 index 0000000..d1cc8f5 --- /dev/null +++ b/apps/home/module-federation.config.js @@ -0,0 +1,6 @@ +module.exports = { + name: 'home', + exposes: { + './Module': './src/remote-entry.ts', + }, +}; diff --git a/apps/home/project.json b/apps/home/project.json new file mode 100644 index 0000000..110726c --- /dev/null +++ b/apps/home/project.json @@ -0,0 +1,105 @@ +{ + "name": "home", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "apps/home/src", + "projectType": "application", + "targets": { + "build": { + "executor": "@nrwl/webpack:webpack", + "outputs": ["{options.outputPath}"], + "defaultConfiguration": "production", + "options": { + "compiler": "babel", + "outputPath": "dist/apps/home", + "index": "apps/home/src/index.html", + "baseHref": "/", + "main": "apps/home/src/main.ts", + "tsConfig": "apps/home/tsconfig.app.json", + "assets": ["apps/home/src/favicon.ico", "apps/home/src/assets","apps/home/src/robots.txt"], + "styles": [], + "scripts": [], + "isolatedConfig": true, + "webpackConfig": "apps/home/webpack.config.js" + }, + "configurations": { + "development": { + "extractLicenses": false, + "optimization": false, + "sourceMap": true, + "vendorChunk": true + }, + "production": { + "fileReplacements": [ + { + "replace": "apps/home/src/environments/environment.ts", + "with": "apps/home/src/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "namedChunks": false, + "extractLicenses": true, + "vendorChunk": false, + "webpackConfig": "apps/home/webpack.config.prod.js" + } + } + }, + "serve": { + "executor": "@nrwl/react:module-federation-dev-server", + "defaultConfiguration": "development", + "options": { + "buildTarget": "home:build", + "hmr": true, + "port": 4203 + }, + "configurations": { + "development": { + "buildTarget": "home:build:development" + }, + "production": { + "buildTarget": "home:build:production", + "hmr": false + } + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["apps/home/**/*.{ts,tsx,js,jsx}"] + } + }, + "serve-static": { + "executor": "@nrwl/web:file-server", + "defaultConfiguration": "development", + "options": { + "buildTarget": "home:build", + "port": 4203 + }, + "configurations": { + "development": { + "buildTarget": "home:build:development" + }, + "production": { + "buildTarget": "home:build:production" + } + } + }, + "test": { + "executor": "@nrwl/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "apps/home/jest.config.ts", + "passWithNoTests": true + }, + "configurations": { + "ci": { + "ci": true, + "codeCoverage": true + } + } + } + }, + "tags": [] +} diff --git a/apps/home/src/app/app.module.scss b/apps/home/src/app/app.module.scss new file mode 100644 index 0000000..e69de29 diff --git a/apps/home/src/app/app.spec.tsx b/apps/home/src/app/app.spec.tsx new file mode 100644 index 0000000..3ed9b7b --- /dev/null +++ b/apps/home/src/app/app.spec.tsx @@ -0,0 +1,3 @@ +import { render } from '@testing-library/react'; + +import App from './app'; diff --git a/apps/home/src/app/app.tsx b/apps/home/src/app/app.tsx new file mode 100644 index 0000000..7909856 --- /dev/null +++ b/apps/home/src/app/app.tsx @@ -0,0 +1,14 @@ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import Box from '@mui/material/Box'; + +export function App() { + return ( + <> + +

Loading ..... Under Development

+
+ + ); +} + +export default App; diff --git a/apps/home/src/assets/.gitkeep b/apps/home/src/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/apps/home/src/bootstrap.tsx b/apps/home/src/bootstrap.tsx new file mode 100644 index 0000000..ed6365b --- /dev/null +++ b/apps/home/src/bootstrap.tsx @@ -0,0 +1,7 @@ +import * as ReactDOM from 'react-dom/client'; +import App from './app/app'; + +const root = ReactDOM.createRoot( + document.getElementById('root') as HTMLElement +); +root.render(); diff --git a/apps/home/src/environments/environment.prod.ts b/apps/home/src/environments/environment.prod.ts new file mode 100644 index 0000000..c966979 --- /dev/null +++ b/apps/home/src/environments/environment.prod.ts @@ -0,0 +1,3 @@ +export const environment = { + production: true, +}; diff --git a/apps/home/src/environments/environment.ts b/apps/home/src/environments/environment.ts new file mode 100644 index 0000000..7ed8376 --- /dev/null +++ b/apps/home/src/environments/environment.ts @@ -0,0 +1,6 @@ +// This file can be replaced during build by using the `fileReplacements` array. +// When building for production, this file is replaced with `environment.prod.ts`. + +export const environment = { + production: false, +}; diff --git a/apps/home/src/favicon-old.ico b/apps/home/src/favicon-old.ico new file mode 100644 index 0000000..317ebcb Binary files /dev/null and b/apps/home/src/favicon-old.ico differ diff --git a/apps/home/src/favicon.ico b/apps/home/src/favicon.ico new file mode 100644 index 0000000..45cf676 Binary files /dev/null and b/apps/home/src/favicon.ico differ diff --git a/apps/home/src/index.html b/apps/home/src/index.html new file mode 100644 index 0000000..aacecc9 --- /dev/null +++ b/apps/home/src/index.html @@ -0,0 +1,14 @@ + + + + + Home + + + + + + +
+ + diff --git a/apps/home/src/main.ts b/apps/home/src/main.ts new file mode 100644 index 0000000..b93c7a0 --- /dev/null +++ b/apps/home/src/main.ts @@ -0,0 +1 @@ +import('./bootstrap'); diff --git a/apps/home/src/remote-entry.ts b/apps/home/src/remote-entry.ts new file mode 100644 index 0000000..8c1fd10 --- /dev/null +++ b/apps/home/src/remote-entry.ts @@ -0,0 +1 @@ +export { default } from './app/app'; diff --git a/apps/home/src/robots.txt b/apps/home/src/robots.txt new file mode 100644 index 0000000..f10c467 --- /dev/null +++ b/apps/home/src/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / \ No newline at end of file diff --git a/apps/home/src/styles.scss b/apps/home/src/styles.scss new file mode 100644 index 0000000..8dbfda7 --- /dev/null +++ b/apps/home/src/styles.scss @@ -0,0 +1,6 @@ +/* You can add global styles to this file, and also import other style files */ + + +body { + background: #d4d4d4 +} \ No newline at end of file diff --git a/apps/home/tsconfig.app.json b/apps/home/tsconfig.app.json new file mode 100644 index 0000000..ce8082f --- /dev/null +++ b/apps/home/tsconfig.app.json @@ -0,0 +1,23 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": ["node"] + }, + "files": [ + "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../node_modules/@nrwl/react/typings/image.d.ts" + ], + "exclude": [ + "jest.config.ts", + "src/**/*.spec.ts", + "src/**/*.test.ts", + "src/**/*.spec.tsx", + "src/**/*.test.tsx", + "src/**/*.spec.js", + "src/**/*.test.js", + "src/**/*.spec.jsx", + "src/**/*.test.jsx" + ], + "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"] +} diff --git a/apps/home/tsconfig.json b/apps/home/tsconfig.json new file mode 100644 index 0000000..21b5071 --- /dev/null +++ b/apps/home/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": false, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } + ], + "extends": "../../tsconfig.base.json" +} diff --git a/apps/home/tsconfig.spec.json b/apps/home/tsconfig.spec.json new file mode 100644 index 0000000..3100599 --- /dev/null +++ b/apps/home/tsconfig.spec.json @@ -0,0 +1,24 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "files": [ + "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../node_modules/@nrwl/react/typings/image.d.ts" + ] +} diff --git a/apps/home/webpack.config.js b/apps/home/webpack.config.js new file mode 100644 index 0000000..5b6c601 --- /dev/null +++ b/apps/home/webpack.config.js @@ -0,0 +1,16 @@ +const { composePlugins, withNx } = require('@nrwl/webpack'); +const { withReact } = require('@nrwl/react'); +const { withModuleFederation } = require('@nrwl/react/module-federation'); + +const baseConfig = require('./module-federation.config'); + +const config = { + ...baseConfig, +}; + +// Nx plugins for webpack to build config object from Nx options and context. +module.exports = composePlugins( + withNx(), + withReact(), + withModuleFederation(config) +); diff --git a/apps/home/webpack.config.prod.js b/apps/home/webpack.config.prod.js new file mode 100644 index 0000000..0c9447d --- /dev/null +++ b/apps/home/webpack.config.prod.js @@ -0,0 +1 @@ +module.exports = require('./webpack.config'); diff --git a/apps/product-detail-e2e/.eslintrc.json b/apps/product-detail-e2e/.eslintrc.json new file mode 100644 index 0000000..696cb8b --- /dev/null +++ b/apps/product-detail-e2e/.eslintrc.json @@ -0,0 +1,10 @@ +{ + "extends": ["plugin:cypress/recommended", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/apps/product-detail-e2e/cypress.config.ts b/apps/product-detail-e2e/cypress.config.ts new file mode 100644 index 0000000..22f7c84 --- /dev/null +++ b/apps/product-detail-e2e/cypress.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'cypress'; +import { nxE2EPreset } from '@nrwl/cypress/plugins/cypress-preset'; + +export default defineConfig({ + e2e: nxE2EPreset(__dirname), +}); diff --git a/apps/product-detail-e2e/project.json b/apps/product-detail-e2e/project.json new file mode 100644 index 0000000..f9dcdd9 --- /dev/null +++ b/apps/product-detail-e2e/project.json @@ -0,0 +1,33 @@ +{ + "name": "product-detail-e2e", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "apps/product-detail-e2e/src", + "projectType": "application", + "targets": { + "e2e": { + "executor": "@nrwl/cypress:cypress", + "options": { + "cypressConfig": "apps/product-detail-e2e/cypress.config.ts", + "devServerTarget": "product-detail:serve:development", + "testingType": "e2e" + }, + "configurations": { + "production": { + "devServerTarget": "product-detail:serve:production" + }, + "ci": { + "devServerTarget": "product-detail:serve-static" + } + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["apps/product-detail-e2e/**/*.{js,ts}"] + } + } + }, + "tags": [], + "implicitDependencies": ["product-detail"] +} diff --git a/apps/product-detail-e2e/src/e2e/app.cy.ts b/apps/product-detail-e2e/src/e2e/app.cy.ts new file mode 100644 index 0000000..251a1e5 --- /dev/null +++ b/apps/product-detail-e2e/src/e2e/app.cy.ts @@ -0,0 +1,13 @@ +import { getGreeting } from '../support/app.po'; + +describe('product-detail', () => { + beforeEach(() => cy.visit('/')); + + it('should display welcome message', () => { + // Custom command example, see `../support/commands.ts` file + cy.login('my-email@something.com', 'myPassword'); + + // Function helper example, see `../support/app.po.ts` file + getGreeting().contains('Welcome product-detail'); + }); +}); diff --git a/apps/product-detail-e2e/src/fixtures/example.json b/apps/product-detail-e2e/src/fixtures/example.json new file mode 100644 index 0000000..294cbed --- /dev/null +++ b/apps/product-detail-e2e/src/fixtures/example.json @@ -0,0 +1,4 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io" +} diff --git a/apps/product-detail-e2e/src/support/app.po.ts b/apps/product-detail-e2e/src/support/app.po.ts new file mode 100644 index 0000000..3293424 --- /dev/null +++ b/apps/product-detail-e2e/src/support/app.po.ts @@ -0,0 +1 @@ +export const getGreeting = () => cy.get('h1'); diff --git a/apps/product-detail-e2e/src/support/commands.ts b/apps/product-detail-e2e/src/support/commands.ts new file mode 100644 index 0000000..310f1fa --- /dev/null +++ b/apps/product-detail-e2e/src/support/commands.ts @@ -0,0 +1,33 @@ +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** + +// eslint-disable-next-line @typescript-eslint/no-namespace +declare namespace Cypress { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + interface Chainable { + login(email: string, password: string): void; + } +} +// +// -- This is a parent command -- +Cypress.Commands.add('login', (email, password) => { + console.log('Custom command example: Login', email, password); +}); +// +// -- This is a child command -- +// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) diff --git a/apps/product-detail-e2e/src/support/e2e.ts b/apps/product-detail-e2e/src/support/e2e.ts new file mode 100644 index 0000000..3d469a6 --- /dev/null +++ b/apps/product-detail-e2e/src/support/e2e.ts @@ -0,0 +1,17 @@ +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands'; diff --git a/apps/product-detail-e2e/tsconfig.json b/apps/product-detail-e2e/tsconfig.json new file mode 100644 index 0000000..cc509a7 --- /dev/null +++ b/apps/product-detail-e2e/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "sourceMap": false, + "outDir": "../../dist/out-tsc", + "allowJs": true, + "types": ["cypress", "node"] + }, + "include": ["src/**/*.ts", "src/**/*.js", "cypress.config.ts"] +} diff --git a/apps/product-detail/.babelrc b/apps/product-detail/.babelrc new file mode 100644 index 0000000..61641ec --- /dev/null +++ b/apps/product-detail/.babelrc @@ -0,0 +1,11 @@ +{ + "presets": [ + [ + "@nrwl/react/babel", + { + "runtime": "automatic" + } + ] + ], + "plugins": [] +} diff --git a/apps/product-detail/.eslintrc.json b/apps/product-detail/.eslintrc.json new file mode 100644 index 0000000..734ddac --- /dev/null +++ b/apps/product-detail/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/apps/product-detail/jest.config.ts b/apps/product-detail/jest.config.ts new file mode 100644 index 0000000..06d1dff --- /dev/null +++ b/apps/product-detail/jest.config.ts @@ -0,0 +1,11 @@ +/* eslint-disable */ +export default { + displayName: 'product-detail', + preset: '../../jest.preset.js', + transform: { + '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest', + '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nrwl/react/babel'] }], + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../coverage/apps/product-detail', +}; diff --git a/apps/product-detail/module-federation.config.js b/apps/product-detail/module-federation.config.js new file mode 100644 index 0000000..0f32cf0 --- /dev/null +++ b/apps/product-detail/module-federation.config.js @@ -0,0 +1,6 @@ +module.exports = { + name: 'product-detail', + exposes: { + './Module': './src/remote-entry.ts', + }, +}; diff --git a/apps/product-detail/project.json b/apps/product-detail/project.json new file mode 100644 index 0000000..19b0d28 --- /dev/null +++ b/apps/product-detail/project.json @@ -0,0 +1,109 @@ +{ + "name": "product-detail", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "apps/product-detail/src", + "projectType": "application", + "targets": { + "build": { + "executor": "@nrwl/webpack:webpack", + "outputs": ["{options.outputPath}"], + "defaultConfiguration": "production", + "options": { + "compiler": "babel", + "outputPath": "dist/apps/product-detail", + "index": "apps/product-detail/src/index.html", + "baseHref": "/", + "main": "apps/product-detail/src/main.ts", + "tsConfig": "apps/product-detail/tsconfig.app.json", + "assets": [ + "apps/product-detail/src/favicon.ico", + "apps/product-detail/src/assets", + "apps/product-detail/src/robots.txt" + ], + "styles": [], + "scripts": [], + "isolatedConfig": true, + "webpackConfig": "apps/product-detail/webpack.config.js" + }, + "configurations": { + "development": { + "extractLicenses": false, + "optimization": false, + "sourceMap": true, + "vendorChunk": true + }, + "production": { + "fileReplacements": [ + { + "replace": "apps/product-detail/src/environments/environment.ts", + "with": "apps/product-detail/src/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "namedChunks": false, + "extractLicenses": true, + "vendorChunk": false, + "webpackConfig": "apps/product-detail/webpack.config.prod.js" + } + } + }, + "serve": { + "executor": "@nrwl/react:module-federation-dev-server", + "defaultConfiguration": "development", + "options": { + "buildTarget": "product-detail:build", + "hmr": true, + "port": 4201 + }, + "configurations": { + "development": { + "buildTarget": "product-detail:build:development" + }, + "production": { + "buildTarget": "product-detail:build:production", + "hmr": false + } + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["apps/product-detail/**/*.{ts,tsx,js,jsx}"] + } + }, + "serve-static": { + "executor": "@nrwl/web:file-server", + "defaultConfiguration": "development", + "options": { + "buildTarget": "product-detail:build", + "port": 4201 + }, + "configurations": { + "development": { + "buildTarget": "product-detail:build:development" + }, + "production": { + "buildTarget": "product-detail:build:production" + } + } + }, + "test": { + "executor": "@nrwl/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "apps/product-detail/jest.config.ts", + "passWithNoTests": true + }, + "configurations": { + "ci": { + "ci": true, + "codeCoverage": true + } + } + } + }, + "tags": [] +} diff --git a/apps/product-detail/src/app/app.module.scss b/apps/product-detail/src/app/app.module.scss new file mode 100644 index 0000000..7b88fba --- /dev/null +++ b/apps/product-detail/src/app/app.module.scss @@ -0,0 +1 @@ +/* Your styles goes here. */ diff --git a/apps/product-detail/src/app/app.spec.tsx b/apps/product-detail/src/app/app.spec.tsx new file mode 100644 index 0000000..c459120 --- /dev/null +++ b/apps/product-detail/src/app/app.spec.tsx @@ -0,0 +1,15 @@ +import { render } from '@testing-library/react'; + +import App from './app'; + +describe('App', () => { + it('should render successfully', () => { + const { baseElement } = render(); + expect(baseElement).toBeTruthy(); + }); + + it('should have a greeting as the title', () => { + const { getByText } = render(); + expect(getByText(/Welcome product-detail/gi)).toBeTruthy(); + }); +}); diff --git a/apps/product-detail/src/app/app.tsx b/apps/product-detail/src/app/app.tsx new file mode 100644 index 0000000..7f2284f --- /dev/null +++ b/apps/product-detail/src/app/app.tsx @@ -0,0 +1,7 @@ +import ProductDetailWrapper from './pages/productDetailWrapper'; + +export function App() { + return

Hello

; +} + +export default App; diff --git a/apps/product-detail/src/app/pages/productDetailWrapper.tsx b/apps/product-detail/src/app/pages/productDetailWrapper.tsx new file mode 100644 index 0000000..609fe6c --- /dev/null +++ b/apps/product-detail/src/app/pages/productDetailWrapper.tsx @@ -0,0 +1,27 @@ +import { Box } from '@mui/material'; +import { + AppDispatch, + RootState, + useAppDispatch, + useAppSelector, +} from '@p2p-exchange/core'; +import React from 'react'; +import { useDispatch, useSelector } from 'react-redux'; + +const productDetailWrapper = () => { + const dispatch = useAppDispatch(); + // const giveAway = useSelector((state) => state.giveAway); + return ( + +

Hello

+
+ ); +}; + +export default productDetailWrapper; diff --git a/apps/product-detail/src/assets/.gitkeep b/apps/product-detail/src/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/apps/product-detail/src/bootstrap.tsx b/apps/product-detail/src/bootstrap.tsx new file mode 100644 index 0000000..ed6365b --- /dev/null +++ b/apps/product-detail/src/bootstrap.tsx @@ -0,0 +1,7 @@ +import * as ReactDOM from 'react-dom/client'; +import App from './app/app'; + +const root = ReactDOM.createRoot( + document.getElementById('root') as HTMLElement +); +root.render(); diff --git a/apps/product-detail/src/environments/environment.prod.ts b/apps/product-detail/src/environments/environment.prod.ts new file mode 100644 index 0000000..c966979 --- /dev/null +++ b/apps/product-detail/src/environments/environment.prod.ts @@ -0,0 +1,3 @@ +export const environment = { + production: true, +}; diff --git a/apps/product-detail/src/environments/environment.ts b/apps/product-detail/src/environments/environment.ts new file mode 100644 index 0000000..7ed8376 --- /dev/null +++ b/apps/product-detail/src/environments/environment.ts @@ -0,0 +1,6 @@ +// This file can be replaced during build by using the `fileReplacements` array. +// When building for production, this file is replaced with `environment.prod.ts`. + +export const environment = { + production: false, +}; diff --git a/apps/product-detail/src/favicon.ico b/apps/product-detail/src/favicon.ico new file mode 100644 index 0000000..317ebcb Binary files /dev/null and b/apps/product-detail/src/favicon.ico differ diff --git a/apps/product-detail/src/index.html b/apps/product-detail/src/index.html new file mode 100644 index 0000000..38a445d --- /dev/null +++ b/apps/product-detail/src/index.html @@ -0,0 +1,14 @@ + + + + + ProductDetail + + + + + + +
+ + diff --git a/apps/product-detail/src/main.ts b/apps/product-detail/src/main.ts new file mode 100644 index 0000000..b93c7a0 --- /dev/null +++ b/apps/product-detail/src/main.ts @@ -0,0 +1 @@ +import('./bootstrap'); diff --git a/apps/product-detail/src/remote-entry.ts b/apps/product-detail/src/remote-entry.ts new file mode 100644 index 0000000..8c1fd10 --- /dev/null +++ b/apps/product-detail/src/remote-entry.ts @@ -0,0 +1 @@ +export { default } from './app/app'; diff --git a/apps/product-detail/src/robots.txt b/apps/product-detail/src/robots.txt new file mode 100644 index 0000000..f10c467 --- /dev/null +++ b/apps/product-detail/src/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / \ No newline at end of file diff --git a/apps/product-detail/src/styles.scss b/apps/product-detail/src/styles.scss new file mode 100644 index 0000000..90d4ee0 --- /dev/null +++ b/apps/product-detail/src/styles.scss @@ -0,0 +1 @@ +/* You can add global styles to this file, and also import other style files */ diff --git a/apps/product-detail/tsconfig.app.json b/apps/product-detail/tsconfig.app.json new file mode 100644 index 0000000..ce8082f --- /dev/null +++ b/apps/product-detail/tsconfig.app.json @@ -0,0 +1,23 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": ["node"] + }, + "files": [ + "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../node_modules/@nrwl/react/typings/image.d.ts" + ], + "exclude": [ + "jest.config.ts", + "src/**/*.spec.ts", + "src/**/*.test.ts", + "src/**/*.spec.tsx", + "src/**/*.test.tsx", + "src/**/*.spec.js", + "src/**/*.test.js", + "src/**/*.spec.jsx", + "src/**/*.test.jsx" + ], + "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"] +} diff --git a/apps/product-detail/tsconfig.json b/apps/product-detail/tsconfig.json new file mode 100644 index 0000000..21b5071 --- /dev/null +++ b/apps/product-detail/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": false, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } + ], + "extends": "../../tsconfig.base.json" +} diff --git a/apps/product-detail/tsconfig.spec.json b/apps/product-detail/tsconfig.spec.json new file mode 100644 index 0000000..3100599 --- /dev/null +++ b/apps/product-detail/tsconfig.spec.json @@ -0,0 +1,24 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "files": [ + "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../node_modules/@nrwl/react/typings/image.d.ts" + ] +} diff --git a/apps/product-detail/webpack.config.js b/apps/product-detail/webpack.config.js new file mode 100644 index 0000000..5b6c601 --- /dev/null +++ b/apps/product-detail/webpack.config.js @@ -0,0 +1,16 @@ +const { composePlugins, withNx } = require('@nrwl/webpack'); +const { withReact } = require('@nrwl/react'); +const { withModuleFederation } = require('@nrwl/react/module-federation'); + +const baseConfig = require('./module-federation.config'); + +const config = { + ...baseConfig, +}; + +// Nx plugins for webpack to build config object from Nx options and context. +module.exports = composePlugins( + withNx(), + withReact(), + withModuleFederation(config) +); diff --git a/apps/product-detail/webpack.config.prod.js b/apps/product-detail/webpack.config.prod.js new file mode 100644 index 0000000..0c9447d --- /dev/null +++ b/apps/product-detail/webpack.config.prod.js @@ -0,0 +1 @@ +module.exports = require('./webpack.config'); diff --git a/apps/product-list-e2e/.eslintrc.json b/apps/product-list-e2e/.eslintrc.json new file mode 100644 index 0000000..696cb8b --- /dev/null +++ b/apps/product-list-e2e/.eslintrc.json @@ -0,0 +1,10 @@ +{ + "extends": ["plugin:cypress/recommended", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/apps/product-list-e2e/cypress.config.ts b/apps/product-list-e2e/cypress.config.ts new file mode 100644 index 0000000..22f7c84 --- /dev/null +++ b/apps/product-list-e2e/cypress.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'cypress'; +import { nxE2EPreset } from '@nrwl/cypress/plugins/cypress-preset'; + +export default defineConfig({ + e2e: nxE2EPreset(__dirname), +}); diff --git a/apps/product-list-e2e/project.json b/apps/product-list-e2e/project.json new file mode 100644 index 0000000..579790b --- /dev/null +++ b/apps/product-list-e2e/project.json @@ -0,0 +1,33 @@ +{ + "name": "product-list-e2e", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "apps/product-list-e2e/src", + "projectType": "application", + "targets": { + "e2e": { + "executor": "@nrwl/cypress:cypress", + "options": { + "cypressConfig": "apps/product-list-e2e/cypress.config.ts", + "devServerTarget": "product-list:serve:development", + "testingType": "e2e" + }, + "configurations": { + "production": { + "devServerTarget": "product-list:serve:production" + }, + "ci": { + "devServerTarget": "product-list:serve-static" + } + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["apps/product-list-e2e/**/*.{js,ts}"] + } + } + }, + "tags": [], + "implicitDependencies": ["product-list"] +} diff --git a/apps/product-list-e2e/src/e2e/app.cy.ts b/apps/product-list-e2e/src/e2e/app.cy.ts new file mode 100644 index 0000000..ec50825 --- /dev/null +++ b/apps/product-list-e2e/src/e2e/app.cy.ts @@ -0,0 +1,13 @@ +import { getGreeting } from '../support/app.po'; + +describe('product-list', () => { + beforeEach(() => cy.visit('/')); + + it('should display welcome message', () => { + // Custom command example, see `../support/commands.ts` file + cy.login('my-email@something.com', 'myPassword'); + + // Function helper example, see `../support/app.po.ts` file + getGreeting().contains('Welcome product-list'); + }); +}); diff --git a/apps/product-list-e2e/src/fixtures/example.json b/apps/product-list-e2e/src/fixtures/example.json new file mode 100644 index 0000000..294cbed --- /dev/null +++ b/apps/product-list-e2e/src/fixtures/example.json @@ -0,0 +1,4 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io" +} diff --git a/apps/product-list-e2e/src/support/app.po.ts b/apps/product-list-e2e/src/support/app.po.ts new file mode 100644 index 0000000..3293424 --- /dev/null +++ b/apps/product-list-e2e/src/support/app.po.ts @@ -0,0 +1 @@ +export const getGreeting = () => cy.get('h1'); diff --git a/apps/product-list-e2e/src/support/commands.ts b/apps/product-list-e2e/src/support/commands.ts new file mode 100644 index 0000000..310f1fa --- /dev/null +++ b/apps/product-list-e2e/src/support/commands.ts @@ -0,0 +1,33 @@ +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** + +// eslint-disable-next-line @typescript-eslint/no-namespace +declare namespace Cypress { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + interface Chainable { + login(email: string, password: string): void; + } +} +// +// -- This is a parent command -- +Cypress.Commands.add('login', (email, password) => { + console.log('Custom command example: Login', email, password); +}); +// +// -- This is a child command -- +// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) diff --git a/apps/product-list-e2e/src/support/e2e.ts b/apps/product-list-e2e/src/support/e2e.ts new file mode 100644 index 0000000..3d469a6 --- /dev/null +++ b/apps/product-list-e2e/src/support/e2e.ts @@ -0,0 +1,17 @@ +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands'; diff --git a/apps/product-list-e2e/tsconfig.json b/apps/product-list-e2e/tsconfig.json new file mode 100644 index 0000000..cc509a7 --- /dev/null +++ b/apps/product-list-e2e/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "sourceMap": false, + "outDir": "../../dist/out-tsc", + "allowJs": true, + "types": ["cypress", "node"] + }, + "include": ["src/**/*.ts", "src/**/*.js", "cypress.config.ts"] +} diff --git a/apps/product-list/.babelrc b/apps/product-list/.babelrc new file mode 100644 index 0000000..61641ec --- /dev/null +++ b/apps/product-list/.babelrc @@ -0,0 +1,11 @@ +{ + "presets": [ + [ + "@nrwl/react/babel", + { + "runtime": "automatic" + } + ] + ], + "plugins": [] +} diff --git a/apps/product-list/.eslintrc.json b/apps/product-list/.eslintrc.json new file mode 100644 index 0000000..734ddac --- /dev/null +++ b/apps/product-list/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/apps/product-list/jest.config.ts b/apps/product-list/jest.config.ts new file mode 100644 index 0000000..5506233 --- /dev/null +++ b/apps/product-list/jest.config.ts @@ -0,0 +1,11 @@ +/* eslint-disable */ +export default { + displayName: 'product-list', + preset: '../../jest.preset.js', + transform: { + '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest', + '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nrwl/react/babel'] }], + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../coverage/apps/product-list', +}; diff --git a/apps/product-list/module-federation.config.js b/apps/product-list/module-federation.config.js new file mode 100644 index 0000000..148cc5f --- /dev/null +++ b/apps/product-list/module-federation.config.js @@ -0,0 +1,6 @@ +module.exports = { + name: 'product-list', + exposes: { + './Module': './src/remote-entry.ts', + }, +}; diff --git a/apps/product-list/project.json b/apps/product-list/project.json new file mode 100644 index 0000000..c468dc8 --- /dev/null +++ b/apps/product-list/project.json @@ -0,0 +1,108 @@ +{ + "name": "product-list", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "apps/product-list/src", + "projectType": "application", + "targets": { + "build": { + "executor": "@nrwl/webpack:webpack", + "outputs": ["{options.outputPath}"], + "defaultConfiguration": "production", + "options": { + "compiler": "babel", + "outputPath": "dist/apps/product-list", + "index": "apps/product-list/src/index.html", + "baseHref": "/", + "main": "apps/product-list/src/main.ts", + "tsConfig": "apps/product-list/tsconfig.app.json", + "assets": [ + "apps/product-list/src/favicon.ico", + "apps/product-list/src/assets","apps/product-list/src/robots.txt" + ], + "styles": [], + "scripts": [], + "isolatedConfig": true, + "webpackConfig": "apps/product-list/webpack.config.js" + }, + "configurations": { + "development": { + "extractLicenses": false, + "optimization": false, + "sourceMap": true, + "vendorChunk": true + }, + "production": { + "fileReplacements": [ + { + "replace": "apps/product-list/src/environments/environment.ts", + "with": "apps/product-list/src/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "namedChunks": false, + "extractLicenses": true, + "vendorChunk": false, + "webpackConfig": "apps/product-list/webpack.config.prod.js" + } + } + }, + "serve": { + "executor": "@nrwl/react:module-federation-dev-server", + "defaultConfiguration": "development", + "options": { + "buildTarget": "product-list:build", + "hmr": true, + "port": 4202 + }, + "configurations": { + "development": { + "buildTarget": "product-list:build:development" + }, + "production": { + "buildTarget": "product-list:build:production", + "hmr": false + } + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["apps/product-list/**/*.{ts,tsx,js,jsx}"] + } + }, + "serve-static": { + "executor": "@nrwl/web:file-server", + "defaultConfiguration": "development", + "options": { + "buildTarget": "product-list:build", + "port": 4202 + }, + "configurations": { + "development": { + "buildTarget": "product-list:build:development" + }, + "production": { + "buildTarget": "product-list:build:production" + } + } + }, + "test": { + "executor": "@nrwl/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "apps/product-list/jest.config.ts", + "passWithNoTests": true + }, + "configurations": { + "ci": { + "ci": true, + "codeCoverage": true + } + } + } + }, + "tags": [] +} diff --git a/apps/product-list/src/app/app.module.scss b/apps/product-list/src/app/app.module.scss new file mode 100644 index 0000000..7b88fba --- /dev/null +++ b/apps/product-list/src/app/app.module.scss @@ -0,0 +1 @@ +/* Your styles goes here. */ diff --git a/apps/product-list/src/app/app.spec.tsx b/apps/product-list/src/app/app.spec.tsx new file mode 100644 index 0000000..96cf78c --- /dev/null +++ b/apps/product-list/src/app/app.spec.tsx @@ -0,0 +1,15 @@ +import { render } from '@testing-library/react'; + +import App from './app'; + +describe('App', () => { + it('should render successfully', () => { + const { baseElement } = render(); + expect(baseElement).toBeTruthy(); + }); + + it('should have a greeting as the title', () => { + const { getByText } = render(); + expect(getByText(/Welcome product-list/gi)).toBeTruthy(); + }); +}); diff --git a/apps/product-list/src/app/app.tsx b/apps/product-list/src/app/app.tsx new file mode 100644 index 0000000..9421188 --- /dev/null +++ b/apps/product-list/src/app/app.tsx @@ -0,0 +1,21 @@ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { get } from 'http'; +import styles from './app.module.scss'; +import ListingWrapper from './pages/listingWrapper'; +import ProductDetailWrapper from './pages/productDetailWrapper'; +import { Route, Routes, useParams } from 'react-router-dom'; + +export function App() { + const params = useParams(); + + const getPage = () => { + if (params.path === 'shop' || params.path === 'search') { + return ; + } else { + return ; + } + }; + return <>{getPage()}; +} + +export default App; diff --git a/apps/product-list/src/app/components/filter.tsx b/apps/product-list/src/app/components/filter.tsx new file mode 100644 index 0000000..5d6999f --- /dev/null +++ b/apps/product-list/src/app/components/filter.tsx @@ -0,0 +1,318 @@ +import React from 'react'; +import { + Box, + Link, + Typography, + useMediaQuery, + Button, + TextField, +} from '@mui/material'; +import List from '@mui/material/List'; +import { + ExpandableNavItem, + FetchLocation, + StickyContainer, + getUserLocation, +} from '@p2p-exchange/shared'; +import FormControl from '@mui/material/FormControl'; +import FormGroup from '@mui/material/FormGroup'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Checkbox from '@mui/material/Checkbox'; +import Radio from '@mui/material/Radio'; +import RadioGroup from '@mui/material/RadioGroup'; +import CancelIcon from '@mui/icons-material/Cancel'; +import IconButton from '@mui/material/IconButton'; +import { + arraySlicer, + stringFormatter, + toggleWordInArray, +} from './helperFunction'; +import { + setOfferTypeFilter, + useAppDispatch, + useAppSelector, + setCategoryFilter, + triggerSearch, + setSortByOption, + GeoPlaceLatLng, + setSearchLoaction, + setMapLatLng, + setLocationInputValue, + setLocationValue, +} from '@p2p-exchange/core'; +import LocationOnIcon from '@mui/icons-material/LocationOn'; + +type FilterProps = { + handleDrawerClose?: Function; +}; +const filter = ({ handleDrawerClose }: FilterProps) => { + const isTabletScreen = useMediaQuery('(max-width:899px)'); + const dispatch = useAppDispatch(); + const isInitialRender = React.useRef(true); + const { offerTypeFilters, categoryFilters, selectedFilters, isLoading } = + useAppSelector((state) => state.search); + const { latitude, longitude, address } = useAppSelector( + (state) => state.user.userPosition + ); + const [location, setLocation] = React.useState(null); + + const [isListingShowAll, setListingShowAll] = React.useState( + offerTypeFilters.length > 4 && selectedFilters.offerTypeFilters.length === 0 + ); + const listingFilter = arraySlicer(offerTypeFilters, isListingShowAll); + + const handleChange = (event: React.ChangeEvent) => { + dispatch( + setOfferTypeFilter( + toggleWordInArray(selectedFilters.offerTypeFilters, event.target.name) + ) + ); + !isTabletScreen && dispatch(triggerSearch()); + }; + + const handleListingShowAll = () => { + setListingShowAll(false); + }; + const [isProductShowAll, setProductShowAll] = React.useState( + categoryFilters.length > 4 && selectedFilters.categoryFilters.length === 0 + ); + const productTypeFilter = arraySlicer(categoryFilters, isProductShowAll); + const handleProductFilterChange = ( + event: React.ChangeEvent + ) => { + dispatch( + setCategoryFilter( + toggleWordInArray(selectedFilters.categoryFilters, event.target.name) + ) + ); + !isTabletScreen && dispatch(triggerSearch()); + }; + const handleProductShowAll = () => { + setProductShowAll(false); + }; + const handleRadioChange = (event: React.ChangeEvent) => { + dispatch(setSortByOption((event.target as HTMLInputElement).value)); + }; + const handleApplyFilters = () => { + dispatch(triggerSearch()); + handleDrawerClose && handleDrawerClose(); + }; + + const isOnlyGiveAwayProducts = + offerTypeFilters.length === 1 && offerTypeFilters[0] === 'giveaway'; + + React.useEffect(() => { + const userLocatioin: GeoPlaceLatLng = { + address, + position: { lat: latitude, lng: longitude }, + }; + setLocation(userLocatioin); + dispatch(setMapLatLng({ lat: latitude, lng: longitude })); + dispatch(setLocationValue({ description: address })); + }, []); + + React.useEffect(() => { + if (!isLoading) { + setListingShowAll( + offerTypeFilters.length > 4 && + selectedFilters.offerTypeFilters.length === 0 + ); + setProductShowAll( + categoryFilters.length > 4 && + selectedFilters.categoryFilters.length === 0 + ); + } + }, [isLoading]); + + const setGeoCode = (location: GeoPlaceLatLng) => { + dispatch(setSearchLoaction(location.position)); + !isTabletScreen && dispatch(triggerSearch()); + }; + + return ( + + + + Filters + + {isTabletScreen && ( + handleDrawerClose && handleDrawerClose()} + size="large" + > + + + )} + + {isTabletScreen && ( + + + + + } + label="Date" + /> + } + label="Relevance" + /> + {!isOnlyGiveAwayProducts && ( + <> + } + label="Price (Low to High)" + /> + } + label="Price (High to Low)" + /> + + )} + + + + + )} + + + + + {listingFilter.map((ele: string, index: number) => ( + + } + label={stringFormatter(ele)} + /> + ))} + + + {isListingShowAll && ( + + + Show All + + + )} + + + + + + + {productTypeFilter.map((ele: string, index: number) => ( + + } + label={stringFormatter(ele)} + /> + ))} + + + {isProductShowAll && ( + + + Show All + + + )} + + + + + { + setLocation(geoPlaceLatLng); + setGeoCode(geoPlaceLatLng); + }} + /> + + + {isTabletScreen && ( + + + + )} + + ); +}; + +export default filter; diff --git a/apps/product-list/src/app/components/helperFunction.tsx b/apps/product-list/src/app/components/helperFunction.tsx new file mode 100644 index 0000000..e26cdd8 --- /dev/null +++ b/apps/product-list/src/app/components/helperFunction.tsx @@ -0,0 +1,34 @@ +export const arraySlicer = (arr: string[], showAll: boolean): string[] => { + if (showAll) return arr.slice(0, 4); + else return arr; +}; +export const stringFormatter = (str: string): string => { + const formattedString = str.replace(/_/g, ' '); + return formattedString.charAt(0).toUpperCase() + formattedString.slice(1); +}; + +export const formatDate = (dateString: string): string => { + const options: Intl.DateTimeFormatOptions = { + day: 'numeric', + month: 'short', + }; + const date = new Date(dateString); + const formattedDate = date.toLocaleDateString('en-US', options); + return formattedDate; +}; +export const formatPrice = (price: string): string => { + if (price === '0' || price === '0.0') { + return 'Free'; + } + return `$${price}`; +}; +export const toggleWordInArray = (array: string[], word: string): string[] => { + const newArray = [...array]; + const index = newArray.indexOf(word); + if (index !== -1) { + newArray.splice(index, 1); + } else { + newArray.push(word); + } + return newArray; +}; diff --git a/apps/product-list/src/app/components/listingContainerHeader.tsx b/apps/product-list/src/app/components/listingContainerHeader.tsx new file mode 100644 index 0000000..e0298f2 --- /dev/null +++ b/apps/product-list/src/app/components/listingContainerHeader.tsx @@ -0,0 +1,189 @@ +import React from 'react'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; +import IconButton from '@mui/material/IconButton'; +import ExpandLess from '@mui/icons-material/ExpandLess'; +import ExpandMore from '@mui/icons-material/ExpandMore'; +import { styled, alpha } from '@mui/material/styles'; +import Menu, { MenuProps } from '@mui/material/Menu'; +import MenuItem from '@mui/material/MenuItem'; +import { useMediaQuery, Button, Drawer } from '@mui/material'; +import FilterListIcon from '@mui/icons-material/FilterList'; +import Filter from './filter'; +import { + globalTheme, + setSortByOption, + triggerSearch, + useAppDispatch, + useAppSelector, +} from '@p2p-exchange/core'; + +const StyledMenu = styled((props: MenuProps) => ( + +))(({ theme }) => ({ + '& .MuiPaper-root': { + borderRadius: 6, + marginTop: theme.spacing(1), + minWidth: 180, + color: + theme.palette.mode === 'light' + ? 'rgb(55, 65, 81)' + : theme.palette.grey[300], + boxShadow: + 'rgb(255, 255, 255) 0px 0px 0px 0px, rgba(0, 0, 0, 0.05) 0px 0px 0px 1px, rgba(0, 0, 0, 0.1) 0px 10px 15px -3px, rgba(0, 0, 0, 0.05) 0px 4px 6px -2px', + '& .MuiMenu-list': { + padding: '4px 0', + }, + + '& .MuiMenuItem-root': { + '& .MuiSvgIcon-root': { + fontSize: 18, + color: theme.palette.text.secondary, + marginRight: theme.spacing(1.5), + }, + + '&:active': { + backgroundColor: alpha( + theme.palette.primary.main, + theme.palette.action.selectedOpacity + ), + }, + }, + }, +})); +type ListingHeaderProps = { + productCount: number; +}; +const listingContainerHeader = ({ productCount }: ListingHeaderProps) => { + const isTabletScreen = useMediaQuery('(min-width:899px)'); + const dispatch = useAppDispatch(); + const [anchorEl, setAnchorEl] = React.useState(null); + const [isDrawerOpen, setDrawerState] = React.useState(false); + const offerTypeFilters = useAppSelector( + (state) => state?.search.offerTypeFilters + ); + const updatedSort = useAppSelector( + (state) => state?.search?.selectedFilters?.selectedSort + ); + const open = Boolean(anchorEl); + const isOnlyGiveAwayProducts = + offerTypeFilters.length === 1 && offerTypeFilters[0] === 'giveaway'; + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + const handleClose = () => { + setAnchorEl(null); + }; + const handleMenuChange = (event: any) => { + dispatch(setSortByOption(event.target.innerText)); + dispatch(triggerSearch()); + setAnchorEl(null); + }; + const toggleDrawer = (value: boolean) => { + setDrawerState(value); + }; + const handleDrawerClose = () => { + setDrawerState(false); + }; + return ( + + + {productCount} Results + + {!isTabletScreen && ( + <> + + toggleDrawer(false)} + sx={{ + '.MuiDrawer-paper': { + backgroundColor: globalTheme.palette.background.default, + height: '100%', + }, + }} + > + + + + + + )} + {isTabletScreen && ( + + + Sort By : + + + {updatedSort} + + + {open ? : } + + + + Date + + + Relevance + + {!isOnlyGiveAwayProducts && ( + <> + + Price (Low to High) + + + Price (High to Low) + + + )} + + + )} + + ); +}; + +export default listingContainerHeader; diff --git a/apps/product-list/src/app/components/searchHeaderBlock.tsx b/apps/product-list/src/app/components/searchHeaderBlock.tsx new file mode 100644 index 0000000..eb928b8 --- /dev/null +++ b/apps/product-list/src/app/components/searchHeaderBlock.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import Typography from '@mui/material/Typography'; +import { Box, useMediaQuery } from '@mui/material'; +type SearchHeaderProps = { + searchTerm: string; + productCount: number; +}; +const searchHeaderBlock = ({ searchTerm, productCount }: SearchHeaderProps) => { + const isMobileScreen = useMediaQuery('(max-width:599px)'); + return ( + + + Search results for + + + {`${searchTerm} (${productCount})`} + + + ); +}; + +export default searchHeaderBlock; diff --git a/apps/product-list/src/app/pages/listingWrapper.tsx b/apps/product-list/src/app/pages/listingWrapper.tsx new file mode 100644 index 0000000..dcd755f --- /dev/null +++ b/apps/product-list/src/app/pages/listingWrapper.tsx @@ -0,0 +1,218 @@ +import React from 'react'; +import { Box, Grid, useMediaQuery } from '@mui/material'; +import SearchHeaderBlock from '../components/searchHeaderBlock'; +import Filters from '../components/filter'; +import { + AlertBar, + BreadCrumbs, + FeaturedCard, + LoaderWrapper, + NotFoundPage, + PageTitle, +} from '@p2p-exchange/shared'; +import ListingContainerHeader from '../components/listingContainerHeader'; +import Pagination from '@mui/material/Pagination'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { + useAppDispatch, + useAppSelector, + fetchSearchResults, + setPageNumber, + flushSearchState, + setCurrentSearchTerm, + flushSearchFilters, + fetchNewlyAdded, +} from '@p2p-exchange/core'; +import { formatDate, formatPrice } from '../components/helperFunction'; +type DataItem = { + id: string; + name: string; + description: string; + category: string; + offerType: string; + images: string[]; + location: string; + geoLocation: { + latitude: string; + longitude: string; + }; + user: string; + price: string; + createdTime: string; +}; +const listingWrapper = () => { + const isTabletScreen = useMediaQuery('(min-width:899px)'); + const isMobileScreen = useMediaQuery('(min-width:599px)'); + const location = useLocation(); + const dispatch = useAppDispatch(); + const navigate = useNavigate(); + const prevProps = React.useRef(); + const breadCrumbsConfig = [ + { buttonText: 'Home', buttonUrl: '/' }, + { buttonText: 'Shop', buttonUrl: '/product/shop' }, + ]; + const cardDimentionConfig = { + width: { xs: '44vw', sm: '44vw', md: '22.5vw', lg: '270px' }, + height: { xs: '56vw', sm: '56vw', md: '28vw', lg: '337.5px' }, + }; + + const handleChange = (event: React.ChangeEvent, value: number) => { + dispatch(setPageNumber(value)); + }; + const { + productsData, + productCount, + searchTerm, + pageLimit, + pageNumber, + triggerApiCounter, + apiSuccessMsg, + isLoading, + currentSearchTerm, + error, + } = useAppSelector((state) => state.search); + const newlyAddedProducts = useAppSelector((state) => state.newlyAdded.data); + + React.useEffect(() => { + dispatch( + fetchSearchResults( + location.pathname === '/product/search' ? searchTerm : '' + ) + ); + if (location.pathname === '/product/search') + dispatch(setCurrentSearchTerm(searchTerm)); + }, [triggerApiCounter, pageNumber, location.pathname]); + React.useEffect(() => { + if (location.pathname === '/product/search' && !searchTerm) { + navigate('/product/shop'); + } + }, [searchTerm]); + React.useEffect(() => { + return () => { + dispatch(flushSearchState()); + }; + }, []); + React.useEffect(() => { + if (prevProps.current !== location.pathname) { + if ( + prevProps.current === '/product/search' && + location.pathname === '/product/shop' + ) { + dispatch(flushSearchFilters()); + dispatch(setCurrentSearchTerm('')); + } + } + prevProps.current = location.pathname; + }, [location.pathname]); + React.useEffect(() => { + if ( + newlyAddedProducts.length === 0 && + apiSuccessMsg === 'No Products Found' + ) + dispatch( + fetchNewlyAdded({ + limit: 20, + pageNumber: 1, + sortBy: 'DATE', + }) + ); + }, [apiSuccessMsg]); + // if (isLoading) return ; + if (apiSuccessMsg === 'No Products Found' && currentSearchTerm) + return ( + + ); + if (error) + return ( + <> + + + + + + + ); + return ( + <> + + + {location.pathname === '/product/search' ? ( + + ) : ( + + )} + + + {isTabletScreen && } + + + {isLoading ? ( + + ) : ( + <> + + + {productsData && + productsData.map((ele: DataItem, index: number) => ( + + + + ))} + + {productsData.length !== 0 && ( + + + + )} + + )} + + + + + ); +}; + +export default listingWrapper; diff --git a/apps/product-list/src/app/pages/productDetailWrapper.tsx b/apps/product-list/src/app/pages/productDetailWrapper.tsx new file mode 100644 index 0000000..8fbc1f4 --- /dev/null +++ b/apps/product-list/src/app/pages/productDetailWrapper.tsx @@ -0,0 +1,842 @@ +import { + Avatar, + Box, + Button, + Grid, + Modal, + Paper, + Popover, + Typography, + styled, + useMediaQuery, + IconButton, + Divider, +} from '@mui/material'; +import { + MapCordinates, + openSnackBar, + setCategoryFilter, + setSortByOption, + useAppDispatch, + useAppSelector, +} from '@p2p-exchange/core'; +import { + CardCarousel, + ChatArea, + FetchLocation, + SocialShare, +} from '@p2p-exchange/shared'; +import React from 'react'; +import ShareOutlinedIcon from '@mui/icons-material/ShareOutlined'; + +import { + fetchProduct, + removeProduct, +} from 'libs/core/src/lib/store/slices/productSlice'; +import { Link, useMatch, useNavigate } from 'react-router-dom'; +import { flushProductState } from 'libs/core/src/lib/store/slices/productSlice'; +import CloseIcon from '@mui/icons-material/Close'; +import capitalizeFirstLetter from '../utils/capitalizeFirstLetter'; + +type DataItem = { + id: string; + name: string; + description: string; + category: string; + offerType: string; + images: string[]; + geoLocation: { + latitude: string; + longitude: string; + }; + user: string; + createdTime: string; +}; + +const Item = styled(Paper)(({ theme }) => ({ + backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff', + ...theme.typography.body2, + padding: theme.spacing(1), + textAlign: 'center', + color: theme.palette.text.secondary, +})); + +const containerStyle = { + width: '100%', + height: '400px', +}; + +const onLoad = (marker: any) => { + console.log('marker: ', marker); +}; + +const productDetailWrapper = () => { + const dispatch = useAppDispatch(); + const newlyAdded = useAppSelector((state) => state.newlyAdded); + const isSmallScreen = useMediaQuery('(max-width:900px)'); + const navigate = useNavigate(); + + const match = useMatch('/product/:id'); + const productId = match?.params.id; + + const productState = useAppSelector((state) => state.product); + const user = useAppSelector((state) => state.user); + + const [latitude, setLatitude] = React.useState(0); + const [longitude, setLongitude] = React.useState(0); + const [address, setAddress] = React.useState(''); + const [mapCordinates, setMapCordinates] = React.useState({ + position: { lat: latitude as number, lng: longitude as number }, + location: address, + }); + + React.useEffect(() => { + window.scrollTo(0, 0); + dispatch(fetchProduct({ id: productId as string })).then((res) => { + const response = res.payload as any; + setMapCordinates({ + position: { + lat: Number(response.data.product.geoLocation.latitude), + lng: Number(response.data.product.geoLocation.longitude), + }, + location: address, + }); + }); + return () => { + dispatch(flushProductState()); + }; + }, []); + + const openInGoogleMaps = () => { + const url = `https://www.google.com/maps/search/?api=1&query=${mapCordinates.position.lat},${mapCordinates.position.lng}`; + window.open(url); + }; + + const handleRemoveProduct = () => { + setAnchorEl(null); + dispatch(removeProduct({ id: productId as string })).then((res) => { + dispatch( + openSnackBar({ + message: 'Product Removed Successfully', + }) + ); + navigate('/'); + }); + }; + + //Popper + const [anchorEl, setAnchorEl] = React.useState( + null + ); + + const handleClickPopover = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleClosePopover = () => { + setAnchorEl(null); + }; + + const StyledPopover = styled(Popover)(({ theme }) => ({ + '& .MuiPaper-root': { + backgroundColor: '#fff', + marginTop: '10px', + padding: '5px', + }, + ':after': { + content: '""', + position: 'absolute', + top: '-10px', + left: '50%', + width: '10px', + height: '10px', + background: 'red', + borderLeft: '1px solid #fffff', + borderRight: '1px solid #fffff', + borderBottom: '1px solid #fffff', + }, + })); + const openPopover = Boolean(anchorEl); + const id = openPopover ? 'simple-popover' : undefined; + + //Scroll Element + const [marginTop, setMarginTop] = React.useState(0); + React.useEffect(() => { + const handleScroll = () => { + const scrollTop = + window.pageYOffset || document.documentElement.scrollTop; + if (scrollTop > 68) { + setMarginTop(scrollTop - 60); + } else { + setMarginTop(0); + } + }; + + window.addEventListener('scroll', handleScroll); + + return () => { + window.removeEventListener('scroll', handleScroll); + }; + }, []); + + const handleClickProductCategoryLink = () => { + dispatch(setCategoryFilter([productState.product.category.toLowerCase()])); + dispatch(setSortByOption('Date')); + }; + + const style = { + position: 'absolute' as 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: '100%', + maxWidth: 600, + bgcolor: 'background.paper', + boxShadow: 24, + pt: 2, + px: 4, + pb: 3, + }; + const [openChatBox, setOpenChatBox] = React.useState(false); + const [openShareBox, setOpenShareBox] = React.useState(false); + const handleEditListing = () => { + navigate(`/post-item/edit/${productState.product.id}`); + }; + + const handleSendNotification = () => { + setOpenChatBox(true); + }; + const handleClose = () => { + setOpenChatBox(false); + setOpenShareBox(false); + }; + const handleCallback = (result: any) => { + // console.log(result); + dispatch( + openSnackBar({ + message: 'Message sent Successfully, Check inbox to see seller message', + }) + ); + setOpenChatBox(false); + // setTimeout(() => { + // navigate(`/inbox/${productState.user.id}/${productState.product.id}`); + // },1000) + }; + + return ( + + + + + Home + {' '} + + + / + + + + {capitalizeFirstLetter(productState.product.category)} + + + + / + + + {productState.product.name} + + + + + + + {productState.product.images.length > 0 && ( + + {productState.product.images.map( + (image: string, index: number) => { + return ( + + + + ); + } + )} + + )} + + + + + + + + {productState.product.name} + + + {/* */} + setOpenShareBox(true)} + aria-label="social share" + > + + + + + + + $ {parseFloat(productState.product.price).toFixed(2)} + + + + {productState.product.offerType} + + + + + + {productState.product.location} + + + {new Date( + productState.product.createdTime + ).toLocaleDateString()} + + + + + + + + Description + + + {productState.product.description} + + + + + + + + + + navigate(`/profile/${productState.user.id}`) + } + /> + + navigate(`/profile/${productState.user.id}`) + } + > + {productState.user.firstName} {productState.user.lastName} + + + {/* navigate(`/profile/${productState.user.id}`)} + > + + + {productState.avgRating} + + */} + + + {productState.user.id !== user.id && ( + + + {!user.id && ( + + Sign in to chat with the + seller + + )} + + )} + + {productState.user.id === user.id && ( + <> + + + + Are you sure? + + + + + )} + + + + + + + + Map Location + + + + + {/* + + + + + + + Open in Google Maps + + */} + + + + + + + + + + Send Message to Seller{' '} + + + + + + + + + + + + + + + + + Share{' '} + + + + + + + + + + + + ); +}; + +export default productDetailWrapper; diff --git a/apps/product-list/src/app/utils/capitalizeFirstLetter.tsx b/apps/product-list/src/app/utils/capitalizeFirstLetter.tsx new file mode 100644 index 0000000..0e832bb --- /dev/null +++ b/apps/product-list/src/app/utils/capitalizeFirstLetter.tsx @@ -0,0 +1,3 @@ +export default function capitalizeFirstLetter(str : String) { + return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase(); +} \ No newline at end of file diff --git a/apps/product-list/src/assets/.gitkeep b/apps/product-list/src/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/apps/product-list/src/bootstrap.tsx b/apps/product-list/src/bootstrap.tsx new file mode 100644 index 0000000..77a691a --- /dev/null +++ b/apps/product-list/src/bootstrap.tsx @@ -0,0 +1,6 @@ +import * as ReactDOM from 'react-dom/client'; +import App from './app/app'; +const root = ReactDOM.createRoot( + document.getElementById('root') as HTMLElement +); +root.render(); diff --git a/apps/product-list/src/environments/environment.prod.ts b/apps/product-list/src/environments/environment.prod.ts new file mode 100644 index 0000000..c966979 --- /dev/null +++ b/apps/product-list/src/environments/environment.prod.ts @@ -0,0 +1,3 @@ +export const environment = { + production: true, +}; diff --git a/apps/product-list/src/environments/environment.ts b/apps/product-list/src/environments/environment.ts new file mode 100644 index 0000000..7ed8376 --- /dev/null +++ b/apps/product-list/src/environments/environment.ts @@ -0,0 +1,6 @@ +// This file can be replaced during build by using the `fileReplacements` array. +// When building for production, this file is replaced with `environment.prod.ts`. + +export const environment = { + production: false, +}; diff --git a/apps/product-list/src/favicon.ico b/apps/product-list/src/favicon.ico new file mode 100644 index 0000000..317ebcb Binary files /dev/null and b/apps/product-list/src/favicon.ico differ diff --git a/apps/product-list/src/index.html b/apps/product-list/src/index.html new file mode 100644 index 0000000..e8138b0 --- /dev/null +++ b/apps/product-list/src/index.html @@ -0,0 +1,14 @@ + + + + + ProductList + + + + + + +
+ + diff --git a/apps/product-list/src/main.ts b/apps/product-list/src/main.ts new file mode 100644 index 0000000..b93c7a0 --- /dev/null +++ b/apps/product-list/src/main.ts @@ -0,0 +1 @@ +import('./bootstrap'); diff --git a/apps/product-list/src/remote-entry.ts b/apps/product-list/src/remote-entry.ts new file mode 100644 index 0000000..8c1fd10 --- /dev/null +++ b/apps/product-list/src/remote-entry.ts @@ -0,0 +1 @@ +export { default } from './app/app'; diff --git a/apps/product-list/src/robots.txt b/apps/product-list/src/robots.txt new file mode 100644 index 0000000..f10c467 --- /dev/null +++ b/apps/product-list/src/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / \ No newline at end of file diff --git a/apps/product-list/src/styles.scss b/apps/product-list/src/styles.scss new file mode 100644 index 0000000..90d4ee0 --- /dev/null +++ b/apps/product-list/src/styles.scss @@ -0,0 +1 @@ +/* You can add global styles to this file, and also import other style files */ diff --git a/apps/product-list/tsconfig.app.json b/apps/product-list/tsconfig.app.json new file mode 100644 index 0000000..ce8082f --- /dev/null +++ b/apps/product-list/tsconfig.app.json @@ -0,0 +1,23 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": ["node"] + }, + "files": [ + "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../node_modules/@nrwl/react/typings/image.d.ts" + ], + "exclude": [ + "jest.config.ts", + "src/**/*.spec.ts", + "src/**/*.test.ts", + "src/**/*.spec.tsx", + "src/**/*.test.tsx", + "src/**/*.spec.js", + "src/**/*.test.js", + "src/**/*.spec.jsx", + "src/**/*.test.jsx" + ], + "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"] +} diff --git a/apps/product-list/tsconfig.json b/apps/product-list/tsconfig.json new file mode 100644 index 0000000..21b5071 --- /dev/null +++ b/apps/product-list/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": false, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } + ], + "extends": "../../tsconfig.base.json" +} diff --git a/apps/product-list/tsconfig.spec.json b/apps/product-list/tsconfig.spec.json new file mode 100644 index 0000000..3100599 --- /dev/null +++ b/apps/product-list/tsconfig.spec.json @@ -0,0 +1,24 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "files": [ + "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../node_modules/@nrwl/react/typings/image.d.ts" + ] +} diff --git a/apps/product-list/webpack.config.js b/apps/product-list/webpack.config.js new file mode 100644 index 0000000..5b6c601 --- /dev/null +++ b/apps/product-list/webpack.config.js @@ -0,0 +1,16 @@ +const { composePlugins, withNx } = require('@nrwl/webpack'); +const { withReact } = require('@nrwl/react'); +const { withModuleFederation } = require('@nrwl/react/module-federation'); + +const baseConfig = require('./module-federation.config'); + +const config = { + ...baseConfig, +}; + +// Nx plugins for webpack to build config object from Nx options and context. +module.exports = composePlugins( + withNx(), + withReact(), + withModuleFederation(config) +); diff --git a/apps/product-list/webpack.config.prod.js b/apps/product-list/webpack.config.prod.js new file mode 100644 index 0000000..0c9447d --- /dev/null +++ b/apps/product-list/webpack.config.prod.js @@ -0,0 +1 @@ +module.exports = require('./webpack.config'); diff --git a/apps/shell-e2e/.eslintrc.json b/apps/shell-e2e/.eslintrc.json new file mode 100644 index 0000000..696cb8b --- /dev/null +++ b/apps/shell-e2e/.eslintrc.json @@ -0,0 +1,10 @@ +{ + "extends": ["plugin:cypress/recommended", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/apps/shell-e2e/cypress.config.ts b/apps/shell-e2e/cypress.config.ts new file mode 100644 index 0000000..22f7c84 --- /dev/null +++ b/apps/shell-e2e/cypress.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'cypress'; +import { nxE2EPreset } from '@nrwl/cypress/plugins/cypress-preset'; + +export default defineConfig({ + e2e: nxE2EPreset(__dirname), +}); diff --git a/apps/shell-e2e/project.json b/apps/shell-e2e/project.json new file mode 100644 index 0000000..adad604 --- /dev/null +++ b/apps/shell-e2e/project.json @@ -0,0 +1,34 @@ +{ + "name": "shell-e2e", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "apps/shell-e2e/src", + "projectType": "application", + "targets": { + "e2e": { + "executor": "@nrwl/cypress:cypress", + "options": { + "cypressConfig": "apps/shell-e2e/cypress.config.ts", + "devServerTarget": "shell:serve:development", + "testingType": "e2e", + "baseUrl": "http://localhost:4200" + }, + "configurations": { + "production": { + "devServerTarget": "shell:serve:production" + }, + "ci": { + "devServerTarget": "shell:serve-static" + } + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["apps/shell-e2e/**/*.{js,ts}"] + } + } + }, + "tags": [], + "implicitDependencies": ["shell"] +} diff --git a/apps/shell-e2e/src/e2e/app.cy.ts b/apps/shell-e2e/src/e2e/app.cy.ts new file mode 100644 index 0000000..89e7eae --- /dev/null +++ b/apps/shell-e2e/src/e2e/app.cy.ts @@ -0,0 +1,13 @@ +import { getGreeting } from '../support/app.po'; + +describe('shell', () => { + beforeEach(() => cy.visit('/')); + + it('should display welcome message', () => { + // Custom command example, see `../support/commands.ts` file + cy.login('my-email@something.com', 'myPassword'); + + // Function helper example, see `../support/app.po.ts` file + getGreeting().contains('Welcome shell'); + }); +}); diff --git a/apps/shell-e2e/src/fixtures/example.json b/apps/shell-e2e/src/fixtures/example.json new file mode 100644 index 0000000..294cbed --- /dev/null +++ b/apps/shell-e2e/src/fixtures/example.json @@ -0,0 +1,4 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io" +} diff --git a/apps/shell-e2e/src/support/app.po.ts b/apps/shell-e2e/src/support/app.po.ts new file mode 100644 index 0000000..3293424 --- /dev/null +++ b/apps/shell-e2e/src/support/app.po.ts @@ -0,0 +1 @@ +export const getGreeting = () => cy.get('h1'); diff --git a/apps/shell-e2e/src/support/commands.ts b/apps/shell-e2e/src/support/commands.ts new file mode 100644 index 0000000..310f1fa --- /dev/null +++ b/apps/shell-e2e/src/support/commands.ts @@ -0,0 +1,33 @@ +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** + +// eslint-disable-next-line @typescript-eslint/no-namespace +declare namespace Cypress { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + interface Chainable { + login(email: string, password: string): void; + } +} +// +// -- This is a parent command -- +Cypress.Commands.add('login', (email, password) => { + console.log('Custom command example: Login', email, password); +}); +// +// -- This is a child command -- +// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) diff --git a/apps/shell-e2e/src/support/e2e.ts b/apps/shell-e2e/src/support/e2e.ts new file mode 100644 index 0000000..3d469a6 --- /dev/null +++ b/apps/shell-e2e/src/support/e2e.ts @@ -0,0 +1,17 @@ +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands'; diff --git a/apps/shell-e2e/tsconfig.json b/apps/shell-e2e/tsconfig.json new file mode 100644 index 0000000..cc509a7 --- /dev/null +++ b/apps/shell-e2e/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "sourceMap": false, + "outDir": "../../dist/out-tsc", + "allowJs": true, + "types": ["cypress", "node"] + }, + "include": ["src/**/*.ts", "src/**/*.js", "cypress.config.ts"] +} diff --git a/apps/shell/.babelrc b/apps/shell/.babelrc new file mode 100644 index 0000000..61641ec --- /dev/null +++ b/apps/shell/.babelrc @@ -0,0 +1,11 @@ +{ + "presets": [ + [ + "@nrwl/react/babel", + { + "runtime": "automatic" + } + ] + ], + "plugins": [] +} diff --git a/apps/shell/.eslintrc.json b/apps/shell/.eslintrc.json new file mode 100644 index 0000000..734ddac --- /dev/null +++ b/apps/shell/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/apps/shell/jest.config.ts b/apps/shell/jest.config.ts new file mode 100644 index 0000000..5d81ef5 --- /dev/null +++ b/apps/shell/jest.config.ts @@ -0,0 +1,11 @@ +/* eslint-disable */ +export default { + displayName: 'shell', + preset: '../../jest.preset.js', + transform: { + '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest', + '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nrwl/react/babel'] }], + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../coverage/apps/shell', +}; diff --git a/apps/shell/module-federation.config.js b/apps/shell/module-federation.config.js new file mode 100644 index 0000000..cb8244d --- /dev/null +++ b/apps/shell/module-federation.config.js @@ -0,0 +1,4 @@ +module.exports = { + name: 'shell', + remotes: ['product-detail', 'product-list', 'home'], +}; diff --git a/apps/shell/project.json b/apps/shell/project.json new file mode 100644 index 0000000..c56fdac --- /dev/null +++ b/apps/shell/project.json @@ -0,0 +1,105 @@ +{ + "name": "shell", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "apps/shell/src", + "projectType": "application", + "targets": { + "build": { + "executor": "@nrwl/webpack:webpack", + "outputs": ["{options.outputPath}"], + "defaultConfiguration": "production", + "options": { + "compiler": "babel", + "outputPath": "dist/apps/shell", + "index": "apps/shell/src/index.html", + "baseHref": "/", + "main": "apps/shell/src/main.ts", + "tsConfig": "apps/shell/tsconfig.app.json", + "assets": ["apps/shell/src/favicon.ico", "apps/shell/src/assets","apps/shell/src/robots.txt"], + "styles": [], + "scripts": [], + "isolatedConfig": true, + "webpackConfig": "apps/shell/webpack.config.js" + }, + "configurations": { + "development": { + "extractLicenses": false, + "optimization": false, + "sourceMap": true, + "vendorChunk": true + }, + "production": { + "fileReplacements": [ + { + "replace": "apps/shell/src/environments/environment.ts", + "with": "apps/shell/src/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "namedChunks": false, + "extractLicenses": true, + "vendorChunk": false, + "webpackConfig": "apps/shell/webpack.config.prod.js" + } + } + }, + "serve": { + "executor": "@nrwl/react:module-federation-dev-server", + "defaultConfiguration": "development", + "options": { + "buildTarget": "shell:build", + "hmr": true, + "port": 4200 + }, + "configurations": { + "development": { + "buildTarget": "shell:build:development" + }, + "production": { + "buildTarget": "shell:build:production", + "hmr": false + } + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["apps/shell/**/*.{ts,tsx,js,jsx}"] + } + }, + "serve-static": { + "executor": "@nrwl/web:file-server", + "defaultConfiguration": "development", + "options": { + "buildTarget": "shell:build", + "port": 4200 + }, + "configurations": { + "development": { + "buildTarget": "shell:build:development" + }, + "production": { + "buildTarget": "shell:build:production" + } + } + }, + "test": { + "executor": "@nrwl/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "apps/shell/jest.config.ts", + "passWithNoTests": true + }, + "configurations": { + "ci": { + "ci": true, + "codeCoverage": true + } + } + } + }, + "tags": [] +} diff --git a/apps/shell/src/app/accountWrapper.tsx b/apps/shell/src/app/accountWrapper.tsx new file mode 100644 index 0000000..ef9cddf --- /dev/null +++ b/apps/shell/src/app/accountWrapper.tsx @@ -0,0 +1,79 @@ +import React from 'react'; +import { Box, Grid } from '@mui/material'; +import AccountNav from './components/accountNav'; +import ProfileListing from './components/profileListing'; +import ProfileEditView from './components/profileEditView'; +import { + useAppSelector, + useAppDispatch, + fetchProfileListing, + fetchProfileData, + setInfoMsg, + fetchDonation, +} from '@p2p-exchange/core'; +import SecurityContainer from './components/securityContainer'; +import { useNavigate } from 'react-router-dom'; + +const accountWrapper = () => { + const dispatch = useAppDispatch(); + const navigate = useNavigate(); + const currentUserId = useAppSelector((state) => state.user.id); + const { pageNumber } = useAppSelector((state) => state.profile.listingData); + const [selectedNav, setSelectedNav] = React.useState('Profile Info'); + const handleNavChange = (val: string) => { + setSelectedNav(val); + }; + React.useEffect(() => { + if (selectedNav === 'Profile Info') + dispatch(fetchProfileData({ id: currentUserId, isSelfView: true })); + if (selectedNav === 'Listings') { + dispatch(fetchProfileListing(currentUserId)); + dispatch(fetchDonation(currentUserId)); + } + }, [selectedNav, pageNumber]); + + const screenMapper: { + [key: string]: React.ReactNode; + } = { + 'Profile Info': , + Listings: , + 'Account Security': , + }; + + React.useEffect(() => { + if (!currentUserId) { + dispatch( + setInfoMsg('To view my account, please login in to your account.') + ); + navigate('/signin'); + } + }, [currentUserId]); + + return ( + + + + + + + + {screenMapper[selectedNav]} + + + + ); +}; + +export default accountWrapper; diff --git a/apps/shell/src/app/app.module.scss b/apps/shell/src/app/app.module.scss new file mode 100644 index 0000000..7b88fba --- /dev/null +++ b/apps/shell/src/app/app.module.scss @@ -0,0 +1 @@ +/* Your styles goes here. */ diff --git a/apps/shell/src/app/app.spec.tsx b/apps/shell/src/app/app.spec.tsx new file mode 100644 index 0000000..5fa587a --- /dev/null +++ b/apps/shell/src/app/app.spec.tsx @@ -0,0 +1,25 @@ +import { render } from '@testing-library/react'; + +import { BrowserRouter } from 'react-router-dom'; + +import App from './app'; + +describe('App', () => { + it('should render successfully', () => { + const { baseElement } = render( + + + + ); + expect(baseElement).toBeTruthy(); + }); + + it('should have a greeting as the title', () => { + const { getByText } = render( + + + + ); + expect(getByText(/Welcome shell/gi)).toBeTruthy(); + }); +}); diff --git a/apps/shell/src/app/app.tsx b/apps/shell/src/app/app.tsx new file mode 100644 index 0000000..56676bb --- /dev/null +++ b/apps/shell/src/app/app.tsx @@ -0,0 +1,214 @@ +import * as React from 'react'; +import '../styles/styles.scss'; +import LandingPage from './landingPage'; +import { Route, Routes, useNavigate } from 'react-router-dom'; +import { ThemeProvider } from '@mui/material/styles'; +import Box from '@mui/material/Box'; +import { + Header, + Footer, + useAppDispatch, + getUserLogout, + useAppSelector, + fetchNotificationCount, + setTriggerCookieCheck, + flushUserData, + setInfoMsg, +} from '@p2p-exchange/core'; +import CssBaseline from '@mui/material/CssBaseline'; +import { globalTheme } from '@p2p-exchange/core'; +import PostItem from '../components/post-item/post-item'; +import { useState, useEffect } from 'react'; +import CreateAccount from './createAccount'; +import SigninPage from './signinPage'; +import ForgotPasswordPage from './forgotPasswordPage'; +import ConfirmForgotPasswordPage from './confirmForgotPasswordPage'; +import ConfirmEmailPage from './confirmEmailPage'; +import { + Snackbars, + LoaderWrapper, + getUserLocation, +} from '@p2p-exchange/shared'; +import PageNotFound from './notfoundPage'; +import ProfileWrapper from './profileWrapper'; +import InboxPage from '../components/inbox/inbox'; +import AccountWrapper from './accountWrapper'; +import ChangePassword from './chagePasswordPage'; +import DonationFormPage from './donationFormPage'; +import AuthGuard from './utils/authguard'; +import DonationLandingPage from './donationLandingPage'; +const Home = React.lazy(() => import('home/Module')); +const ProductDetail = React.lazy(() => import('product-detail/Module')); +const ProductList = React.lazy(() => import('product-list/Module')); +interface IPosition { + latitude: number | null; + longitude: number | null; + error: string | null; +} + +export function App() { + const dispatch = useAppDispatch(); + const loaderCount = useAppSelector((state) => state.globalLoader.counter); + const isInitialRender = React.useRef(true); + const navigate = useNavigate(); + const triggerCookieCheck = useAppSelector( + (state) => state.user.triggerCookieCheck + ); + const handleLogout = () => { + dispatch(getUserLogout()); + }; + + const checkAuthCookie = (): number => { + const authCookie = document.cookie + .split(';') + .find((cookie) => cookie.trim().startsWith('auth=')); + + if (authCookie) { + const value = authCookie?.split('=')[1]; + const checkAfterTimeInterval = Number(value?.split('_')[1]); + const newCookieeAge = checkAfterTimeInterval * 1000 + 10; + return newCookieeAge; + } else { + dispatch(flushUserData()); + // if ( + // window.location.pathname === '/' || + // window.location.pathname === '/product/:path' || + // window.location.pathname === '/profile/:id' || + // window.location.pathname === '/register' || + // window.location.pathname === '/signin' || + // window.location.pathname === '/confirm-email' || + // window.location.pathname === '/donate' + // ) { + // } else { + // dispatch( + // setInfoMsg('To view this page, please login in to your account.') + // ); + // navigate('/signin'); + // } + } + return 0; + }; + + useEffect(() => { + checkAuthCookie() && dispatch(fetchNotificationCount()); + getUserLocation(dispatch); + }, []); + + React.useEffect(() => { + let expTime = checkAuthCookie(); + let debounceTimer: NodeJS.Timeout | undefined; + const debounceCheck = () => { + if (debounceTimer) { + clearTimeout(debounceTimer); + } + debounceTimer = setTimeout(() => { + checkAuthCookie(); + }, expTime); + }; + debounceCheck(); + return () => { + if (debounceTimer) { + clearTimeout(debounceTimer); + } + }; + }, [triggerCookieCheck]); + + return ( + + + + 0} /> + + +
+
+ + } /> + } /> + } /> + } /> + } /> + {/* } /> + } + /> */} + + + + } + /> + + + + + } + /> + {/* } /> */} + + + + } + /> + } /> + } /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + } /> + +
+