├── .DS_Store ├── .vscode └── settings.json ├── README.md ├── docker-compose.yml ├── nestjs-cars-app ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── README.md ├── codegen.yml ├── graphql.schema.json ├── nest-cli.json ├── ormconfig.json ├── package.json ├── src │ ├── .env │ ├── app.controller.spec.ts │ ├── app.controller.ts │ ├── app.module.ts │ ├── app.service.ts │ ├── components │ │ ├── auth │ │ │ ├── auth.controller.ts │ │ │ ├── auth.module.ts │ │ │ ├── auth.service.ts │ │ │ ├── constants.ts │ │ │ ├── current_user.decorator.ts │ │ │ ├── guards │ │ │ │ ├── gql-auth.guard.ts │ │ │ │ ├── jwt-auth.guard.ts │ │ │ │ └── local-host.guard.ts │ │ │ └── stratagies │ │ │ │ ├── jwt.stratage.ts │ │ │ │ └── local.stratagy.ts │ │ ├── cars │ │ │ ├── cars.module.ts │ │ │ ├── cars.resolver.ts │ │ │ ├── cars.services.ts │ │ │ ├── dto │ │ │ │ ├── new-car.input.ts │ │ │ │ ├── order-quantity-input.ts │ │ │ │ └── update-car.input.ts │ │ │ └── entities │ │ │ │ └── car.ts │ │ ├── components.module.ts │ │ ├── orders │ │ │ ├── dto │ │ │ │ ├── new-order.input.ts │ │ │ │ └── update-order.input.ts │ │ │ ├── entities │ │ │ │ └── order.ts │ │ │ ├── orders.module.ts │ │ │ ├── orders.resolvers.ts │ │ │ ├── orders.services.ts │ │ │ └── orders.ts │ │ └── users │ │ │ ├── dto │ │ │ ├── new-user.input.ts │ │ │ └── update-user.input.ts │ │ │ ├── entities │ │ │ └── user.ts │ │ │ ├── user.decorator.ts │ │ │ ├── users.module.ts │ │ │ ├── users.resolver.ts │ │ │ └── users.services.ts │ ├── database │ │ └── database.module.ts │ ├── generated │ │ └── graphql.ts │ ├── main.ts │ ├── migrations │ │ └── 1622304168366-CreateDatabase.ts │ └── schema.gql ├── test │ ├── app.e2e-spec.ts │ └── jest-e2e.json ├── tsconfig.build.json ├── tsconfig.json └── yarn.lock ├── react-cars-app ├── .env ├── .gitignore ├── Dockerfile ├── HomePage_Demo.png ├── README.md ├── __generated__ │ └── globalTypes.ts ├── codegen.yml ├── craco.config.js ├── package.json ├── public │ ├── car-logo.png │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt ├── server.js ├── src │ ├── App.css │ ├── App.test.tsx │ ├── App.tsx │ ├── accessToken.ts │ ├── app │ │ ├── components │ │ │ ├── bookCard │ │ │ │ └── index.tsx │ │ │ ├── button │ │ │ │ └── index.tsx │ │ │ ├── car │ │ │ │ └── index.tsx │ │ │ ├── footer │ │ │ │ └── index.tsx │ │ │ ├── logo │ │ │ │ └── index.tsx │ │ │ ├── marginer │ │ │ │ └── index.tsx │ │ │ ├── navbar │ │ │ │ ├── index.tsx │ │ │ │ ├── menuStyles.ts │ │ │ │ └── navItems.tsx │ │ │ └── responsive │ │ │ │ └── index.ts │ │ ├── containers │ │ │ ├── CarPage │ │ │ │ ├── aboutUs.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── selectors.ts │ │ │ │ ├── slice.ts │ │ │ │ ├── topHatchBack.tsx │ │ │ │ ├── topHybird.tsx │ │ │ │ ├── topSUV.tsx │ │ │ │ ├── topSection.tsx │ │ │ │ ├── topVan.tsx │ │ │ │ └── type.ts │ │ │ ├── HomePage │ │ │ │ ├── aboutUs.tsx │ │ │ │ ├── bookingSteps.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── selectors.ts │ │ │ │ ├── slice.ts │ │ │ │ ├── topCars.tsx │ │ │ │ ├── topSection.tsx │ │ │ │ └── type.ts │ │ │ ├── UserLogin │ │ │ │ ├── index.tsx │ │ │ │ ├── loginSection.tsx │ │ │ │ ├── loginSection2.tsx │ │ │ │ ├── selectors.ts │ │ │ │ ├── slice.ts │ │ │ │ ├── topSection.tsx │ │ │ │ └── type.ts │ │ │ ├── orderPage │ │ │ │ ├── bookingSteps.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── topSection.tsx │ │ │ └── signupPage │ │ │ │ ├── index.tsx │ │ │ │ ├── signupSection.tsx │ │ │ │ └── topSection.tsx │ │ ├── graphql.schema.json │ │ ├── graphql.ts │ │ ├── hooks.ts │ │ ├── services │ │ │ ├── carService │ │ │ │ ├── __generated__ │ │ │ │ │ ├── GetCars.ts │ │ │ │ │ └── GetCarsByCategory.ts │ │ │ │ ├── findCarByCatagory.ts │ │ │ │ ├── index.ts │ │ │ │ └── queries.ts │ │ │ └── userService │ │ │ │ ├── CurreUser.ts │ │ │ │ ├── SignUp.ts │ │ │ │ ├── __generated__ │ │ │ │ ├── AddUser.ts │ │ │ │ ├── LoginUser.ts │ │ │ │ └── UserNow.ts │ │ │ │ ├── index.ts │ │ │ │ └── userLogin.ts │ │ └── store.ts │ ├── assets │ │ └── images │ │ │ ├── blob.svg │ │ │ ├── car-logo-dark.png │ │ │ ├── car-logo.png │ │ │ ├── homepage_design.png │ │ │ ├── jeep.png │ │ │ ├── landrover-sport.png │ │ │ ├── mclaren-orange-big.png │ │ │ ├── mclaren-orange.png │ │ │ └── porche.png │ ├── generated │ │ └── graphql.ts │ ├── index.css │ ├── index.tsx │ ├── logo.svg │ ├── react-app-env.d.ts │ ├── serviceWorker.ts │ ├── setupTests.ts │ └── typings │ │ ├── car.ts │ │ ├── index.ts │ │ ├── react-burger-menu.d.ts │ │ ├── react-carousel.d.ts │ │ └── user.ts ├── tailwind.config.js ├── tsconfig.json └── yarn.lock └── yarn.lock /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daydreamerli/nestjs-graphql-react-fullstack/d6dd1dec1eda1130c162936a254ff3614daae21c/.DS_Store -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.ignoreLimitWarning": true 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Full Web App with Nodejs NestJS framwork GraphQL MySQL React &&Tailwind 2 | 3 | Frontend : React ApolloClient@GraphQL & Tailwind CSS 4 | 5 | #using: npx create-react-app my-app --template tailwindcss-typescript 6 | 7 | #tailwind init 8 | 9 | # good sample tailwind config: 10 | 11 | module.exports = { 12 | purge: ["./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"], 13 | darkMode: false, // or 'media' or 'class' 14 | theme: { 15 | extend: {}, 16 | screens: { 17 | sm: "640px", 18 | // => @media (min-width: 640px) { ... } 19 | 20 | md: "768px", 21 | // => @media (min-width: 768px) { ... } 22 | 23 | lg: "1024px", 24 | // => @media (min-width: 1024px) { ... } 25 | 26 | xl: "1280px", 27 | // => @media (min-width: 1280px) { ... } 28 | 29 | "2xl": "1536px", 30 | // => @media (min-width: 1536px) { ... } 31 | }, 32 | }, 33 | variants: { 34 | extend: {}, 35 | }, 36 | plugins: [], 37 | }; 38 | 39 | # Using Graphql codegen to autogenerate code : 40 | 41 | "schema:download": "npx apollo service:download --endpoint=http://localhost:9000/graphql ./src/app/graphql.schema.json", 42 | "schema:generate-watch": "npx apollo codegen:generate --localSchemaFile=./src/app/graphql.schema.json --target=typescript --tagName=gql --watch", 43 | 44 | # bad choice : Redux with apolloClient should use apolloclient only for handling the data state and pipe line 45 | 46 | Backend: Nodejs=> NestJs framwork with GranphQL and TypeORM, MySQL database 47 | 48 | # why nestjs --> easy to use, and powerful 49 | 50 | # typeORM is strongly recommended rather than othe ORM 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /nestjs-cars-app/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/recommended', 10 | 'plugin:prettier/recommended', 11 | ], 12 | root: true, 13 | env: { 14 | node: true, 15 | jest: true, 16 | }, 17 | ignorePatterns: ['.eslintrc.js'], 18 | rules: { 19 | '@typescript-eslint/interface-name-prefix': 'off', 20 | '@typescript-eslint/explicit-function-return-type': 'off', 21 | '@typescript-eslint/explicit-module-boundary-types': 'off', 22 | '@typescript-eslint/no-explicit-any': 'off', 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /nestjs-cars-app/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # OS 14 | .DS_Store 15 | 16 | # Tests 17 | /coverage 18 | /.nyc_output 19 | 20 | # IDEs and editors 21 | /.idea 22 | .project 23 | .classpath 24 | .c9/ 25 | *.launch 26 | .settings/ 27 | *.sublime-workspace 28 | 29 | # IDE - VSCode 30 | .vscode/* 31 | !.vscode/settings.json 32 | !.vscode/tasks.json 33 | !.vscode/launch.json 34 | !.vscode/extensions.json -------------------------------------------------------------------------------- /nestjs-cars-app/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /nestjs-cars-app/README.md: -------------------------------------------------------------------------------- 1 |

2 | Nest Logo 3 |

4 | 5 | [circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 6 | [circleci-url]: https://circleci.com/gh/nestjs/nest 7 | 8 |

A progressive Node.js framework for building efficient and scalable server-side applications.

9 |

10 | NPM Version 11 | Package License 12 | NPM Downloads 13 | CircleCI 14 | Coverage 15 | Discord 16 | Backers on Open Collective 17 | Sponsors on Open Collective 18 | 19 | Support us 20 | 21 |

22 | 24 | 25 | ## Description 26 | 27 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 28 | 29 | ## Installation 30 | 31 | ```bash 32 | $ npm install 33 | ``` 34 | 35 | ## Running the app 36 | 37 | ```bash 38 | # development 39 | $ npm run start 40 | 41 | # watch mode 42 | $ npm run start:dev 43 | 44 | # production mode 45 | $ npm run start:prod 46 | ``` 47 | 48 | ## Test 49 | 50 | ```bash 51 | # unit tests 52 | $ npm run test 53 | 54 | # e2e tests 55 | $ npm run test:e2e 56 | 57 | # test coverage 58 | $ npm run test:cov 59 | ``` 60 | 61 | ## Support 62 | 63 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). 64 | 65 | ## Stay in touch 66 | 67 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) 68 | - Website - [https://nestjs.com](https://nestjs.com/) 69 | - Twitter - [@nestframework](https://twitter.com/nestframework) 70 | 71 | ## License 72 | 73 | Nest is [MIT licensed](LICENSE). 74 | -------------------------------------------------------------------------------- /nestjs-cars-app/codegen.yml: -------------------------------------------------------------------------------- 1 | overwrite: true 2 | schema: "http://localhost:9000/graphql" 3 | generates: 4 | ./src/generated/graphql.ts: 5 | plugins: 6 | - "typescript" 7 | - "typescript-resolvers" 8 | ./graphql.schema.json: 9 | plugins: 10 | - "introspection" -------------------------------------------------------------------------------- /nestjs-cars-app/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /nestjs-cars-app/ormconfig.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "default", 4 | "type": "mysql", 5 | "host": "localhost", 6 | "port": 3306, 7 | "username": "root", 8 | "password": "rootpassword", 9 | "database": "mycars", 10 | "entities": ["dist/**/entities/*{.ts,.js}"], 11 | "synchronize": true, 12 | "migrations": ["dist/migrations/*{.ts,.js}"], 13 | "cli": { 14 | "migrationsDir": "src/migrations" 15 | } 16 | }, 17 | { 18 | "name": "prod", 19 | "type": "mysql", 20 | "host": "mysqldb", 21 | "port": 3306, 22 | "username": "root", 23 | "password": "******", 24 | "database": "greatcars", 25 | "entities": ["dist/**/entities/*{.ts,.js}"], 26 | "synchronize": true, 27 | "migrations": ["dist/migrations/*{.ts,.js}"], 28 | "cli": { 29 | "migrationsDir": "src/migrations" 30 | } 31 | } 32 | ] 33 | -------------------------------------------------------------------------------- /nestjs-cars-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nestjs-cars-app", 3 | "version": "0.0.1", 4 | "description": "Industry project backend graphql server", 5 | "author": "frankli", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "prebuild": "rimraf dist", 10 | "build": "nest build", 11 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 12 | "start": " nest start", 13 | "start:dev": "nest start --watch", 14 | "start:debug": "nest start --debug --watch", 15 | "start:prod": "cross-env NODE_ENV=production node dist/main", 16 | "codegen": "graphql-codegen --config ./codegen.yml", 17 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 18 | "test": "jest", 19 | "test:watch": "jest --watch", 20 | "test:cov": "jest --coverage", 21 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 22 | "test:e2e": "jest --config ./test/jest-e2e.json" 23 | }, 24 | "dependencies": { 25 | "@nestjs/common": "^7.6.15", 26 | "@nestjs/config": "^1.0.0", 27 | "@nestjs/core": "^7.6.15", 28 | "@nestjs/graphql": "^7.10.6", 29 | "@nestjs/jwt": "^8.0.0", 30 | "@nestjs/passport": "^8.0.1", 31 | "@nestjs/platform-express": "^7.6.15", 32 | "@nestjs/typeorm": "^7.1.5", 33 | "@types/bcrypt": "^5.0.0", 34 | "@types/jsonwebtoken": "^8.5.5", 35 | "apollo-server-express": "^2.24.1", 36 | "bcrypt": "^5.0.1", 37 | "class-transformer": "^0.4.0", 38 | "class-validator": "^0.13.1", 39 | "cross-env": "^7.0.3", 40 | "gql-to-json-schema": "^1.0.1", 41 | "graphql": "^15.5.0", 42 | "graphql-tools": "^7.0.5", 43 | "jsonwebtoken": "^8.5.1", 44 | "mysql2": "^2.2.5", 45 | "passport": "^0.4.1", 46 | "passport-jwt": "^4.0.0", 47 | "passport-local": "^1.0.0", 48 | "pg": "^8.7.1", 49 | "reflect-metadata": "^0.1.13", 50 | "rimraf": "^3.0.2", 51 | "rxjs": "^6.6.6", 52 | "typeorm": "^0.2.32" 53 | }, 54 | "devDependencies": { 55 | "@graphql-codegen/cli": "^2.2.0", 56 | "@graphql-codegen/introspection": "^2.1.0", 57 | "@graphql-codegen/typescript": "^2.2.1", 58 | "@graphql-codegen/typescript-resolvers": "^2.2.0", 59 | "@nestjs/cli": "^7.6.0", 60 | "@nestjs/schematics": "^7.3.0", 61 | "@nestjs/testing": "^7.6.15", 62 | "@types/express": "^4.17.11", 63 | "@types/jest": "^26.0.22", 64 | "@types/node": "^14.14.36", 65 | "@types/passport-local": "^1.0.34", 66 | "@types/supertest": "^2.0.10", 67 | "@typescript-eslint/eslint-plugin": "^4.19.0", 68 | "@typescript-eslint/parser": "^4.19.0", 69 | "eslint": "^7.22.0", 70 | "eslint-config-prettier": "^8.1.0", 71 | "eslint-plugin-prettier": "^3.3.1", 72 | "jest": "^26.6.3", 73 | "prettier": "^2.2.1", 74 | "supertest": "^6.1.3", 75 | "ts-jest": "^26.5.4", 76 | "ts-loader": "^8.0.18", 77 | "ts-node": "^9.1.1", 78 | "tsconfig-paths": "^3.9.0", 79 | "typescript": "^4.2.3" 80 | }, 81 | "jest": { 82 | "moduleFileExtensions": [ 83 | "js", 84 | "json", 85 | "ts" 86 | ], 87 | "rootDir": "src", 88 | "testRegex": ".*\\.spec\\.ts$", 89 | "transform": { 90 | "^.+\\.(t|j)s$": "ts-jest" 91 | }, 92 | "collectCoverageFrom": [ 93 | "**/*.(t|j)s" 94 | ], 95 | "coverageDirectory": "../coverage", 96 | "testEnvironment": "node" 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /nestjs-cars-app/src/.env: -------------------------------------------------------------------------------- 1 | PORT=9000 2 | JWT_SECRET= 'hard!to-guess_secret' 3 | DB_NAME=mycars 4 | DB_USER=root 5 | DB_PASS=nestjs2021 -------------------------------------------------------------------------------- /nestjs-cars-app/src/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | 5 | describe('AppController', () => { 6 | let appController: AppController; 7 | 8 | beforeEach(async () => { 9 | const app: TestingModule = await Test.createTestingModule({ 10 | controllers: [AppController], 11 | providers: [AppService], 12 | }).compile(); 13 | 14 | appController = app.get(AppController); 15 | }); 16 | 17 | describe('root', () => { 18 | it('should return "Hello World!"', () => { 19 | expect(appController.getHello()).toBe('Hello World!'); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /nestjs-cars-app/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | import { AppService } from './app.service'; 3 | 4 | @Controller() 5 | export class AppController { 6 | constructor(private readonly appService: AppService) {} 7 | 8 | @Get() 9 | getHello(): string { 10 | return this.appService.getHello(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /nestjs-cars-app/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | import { ConfigModule } from '@nestjs/config'; 5 | import { DatabaseModule } from './database/database.module'; 6 | import { GraphQLModule } from '@nestjs/graphql'; 7 | import { ComponentsModule } from './components/components.module'; 8 | import { join } from 'path'; 9 | 10 | @Module({ 11 | imports: [ 12 | ConfigModule.forRoot({ isGlobal: true }), 13 | DatabaseModule, 14 | GraphQLModule.forRoot({ 15 | playground: true, 16 | debug: true, 17 | // autoSchemaFile: true, // In memory Schema 18 | autoSchemaFile: join(process.cwd(), 'src/schema.gql'), 19 | sortSchema: true, 20 | context: ({ req,res }) => ({ req,res }) 21 | }), 22 | ComponentsModule, 23 | ], 24 | controllers: [AppController], 25 | providers: [AppService], 26 | }) 27 | export class AppModule {} -------------------------------------------------------------------------------- /nestjs-cars-app/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Hello World!'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/auth/auth.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Post, Req, UseGuards } from '@nestjs/common'; 2 | import { Request } from 'express'; 3 | import { User } from '../users/entities/user'; 4 | import { AuthService, LoginResponse } from './auth.service'; 5 | import { LocalAuthGuard } from './guards/local-host.guard'; 6 | import { JwtAuthGuard } from './guards/jwt-auth.guard'; 7 | import { GqlAuthGuard } from './guards/gql-auth.guard'; 8 | 9 | @Controller('auth') 10 | export class AuthController { 11 | 12 | constructor(private readonly authService: AuthService) { } 13 | 14 | 15 | @UseGuards(LocalAuthGuard) 16 | @Post('login') 17 | async login(@Req() req: Request): Promise { 18 | 19 | return this.authService.login(req.user as User); 20 | } 21 | 22 | @Post('signup') 23 | async signup(@Req() req: Request): Promise { 24 | 25 | return this.authService.signup(req.body); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { forwardRef, Module } from '@nestjs/common'; 2 | import { AuthService } from './auth.service'; 3 | import { JwtModule } from '@nestjs/jwt'; 4 | import { PassportModule } from '@nestjs/passport'; 5 | import { UsersModule } from '../users/users.module'; 6 | import { jwtSecret } from './constants'; 7 | import { LocalStrategy } from './stratagies/local.stratagy'; 8 | import { AuthController } from './auth.controller'; 9 | import { JwtStrategy } from './stratagies/jwt.stratage'; 10 | 11 | @Module({ 12 | imports: [ 13 | forwardRef(() => UsersModule), 14 | PassportModule.register({ defaultStrategy: 'jwt' }), 15 | JwtModule.registerAsync({ 16 | useFactory: () => ({ 17 | secret: jwtSecret, 18 | signOptions: { expiresIn: '24h' } 19 | }) 20 | })], 21 | controllers:[AuthController], 22 | providers: [AuthService, JwtStrategy, LocalStrategy], 23 | exports: [AuthService,JwtStrategy, LocalStrategy], 24 | }) 25 | export class AuthModule {} 26 | -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/auth/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { forwardRef, Inject, Injectable } from '@nestjs/common'; 2 | import {JwtService} from '@nestjs/jwt' 3 | import { User } from '../users/entities/user'; 4 | import { UsersService } from '../users/users.services'; 5 | import { jwtSecret } from './constants'; 6 | import * as bcrypt from 'bcrypt'; 7 | import { Field, ObjectType } from '@nestjs/graphql'; 8 | import { NewUserInput } from '../../components/users/dto/new-user.input'; 9 | 10 | @ObjectType() 11 | export class LoginResponse{ 12 | @Field() 13 | access_token: string; 14 | // @Field( ()=>User) 15 | // user: User; 16 | } 17 | @Injectable() 18 | export class AuthService { 19 | constructor( 20 | private readonly userService: UsersService, 21 | private readonly jwtService: JwtService,) {} 22 | 23 | async validate(email: string, password: string): Promise { 24 | 25 | const user = await this.userService.findByUserEmail(email) 26 | console.log(`User with email : ${email} is trying to login`) 27 | if (!user) { 28 | return null; 29 | } 30 | const passwordIsValid = await bcrypt.compare(password, user.password); 31 | console.log('The user input password is valid :', passwordIsValid) 32 | if (!passwordIsValid) { 33 | return null; 34 | } else { 35 | 36 | return user; 37 | } 38 | } 39 | 40 | async login(user: User): Promise { 41 | 42 | const payload = { id: user.id, email:user.email } 43 | return{ access_token:this.jwtService.sign(payload)} 44 | } 45 | 46 | async signup(NewUserDate:NewUserInput): Promise { 47 | 48 | return await this.userService.createUser(NewUserDate); 49 | } 50 | 51 | async verify(token: string): Promise { 52 | 53 | const decoded = this.jwtService.verify(token, { secret: jwtSecret }); 54 | const user = await this.userService.findByUserEmail(decoded.email) 55 | 56 | if (!user) { 57 | throw new Error('Unable to get the user from the token service!') 58 | } 59 | 60 | return user; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/auth/constants.ts: -------------------------------------------------------------------------------- 1 | export const jwtSecret = 'hard!to-guess_secret'; -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/auth/current_user.decorator.ts: -------------------------------------------------------------------------------- 1 | 2 | import { createParamDecorator, ExecutionContext } from "@nestjs/common"; 3 | import { GqlExecutionContext } from "@nestjs/graphql"; 4 | 5 | export const CurrentUser = createParamDecorator( 6 | (_data: unknown, context: ExecutionContext) => { 7 | if (context.getType() === 'http') { 8 | return context.switchToHttp().getRequest().user; 9 | } 10 | 11 | const ctx = GqlExecutionContext.create(context); 12 | return ctx.getContext().req.user; 13 | } 14 | ) -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/auth/guards/gql-auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { ExecutionContext } from "@nestjs/common"; 2 | import { GqlExecutionContext } from "@nestjs/graphql"; 3 | import { AuthGuard } from "@nestjs/passport"; 4 | 5 | 6 | export class GqlAuthGuard extends AuthGuard('jwt') { 7 | getRequest(context: ExecutionContext): any{ 8 | const ctx = GqlExecutionContext.create(context); 9 | return ctx.getContext().req; 10 | } 11 | } -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/auth/guards/jwt-auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { AuthGuard } from "@nestjs/passport"; 2 | 3 | export class JwtAuthGuard extends AuthGuard('jwt') {} -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/auth/guards/local-host.guard.ts: -------------------------------------------------------------------------------- 1 | import { AuthGuard } from "@nestjs/passport"; 2 | 3 | export class LocalAuthGuard extends AuthGuard('local') {} 4 | 5 | -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/auth/stratagies/jwt.stratage.ts: -------------------------------------------------------------------------------- 1 | import { ExtractJwt, Strategy } from 'passport-jwt'; 2 | import { PassportStrategy } from '@nestjs/passport'; 3 | import { Injectable } from '@nestjs/common'; 4 | import { jwtSecret } from '../constants'; 5 | import { User } from '../../users/entities/user'; 6 | import { UsersService } from '../../users/users.services'; 7 | 8 | @Injectable() 9 | export class JwtStrategy extends PassportStrategy(Strategy) { 10 | constructor( 11 | private readonly usersService: UsersService 12 | ) { 13 | super({ 14 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 15 | ignoreExpiration: false, 16 | secretOrKey: jwtSecret, 17 | }); 18 | } 19 | 20 | async validate(payload: any): Promise { 21 | const { id, email } = payload; 22 | return this.usersService.findByUserEmail(payload.email); 23 | } 24 | } -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/auth/stratagies/local.stratagy.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, UnauthorizedException } from '@nestjs/common'; 2 | import { PassportStrategy } from '@nestjs/passport'; 3 | import { Strategy } from 'passport-local'; 4 | import { User } from '../../users/entities/user'; 5 | import { AuthService } from '../auth.service'; 6 | 7 | @Injectable() 8 | export class LocalStrategy extends PassportStrategy(Strategy) { 9 | constructor( 10 | private readonly authService: AuthService 11 | ) { 12 | super({ usernameField: 'email' }); 13 | } 14 | 15 | async validate(email: string, password: string): Promise{ 16 | 17 | const user = await this.authService.validate(email, password) 18 | 19 | if (!user) { 20 | throw new UnauthorizedException(); 21 | } 22 | return user; 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/cars/cars.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { CarsResolver } from './cars.resolver'; 4 | import { CarsService } from './cars.services'; 5 | import { Car } from './entities/car'; 6 | 7 | @Module({ 8 | imports: [TypeOrmModule.forFeature([Car])], 9 | providers: [CarsService, CarsResolver], 10 | exports: [CarsService], 11 | }) 12 | export class CarsModule {} -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/cars/cars.resolver.ts: -------------------------------------------------------------------------------- 1 | import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; 2 | import { Console } from 'console'; 3 | import { CarsService } from './cars.services'; 4 | import { NewCarInput } from './dto/new-car.input'; 5 | import { Car } from './entities/car'; 6 | import { UpdateCarInput } from './dto/update-car.input'; 7 | import { OrderQuantityInput } from './dto/order-quantity-input'; 8 | 9 | @Resolver() 10 | export class CarsResolver { 11 | constructor(private carsService: CarsService) {} 12 | 13 | @Query((returns) => [Car]) 14 | public async cars(): Promise { 15 | return await this.carsService.getAllCars().catch((err) => { 16 | throw err; 17 | }); 18 | } 19 | 20 | @Query((returns) => [Car]) 21 | public async findOrderCars(@Args('ordersId') id: string): Promise { 22 | return await this.carsService.findOrderCars(id).catch((err) => { 23 | throw err; 24 | }); 25 | } 26 | 27 | @Query((returns) => [Car]) 28 | public async findByCategory(@Args('category') category: string): Promise { 29 | return await this.carsService.findByCategory(category).catch((err) => { 30 | throw err; 31 | }); 32 | } 33 | 34 | @Query((returns) => [Car]) 35 | public async findByDrivetrain(@Args('driveTrain') driveTrain: string): Promise { 36 | return await this.carsService.findByDrivetrain(driveTrain).catch((err) => { 37 | throw err; 38 | }); 39 | } 40 | 41 | @Query((returns) => Car) 42 | public async findByName(@Args('name') name: string): Promise { 43 | return await this.carsService.findByName(name).catch((err) => { 44 | throw err; 45 | }); 46 | } 47 | 48 | @Query((returns) => String) 49 | public async CheckAvailable(@Args('name') name: string) { 50 | return await this.carsService.checkAvailable(name).catch((err) => { 51 | throw err; 52 | }); 53 | } 54 | 55 | @Mutation(() => Boolean) 56 | public async deleteOne(@Args('id') id: string) { 57 | return await this.carsService.deleteCar(id).catch((err) => { 58 | throw err; 59 | }); 60 | } 61 | 62 | @Mutation((returns) => Car) 63 | public async addNewCar( 64 | @Args('newCarData') newCarData: NewCarInput, 65 | ): Promise { 66 | return await this.carsService.addCar(newCarData).catch((err) => { 67 | throw err; 68 | }); 69 | } 70 | 71 | @Mutation((returns) => Car) 72 | public async updateCar(@Args('name') name: string, 73 | @Args('updateData') updateData: UpdateCarInput, 74 | ): Promise { 75 | return await this.carsService.updateCarInfo(name,updateData).catch((err) => { 76 | throw err; 77 | }); 78 | } 79 | 80 | @Mutation((returns) => Car) 81 | public async updateQuantity(@Args('name') name: string, 82 | @Args('orderQuantity') orderQuantity: OrderQuantityInput, 83 | ): Promise { 84 | return await this.carsService.updateCarQuantity(name,orderQuantity).catch((err) => { 85 | throw err; 86 | }); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/cars/cars.services.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, InternalServerErrorException } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { truncate } from 'fs'; 4 | import { getConnection, Repository } from 'typeorm'; 5 | import { NewCarInput } from './dto/new-car.input'; 6 | import { UpdateCarInput } from './dto/update-car.input'; 7 | import { Car } from './entities/car'; 8 | import { OrderQuantityInput } from './dto/order-quantity-input'; 9 | import { Int } from '@nestjs/graphql'; 10 | import { Order } from '../orders/entities/order'; 11 | 12 | @Injectable() 13 | export class CarsService { 14 | constructor(@InjectRepository(Car) private carRepository: Repository) {} 15 | 16 | public async getAllCars(): Promise { 17 | 18 | return await this.carRepository.find({}).catch((err) => { 19 | throw new InternalServerErrorException(); 20 | }); 21 | } 22 | 23 | public async findOrderCars(id: string): Promise{ 24 | 25 | // Error : connect "default " was not found ---> problem at the ormconfig-> database.module 26 | return await getConnection() 27 | .createQueryBuilder() 28 | .relation(Order, "cars") 29 | .of(id) 30 | .loadMany().catch((err) => { 31 | throw new InternalServerErrorException(); 32 | }); 33 | } 34 | 35 | public async findByCategory(category:string) :Promise{ 36 | 37 | return await this.carRepository.find({category:category }); 38 | 39 | } 40 | 41 | public async findByDrivetrain(driveTrain:string) :Promise{ 42 | 43 | return await this.carRepository.find({driveTrain:driveTrain }); 44 | 45 | } 46 | 47 | public async findByName(name:string) :Promise{ 48 | 49 | return await this.carRepository.findOne({ name }); 50 | 51 | } 52 | 53 | public async checkAvailable(name:string): Promise{ 54 | 55 | const thisModelCar = await this.carRepository.findOne({ name }); 56 | let avaliableNumber = thisModelCar.quantity; 57 | 58 | if (avaliableNumber < 1) { 59 | // raise alert or send custom err message for this one 60 | alert("Sorry! No availble cars for this model") 61 | return "No availibale cars for this model"; 62 | }else{ 63 | return "There are "+avaliableNumber+` ${name}`+" avaliable now"; 64 | } 65 | 66 | } 67 | 68 | public async addCar(newCarData: NewCarInput): Promise { 69 | 70 | const newCar = this.carRepository.create(newCarData); 71 | 72 | await this.carRepository.save(newCar).catch((err) => { 73 | new InternalServerErrorException(); 74 | }); 75 | 76 | return newCar; 77 | } 78 | 79 | 80 | public async updateCarInfo(name: string, updateCarData: UpdateCarInput): Promise { 81 | 82 | await this.carRepository.update(name, 83 | { 84 | dailyPrice: updateCarData.dailyPrice, 85 | monthlyPrice: updateCarData.monthlyPrice, 86 | mileage:updateCarData.mileage 87 | }); 88 | 89 | const updatedCar = await this.carRepository.findOne({ name }); 90 | 91 | return updatedCar; 92 | } 93 | 94 | public async deleteCar(id:string):Promise{ 95 | 96 | await this.carRepository.delete({ id }); 97 | 98 | return true; 99 | 100 | } 101 | 102 | public async updateCarQuantity(name: string, orderQuantity: OrderQuantityInput): Promise { 103 | 104 | let choosenCar = await this.carRepository.findOne({ name }); 105 | console.log(choosenCar.quantity); 106 | 107 | await this.carRepository.update(choosenCar, { 108 | quantity: choosenCar.quantity-orderQuantity.orderNumber 109 | }) 110 | 111 | const updatedQuantityCar = await this.carRepository.findOne({ name }); 112 | 113 | return updatedQuantityCar; 114 | } 115 | 116 | 117 | } -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/cars/dto/new-car.input.ts: -------------------------------------------------------------------------------- 1 | import { Field, InputType, Int } from '@nestjs/graphql'; 2 | import { Max, Min } from 'class-validator'; 3 | 4 | @InputType() 5 | export class NewCarInput { 6 | 7 | @Field() 8 | name: string; 9 | 10 | @Field() 11 | category: string; 12 | 13 | 14 | @Field() 15 | year: string; 16 | 17 | 18 | @Field((type) => Int) 19 | @Max(100, { message: "we are not No#1 in NZ" }) 20 | @Min(5) 21 | quantity: number; 22 | 23 | @Field((type) => Int) 24 | @Max(9000) 25 | @Min(900) 26 | monthlyPrice: number; 27 | 28 | @Field((type) => Int) 29 | @Max(800) 30 | @Min(10, { message: "Daily price can't be that low!" }) 31 | dailyPrice: number; 32 | 33 | @Field() 34 | mileage: string; 35 | 36 | @Field() 37 | gas: string; 38 | 39 | @Field() 40 | gearType: string; 41 | 42 | @Field() 43 | driveTrain: string; 44 | 45 | @Field() 46 | thumbnailUrl: string; 47 | } 48 | -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/cars/dto/order-quantity-input.ts: -------------------------------------------------------------------------------- 1 | import { Field, InputType, Int } from '@nestjs/graphql'; 2 | import { Max, Min } from 'class-validator'; 3 | 4 | @InputType() 5 | export class OrderQuantityInput { 6 | @Field((type) => Int) 7 | @Max(10, { message: "what you need more than 10 cars for?" }) 8 | @Min(1) 9 | orderNumber: number; 10 | 11 | } 12 | 13 | -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/cars/dto/update-car.input.ts: -------------------------------------------------------------------------------- 1 | import { Field, InputType, Int } from '@nestjs/graphql'; 2 | import { Max, Min } from 'class-validator'; 3 | 4 | @InputType() 5 | export class UpdateCarInput { 6 | 7 | @Field((type) => Int) 8 | @Max(9000) 9 | @Min(900) 10 | monthlyPrice: number; 11 | 12 | @Field((type) => Int) 13 | @Max(800) 14 | @Min(10, { message: "Daily price can't be that low!" }) 15 | dailyPrice: number; 16 | 17 | @Field({nullable:true}) 18 | mileage: string; 19 | 20 | @Field({nullable:true}) 21 | thumbnailUrl: string; 22 | } 23 | -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/cars/entities/car.ts: -------------------------------------------------------------------------------- 1 | import { Field, ObjectType } from '@nestjs/graphql'; 2 | import { Order } from 'src/components/orders/entities/order'; 3 | import { Column, Entity, JoinTable, ManyToMany, PrimaryGeneratedColumn } from 'typeorm'; 4 | 5 | @Entity({ name: 'cars' }) 6 | @ObjectType() 7 | export class Car { 8 | 9 | @PrimaryGeneratedColumn('uuid') 10 | @Field() 11 | id: string; 12 | 13 | @Column() 14 | @Field() 15 | name: string; 16 | 17 | @Column() 18 | @Field() 19 | category: string; 20 | 21 | @Column() 22 | @Field() 23 | year: string; 24 | 25 | @Column() 26 | @Field() 27 | quantity: number; 28 | 29 | @Column() 30 | @Field() 31 | dailyPrice: number; 32 | 33 | @Column() 34 | @Field() 35 | monthlyPrice: number; 36 | 37 | @Column() 38 | @Field() 39 | mileage: string; 40 | 41 | @Column() 42 | @Field() 43 | gas: string; 44 | 45 | @Column() 46 | @Field() 47 | gearType: string; 48 | 49 | @Column() 50 | @Field() 51 | driveTrain: string; 52 | 53 | @Column() 54 | @Field() 55 | thumbnailUrl: string; 56 | 57 | @ManyToMany(() => Order, orders => orders.cars) 58 | @JoinTable() 59 | orders: Order[]; 60 | } 61 | -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/components.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AuthModule } from './auth/auth.module'; 3 | import { CarsModule } from './cars/cars.module'; 4 | import { OrdersModule } from './orders/orders.module'; 5 | import { UsersModule } from './users/users.module'; 6 | 7 | 8 | @Module({ 9 | imports: [CarsModule,UsersModule,OrdersModule,AuthModule], 10 | }) 11 | export class ComponentsModule {} -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/orders/dto/new-order.input.ts: -------------------------------------------------------------------------------- 1 | import { Field, InputType, Int } from '@nestjs/graphql'; 2 | import { Max, Min } from 'class-validator'; 3 | import { User } from '../../users/entities/user'; 4 | import { Car } from '../../cars/entities/car'; 5 | import { JoinColumn, RelationId } from 'typeorm'; 6 | import { CarsModule } from '../../cars/cars.module'; 7 | import { userInfo } from 'os'; 8 | 9 | 10 | @InputType() 11 | export class NewOrderInput { 12 | 13 | @Field() 14 | ownerId: string 15 | 16 | @Field(type=>Int) 17 | @Min(20) 18 | amount: number; 19 | 20 | @Field() 21 | startDate: string; 22 | 23 | @Field() 24 | endDate: string; 25 | 26 | 27 | @Field(type=>Int) 28 | @Min(1) 29 | @Max(120) 30 | duration: number; 31 | 32 | //to-do:change this input field to make the relation saves when create new order-data 33 | @Field() 34 | orderedCars: string; 35 | 36 | 37 | 38 | 39 | } -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/orders/dto/update-order.input.ts: -------------------------------------------------------------------------------- 1 | import { Field, InputType, Int } from '@nestjs/graphql'; 2 | import { Max, Min } from 'class-validator'; 3 | 4 | @InputType() 5 | export class UpdateOrderInput { 6 | 7 | 8 | @Field() 9 | startDate: string; 10 | 11 | @Field() 12 | endDate: string; 13 | 14 | 15 | @Field(type=>Int) 16 | @Min(1) 17 | @Max(120) 18 | duration: number; 19 | 20 | @Field(type=>Int) 21 | @Min(20) 22 | amount: number; 23 | 24 | @Field() 25 | orderedCars: String; 26 | 27 | 28 | 29 | } -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/orders/entities/order.ts: -------------------------------------------------------------------------------- 1 | import { Field, ObjectType } from '@nestjs/graphql'; 2 | import { Column, Entity, PrimaryGeneratedColumn, ManyToOne, RelationId, JoinColumn, BaseEntity, OneToMany, ManyToMany, CreateDateColumn } from 'typeorm'; 3 | import { IsDate } from 'class-validator'; 4 | import { Car } from 'src/components/cars/entities/car'; 5 | import { User } from 'src/components/users/entities/user'; 6 | 7 | @Entity({ name: 'orders' }) 8 | @ObjectType() 9 | 10 | export class Order { 11 | 12 | @PrimaryGeneratedColumn('uuid') 13 | @Field() 14 | id: string; 15 | 16 | @Column() 17 | @Field() 18 | amount: number; 19 | 20 | @CreateDateColumn() 21 | createTime: Date; 22 | 23 | @CreateDateColumn() 24 | updateTime:Date 25 | 26 | @Column() 27 | @Field() 28 | startDate: string 29 | 30 | @Column() 31 | @Field() 32 | endDate:string 33 | 34 | @Column() 35 | @Field() 36 | duration: number; 37 | 38 | //foreignkey id for specific query 39 | @Column() 40 | ownerId: string 41 | 42 | @ManyToOne(() => User, user => user.orders, { onDelete: 'CASCADE' }) 43 | owner: User; 44 | 45 | @ManyToMany(() => Car, cars => cars.orders) 46 | cars: Car[] 47 | 48 | //to-do: make this fuctional with neworder.input 49 | addCars(cars: Car) { 50 | if (this.cars == null) { 51 | this.cars = new Array(); 52 | } 53 | this.cars.push(cars) 54 | } 55 | 56 | 57 | // to-do: need to save the new order while saving the related carId to middleware table 58 | @Column() 59 | @Field() 60 | orderedCars: string; 61 | 62 | } 63 | -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/orders/orders.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { OrdersResolver } from './orders.resolvers'; 4 | import { OrdersService } from './orders.services'; 5 | import { Order } from './entities/order'; 6 | 7 | @Module({ 8 | imports: [TypeOrmModule.forFeature([Order])], 9 | providers: [OrdersService, OrdersResolver], 10 | exports: [OrdersService], 11 | }) 12 | export class OrdersModule {} -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/orders/orders.resolvers.ts: -------------------------------------------------------------------------------- 1 | import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; 2 | import { Console } from 'console'; 3 | import { OrdersService } from './orders.services'; 4 | import { NewOrderInput } from './dto/new-order.input'; 5 | import { Order } from './entities/order'; 6 | 7 | 8 | @Resolver() 9 | export class OrdersResolver { 10 | constructor(private orderService: OrdersService) { } 11 | 12 | @Query((returns) => [Order]) 13 | public async getAllOrders(): Promise { 14 | return await this.orderService.getAllOrders().catch((err) => { 15 | throw err; 16 | }); 17 | } 18 | 19 | @Query((returns) => [Order]) 20 | public async getUserOrders(@Args('ownerId') ownerId:string): Promise { 21 | return await this.orderService.getUserOrders(ownerId).catch((err) => { 22 | throw err; 23 | }); 24 | } 25 | @Query((returns) => [Order]) 26 | public async getCarOrders(@Args('carsId') carsId:string): Promise { 27 | return await this.orderService.getCarOrders(carsId).catch((err) => { 28 | throw err; 29 | }); 30 | } 31 | 32 | @Mutation((returns) => Order) 33 | public async addNewOrder( 34 | @Args('newOrderData') newOrderData: NewOrderInput,@Args('carsId') carsId:string 35 | ): Promise { 36 | return await this.orderService.addOrder(newOrderData,carsId).catch((err) => { 37 | throw err; 38 | }); 39 | } 40 | 41 | @Mutation(() => Boolean) 42 | public async deleteAllOrders(){ 43 | await this.orderService.deleteAllOrders().catch((err) => { 44 | throw err; 45 | }); 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/orders/orders.services.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, InternalServerErrorException } from '@nestjs/common'; 2 | import { InjectRepository,InjectEntityManager, TypeOrmModule } from '@nestjs/typeorm'; 3 | import { Connection, Repository ,getConnection,getConnectionManager,getRepository} from 'typeorm'; 4 | import { NewOrderInput } from './dto/new-order.input'; 5 | import { UpdateOrderInput } from './dto/update-order.input'; 6 | import { Order } from './entities/order'; 7 | import { User } from '../users/entities/user'; 8 | import { Car } from '../cars/entities/car'; 9 | 10 | 11 | @Injectable() 12 | export class OrdersService { 13 | constructor( 14 | @InjectRepository(Order) 15 | private orderRepository: Repository) {} 16 | 17 | public async getAllOrders(): Promise { 18 | 19 | return await this.orderRepository.find({}).catch((err) => { 20 | throw new InternalServerErrorException(); 21 | }); 22 | } 23 | 24 | public async getCarOrders(id: string): Promise{ 25 | return await getConnection() 26 | .createQueryBuilder() 27 | .relation(Car, "orders") 28 | .of(id) 29 | .loadMany().catch((err) => { 30 | throw new InternalServerErrorException(); 31 | }); 32 | 33 | } 34 | 35 | public async getUserOrders(ownerId: string): Promise { 36 | 37 | return await this.orderRepository.find({relations:['owner'],where:{owner:{id:ownerId}}}).catch((err) => { 38 | throw new InternalServerErrorException(); 39 | }); 40 | } 41 | 42 | public async deleteAllOrders(): Promise { 43 | 44 | await this.orderRepository.delete({}).catch((err) => { 45 | throw new InternalServerErrorException(); 46 | }); 47 | return true; 48 | } 49 | 50 | public async addOrder(NewOrderData: NewOrderInput, carsId: string): Promise { 51 | 52 | const userId = NewOrderData.ownerId; 53 | // covert the simple string to carId array 54 | const carsIdArray = carsId.split(",") 55 | const newOrder = this.orderRepository.create(NewOrderData); 56 | await this.orderRepository.save(newOrder); 57 | // achieve manytoone and manytomany relation connection 58 | await getConnection() 59 | .createQueryBuilder() 60 | .relation(Order, "cars") 61 | .of(newOrder) 62 | .add(carsIdArray); 63 | 64 | await getConnection() 65 | .createQueryBuilder() 66 | .relation(Order, "owner") 67 | .of(newOrder) 68 | .set(userId); 69 | 70 | return newOrder; 71 | } 72 | 73 | 74 | } -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/orders/orders.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daydreamerli/nestjs-graphql-react-fullstack/d6dd1dec1eda1130c162936a254ff3614daae21c/nestjs-cars-app/src/components/orders/orders.ts -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/users/dto/new-user.input.ts: -------------------------------------------------------------------------------- 1 | import { Field, InputType, Int } from '@nestjs/graphql'; 2 | import { IsEmail, IS_NUMBER_STRING, Length, Max, min, Min, minLength } from 'class-validator'; 3 | import { validate } from 'graphql'; 4 | 5 | @InputType() 6 | export class NewUserInput { 7 | 8 | @Field() 9 | username: string; 10 | 11 | @Field() 12 | @IsEmail() 13 | email: string; 14 | 15 | @Field() 16 | @Length(8,512) 17 | password: string; 18 | 19 | @Field({nullable:true}) 20 | country: string; 21 | 22 | @Field({nullable:true}) 23 | thumbnailUrl: string; 24 | 25 | } 26 | 27 | 28 | -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/users/dto/update-user.input.ts: -------------------------------------------------------------------------------- 1 | import { Field, InputType, Int } from '@nestjs/graphql'; 2 | import { Length, Max, Min } from 'class-validator'; 3 | 4 | @InputType() 5 | export class UpdateUserInput { 6 | 7 | @Field() 8 | username: string; 9 | 10 | @Field({nullable:true}) 11 | country: string; 12 | 13 | @Field({nullable:true}) 14 | thumbnailUrl: string; 15 | 16 | } -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/users/entities/user.ts: -------------------------------------------------------------------------------- 1 | import { Field, ObjectType } from '@nestjs/graphql'; 2 | import { IsEmail, isUUID, Length, minLength } from 'class-validator'; 3 | import { Order } from 'src/components/orders/entities/order'; 4 | import { Column, Entity, PrimaryGeneratedColumn ,OneToMany, BaseEntity, JoinColumn} from 'typeorm'; 5 | 6 | @Entity({ name: 'users' }) 7 | @ObjectType() 8 | 9 | export class User extends BaseEntity{ 10 | 11 | @PrimaryGeneratedColumn('uuid') 12 | @Field() 13 | id: string; 14 | 15 | @Column({ nullable: false }) 16 | @Field() 17 | username: string; 18 | 19 | @Column({ unique: true,length: 128,nullable: false }) 20 | @Field() 21 | @IsEmail() 22 | email: string; 23 | 24 | @Column({ length: 512, nullable: false }) 25 | @Field() 26 | @Length(6,512) 27 | password: string; 28 | 29 | @Column({nullable:true}) 30 | @Field() 31 | country: string; 32 | 33 | @Column({ length: 512, nullable: true }) 34 | @Field() 35 | thumbnailUrl: string; 36 | 37 | @OneToMany(() => Order, order => order.owner, { cascade: true }) 38 | @JoinColumn() 39 | orders: Order[] 40 | 41 | addOrder(order: Order) { 42 | if (this.orders == null) { 43 | this.orders = new Array(); 44 | } 45 | this.orders.push(order) 46 | } 47 | 48 | 49 | } -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/users/user.decorator.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from '@nestjs/common'; 2 | import { GqlExecutionContext } from '@nestjs/graphql'; 3 | 4 | export const CurrentUser = createParamDecorator( 5 | (_data: unknown, context: ExecutionContext) => { 6 | if (context.getType() === 'http') { 7 | console.log(context.switchToHttp().getRequest().user.id) 8 | return context.switchToHttp().getRequest().user; 9 | } 10 | 11 | const ctx = GqlExecutionContext.create(context); 12 | console.log("Current User: "+ctx.getContext().req.user.email) 13 | return ctx.getContext().req.user; 14 | } 15 | ) 16 | 17 | // export const CurrentUser = createParamDecorator( 18 | // (data: unknown, context: ExecutionContext) => { 19 | // console.log(GqlExecutionContext.create(context).getContext().req.user.id ); 20 | // const { id,email } = GqlExecutionContext.create( 21 | // context, 22 | // ).getContext().req.user; 23 | // return { 24 | // id,email 25 | // }; 26 | // }, 27 | // ); -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/users/users.module.ts: -------------------------------------------------------------------------------- 1 | import { forwardRef, Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { UsersResolver } from './users.resolver'; 4 | import { UsersService } from './users.services'; 5 | import { User } from './entities/user'; 6 | import { AuthModule } from '../auth/auth.module'; 7 | 8 | 9 | 10 | @Module({ 11 | imports: [TypeOrmModule.forFeature([User]),forwardRef(() => AuthModule) 12 | ], 13 | providers: [UsersService, UsersResolver], 14 | exports: [UsersService], 15 | }) 16 | export class UsersModule {} -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/users/users.resolver.ts: -------------------------------------------------------------------------------- 1 | import { Args, Mutation, Query, Resolver, Context } from '@nestjs/graphql'; 2 | import { UsersService } from './users.services'; 3 | import { NewUserInput } from './dto/new-user.input'; 4 | import { UpdateUserInput } from './dto/update-user.input'; 5 | import { User } from './entities/user'; 6 | import { CurrentUser } from './user.decorator'; 7 | import { SimpleConsoleLogger } from 'typeorm'; 8 | import { HttpException, HttpStatus, NotFoundException, UseGuards } from '@nestjs/common'; 9 | import { GqlAuthGuard } from '../auth/guards/gql-auth.guard'; 10 | import { LoginResponse } from '../auth/auth.service'; 11 | 12 | 13 | @Resolver() 14 | export class UsersResolver { 15 | constructor(private userService: UsersService) { } 16 | 17 | @Query((returns) => [User]) 18 | public async getAllUsers(): Promise { 19 | return await this.userService.getAllUsers().catch((err) => { 20 | throw err; 21 | }); 22 | } 23 | 24 | @Query((returns) => User) 25 | @UseGuards(GqlAuthGuard) 26 | public async finduserById(@Args('id') id: string): Promise { 27 | return await this.userService.getUserById(id).catch((err) => { 28 | throw err; 29 | }); 30 | } 31 | 32 | @Query((returns) => User) 33 | @UseGuards(GqlAuthGuard) 34 | public async finduserByEmail(@Args('email') email: string): Promise { 35 | return await this.userService.findByUserEmail(email).catch((err) => { 36 | throw err; 37 | }); 38 | } 39 | // use mutation create jwt for user login request 40 | @Mutation((returns) => LoginResponse) 41 | public async login( 42 | @Args('email') email: string, 43 | @Args('password') password: string, 44 | ) { 45 | try { 46 | return await this.userService.login(email,password); 47 | } catch (err) { 48 | console.error(err); 49 | } 50 | } 51 | 52 | @Query(() => User) 53 | @UseGuards(GqlAuthGuard) 54 | public async CurrentUser(@CurrentUser() user: User) { 55 | try { 56 | return await this.userService.getUserById(user.id); 57 | } catch (err) { 58 | console.error(err); 59 | } 60 | } 61 | 62 | @Mutation((returns) => User) 63 | public async addNewUser( 64 | @Args('newUserData') newUserData: NewUserInput, 65 | ): Promise { 66 | return await this.userService.createUser(newUserData).catch((err) => { 67 | throw err; 68 | }); 69 | } 70 | 71 | @Mutation(() => User) 72 | @UseGuards(GqlAuthGuard) 73 | async UpdateUserPass( 74 | @CurrentUser() user: User, 75 | @Args('currPass') currPass: string, 76 | @Args('newPass') newPass: string, 77 | ) { 78 | try { 79 | return await this.userService.updatePassword(user.id,currPass,newPass); 80 | } catch (err) { 81 | console.error(err); 82 | } 83 | } 84 | @Mutation((returns) => User) 85 | @UseGuards(GqlAuthGuard) 86 | public async updateUserInfo(@CurrentUser() user: User, 87 | @Args('updateData') updateData: UpdateUserInput, 88 | ): Promise { 89 | return await this.userService.updateUserInfo(user.id, updateData).catch((err) => { 90 | throw err; 91 | }); 92 | } 93 | 94 | @Mutation(() => Boolean) 95 | @UseGuards(GqlAuthGuard) 96 | public async deleteUser(@CurrentUser() user: User) { 97 | return await this.userService.deleteUser(user.id).catch((err) => { 98 | throw err; 99 | }); 100 | } 101 | 102 | //will delete this function at production code 103 | @Mutation((returns) => Boolean) 104 | public async deleteAllUsers() { 105 | return await this.userService.deleteAllUsers().catch((err) => { 106 | throw err; 107 | }); 108 | } 109 | 110 | } -------------------------------------------------------------------------------- /nestjs-cars-app/src/components/users/users.services.ts: -------------------------------------------------------------------------------- 1 | import { forwardRef, Inject, Injectable, InternalServerErrorException } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { truncate } from 'fs'; 4 | import { Repository } from 'typeorm'; 5 | import { NewUserInput } from './dto/new-user.input'; 6 | import { UpdateUserInput } from './dto/update-user.input'; 7 | import { User } from './entities/user'; 8 | import * as bcrypt from 'bcrypt'; 9 | import { GraphQLError } from 'graphql'; 10 | import { JwtService } from '@nestjs/jwt'; 11 | import { AuthService, LoginResponse } from '../auth/auth.service'; 12 | 13 | 14 | @Injectable() 15 | export class UsersService { 16 | 17 | constructor(@Inject(forwardRef(() => AuthService)) 18 | private authService: AuthService, 19 | @InjectRepository(User) private userRepository: Repository) { } 20 | 21 | 22 | public async getAllUsers(): Promise { 23 | 24 | return await this.userRepository.find().catch((err) => { 25 | throw new InternalServerErrorException(); 26 | }); 27 | } 28 | 29 | public async getUserById(id: string): Promise { 30 | 31 | return await this.userRepository.findOneOrFail(id,{relations:['orders']}).catch((err) => { 32 | throw new InternalServerErrorException(); 33 | }); 34 | } 35 | 36 | public async findByUserEmail(email:string) :Promise{ 37 | 38 | return await this.userRepository.findOne({ email}).catch((err) => { 39 | throw alert(`User with ${email} doesn't exists!Please check the email again!`); 40 | }); 41 | } 42 | 43 | public async login(email: string, password: string): Promise< LoginResponse> { 44 | 45 | const IsvalidUser = await this.authService.validate(email, password); 46 | 47 | if (!IsvalidUser) { 48 | return null; 49 | } else { 50 | return await this.authService.login(IsvalidUser); 51 | } 52 | // try { 53 | // const user = await this.userRepository.findOne({ email }); 54 | // return user && (await bcrypt.compare(password, user.password)) 55 | // ? await this.jwtService.signAsync({ email, id: user.id }) 56 | // : new GraphQLError('Sorry, wrong password/email'); 57 | // } catch (err) { 58 | // console.error(err); 59 | // } 60 | } 61 | 62 | public async createUser(newUserData: NewUserInput): Promise { 63 | 64 | newUserData.password = await bcrypt 65 | .hash(newUserData.password, 10) 66 | .then((r)=>r) 67 | 68 | const newUser = this.userRepository.create(newUserData); 69 | 70 | await this.userRepository.save(newUser).catch((err) => { 71 | new InternalServerErrorException(); 72 | }); 73 | 74 | return newUser; 75 | } 76 | 77 | public async updatePassword(id: string, password:string, newPass:string) { 78 | try { 79 | const user = await this.userRepository.findOne(id); 80 | if (await bcrypt.compare(password, user.password)) { 81 | user.password = await bcrypt.hash(newPass, 10); 82 | return await this.userRepository.save(user); 83 | } 84 | } catch (err) { 85 | console.error(err); 86 | } 87 | } 88 | 89 | public async updateUserInfo(id: string, updateUserData: UpdateUserInput): Promise { 90 | 91 | const user = await this.userRepository.findOne({ id }); 92 | 93 | await this.userRepository.update(id, 94 | { 95 | username: updateUserData.username, 96 | country: updateUserData.country, 97 | thumbnailUrl: updateUserData.thumbnailUrl, 98 | }); 99 | 100 | return user; 101 | } 102 | 103 | public async deleteUser(id: string){ 104 | 105 | await this.userRepository.delete({ id }).catch((err) => { 106 | throw new InternalServerErrorException() 107 | }); 108 | 109 | return true; 110 | } 111 | 112 | public async deleteAllUsers(){ 113 | 114 | await this.userRepository.delete({}).catch((err) => { 115 | throw new InternalServerErrorException(); 116 | }); 117 | 118 | return true; 119 | 120 | } 121 | 122 | 123 | } -------------------------------------------------------------------------------- /nestjs-cars-app/src/database/database.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { Connection, getConnectionOptions } from 'typeorm'; 3 | import { TypeOrmModule } from '@nestjs/typeorm'; 4 | 5 | @Module({ 6 | imports: [ 7 | TypeOrmModule.forRootAsync({ 8 | useFactory: async () => 9 | Object.assign( 10 | await getConnectionOptions( 11 | process.env.NODE_ENV === 'production' ? 'prod' : 'default', 12 | ), 13 | ), 14 | }), 15 | ], 16 | exports: [TypeOrmModule], 17 | }) 18 | export class DatabaseModule { 19 | constructor(connection: Connection) { 20 | if (connection.isConnected) console.log('DB Connected Successfully!'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /nestjs-cars-app/src/main.ts: -------------------------------------------------------------------------------- 1 | import { ValidationPipe } from '@nestjs/common'; 2 | import { BaseExceptionFilter, NestFactory } from '@nestjs/core'; 3 | import { AppModule } from './app.module'; 4 | import { config } from 'dotenv'; 5 | config(); 6 | 7 | async function bootstrap() { 8 | const app = await NestFactory.create(AppModule); 9 | app.useGlobalPipes(new ValidationPipe()); 10 | 11 | app.enableCors(); 12 | 13 | await app.listen(process.env.PORT || 9000); 14 | } 15 | bootstrap(); 16 | -------------------------------------------------------------------------------- /nestjs-cars-app/src/migrations/1622304168366-CreateDatabase.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from 'typeorm'; 2 | 3 | export class CreateDatabase1622304168366 implements MigrationInterface { 4 | 5 | public async up(queryRunner: QueryRunner): Promise { 6 | queryRunner.createDatabase('mycars', true); 7 | } 8 | 9 | public async down(queryRunner: QueryRunner): Promise { 10 | queryRunner.dropDatabase('mycars', true); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /nestjs-cars-app/src/schema.gql: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------ 2 | # THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) 3 | # ------------------------------------------------------ 4 | 5 | type Car { 6 | category: String! 7 | dailyPrice: Float! 8 | driveTrain: String! 9 | gas: String! 10 | gearType: String! 11 | id: String! 12 | mileage: String! 13 | monthlyPrice: Float! 14 | name: String! 15 | quantity: Float! 16 | thumbnailUrl: String! 17 | year: String! 18 | } 19 | 20 | type LoginResponse { 21 | access_token: String! 22 | } 23 | 24 | type Mutation { 25 | UpdateUserPass(currPass: String!, newPass: String!): User! 26 | addNewCar(newCarData: NewCarInput!): Car! 27 | addNewOrder(carsId: String!, newOrderData: NewOrderInput!): Order! 28 | addNewUser(newUserData: NewUserInput!): User! 29 | deleteAllOrders: Boolean! 30 | deleteAllUsers: Boolean! 31 | deleteOne(id: String!): Boolean! 32 | deleteUser: Boolean! 33 | login(email: String!, password: String!): LoginResponse! 34 | updateCar(name: String!, updateData: UpdateCarInput!): Car! 35 | updateQuantity(name: String!, orderQuantity: OrderQuantityInput!): Car! 36 | updateUserInfo(updateData: UpdateUserInput!): User! 37 | } 38 | 39 | input NewCarInput { 40 | category: String! 41 | dailyPrice: Int! 42 | driveTrain: String! 43 | gas: String! 44 | gearType: String! 45 | mileage: String! 46 | monthlyPrice: Int! 47 | name: String! 48 | quantity: Int! 49 | thumbnailUrl: String! 50 | year: String! 51 | } 52 | 53 | input NewOrderInput { 54 | amount: Int! 55 | duration: Int! 56 | endDate: String! 57 | orderedCars: String! 58 | ownerId: String! 59 | startDate: String! 60 | } 61 | 62 | input NewUserInput { 63 | country: String 64 | email: String! 65 | password: String! 66 | thumbnailUrl: String 67 | username: String! 68 | } 69 | 70 | type Order { 71 | amount: Float! 72 | duration: Float! 73 | endDate: String! 74 | id: String! 75 | orderedCars: String! 76 | startDate: String! 77 | } 78 | 79 | input OrderQuantityInput { 80 | orderNumber: Int! 81 | } 82 | 83 | type Query { 84 | CheckAvailable(name: String!): String! 85 | CurrentUser: User! 86 | cars: [Car!]! 87 | findByCategory(category: String!): [Car!]! 88 | findByDrivetrain(driveTrain: String!): [Car!]! 89 | findByName(name: String!): Car! 90 | findOrderCars(ordersId: String!): [Car!]! 91 | finduserByEmail(email: String!): User! 92 | finduserById(id: String!): User! 93 | getAllOrders: [Order!]! 94 | getAllUsers: [User!]! 95 | getCarOrders(carsId: String!): [Order!]! 96 | getUserOrders(ownerId: String!): [Order!]! 97 | } 98 | 99 | input UpdateCarInput { 100 | dailyPrice: Int! 101 | mileage: String 102 | monthlyPrice: Int! 103 | thumbnailUrl: String 104 | } 105 | 106 | input UpdateUserInput { 107 | country: String 108 | thumbnailUrl: String 109 | username: String! 110 | } 111 | 112 | type User { 113 | country: String! 114 | email: String! 115 | id: String! 116 | password: String! 117 | thumbnailUrl: String! 118 | username: String! 119 | } 120 | -------------------------------------------------------------------------------- /nestjs-cars-app/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from './../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /nestjs-cars-app/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /nestjs-cars-app/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /nestjs-cars-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /react-cars-app/.env: -------------------------------------------------------------------------------- 1 | PORT=3000 2 | REACT_APP_API_URL=http://localhost:9000 -------------------------------------------------------------------------------- /react-cars-app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /react-cars-app/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14 as production 2 | 3 | ARG NODE_ENV=production 4 | ENV NODE_ENV=${NODE_ENV} 5 | 6 | WORKDIR /usr/src/app 7 | 8 | COPY package.json . 9 | COPY yarn.lock . 10 | 11 | RUN yarn install --production=false 12 | 13 | COPY . . 14 | 15 | RUN yarn build 16 | 17 | CMD ["node", "server.js"] -------------------------------------------------------------------------------- /react-cars-app/HomePage_Demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daydreamerli/nestjs-graphql-react-fullstack/d6dd1dec1eda1130c162936a254ff3614daae21c/react-cars-app/HomePage_Demo.png -------------------------------------------------------------------------------- /react-cars-app/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app), using the [Redux](https://redux.js.org/) and [Redux Toolkit](https://redux-toolkit.js.org/) template. 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `yarn start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `yarn test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `yarn build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `yarn eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | -------------------------------------------------------------------------------- /react-cars-app/__generated__/globalTypes.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | // @generated 4 | // This file was automatically generated and should not be edited. 5 | 6 | //============================================================== 7 | // START Enums and Input Objects 8 | //============================================================== 9 | 10 | export interface NewUserInput { 11 | country?: string | null; 12 | email: string; 13 | password: string; 14 | thumbnailUrl?: string | null; 15 | username: string; 16 | } 17 | 18 | //============================================================== 19 | // END Enums and Input Objects 20 | //============================================================== 21 | -------------------------------------------------------------------------------- /react-cars-app/codegen.yml: -------------------------------------------------------------------------------- 1 | overwrite: true 2 | schema: http://localhost:9000/graphql 3 | # documents: './src/**/*.graphql' 4 | generates: 5 | ./src/generated/graphql.ts: 6 | plugins: 7 | - typescript 8 | - typescript-operations 9 | -------------------------------------------------------------------------------- /react-cars-app/craco.config.js: -------------------------------------------------------------------------------- 1 | // craco.config.js 2 | module.exports = { 3 | style: { 4 | postcss: { 5 | plugins: [require("tailwindcss"), require("autoprefixer")], 6 | }, 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /react-cars-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-cars-app", 3 | "version": "0.1.0", 4 | "description": "Industry project react-redux-tailwindcss frontend app", 5 | "author": "frankli", 6 | "proxy": "http://localhost:3000", 7 | "private": true, 8 | "license": "MIT", 9 | "dependencies": { 10 | "@apollo/client": "^3.3.19", 11 | "@apollo/react-hooks": "^4.0.0", 12 | "@brainhubeu/react-carousel": "^2.0.3", 13 | "@craco/craco": "^6.1.2", 14 | "@fortawesome/fontawesome-svg-core": "^1.2.35", 15 | "@fortawesome/free-brands-svg-icons": "^5.15.3", 16 | "@fortawesome/free-solid-svg-icons": "^5.15.3", 17 | "@fortawesome/react-fontawesome": "^0.1.14", 18 | "@material-ui/core": "^4.12.3", 19 | "@reduxjs/toolkit": "^1.5.1", 20 | "@testing-library/jest-dom": "^4.2.4", 21 | "@testing-library/react": "^9.3.2", 22 | "@testing-library/user-event": "^7.1.2", 23 | "@types/jest": "^24.0.0", 24 | "@types/node": "^12.0.0", 25 | "@types/react": "^16.9.0", 26 | "@types/react-cookies": "^0.1.0", 27 | "@types/react-dom": "^16.9.0", 28 | "@types/react-redux": "^7.1.7", 29 | "@types/react-responsive": "^8.0.2", 30 | "axios": "^0.21.4", 31 | "express": "^4.17.1", 32 | "graphql": "^15.5.0", 33 | "jwt-decode": "^3.1.2", 34 | "react": "^17.0.2", 35 | "react-burger-menu": "^3.0.6", 36 | "react-calendar": "^3.4.0", 37 | "react-cookie": "^4.1.1", 38 | "react-cookies": "^0.1.1", 39 | "react-dom": "^17.0.2", 40 | "react-hook-form": "^7.15.4", 41 | "react-redux": "^7.2.0", 42 | "react-responsive": "^8.2.0", 43 | "react-router-dom": "^5.3.0", 44 | "react-scripts": "4.0.3", 45 | "react-select-country-list": "^2.2.3", 46 | "react-spinners": "^0.11.0", 47 | "redux-logger": "^3.0.6", 48 | "reselect": "^4.0.0", 49 | "styled-components": "^5.3.0", 50 | "twin.macro": "^2.4.1", 51 | "typescript": "~4.1.5" 52 | }, 53 | "scripts": { 54 | "start": "craco start", 55 | "build": "craco build", 56 | "test": "craco test", 57 | "eject": "react-scripts eject", 58 | "codegen": "graphql-codegen --config ./codegen.yml", 59 | "schema:download": "npx apollo service:download --endpoint=http://localhost:9000/graphql ./src/app/graphql.schema.json", 60 | "schema:generate-watch": "npx apollo codegen:generate --localSchemaFile=./src/app/graphql.schema.json --target=typescript --tagName=gql --watch", 61 | "start:prod": "node ./server.js" 62 | }, 63 | "eslintConfig": { 64 | "extends": "react-app" 65 | }, 66 | "browserslist": { 67 | "production": [ 68 | ">0.2%", 69 | "not dead", 70 | "not op_mini all" 71 | ], 72 | "development": [ 73 | "last 1 chrome version", 74 | "last 1 firefox version", 75 | "last 1 safari version" 76 | ] 77 | }, 78 | "devDependencies": { 79 | "@graphql-codegen/cli": "^2.2.0", 80 | "@graphql-codegen/typescript": "^2.2.2", 81 | "@graphql-codegen/typescript-operations": "^2.1.4", 82 | "@graphql-codegen/typescript-react-apollo": "^3.1.4", 83 | "@types/graphql": "^14.5.0", 84 | "@types/react-calendar": "^3.1.6", 85 | "@types/react-router-dom": "^5.1.8", 86 | "@types/redux-logger": "^3.0.8", 87 | "@types/styled-components": "^5.1.9", 88 | "autoprefixer": "^9", 89 | "postcss": "^7", 90 | "tailwindcss": "npm:@tailwindcss/postcss7-compat" 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /react-cars-app/public/car-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daydreamerli/nestjs-graphql-react-fullstack/d6dd1dec1eda1130c162936a254ff3614daae21c/react-cars-app/public/car-logo.png -------------------------------------------------------------------------------- /react-cars-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daydreamerli/nestjs-graphql-react-fullstack/d6dd1dec1eda1130c162936a254ff3614daae21c/react-cars-app/public/favicon.ico -------------------------------------------------------------------------------- /react-cars-app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | GreatMotors Rental 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /react-cars-app/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daydreamerli/nestjs-graphql-react-fullstack/d6dd1dec1eda1130c162936a254ff3614daae21c/react-cars-app/public/logo192.png -------------------------------------------------------------------------------- /react-cars-app/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daydreamerli/nestjs-graphql-react-fullstack/d6dd1dec1eda1130c162936a254ff3614daae21c/react-cars-app/public/logo512.png -------------------------------------------------------------------------------- /react-cars-app/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /react-cars-app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /react-cars-app/server.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | 3 | const path = require("path"); 4 | const app = express(); 5 | 6 | const PORT = 3000; 7 | 8 | app.use(express.static(path.join(__dirname, "build"))); 9 | 10 | app.get("/", function (req, res) { 11 | res.sendFile(path.join(__dirname, "build", "index.html")); 12 | }); 13 | 14 | app.listen(PORT); 15 | 16 | console.log("React Server is Running on PORT: ", PORT); 17 | -------------------------------------------------------------------------------- /react-cars-app/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-float infinite 3s ease-in-out; 13 | } 14 | } 15 | 16 | .App-header { 17 | min-height: 100vh; 18 | display: flex; 19 | flex-direction: column; 20 | align-items: center; 21 | justify-content: center; 22 | font-size: calc(10px + 2vmin); 23 | } 24 | 25 | .App-link { 26 | color: rgb(112, 76, 182); 27 | } 28 | 29 | @keyframes App-logo-float { 30 | 0% { 31 | transform: translateY(0); 32 | } 33 | 50% { 34 | transform: translateY(10px); 35 | } 36 | 100% { 37 | transform: translateY(0px); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /react-cars-app/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import { Provider } from 'react-redux'; 4 | import { store } from './app/store'; 5 | import App from './App'; 6 | 7 | test('renders learn react link', () => { 8 | const { getByText } = render( 9 | 10 | 11 | 12 | ); 13 | 14 | expect(getByText(/learn/i)).toBeInTheDocument(); 15 | }); 16 | -------------------------------------------------------------------------------- /react-cars-app/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import tw from "twin.macro"; 4 | import "./App.css"; 5 | import { HomePage } from "./app/containers/HomePage"; 6 | import { Route, BrowserRouter as Router, } from "react-router-dom"; 7 | import { CarPage } from "./app/containers/CarPage"; 8 | import { LoginPage } from "./app/containers/UserLogin"; 9 | import { SignupPage } from "./app/containers/signupPage"; 10 | import { OrderPage } from "./app/containers/orderPage"; 11 | 12 | 13 | const AppContainer = styled.div` 14 | ${tw` 15 | w-full 16 | h-full 17 | flex 18 | flex-col 19 | `}; 20 | `; 21 | 22 | 23 | function App() { 24 | 25 | return ( 26 | 27 | 28 | 29 | } /> 30 | } /> 31 | } /> 32 | } /> 33 | } /> 34 | 35 | 36 | 37 | ); 38 | } 39 | 40 | export default App; 41 | 42 | 43 | -------------------------------------------------------------------------------- /react-cars-app/src/accessToken.ts: -------------------------------------------------------------------------------- 1 | let accessToken = ""; 2 | 3 | export const setAccessToken = (s: string) => { 4 | accessToken = s; 5 | }; 6 | 7 | export const getAccessToken = () => { 8 | return accessToken; 9 | }; 10 | export default accessToken; -------------------------------------------------------------------------------- /react-cars-app/src/app/components/bookCard/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { 3 | faCalendarAlt, 4 | faCaretDown, 5 | faCaretUp, 6 | } from "@fortawesome/free-solid-svg-icons"; 7 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 8 | import styled, { css } from "styled-components"; 9 | import tw from "twin.macro"; 10 | import { Button } from "../button"; 11 | import { Marginer } from "../marginer"; 12 | 13 | import Calendar from "react-calendar"; 14 | import "react-calendar/dist/Calendar.css"; 15 | import { SCREENS } from "../responsive"; 16 | 17 | const CardContainer = styled.div` 18 | min-height: 4.3em; 19 | box-shadow: 0 1.3px 12px -3px rgba(0, 0, 0, 0.4); 20 | ${tw` 21 | flex 22 | justify-center 23 | items-center 24 | rounded-md 25 | bg-white 26 | pt-1 27 | pb-1 28 | pr-2 29 | pl-2 30 | md:pt-2 31 | md:pb-2 32 | md:pl-9 33 | md:pr-9 34 | `}; 35 | `; 36 | 37 | const ItemContainer = styled.div` 38 | ${tw`flex relative`}; 39 | `; 40 | 41 | const Icon = styled.span` 42 | ${tw` 43 | text-red-500 44 | fill-current 45 | text-xs 46 | md:text-base 47 | mr-1 48 | md:mr-3 49 | `}; 50 | `; 51 | 52 | const SmallIcon = styled.span` 53 | ${tw` 54 | text-gray-500 55 | fill-current 56 | text-xs 57 | md:text-base 58 | ml-1 59 | `}; 60 | `; 61 | 62 | const Name = styled.span` 63 | ${tw` 64 | text-gray-600 65 | text-xs 66 | md:text-sm 67 | cursor-pointer 68 | select-none 69 | `}; 70 | `; 71 | 72 | const LineSeperator = styled.span` 73 | width: 2px; 74 | height: 45%; 75 | ${tw` 76 | bg-gray-300 77 | mr-2 78 | ml-2 79 | md:mr-5 80 | md:ml-5 81 | `}; 82 | `; 83 | 84 | const DateCalendar = styled(Calendar)` 85 | position: absolute; 86 | max-width: none; 87 | user-select: none; 88 | top: 2em; 89 | left: 0; 90 | 91 | ${({ offset }: any) => 92 | offset && 93 | css` 94 | left: -6em; 95 | `}; 96 | 97 | @media (min-width: ${SCREENS.md}) { 98 | top: 3.5em; 99 | left: -2em; 100 | } 101 | ` as any; 102 | 103 | export function BookCard() { 104 | 105 | const [startDate, setStartDate] = useState(new Date()); 106 | const [isStartCalendarOpen, setStartCalendarOpen] = useState(false); 107 | const [returnDate, setReturnDate] = useState(new Date()); 108 | const [isReturnCalendarOpen, setReturnCalendarOpen] = useState(false); 109 | 110 | const toggleStartDateCalendar = () => { 111 | setStartCalendarOpen(!isStartCalendarOpen); 112 | console.log(startDate); 113 | if (isReturnCalendarOpen) setReturnCalendarOpen(false); 114 | }; 115 | 116 | const toggleReturnDateCalendar = () => { 117 | setReturnCalendarOpen(!isReturnCalendarOpen); 118 | console.log(returnDate); 119 | if (isStartCalendarOpen) setStartCalendarOpen(false); 120 | }; 121 | 122 | return ( 123 | 124 | 125 | 126 | 127 | 128 | Pick Up Date 129 | 130 | 133 | 134 | {isStartCalendarOpen && ( 135 | 136 | )} 137 | 138 | 139 | 140 | 141 | 142 | 143 | Return Date 144 | 145 | 148 | 149 | {isReturnCalendarOpen && ( 150 | 155 | )} 156 | 157 | 158 | 109 | 110 | 111 |

112 | No Account Yet,Register and Join Us Now. 113 |

114 | 115 | 116 | 117 | 118 | ); 119 | 120 | } -------------------------------------------------------------------------------- /react-cars-app/src/app/containers/UserLogin/loginSection2.tsx: -------------------------------------------------------------------------------- 1 | import { gql, useMutation ,useQuery } from "@apollo/client"; 2 | import React, {SyntheticEvent, useState } from "react"; 3 | import { useHistory, Link, Redirect } from "react-router-dom"; 4 | import styled from "styled-components"; 5 | import tw from "twin.macro"; 6 | import { Button } from "../../components/button"; 7 | import { apolloClient } from "../../graphql"; 8 | import userService from "../../services/userService"; 9 | import { USER_LOGIN } from "../../services/userService/userLogin"; 10 | import { LoginUser } from '../../services/userService/__generated__/LoginUser'; 11 | import jwt_decode from "jwt-decode"; 12 | import { response } from "express"; 13 | 14 | // export const LOGIN = gql` 15 | // mutation LoginUser($email: String!, $password:String!) { 16 | // login(email:$email, password:$password){ 17 | // access_token 18 | // } 19 | // } 20 | // `; 21 | 22 | // export const ME = gql` 23 | // query USER_NOW{ 24 | // CurrentUser{ 25 | // id 26 | // username 27 | // email 28 | // } 29 | // } 30 | // `; 31 | 32 | interface ILogin2Props { 33 | // set the props as username and email 34 | } 35 | 36 | export function LoginSection2(props: ILogin2Props){ 37 | 38 | 39 | const [email, setEmail] = useState("") 40 | const [password, setPassword] = useState("") 41 | const [redirect, setRedirect] = useState(false); 42 | 43 | const submit = async (e: SyntheticEvent) => { 44 | e.preventDefault(); 45 | 46 | const variables = {email,password} 47 | 48 | const response = await userService.login(variables) 49 | 50 | if (response === "") { 51 | alert("Invalid Input, Please Check your input!") 52 | } else { 53 | setRedirect(true) 54 | // const userId = await jwt_decode(response).payload.id 55 | console.log(response) 56 | } 57 | } 58 | 59 | if (redirect) { 60 | return ; 61 | } 62 | 63 | return ( 64 | 65 |
66 |
67 |
68 | 74 | setEmail(e.target.value)} 79 | value={email} 80 | placeholder="alex@example.com" 81 | required 82 | /> 83 |
84 |
85 | 91 | setPassword(e.target.value)} 95 | value={password} 96 | type="password" 97 | required 98 | /> 99 |
100 | 101 | 102 |
103 |

104 | No Account Yet,Register and Join Us Now. 105 |

106 |
107 | 108 | 109 | 110 | ); 111 | 112 | } 113 | 114 | 115 | -------------------------------------------------------------------------------- /react-cars-app/src/app/containers/UserLogin/selectors.ts: -------------------------------------------------------------------------------- 1 | import { createSelector } from "reselect"; 2 | import { IRootAppState } from "../../../typings"; -------------------------------------------------------------------------------- /react-cars-app/src/app/containers/UserLogin/slice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | import { ILoginPageState } from './type'; 3 | 4 | 5 | const initialState: ILoginPageState = { 6 | accessToken: "", 7 | user:null, 8 | } 9 | 10 | const loginPageSlice = createSlice({ 11 | name: "loginPage", 12 | initialState, 13 | reducers: { 14 | login: (state, action) => { 15 | state.accessToken = action.payload.access_token; 16 | state.user = action.payload.user; 17 | }, 18 | logout: (state, action) => { 19 | state.accessToken = ""; 20 | state.user = null; 21 | } 22 | } 23 | }) 24 | 25 | export const { login, logout } = loginPageSlice.actions; 26 | 27 | export default loginPageSlice.reducer; -------------------------------------------------------------------------------- /react-cars-app/src/app/containers/UserLogin/topSection.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import tw from "twin.macro"; 4 | 5 | import MclarenCarImg from "../../../assets/images/mclaren-orange-big.png"; 6 | import BlobImg from "../../../assets/images/blob.svg"; 7 | import { SCREENS } from "../../components/responsive"; 8 | import { Button } from "../../components/button"; 9 | 10 | const TopSectionContainer = styled.div` 11 | min-height: 400px; 12 | margin-top: 6em; 13 | ${tw` 14 | w-full 15 | max-w-screen-2xl 16 | flex 17 | justify-between 18 | pl-3 19 | pr-3 20 | lg:pl-12 21 | lg:pr-12 22 | `}; 23 | `; 24 | 25 | const LeftContainer = styled.div` 26 | ${tw` 27 | w-1/2 28 | flex 29 | flex-col 30 | `}; 31 | `; 32 | 33 | const RightContainer = styled.div` 34 | ${tw` 35 | w-1/2 36 | flex 37 | flex-col 38 | relative 39 | mt-20 40 | `}; 41 | `; 42 | 43 | const Slogan = styled.h1` 44 | ${tw` 45 | font-bold 46 | text-2xl 47 | xl:text-6xl 48 | sm:text-3xl 49 | md:text-5xl 50 | lg:font-black 51 | md:font-extrabold 52 | text-black 53 | mb-4 54 | sm:leading-snug 55 | lg:leading-normal 56 | xl:leading-relaxed 57 | `}; 58 | `; 59 | 60 | const Description = styled.p` 61 | ${tw` 62 | text-xs 63 | lg:text-sm 64 | xl:text-lg 65 | sm:max-h-full 66 | overflow-hidden 67 | max-h-12 68 | text-gray-800 69 | `}; 70 | `; 71 | 72 | const BlobContainer = styled.div` 73 | width: 20em; 74 | height: 10em; 75 | position: absolute; 76 | right: -5em; 77 | top: -9em; 78 | z-index: -1; 79 | transform: rotate(-30deg); 80 | 81 | img { 82 | width: 100%; 83 | height: auto; 84 | max-height: max-content; 85 | } 86 | 87 | @media (min-width: ${SCREENS.sm}) { 88 | width: 40em; 89 | max-height: 10em; 90 | right: -9em; 91 | top: -16em; 92 | transform: rotate(-25deg); 93 | } 94 | 95 | @media (min-width: ${SCREENS.lg}) { 96 | width: 50em; 97 | max-height: 30em; 98 | right: -7em; 99 | top: -15em; 100 | transform: rotate(-30deg); 101 | } 102 | 103 | @media (min-width: ${SCREENS.xl}) { 104 | width: 70em; 105 | max-height: 30em; 106 | right: -15em; 107 | top: -25em; 108 | transform: rotate(-20deg); 109 | } 110 | `; 111 | 112 | const StandaloneCar = styled.div` 113 | width: auto; 114 | height: 10em; 115 | right: -6em; 116 | top: -5em; 117 | position: absolute; 118 | 119 | img { 120 | width: auto; 121 | height: 100%; 122 | max-width: fit-content; 123 | } 124 | 125 | @media (min-width: ${SCREENS.sm}) { 126 | height: 16em; 127 | right: -6em; 128 | top: -6em; 129 | } 130 | 131 | @media (min-width: ${SCREENS.lg}) { 132 | height: 21em; 133 | right: -8em; 134 | top: -5em; 135 | } 136 | 137 | @media (min-width: ${SCREENS.xl}) { 138 | height: 30em; 139 | right: -13em; 140 | top: -9em; 141 | } 142 | `; 143 | 144 | const ButtonsContainer = styled.div` 145 | ${tw` 146 | flex 147 | flex-wrap 148 | mt-4 149 | `}; 150 | `; 151 | 152 | export function TopSection() { 153 | return ( 154 | 155 | 156 | Get The Best Deal Car's With Us 157 | 158 | Choose the best car from our local stores or order it remotely 159 | at the best price for your tour and get the best quality cars for as long as 160 | you like! 161 | 162 | 163 | 182 |

183 | Alread Got The Account,Please Login! 184 |

185 | 186 | 187 | 188 | 189 | 190 | 191 | ); 192 | 193 | } -------------------------------------------------------------------------------- /react-cars-app/src/app/containers/signupPage/topSection.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import tw from "twin.macro"; 4 | 5 | import MclarenCarImg from "../../../assets/images/mclaren-orange-big.png"; 6 | import BlobImg from "../../../assets/images/blob.svg"; 7 | import { SCREENS } from "../../components/responsive"; 8 | import { Button } from "../../components/button"; 9 | 10 | const TopSectionContainer = styled.div` 11 | min-height: 400px; 12 | margin-top: 6em; 13 | ${tw` 14 | w-full 15 | max-w-screen-2xl 16 | flex 17 | justify-between 18 | pl-3 19 | pr-3 20 | lg:pl-12 21 | lg:pr-12 22 | `}; 23 | `; 24 | 25 | const LeftContainer = styled.div` 26 | ${tw` 27 | w-1/2 28 | flex 29 | flex-col 30 | `}; 31 | `; 32 | 33 | const RightContainer = styled.div` 34 | ${tw` 35 | w-1/2 36 | flex 37 | flex-col 38 | relative 39 | mt-20 40 | `}; 41 | `; 42 | 43 | const Slogan = styled.h1` 44 | ${tw` 45 | font-bold 46 | text-2xl 47 | xl:text-6xl 48 | sm:text-3xl 49 | md:text-5xl 50 | lg:font-black 51 | md:font-extrabold 52 | text-black 53 | mb-4 54 | sm:leading-snug 55 | lg:leading-normal 56 | xl:leading-relaxed 57 | `}; 58 | `; 59 | 60 | const Description = styled.p` 61 | ${tw` 62 | text-xs 63 | lg:text-sm 64 | xl:text-lg 65 | sm:max-h-full 66 | overflow-hidden 67 | max-h-12 68 | text-gray-800 69 | `}; 70 | `; 71 | 72 | const BlobContainer = styled.div` 73 | width: 20em; 74 | height: 10em; 75 | position: absolute; 76 | right: -5em; 77 | top: -9em; 78 | z-index: -1; 79 | transform: rotate(-30deg); 80 | 81 | img { 82 | width: 100%; 83 | height: auto; 84 | max-height: max-content; 85 | } 86 | 87 | @media (min-width: ${SCREENS.sm}) { 88 | width: 40em; 89 | max-height: 10em; 90 | right: -9em; 91 | top: -16em; 92 | transform: rotate(-25deg); 93 | } 94 | 95 | @media (min-width: ${SCREENS.lg}) { 96 | width: 50em; 97 | max-height: 30em; 98 | right: -7em; 99 | top: -15em; 100 | transform: rotate(-30deg); 101 | } 102 | 103 | @media (min-width: ${SCREENS.xl}) { 104 | width: 70em; 105 | max-height: 30em; 106 | right: -15em; 107 | top: -25em; 108 | transform: rotate(-20deg); 109 | } 110 | `; 111 | 112 | const StandaloneCar = styled.div` 113 | width: auto; 114 | height: 10em; 115 | right: -6em; 116 | top: -5em; 117 | position: absolute; 118 | 119 | img { 120 | width: auto; 121 | height: 100%; 122 | max-width: fit-content; 123 | } 124 | 125 | @media (min-width: ${SCREENS.sm}) { 126 | height: 16em; 127 | right: -6em; 128 | top: -6em; 129 | } 130 | 131 | @media (min-width: ${SCREENS.lg}) { 132 | height: 21em; 133 | right: -8em; 134 | top: -5em; 135 | } 136 | 137 | @media (min-width: ${SCREENS.xl}) { 138 | height: 30em; 139 | right: -13em; 140 | top: -9em; 141 | } 142 | `; 143 | 144 | const ButtonsContainer = styled.div` 145 | ${tw` 146 | flex 147 | flex-wrap 148 | mt-4 149 | `}; 150 | `; 151 | 152 | export function TopSection() { 153 | return ( 154 | 155 | 156 | Get The Best Deal Car's With Us 157 | 158 | Choose the best car from our local stores or order it remotely 159 | at the best price for your tour and get the best quality cars for as long as 160 | you like! 161 | 162 | 163 |