├── .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 |
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 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
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 |
159 |
160 | );
161 | }
162 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/components/button/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import tw from "twin.macro";
4 |
5 | interface IButtonProps {
6 | theme?: "filled" | "outlined";
7 | text: string;
8 | className?: string;
9 | }
10 |
11 | const BaseButton = styled.button`
12 | ${tw`
13 | pl-5
14 | pr-5
15 | pt-3
16 | pb-3
17 | outline-none
18 | rounded-md
19 | text-white
20 | text-xs
21 | font-semibold
22 | border-transparent
23 | border-2
24 | border-solid
25 | focus:outline-none
26 | transition-all
27 | duration-200
28 | ease-in-out
29 | m-1
30 | `};
31 | `;
32 |
33 | const OutlinedButton = styled(BaseButton)`
34 | ${tw`
35 | bg-red-500
36 | hover:bg-transparent
37 | hover:text-red-500
38 | hover:border-red-500
39 | `};
40 | `;
41 |
42 | const FilledButton = styled(BaseButton)`
43 | ${tw`
44 | border-red-500
45 | text-red-500
46 | bg-transparent
47 | hover:bg-red-500
48 | hover:text-white
49 | hover:border-transparent
50 | `};
51 | `;
52 |
53 | export function Button(props: IButtonProps) {
54 | const { theme, text, className } = props;
55 |
56 | if (theme === "filled")
57 | return {text};
58 | else return {text};
59 | }
60 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/components/car/index.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | faEllipsisH,
3 | faFillDrip,
4 | faTachometerAlt,
5 | } from "@fortawesome/free-solid-svg-icons";
6 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
7 | import React from "react";
8 | import styled from "styled-components";
9 | import tw from "twin.macro";
10 | import { ICar } from "../../../typings/car";
11 | import { Button } from "../button";
12 |
13 | interface ICarProps extends ICar {}
14 |
15 | const CarContainer = styled.div`
16 | width: 16.5em;
17 | min-height: 23em;
18 | max-height: 23em;
19 | box-shadow: 0 1.3px 17px -2px rgba(0, 0, 0, 0.4);
20 | ${tw`
21 | flex
22 | flex-col
23 | items-center
24 | p-3
25 | pb-4
26 | bg-white
27 | rounded-md
28 | m-1
29 | sm:m-3
30 | md:m-6
31 | `};
32 | `;
33 |
34 | const CarThumbnail = styled.div`
35 | width: 100%;
36 | height: auto;
37 |
38 | img {
39 | width: 100%;
40 | height: 100%;
41 | }
42 | `;
43 |
44 | const CarName = styled.h3`
45 | ${tw`
46 | text-base
47 | font-bold
48 | text-black
49 | mt-1
50 | mb-1
51 | `};
52 | `;
53 |
54 | const PricesContainer = styled.div`
55 | ${tw`
56 | w-full
57 | flex
58 | justify-start
59 | mt-3
60 | `};
61 | `;
62 |
63 | const SmallText = styled.p`
64 | color: inherit;
65 | ${tw`
66 | inline-flex
67 | text-xs
68 | font-thin
69 | `};
70 | `;
71 |
72 | const DailyPrice = styled.h5`
73 | ${tw`
74 | text-red-500
75 | font-bold
76 | text-sm
77 | mr-3
78 | `};
79 | `;
80 |
81 | const MonthlyPrice = styled.h5`
82 | ${tw`
83 | text-gray-500
84 | font-bold
85 | text-sm
86 | `};
87 | `;
88 |
89 | const SmallIcon = styled.span`
90 | ${tw`
91 | text-gray-400
92 | text-sm
93 | mr-1
94 | `};
95 | `;
96 |
97 | const CarDetailsContainer = styled.div`
98 | ${tw`
99 | flex
100 | w-full
101 | justify-between
102 | `};
103 | `;
104 |
105 | const CarDetail = styled.span`
106 | ${tw`
107 | flex
108 | items-center
109 | `};
110 | `;
111 |
112 | const CarInfo = styled.h6`
113 | ${tw`
114 | text-gray-400
115 | text-xs
116 | `};
117 | `;
118 |
119 | const Seperator = styled.div`
120 | min-width: 100%;
121 | min-height: 1px;
122 | ${tw`
123 | flex
124 | bg-gray-300
125 | mt-2
126 | mb-2
127 | `};
128 | `;
129 |
130 | const RentButton = styled(Button)`
131 | ${tw`
132 | min-w-full
133 | mt-5
134 | `};
135 | `;
136 |
137 | export function Car(props: ICarProps) {
138 | const {
139 | name,
140 | thumbnailSrc,
141 | dailyPrice,
142 | monthlyPrice,
143 | mileage,
144 | gearType,
145 | gas,
146 | } = props;
147 |
148 | return (
149 |
150 |
151 |
152 |
153 | {name}
154 |
155 |
156 | ${dailyPrice}
157 | /Day
158 |
159 |
160 | ${monthlyPrice}
161 | /Month
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 | {mileage}
171 |
172 |
173 |
174 |
175 |
176 | {gearType}
177 |
178 |
179 |
180 |
181 |
182 | {gas}
183 |
184 |
185 |
186 |
187 | );
188 | }
189 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/components/footer/index.tsx:
--------------------------------------------------------------------------------
1 | import { faEnvelope, faPhoneAlt } from "@fortawesome/free-solid-svg-icons";
2 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3 | import React from "react";
4 | import styled from "styled-components";
5 | import tw from "twin.macro";
6 | import { Logo } from "../logo";
7 |
8 | const FooterContainer = styled.div`
9 | //min-height: 24em;
10 | background-color: #1d2124;
11 | ${tw`
12 | flex
13 | flex-col
14 | min-w-full
15 | pt-10
16 | md:pt-16
17 | items-center
18 | justify-center
19 | `};
20 | `;
21 |
22 | const InnerContainer = styled.div`
23 | ${tw`
24 | flex
25 | w-full
26 | h-full
27 | max-w-screen-2xl
28 | flex-wrap
29 | `};
30 | `;
31 |
32 | const BottomContainer = styled.div`
33 | ${tw`
34 | w-full
35 | flex
36 | max-w-screen-2xl
37 | justify-center
38 | md:justify-start
39 | mt-7
40 | ml-3
41 | mb-3
42 | `};
43 | `;
44 |
45 | const CopyrightText = styled.small`
46 | font-size: 14px;
47 | ${tw`
48 | text-gray-300
49 | `}
50 | `;
51 |
52 | const AboutContainer = styled.div`
53 | ${tw`
54 | flex
55 | flex-col
56 | mr-2
57 | md:mr-16
58 | pl-10
59 | pr-10
60 | md:pl-3
61 | md:pr-3
62 | `};
63 | `;
64 |
65 | const AboutText = styled.p`
66 | ${tw`
67 | text-white
68 | text-sm
69 | font-normal
70 | max-w-xs
71 | leading-5
72 | mt-2
73 | `};
74 | `;
75 |
76 | const SectionContainer = styled.div`
77 | ${tw`
78 | w-full
79 | md:w-auto
80 | flex
81 | flex-col
82 | mr-2
83 | md:mr-16
84 | pl-10
85 | pr-10
86 | md:pl-3
87 | md:pr-3
88 | mt-7
89 | md:mt-0
90 | `};
91 | `;
92 |
93 | const LinksList = styled.ul`
94 | ${tw`
95 | outline-none
96 | list-none
97 | flex
98 | flex-col
99 | `};
100 | `;
101 |
102 | const ListItem = styled.li`
103 | ${tw`
104 | mb-3
105 | `};
106 |
107 | & > a {
108 | ${tw`
109 | text-sm
110 | text-white
111 | transition-all
112 | hover:text-gray-200
113 | `};
114 | }
115 | `;
116 |
117 | const HeaderTitle = styled.h3`
118 | ${tw`
119 | text-2xl
120 | font-bold
121 | text-white
122 | mb-3
123 | `};
124 | `;
125 |
126 | const HorizontalContainer = styled.div`
127 | ${tw`
128 | flex
129 | items-center
130 | `};
131 | `;
132 |
133 | const RedIcon = styled.span`
134 | ${tw`
135 | w-9
136 | h-9
137 | rounded-full
138 | bg-red-500
139 | flex
140 | items-center
141 | justify-center
142 | text-white
143 | text-base
144 | mr-2
145 | `};
146 | `;
147 |
148 | const SmallText = styled.h6`
149 | ${tw`
150 | text-sm
151 | text-white
152 | `};
153 | `;
154 |
155 | export function Footer() {
156 | return (
157 |
158 |
159 |
160 |
161 |
162 | GreatMotors is a Car renting and selling company located in two
163 | sites at Auckland City,New Zealand, we have high quality cars and top rated
164 | service.
165 |
166 |
167 |
168 | Our Links
169 |
170 |
171 | Home
172 |
173 |
174 | About Us
175 |
176 |
177 | Services
178 |
179 |
180 | Models
181 |
182 |
183 | Blog
184 |
185 |
186 |
187 |
188 | Other Links
189 |
190 |
191 | FAQ
192 |
193 |
194 | Contact Us
195 |
196 |
197 | Support
198 |
199 |
200 | Privacy Policy
201 |
202 |
203 | Terms & Conditions
204 |
205 |
206 |
207 |
208 | Call Now
209 |
210 |
211 |
212 |
213 | +64 09-818-8881
214 |
215 |
216 |
217 | Mail
218 |
219 |
220 |
221 |
222 | info@greatmotors.co.nz
223 |
224 |
225 |
226 |
227 |
228 | Copyright © {new Date().getFullYear()} GreatMotors. All rights
229 | reserved.
230 |
231 |
232 |
233 | );
234 | }
235 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/components/logo/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import tw from "twin.macro";
4 |
5 | import CarLogoImg from "../../../assets/images/car-logo.png";
6 | import CarLogoDarkImg from "../../../assets/images/car-logo-dark.png";
7 |
8 | interface ILogoProps {
9 | color?: "white" | "dark";
10 | bgColor?: "white" | "dark";
11 | }
12 |
13 | const LogoContainer = styled.div`
14 | ${tw`
15 | flex
16 | items-center
17 | `};
18 | `;
19 |
20 | const LogoText = styled.div`
21 | ${tw`
22 | text-xl
23 | md:text-2xl
24 | font-bold
25 | text-black
26 | m-1
27 | `};
28 | ${({ color }: any) => (color === "white" ? tw`text-white` : tw`text-black`)}
29 | ` as any;
30 |
31 | const Image = styled.div`
32 | width: auto;
33 | ${tw`h-6 md:h-9`};
34 |
35 | img {
36 | width: auto;
37 | height: 100%;
38 | }
39 | `;
40 |
41 | export function Logo(props: ILogoProps) {
42 | const { color, bgColor } = props;
43 |
44 | return (
45 |
46 |
47 |
48 |
49 | GreatMotors.
50 |
51 | );
52 | }
53 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/components/marginer/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | export interface IMarginerProps {
5 | margin: number | string;
6 | direction?: "horizontal" | "vertical";
7 | }
8 |
9 | const HorizontalMargin = styled.span`
10 | display: flex;
11 | min-width: ${({ margin }) =>
12 | typeof margin === "string" ? margin : `${margin}px`};
13 | `;
14 |
15 | const VerticalMargin = styled.span`
16 | display: flex;
17 | min-height: ${({ margin }) =>
18 | typeof margin === "string" ? margin : `${margin}px`};
19 | `;
20 |
21 | function Marginer(props: IMarginerProps) {
22 | const { direction } = props;
23 |
24 | if (direction === "horizontal") return ;
25 | else {
26 | return ;
27 | }
28 | }
29 |
30 | Marginer.defaultProps = {
31 | direction: "horizontal",
32 | };
33 |
34 | export { Marginer };
35 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/components/navbar/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import tw from "twin.macro";
4 | import { Logo } from "../logo";
5 | import { NavItems } from "./navItems";
6 |
7 | const NavbarContainer = styled.div`
8 | min-height: 68px;
9 | ${tw`
10 | w-full
11 | max-w-screen-2xl
12 | flex
13 | flex-row
14 | items-center
15 | lg:pl-12
16 | lg:pr-12
17 | justify-between
18 | `};
19 | `;
20 |
21 | const LogoContainer = styled.div``;
22 |
23 | export function Navbar() {
24 | return (
25 |
26 |
27 |
28 |
29 |
30 |
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/components/navbar/menuStyles.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | bmBurgerButton: {
3 | position: "absolute",
4 | width: "20px",
5 | height: "20px",
6 | right: "20px",
7 | top: "20px",
8 | },
9 | bmBurgerBars: {
10 | background: "#373a47",
11 | },
12 | bmBurgerBarsHover: {
13 | background: "#a90000",
14 | },
15 | bmCrossButton: {
16 | height: "24px",
17 | width: "24px",
18 | },
19 | bmCross: {
20 | background: "#bdc3c7",
21 | },
22 | bmMenuWrap: {
23 | position: "fixed",
24 | width: "60%",
25 | height: "100%",
26 | top: "0px",
27 | },
28 | bmMenu: {
29 | background: "#373a47",
30 | padding: "2.5em 1.5em 0",
31 | fontSize: "1.15em",
32 | },
33 | bmMorphShape: {
34 | fill: "#373a47",
35 | },
36 | bmItemList: {
37 | color: "#b8b7ad",
38 | padding: "0.8em",
39 | },
40 | bmItem: {
41 | display: "inline-block",
42 | },
43 | bmOverlay: {
44 | background: "rgba(0, 0, 0, 0.3)",
45 | },
46 | };
47 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/components/navbar/navItems.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled, { css } from "styled-components";
3 | import tw from "twin.macro";
4 | import { slide as Menu } from "react-burger-menu";
5 | import { useMediaQuery } from "react-responsive";
6 | import { SCREENS } from "../responsive";
7 | import menuStyles from "./menuStyles";
8 |
9 | const ListContainer = styled.ul`
10 | ${tw`
11 | flex
12 | list-none
13 | `};
14 | `;
15 |
16 | const NavItem = styled.li<{ menu?: any }>`
17 | ${tw`
18 | text-sm
19 | md:text-base
20 | text-black
21 | font-medium
22 | mr-1
23 | md:mr-5
24 | cursor-pointer
25 | transition
26 | duration-300
27 | ease-in-out
28 | hover:text-gray-700
29 | `};
30 |
31 | ${({ menu }) =>
32 | menu &&
33 | css`
34 | ${tw`
35 | text-white
36 | text-xl
37 | mb-3
38 | focus:text-white
39 | `};
40 | `};
41 | `;
42 |
43 | export function NavItems() {
44 | const isMobile = useMediaQuery({ maxWidth: SCREENS.sm });
45 |
46 | if (isMobile)
47 | return (
48 |
67 | );
68 |
69 | return (
70 |
71 |
72 | Home
73 |
74 |
75 | Cars
76 |
77 |
78 | Booking
79 |
80 |
81 | Login
82 |
83 |
84 | SignUp
85 |
86 |
87 | );
88 | }
89 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/components/responsive/index.ts:
--------------------------------------------------------------------------------
1 | export const SCREENS = {
2 | sm: "640px",
3 | // => @media (min-width: 640px) { ... }
4 |
5 | md: "768px",
6 | // => @media (min-width: 768px) { ... }
7 |
8 | lg: "1024px",
9 | // => @media (min-width: 1024px) { ... }
10 |
11 | xl: "1280px",
12 | // => @media (min-width: 1280px) { ... }
13 |
14 | "2xl": "1536px",
15 | // => @media (min-width: 1536px) { ... }
16 | };
17 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/containers/CarPage/aboutUs.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import tw from "twin.macro";
4 | import { SCREENS } from "../../components/responsive";
5 |
6 | import JeepImg from "../../../assets/images/jeep.png";
7 |
8 | const AboutUsContainer = styled.div`
9 | ${tw`
10 | w-full
11 | flex
12 | flex-wrap
13 | items-center
14 | justify-center
15 | pt-4
16 | pb-4
17 | pr-7
18 | pl-7
19 | md:pl-0
20 | md:pr-0
21 | bg-white
22 | `};
23 | `;
24 |
25 | const CarContainer = styled.div`
26 | width: auto;
27 | height: 15em;
28 | margin-left: -50px;
29 |
30 | img {
31 | width: auto;
32 | height: 100%;
33 | }
34 |
35 | @media (min-width: ${SCREENS.md}) {
36 | height: 28em;
37 | }
38 |
39 | @media (min-width: ${SCREENS.lg}) {
40 | height: 30em;
41 | }
42 |
43 | @media (min-width: ${SCREENS["2xl"]}) {
44 | height: 35em;
45 | margin-left: 0;
46 | }
47 | `;
48 |
49 | const InfoContainer = styled.div`
50 | ${tw`
51 | md:w-1/2
52 | flex
53 | flex-col
54 | md:ml-6
55 | 2xl:ml-16
56 | `};
57 | `;
58 |
59 | const Title = styled.h1`
60 | ${tw`
61 | text-black
62 | text-2xl
63 | md:text-5xl
64 | font-extrabold
65 | md:font-black
66 | md:leading-normal
67 | `};
68 | `;
69 |
70 | const InfoText = styled.p`
71 | ${tw`
72 | md:max-w-2xl
73 | text-sm
74 | md:text-base
75 | text-gray-500
76 | font-normal
77 | mt-4
78 | `};
79 | `;
80 |
81 | export function AboutUs() {
82 | return (
83 |
84 |
85 |
86 |
87 |
88 | Feel The Best Experience With Our Rental Deals
89 |
90 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
91 | eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
92 | minim veniam, quis nostrud exercitation ullamco laboris nisi ut
93 | aliquip ex ea commodo consequat. Duis aute irure dolor in
94 | reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
95 | pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
96 | culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum
97 | dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
98 | culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum
99 | dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.
100 |
101 |
102 |
103 | );
104 | }
105 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/containers/CarPage/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import tw from "twin.macro";
4 | import { Footer } from "../../components/footer";
5 | import { Marginer } from "../../components/marginer";
6 | import { Navbar } from "../../components/navbar";
7 | import { TopSUV } from "./topSUV";
8 | import { TopSection } from "./topSection";
9 | import { TopHatchBack } from "./topHatchBack";
10 | import {TopHybird} from "./topHybird";
11 |
12 | const PageContainer = styled.div`
13 | ${tw`
14 | flex
15 | flex-col
16 | w-full
17 | h-full
18 | items-center
19 | overflow-x-hidden
20 | `}
21 | `;
22 |
23 | export function CarPage() {
24 | return (
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/containers/CarPage/selectors.ts:
--------------------------------------------------------------------------------
1 | import { createSelector } from "reselect";
2 | import { IRootAppState } from "../../../typings";
3 |
4 | const selectHomePage = (state: IRootAppState) => state.homePage;
5 |
6 | export const makeSelectTopCars = createSelector(
7 | selectHomePage,
8 | (homePage) => homePage.topCars
9 | );
10 |
11 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/containers/CarPage/slice.ts:
--------------------------------------------------------------------------------
1 | import { Action, createSlice } from "@reduxjs/toolkit";
2 | import { IHomePageState } from "./type";
3 |
4 | const initialState: IHomePageState = {
5 | topCars: [],
6 | };
7 |
8 | const homePageSlice = createSlice({
9 | name: "homePage",
10 | initialState,
11 | reducers: {
12 | setTopCars: (state, action) => {
13 | state.topCars = action.payload;
14 | },
15 | },
16 | });
17 |
18 | export const { setTopCars } = homePageSlice.actions;
19 | export default homePageSlice.reducer;
20 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/containers/CarPage/topHatchBack.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import styled from "styled-components";
3 | import tw from "twin.macro";
4 | import { ICar } from "../../../typings/car";
5 | import { Car } from "../../components/car";
6 | import Carousel, { Dots, slidesToShowPlugin } from "@brainhubeu/react-carousel";
7 | import "@brainhubeu/react-carousel/lib/style.css";
8 | import { useMediaQuery } from "react-responsive";
9 | import { SCREENS } from "../../components/responsive";
10 | import carService from "../../services/carService";
11 | import { Dispatch } from "redux";
12 | import { GetCars_cars } from "../../services/carService/__generated__/GetCars";
13 | import { setTopCars } from "./slice";
14 | import { useDispatch, useSelector } from "react-redux";
15 | import { createSelector } from "reselect";
16 | import { makeSelectTopCars } from "./selectors";
17 | import MoonLoader from "react-spinners/MoonLoader";
18 |
19 |
20 |
21 | const TopCarsContainer = styled.div`
22 | ${tw`
23 | max-w-screen-2xl
24 | w-full
25 | flex
26 | flex-col
27 |
28 | justify-center
29 | pl-4
30 | pr-3
31 | lg:pl-12
32 | lg:pr-12
33 |
34 | `};
35 | `;
36 |
37 | const Title = styled.h2`
38 | ${tw`
39 | text-xl
40 | lg:text-5xl
41 | text-black
42 | font-extrabold
43 | `};
44 | `;
45 |
46 | const CarsContainer = styled.div`
47 | ${tw`
48 | w-full
49 | flex
50 | flex-wrap
51 | justify-center
52 | mt-7
53 | md:mt-10
54 | `};
55 | `;
56 |
57 | const EmptyCars = styled.div`
58 | ${tw`
59 | w-full
60 | flex
61 | justify-center
62 | items-center
63 | text-sm
64 | text-gray-500
65 | `};
66 | `;
67 |
68 | const LoadingContainer = styled.div`
69 | ${tw`
70 | w-full
71 | mt-9
72 | flex
73 | justify-center
74 | items-center
75 | text-base
76 | text-black
77 | `};
78 | `;
79 |
80 | const actionDispatch = (dispatch: Dispatch) => ({
81 | setTopCars: (cars: GetCars_cars[]) => dispatch(setTopCars(cars)),
82 | });
83 |
84 | const stateSelector = createSelector(makeSelectTopCars, (topCars) => ({
85 | topCars,
86 | }));
87 |
88 | const wait = (timeout: number) => new Promise((rs) => setTimeout(rs, timeout));
89 |
90 | export function TopHatchBack() {
91 | const [current, setCurrent] = useState(0);
92 | const [isLoading, setLoading] = useState(false);
93 |
94 | const isMobile = useMediaQuery({ maxWidth: SCREENS.sm });
95 |
96 | const { topCars } = useSelector(stateSelector);
97 | const { setTopCars } = actionDispatch(useDispatch());
98 |
99 | console.log("Cars", topCars);
100 |
101 |
102 | const fetchTopCars = async () => {
103 | setLoading(true);
104 | const cars = await carService.getCars().catch((err) => {
105 | console.log("Error: ", err);
106 | });
107 |
108 | console.log("Cars: ", cars);
109 | if (cars) setTopCars(cars);
110 | setLoading(false);
111 | };
112 |
113 | useEffect(() => {
114 | fetchTopCars();
115 | }, []);
116 |
117 | const isEmptyTopCars = !topCars || topCars.length === 0;
118 |
119 |
120 | let suvCars= topCars.filter(e => e.category == "Hatchback") ;
121 |
122 | console.log(suvCars);
123 |
124 | const cars =
125 | (!isEmptyTopCars &&
126 | suvCars.map((car) => )) ||
127 | [];
128 |
129 | const numberOfDots = isMobile ? cars.length : Math.ceil(cars.length / 2);
130 |
131 | return (
132 |
133 | Enjoy Journey With hottest Hatchback
134 | {isLoading && (
135 |
136 |
137 |
138 | )}
139 | {isEmptyTopCars && !isLoading && No Cars To Show!}
140 | {!isEmptyTopCars && !isLoading && (
141 |
142 |
178 |
179 |
180 | )}
181 |
182 | );
183 | }
184 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/containers/CarPage/topHybird.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import styled from "styled-components";
3 | import tw from "twin.macro";
4 | import { ICar } from "../../../typings/car";
5 | import { Car } from "../../components/car";
6 | import Carousel, { Dots, slidesToShowPlugin } from "@brainhubeu/react-carousel";
7 | import "@brainhubeu/react-carousel/lib/style.css";
8 | import { useMediaQuery } from "react-responsive";
9 | import { SCREENS } from "../../components/responsive";
10 | import carService from "../../services/carService";
11 | import { Dispatch } from "redux";
12 | import { GetCars_cars } from "../../services/carService/__generated__/GetCars";
13 | import { setTopCars } from "./slice";
14 | import { useDispatch, useSelector } from "react-redux";
15 | import { createSelector } from "reselect";
16 | import { makeSelectTopCars } from "./selectors";
17 | import MoonLoader from "react-spinners/MoonLoader";
18 |
19 |
20 |
21 | const TopCarsContainer = styled.div`
22 | ${tw`
23 | max-w-screen-2xl
24 | w-full
25 | flex
26 | flex-col
27 |
28 | justify-center
29 | pl-4
30 | pr-3
31 | lg:pl-12
32 | lg:pr-12
33 |
34 | `};
35 | `;
36 |
37 | const Title = styled.h2`
38 | ${tw`
39 | text-xl
40 | lg:text-5xl
41 | text-black
42 | font-extrabold
43 | `};
44 | `;
45 |
46 | const CarsContainer = styled.div`
47 | ${tw`
48 | w-full
49 | flex
50 | flex-wrap
51 | justify-center
52 | mt-7
53 | md:mt-10
54 | `};
55 | `;
56 |
57 | const EmptyCars = styled.div`
58 | ${tw`
59 | w-full
60 | flex
61 | justify-center
62 | items-center
63 | text-sm
64 | text-gray-500
65 | `};
66 | `;
67 |
68 | const LoadingContainer = styled.div`
69 | ${tw`
70 | w-full
71 | mt-9
72 | flex
73 | justify-center
74 | items-center
75 | text-base
76 | text-black
77 | `};
78 | `;
79 |
80 | const actionDispatch = (dispatch: Dispatch) => ({
81 | setTopCars: (cars: GetCars_cars[]) => dispatch(setTopCars(cars)),
82 | });
83 |
84 | const stateSelector = createSelector(makeSelectTopCars, (topCars) => ({
85 | topCars,
86 | }));
87 |
88 | const wait = (timeout: number) => new Promise((rs) => setTimeout(rs, timeout));
89 |
90 | export function TopHybird() {
91 | const [current, setCurrent] = useState(0);
92 | const [isLoading, setLoading] = useState(false);
93 |
94 | const isMobile = useMediaQuery({ maxWidth: SCREENS.sm });
95 |
96 | const { topCars } = useSelector(stateSelector);
97 | const { setTopCars } = actionDispatch(useDispatch());
98 |
99 | console.log("Cars", topCars);
100 |
101 |
102 | const fetchTopCars = async () => {
103 | setLoading(true);
104 | const cars = await carService.getCars().catch((err) => {
105 | console.log("Error: ", err);
106 | });
107 |
108 | console.log("Cars: ", cars);
109 | if (cars) setTopCars(cars);
110 | setLoading(false);
111 | };
112 |
113 | useEffect(() => {
114 | fetchTopCars();
115 | }, []);
116 |
117 | const isEmptyTopCars = !topCars || topCars.length === 0;
118 |
119 |
120 | let suvCars= topCars.filter(e => e.category == "Hybird") ;
121 |
122 | console.log(suvCars);
123 |
124 | const cars =
125 | (!isEmptyTopCars &&
126 | suvCars.map((car) => )) ||
127 | [];
128 |
129 | const numberOfDots = isMobile ? cars.length : Math.ceil(cars.length / 2);
130 |
131 | return (
132 |
133 | Great Journey With Great Fuel Efficiency
134 | {isLoading && (
135 |
136 |
137 |
138 | )}
139 | {isEmptyTopCars && !isLoading && No Cars To Show!}
140 | {!isEmptyTopCars && !isLoading && (
141 |
142 |
178 |
179 |
180 | )}
181 |
182 | );
183 | }
184 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/containers/CarPage/topSUV.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import styled from "styled-components";
3 | import tw from "twin.macro";
4 | import { ICar } from "../../../typings/car";
5 | import { Car } from "../../components/car";
6 | import Carousel, { Dots, slidesToShowPlugin } from "@brainhubeu/react-carousel";
7 | import "@brainhubeu/react-carousel/lib/style.css";
8 | import { useMediaQuery } from "react-responsive";
9 | import { SCREENS } from "../../components/responsive";
10 | import carService from "../../services/carService";
11 | import { Dispatch } from "redux";
12 | import { GetCars_cars } from "../../services/carService/__generated__/GetCars";
13 | import { setTopCars } from "./slice";
14 | import { useDispatch, useSelector } from "react-redux";
15 | import { createSelector } from "reselect";
16 | import { makeSelectTopCars } from "./selectors";
17 | import MoonLoader from "react-spinners/MoonLoader";
18 |
19 |
20 |
21 | const TopCarsContainer = styled.div`
22 | ${tw`
23 | max-w-screen-2xl
24 | w-full
25 | flex
26 | flex-col
27 |
28 | justify-center
29 | pl-3
30 | pr-3
31 | lg:pl-12
32 | lg:pr-12
33 |
34 | `};
35 | `;
36 |
37 | const Title = styled.h2`
38 | ${tw`
39 | text-xl
40 | lg:text-5xl
41 | text-black
42 | font-extrabold
43 | `};
44 | `;
45 |
46 | const CarsContainer = styled.div`
47 | ${tw`
48 | w-full
49 | flex
50 | flex-wrap
51 | justify-center
52 | mt-7
53 | md:mt-10
54 | `};
55 | `;
56 |
57 | const EmptyCars = styled.div`
58 | ${tw`
59 | w-full
60 | flex
61 | justify-center
62 | items-center
63 | text-sm
64 | text-gray-500
65 | `};
66 | `;
67 |
68 | const LoadingContainer = styled.div`
69 | ${tw`
70 | w-full
71 | mt-9
72 | flex
73 | justify-center
74 | items-center
75 | text-base
76 | text-black
77 | `};
78 | `;
79 |
80 | const actionDispatch = (dispatch: Dispatch) => ({
81 | setTopCars: (cars: GetCars_cars[]) => dispatch(setTopCars(cars)),
82 | });
83 |
84 | const stateSelector = createSelector(makeSelectTopCars, (topCars) => ({
85 | topCars,
86 | }));
87 |
88 | const wait = (timeout: number) => new Promise((rs) => setTimeout(rs, timeout));
89 |
90 | export function TopSUV() {
91 | const [current, setCurrent] = useState(0);
92 | const [isLoading, setLoading] = useState(false);
93 |
94 | const isMobile = useMediaQuery({ maxWidth: SCREENS.sm });
95 |
96 | const { topCars } = useSelector(stateSelector);
97 | const { setTopCars } = actionDispatch(useDispatch());
98 |
99 | console.log("Cars", topCars);
100 |
101 |
102 | const fetchTopCars = async () => {
103 | setLoading(true);
104 | const cars = await carService.getCars().catch((err) => {
105 | console.log("Error: ", err);
106 | });
107 |
108 | console.log("Cars: ", cars);
109 | if (cars) setTopCars(cars);
110 | setLoading(false);
111 | };
112 |
113 | useEffect(() => {
114 | fetchTopCars();
115 | }, []);
116 |
117 | const isEmptyTopCars = !topCars || topCars.length === 0;
118 |
119 |
120 | let suvCars= topCars.filter(e => e.category == "SUV") ;
121 |
122 | console.log(suvCars);
123 |
124 | const cars =
125 | (!isEmptyTopCars &&
126 | suvCars.map((car) => )) ||
127 | [];
128 |
129 | const numberOfDots = isMobile ? cars.length : Math.ceil(cars.length / 2);
130 |
131 | return (
132 |
133 | Explore New Zealand With The Best SUV
134 | {isLoading && (
135 |
136 |
137 |
138 | )}
139 | {isEmptyTopCars && !isLoading && No Cars To Show!}
140 | {!isEmptyTopCars && !isLoading && (
141 |
142 |
179 |
180 |
181 | )}
182 |
183 | );
184 | }
185 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/containers/CarPage/topSection.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import tw from "twin.macro";
4 | import {Link} from 'react-router-dom'
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 | Rent The Best Qulity Car's With Us
157 |
158 | Always choose the best car from our local stores or order it remotely
159 | at the best price for you and get the best quality cars for as long as
160 | you like
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 | );
177 | }
178 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/containers/CarPage/topVan.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import styled from "styled-components";
3 | import tw from "twin.macro";
4 | import { Car } from "../../components/car";
5 | import Carousel, { Dots, slidesToShowPlugin } from "@brainhubeu/react-carousel";
6 | import "@brainhubeu/react-carousel/lib/style.css";
7 | import { useMediaQuery } from "react-responsive";
8 | import { SCREENS } from "../../components/responsive";
9 | import carService from "../../services/carService";
10 | import { Dispatch } from "redux";
11 | import { GetCars_cars } from "../../services/carService/__generated__/GetCars";
12 | import { setTopCars } from "./slice";
13 | import { useDispatch, useSelector } from "react-redux";
14 | import { createSelector } from "reselect";
15 | import { makeSelectTopCars } from "./selectors";
16 | import MoonLoader from "react-spinners/MoonLoader";
17 |
18 |
19 |
20 | const TopVanContainer = styled.div`
21 | ${tw`
22 | max-w-screen-2xl
23 | w-full
24 | flex
25 | flex-col
26 |
27 | justify-center
28 | pl-3
29 | pr-3
30 | lg:pl-12
31 | lg:pr-12
32 |
33 | `};
34 | `;
35 |
36 | const Title = styled.h2`
37 | ${tw`
38 | text-xl
39 | lg:text-5xl
40 | text-black
41 | font-extrabold
42 | `};
43 | `;
44 |
45 | const CarsContainer = styled.div`
46 | ${tw`
47 | w-full
48 | flex
49 | flex-wrap
50 | justify-center
51 | mt-7
52 | md:mt-10
53 | `};
54 | `;
55 |
56 | const EmptyCars = styled.div`
57 | ${tw`
58 | w-full
59 | flex
60 | justify-center
61 | items-center
62 | text-sm
63 | text-gray-500
64 | `};
65 | `;
66 |
67 | const LoadingContainer = styled.div`
68 | ${tw`
69 | w-full
70 | mt-9
71 | flex
72 | justify-center
73 | items-center
74 | text-base
75 | text-black
76 | `};
77 | `;
78 |
79 | const actionDispatch = (dispatch: Dispatch) => ({
80 | setTopCars: (cars: GetCars_cars[]) => dispatch(setTopCars(cars)),
81 | });
82 |
83 | const stateSelector = createSelector(makeSelectTopCars, (topCars) => ({
84 | topCars,
85 | }));
86 |
87 | const wait = (timeout: number) => new Promise((rs) => setTimeout(rs, timeout));
88 |
89 | export function TopVan() {
90 | const [current, setCurrent] = useState(0);
91 | const [isLoading, setLoading] = useState(false);
92 |
93 | const isMobile = useMediaQuery({ maxWidth: SCREENS.sm });
94 |
95 | const { topCars } = useSelector(stateSelector);
96 | const { setTopCars } = actionDispatch(useDispatch());
97 |
98 | console.log("Cars", topCars);
99 |
100 | const fetchTopCars = async () => {
101 | setLoading(true);
102 | const cars = await carService.getCars().catch((err) => {
103 | console.log("Error: ", err);
104 | });
105 |
106 | console.log("Cars: ", cars);
107 | if (cars) setTopCars(cars);
108 | setLoading(false);
109 | };
110 |
111 | useEffect(() => {
112 | fetchTopCars();
113 | }, []);
114 |
115 | const isEmptyTopCars = !topCars || topCars.length === 0;
116 |
117 | let vanCars= topCars.filter(e => e.category == "VAN") ;
118 |
119 | console.log(vanCars);
120 |
121 | const cars =
122 | (!isEmptyTopCars &&
123 | vanCars.map((car) => )) ||
124 | [];
125 |
126 | const numberOfDots = isMobile ? cars.length : Math.ceil(cars.length / 2);
127 |
128 | return (
129 |
130 | Explore New Zealand With The Best SUV
131 | {isLoading && (
132 |
133 |
134 |
135 | )}
136 | {isEmptyTopCars && !isLoading && No Vans To Show!}
137 | {!isEmptyTopCars && !isLoading && (
138 |
139 |
176 |
177 |
178 | )}
179 |
180 | );
181 | }
--------------------------------------------------------------------------------
/react-cars-app/src/app/containers/CarPage/type.ts:
--------------------------------------------------------------------------------
1 | import { GetCars_cars } from "../../services/carService/__generated__/GetCars";
2 |
3 | export interface IHomePageState {
4 | topCars: GetCars_cars[];
5 | }
6 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/containers/HomePage/aboutUs.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import tw from "twin.macro";
4 | import { SCREENS } from "../../components/responsive";
5 |
6 | import JeepImg from "../../../assets/images/jeep.png";
7 | import landroverImg from "../../../assets/images/landrover-sport.png";
8 |
9 | const AboutUsContainer = styled.div`
10 | ${tw`
11 | w-full
12 | flex
13 | flex-wrap
14 | items-center
15 | justify-center
16 | pt-1
17 | pb-4
18 | pr-7
19 | pl-7
20 | md:pl-0
21 | md:pr-0
22 | bg-white
23 | `};
24 | `;
25 |
26 | const CarContainer = styled.div`
27 | width: auto;
28 | height: 15em;
29 | margin-left: -50px;
30 |
31 | img {
32 | width: auto;
33 | height: 100%;
34 | }
35 |
36 | @media (min-width: ${SCREENS.md}) {
37 | height: 28em;
38 | }
39 |
40 | @media (min-width: ${SCREENS.lg}) {
41 | height: 30em;
42 | }
43 |
44 | @media (min-width: ${SCREENS["2xl"]}) {
45 | height: 35em;
46 | margin-left: 0;
47 | }
48 | `;
49 |
50 | const InfoContainer = styled.div`
51 | ${tw`
52 | md:w-1/2
53 | flex
54 | flex-col
55 | md:ml-6
56 | 2xl:ml-16
57 | `};
58 | `;
59 |
60 | const Title = styled.h1`
61 | ${tw`
62 | text-black
63 | text-2xl
64 | md:text-5xl
65 | font-extrabold
66 | md:font-black
67 | md:leading-normal
68 | `};
69 | `;
70 |
71 | const InfoText = styled.p`
72 | ${tw`
73 | md:max-w-2xl
74 | text-sm
75 | md:text-base
76 | text-gray-500
77 | font-normal
78 | mt-4
79 | `};
80 | `;
81 |
82 | export function AboutUs() {
83 | return (
84 |
85 |
86 |
87 |
88 |
89 | Feel The Best Experience With Our Rental Deals
90 |
91 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
92 | eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
93 | minim veniam, quis nostrud exercitation ullamco laboris nisi ut
94 | aliquip ex ea commodo consequat. Duis aute irure dolor in
95 | reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
96 | pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
97 | culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum
98 | dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
99 | culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum
100 | dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.
101 |
102 |
103 |
104 | );
105 | }
106 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/containers/HomePage/bookingSteps.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | faCalendarAlt,
3 | faCarSide,
4 | faMapMarkedAlt,
5 | } from "@fortawesome/free-solid-svg-icons";
6 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
7 | import React from "react";
8 | import styled from "styled-components";
9 | import tw from "twin.macro";
10 |
11 | const Container = styled.div`
12 | ${tw`
13 | w-full
14 | flex
15 | flex-col
16 | items-center
17 | pt-3
18 | pb-3
19 | lg:pt-6
20 | lg:pb-6
21 | `};
22 | `;
23 |
24 | const Title = styled.h2`
25 | ${tw`
26 | text-3xl
27 | lg:text-5xl
28 | text-black
29 | font-extrabold
30 | `};
31 | `;
32 |
33 | const StepsContainer = styled.div`
34 | ${tw`
35 | flex
36 | justify-evenly
37 | flex-wrap
38 | mt-7
39 | lg:mt-16
40 | `};
41 | `;
42 |
43 | const StepContainer = styled.div`
44 | ${tw`
45 | flex
46 | flex-col
47 | md:w-96
48 | items-center
49 | transition-colors
50 | hover:text-red-500
51 | m-3
52 | `};
53 | `;
54 |
55 | const Step = styled.div`
56 | box-shadow: 0 1.3px 12px -3px rgba(0, 0, 0, 0.4);
57 | ${tw`
58 | flex
59 | rounded-lg
60 | items-center
61 | justify-center
62 | p-6
63 | `};
64 | `;
65 |
66 | const StepTitle = styled.h4`
67 | ${tw`
68 | text-black
69 | text-lg
70 | font-semibold
71 | mt-4
72 | `};
73 | `;
74 |
75 | const StepDescription = styled.p`
76 | ${tw`
77 | w-10/12
78 | text-xs
79 | md:text-sm
80 | text-center
81 | text-gray-600
82 | `};
83 | `;
84 |
85 | const StepIcon = styled.span`
86 | ${tw`
87 | text-red-500
88 | text-3xl
89 | `};
90 | `;
91 |
92 | export function BookingSteps() {
93 | return (
94 |
95 | Our Working Steps
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 | Choose Location
104 |
105 | Find the nearest Yourcar point and book your car.
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | Pick-Up Date
115 |
116 | Pickup the Best Date to rent a car for you.
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 | Book Your Car
126 |
127 | Book your nice car with ease in one single click
128 |
129 |
130 |
131 |
132 | );
133 | }
134 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/containers/HomePage/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import tw from "twin.macro";
4 | import { BookCard } from "../../components/bookCard";
5 | import { Footer } from "../../components/footer";
6 | import { Marginer } from "../../components/marginer";
7 | import { Navbar } from "../../components/navbar";
8 | import { AboutUs } from "./aboutUs";
9 | import { BookingSteps } from "./bookingSteps";
10 | import { TopCars } from "./topCars";
11 | import { TopSection } from "./topSection";
12 |
13 | const PageContainer = styled.div`
14 | ${tw`
15 | flex
16 | flex-col
17 | w-full
18 | h-full
19 | items-center
20 | overflow-x-hidden
21 | `}
22 | `;
23 |
24 | export function HomePage() {
25 | return (
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/containers/HomePage/selectors.ts:
--------------------------------------------------------------------------------
1 | import { createSelector } from "reselect";
2 | import { IRootAppState } from "../../../typings";
3 |
4 | const selectHomePage = (state: IRootAppState) => state.homePage;
5 |
6 | export const makeSelectTopCars = createSelector(
7 | selectHomePage,
8 | (homePage) => homePage.topCars
9 | );
10 |
11 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/containers/HomePage/slice.ts:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 | import { IHomePageState } from "./type";
3 |
4 | const initialState: IHomePageState = {
5 | topCars: [],
6 | };
7 |
8 | const homePageSlice = createSlice({
9 | name: "homePage",
10 | initialState,
11 | reducers: {
12 | setTopCars: (state, action) => {
13 | state.topCars = action.payload;
14 | },
15 | },
16 | });
17 |
18 | export const { setTopCars } = homePageSlice.actions;
19 | export default homePageSlice.reducer;
20 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/containers/HomePage/topCars.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import styled from "styled-components";
3 | import tw from "twin.macro";
4 | import { ICar } from "../../../typings/car";
5 | import { Car } from "../../components/car";
6 | import Carousel, { Dots, slidesToShowPlugin } from "@brainhubeu/react-carousel";
7 | import "@brainhubeu/react-carousel/lib/style.css";
8 | import { useMediaQuery } from "react-responsive";
9 | import { SCREENS } from "../../components/responsive";
10 | import carService from "../../services/carService";
11 | import { Dispatch } from "redux";
12 | import { GetCars_cars } from "../../services/carService/__generated__/GetCars";
13 | import { setTopCars } from "./slice";
14 | import { useDispatch, useSelector } from "react-redux";
15 | import { createSelector } from "reselect";
16 | import { makeSelectTopCars } from "./selectors";
17 | import MoonLoader from "react-spinners/MoonLoader";
18 |
19 | const TopCarsContainer = styled.div`
20 | ${tw`
21 | max-w-screen-lg
22 | w-full
23 | flex
24 | flex-col
25 | items-center
26 | justify-center
27 | pr-4
28 | pl-4
29 | md:pl-0
30 | md:pr-0
31 | mb-10
32 | `};
33 | `;
34 |
35 | const Title = styled.h2`
36 | ${tw`
37 | text-3xl
38 | lg:text-5xl
39 | text-black
40 | font-extrabold
41 | `};
42 | `;
43 |
44 | const CarsContainer = styled.div`
45 | ${tw`
46 | w-full
47 | flex
48 | flex-wrap
49 | justify-center
50 | mt-7
51 | md:mt-10
52 | `};
53 | `;
54 |
55 | const EmptyCars = styled.div`
56 | ${tw`
57 | w-full
58 | flex
59 | justify-center
60 | items-center
61 | text-sm
62 | text-gray-500
63 | `};
64 | `;
65 |
66 | const LoadingContainer = styled.div`
67 | ${tw`
68 | w-full
69 | mt-9
70 | flex
71 | justify-center
72 | items-center
73 | text-base
74 | text-black
75 | `};
76 | `;
77 |
78 | const actionDispatch = (dispatch: Dispatch) => ({
79 | setTopCars: (cars: GetCars_cars[]) => dispatch(setTopCars(cars)),
80 | });
81 |
82 | const stateSelector = createSelector(makeSelectTopCars, (topCars) => ({
83 | topCars,
84 | }));
85 |
86 | const wait = (timeout: number) => new Promise((rs) => setTimeout(rs, timeout));
87 |
88 | export function TopCars() {
89 | const [current, setCurrent] = useState(0);
90 | const [isLoading, setLoading] = useState(false);
91 |
92 | const isMobile = useMediaQuery({ maxWidth: SCREENS.sm });
93 |
94 | const { topCars } = useSelector(stateSelector);
95 | const { setTopCars } = actionDispatch(useDispatch());
96 |
97 | console.log("Cars", topCars);
98 |
99 | const fetchTopCars = async () => {
100 | setLoading(true);
101 | const cars = await carService.getCars().catch((err) => {
102 | console.log("Error: ", err);
103 | });
104 |
105 | console.log("Cars: ", cars);
106 | if (cars) setTopCars(cars);
107 | setLoading(false);
108 | };
109 |
110 | useEffect(() => {
111 | fetchTopCars();
112 | }, []);
113 |
114 | const isEmptyTopCars = !topCars || topCars.length === 0;
115 |
116 | const topChoice = topCars.filter(e => e.dailyPrice < 100);
117 | const sortedTopChoice = topChoice.sort((first, second) => 0 - (first.dailyPrice > second.dailyPrice ? -1 : 1));
118 |
119 | const cars =
120 | (!isEmptyTopCars &&
121 | sortedTopChoice.map((car) => )) ||
122 | [];
123 |
124 | const numberOfDots = isMobile ? cars.length : Math.ceil(cars.length / 4);
125 |
126 | return (
127 |
128 | Explore Our Top Deals
129 | {isLoading && (
130 |
131 |
132 |
133 | )}
134 | {isEmptyTopCars && !isLoading && No Cars To Show!}
135 | {!isEmptyTopCars && !isLoading && (
136 |
137 |
173 |
174 |
175 | )}
176 |
177 | );
178 | }
179 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/containers/HomePage/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 | Explore New Zealand with Us
157 |
158 | Kia Ora,Want to discover the beauty of New Zealand? 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 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 | );
177 | }
178 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/containers/HomePage/type.ts:
--------------------------------------------------------------------------------
1 | import { GetCars_cars } from "../../services/carService/__generated__/GetCars";
2 |
3 | export interface IHomePageState {
4 | topCars: GetCars_cars[];
5 | }
6 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/containers/UserLogin/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import tw from "twin.macro";
4 | import { Footer } from "../../components/footer";
5 | import { Marginer } from "../../components/marginer";
6 | import { Navbar } from "../../components/navbar";
7 | import { LoginSection } from "./loginSection";
8 | import { LoginSection2 } from "./loginSection2";
9 | import { TopSection } from "./topSection";
10 |
11 | const PageContainer = styled.div`
12 | ${tw`
13 | flex
14 | flex-col
15 | w-full
16 | h-full
17 | items-center
18 | overflow-x-hidden
19 | `}
20 | `;
21 |
22 | export function LoginPage() {
23 | return (
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/containers/UserLogin/loginSection.tsx:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import React, {SyntheticEvent, useState } from "react";
3 | import { Redirect } from "react-router-dom";
4 | import styled from "styled-components";
5 | import tw from "twin.macro";
6 |
7 |
8 |
9 | const LoginContainer = styled.div`
10 | font-family:'Lato',sans-serif;
11 | ${tw`
12 | h-full
13 | w-full
14 | md:pt-20
15 | pb-6
16 | px-2
17 | md:px-0
18 | `};
19 | `;
20 |
21 | const Header = styled.div`
22 | ${tw`
23 | max-w-lg mx-auto
24 | `};
25 | `;
26 |
27 | const Title = styled.div`
28 | ${tw`
29 | text-4xl
30 | font-bold
31 | text-white
32 | text-center
33 | `};
34 | `;
35 |
36 |
37 | interface ILoginProps{
38 | // set the props as username/email with id for create orders
39 | }
40 |
41 | export function LoginSection(props: ILoginProps){
42 |
43 | const [email, setEmail] = useState("")
44 | const [password, setPassword] = useState("")
45 | const [redirect, setRedirect] = useState(false);
46 |
47 | const submit = async (e: SyntheticEvent) => {
48 | e.preventDefault();
49 |
50 | await axios.post('http://localhost:9000/auth/login', {
51 | email, password
52 | }).then(function (response) {
53 | const res = response.data;
54 | localStorage.setItem('accessToken', res.access_token);
55 | if (res){
56 | setRedirect(true);
57 | return res;
58 | }else{
59 | alert('Invalid credentials')
60 | }
61 | return null;
62 | })
63 |
64 | }
65 |
66 | if (redirect) {
67 |
68 | return ;
69 | }
70 | return (
71 |
72 |
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 |
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 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 | );
177 | }
178 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/containers/UserLogin/type.ts:
--------------------------------------------------------------------------------
1 | import { LoginUser_login,LoginUser_login_user } from "../../services/userService/__generated__/LoginUser";
2 | import { User } from '../../../generated/graphql';
3 |
4 |
5 |
6 | export interface ILoginPageState{
7 | accessToken: LoginUser_login["access_token"],
8 | user:any
9 | }
--------------------------------------------------------------------------------
/react-cars-app/src/app/containers/orderPage/bookingSteps.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | faCalendarAlt,
3 | faCarSide,
4 | faMapMarkedAlt,
5 | } from "@fortawesome/free-solid-svg-icons";
6 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
7 | import React from "react";
8 | import styled from "styled-components";
9 | import tw from "twin.macro";
10 |
11 | const Container = styled.div`
12 | ${tw`
13 | w-full
14 | flex
15 | flex-col
16 | items-center
17 | pt-3
18 | pb-3
19 | lg:pt-6
20 | lg:pb-6
21 | `};
22 | `;
23 |
24 | const Title = styled.h2`
25 | ${tw`
26 | text-3xl
27 | lg:text-5xl
28 | text-black
29 | font-extrabold
30 | `};
31 | `;
32 |
33 | const StepsContainer = styled.div`
34 | ${tw`
35 | flex
36 | justify-evenly
37 | flex-wrap
38 | mt-7
39 | lg:mt-16
40 | `};
41 | `;
42 |
43 | const StepContainer = styled.div`
44 | ${tw`
45 | flex
46 | flex-col
47 | md:w-96
48 | items-center
49 | transition-colors
50 | hover:text-red-500
51 | m-3
52 | `};
53 | `;
54 |
55 | const Step = styled.div`
56 | box-shadow: 0 1.3px 12px -3px rgba(0, 0, 0, 0.4);
57 | ${tw`
58 | flex
59 | rounded-lg
60 | items-center
61 | justify-center
62 | p-6
63 | `};
64 | `;
65 |
66 | const StepTitle = styled.h4`
67 | ${tw`
68 | text-black
69 | text-lg
70 | font-semibold
71 | mt-4
72 | `};
73 | `;
74 |
75 | const StepDescription = styled.p`
76 | ${tw`
77 | w-10/12
78 | text-xs
79 | md:text-sm
80 | text-center
81 | text-gray-600
82 | `};
83 | `;
84 |
85 | const StepIcon = styled.span`
86 | ${tw`
87 | text-red-500
88 | text-3xl
89 | `};
90 | `;
91 |
92 | export function BookingSteps() {
93 | return (
94 |
95 | Our Working Steps
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 | Choose Location
104 |
105 | Find the nearest Yourcar point and book your car.
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | Pick-Up Date
115 |
116 | Pickup the Best Date to rent a car for you.
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 | Book Your Car
126 |
127 | Book your nice car with ease in one single click
128 |
129 |
130 |
131 |
132 | );
133 | }
134 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/containers/orderPage/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import tw from "twin.macro";
4 | import { BookCard } from "../../components/bookCard";
5 | import { Footer } from "../../components/footer";
6 | import { Marginer } from "../../components/marginer";
7 | import { Navbar } from "../../components/navbar";
8 | import { BookingSteps } from "./bookingSteps";
9 | import { TopSection } from "./topSection";
10 |
11 |
12 | const PageContainer = styled.div`
13 | ${tw`
14 | flex
15 | flex-col
16 | w-full
17 | h-full
18 | items-center
19 | overflow-x-hidden
20 | `}
21 | `;
22 |
23 | export function OrderPage() {
24 | return (
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/containers/orderPage/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 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 | );
177 | }
178 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/containers/signupPage/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import tw from "twin.macro";
4 | import { Footer } from "../../components/footer";
5 | import { Navbar } from "../../components/navbar";
6 | import { SignupSection } from "./signupSection";
7 |
8 | import { TopSection } from "./topSection";
9 |
10 | const PageContainer = styled.div`
11 | ${tw`
12 | flex
13 | flex-col
14 | w-full
15 | h-full
16 | items-center
17 | overflow-x-hidden
18 | `}
19 | `;
20 |
21 | export function SignupPage() {
22 | return (
23 |
24 |
25 |
26 | < SignupSection />
27 |
28 |
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/containers/signupPage/signupSection.tsx:
--------------------------------------------------------------------------------
1 | import { gql, useMutation } from "@apollo/client";
2 | import { useQuery } from "@apollo/react-hooks";
3 | import React, {SyntheticEvent, useState } from "react";
4 | import { useHistory, Link, Redirect } from "react-router-dom";
5 | import styled from "styled-components";
6 | import tw from "twin.macro";
7 | import { Button } from "../../components/button";
8 | import accessToken from "../../../accessToken"
9 | import { setAccessToken } from '../../../accessToken';
10 |
11 |
12 |
13 |
14 | const SignupContainer = styled.div`
15 | font-family:'Lato',sans-serif;
16 | ${tw`
17 | h-full
18 | w-full
19 | md:pt-20
20 | pb-6
21 | px-2
22 | md:px-0
23 | `};
24 | `;
25 |
26 | const Header = styled.div`
27 | ${tw`
28 | max-w-lg mx-auto
29 | `};
30 | `;
31 |
32 | const Title = styled.div`
33 | ${tw`
34 | text-4xl
35 | font-bold
36 | text-white
37 | text-center
38 | `};
39 | `;
40 |
41 |
42 | interface ISingupProps{
43 |
44 | }
45 |
46 | export function SignupSection(props: ISingupProps){
47 |
48 | const history = useHistory();
49 | const [username, setUsername] = useState("")
50 | const [email, setEmail] = useState("")
51 | const [password, setPassword] = useState("")
52 | const [confirmPassword, setConfirmPassword] = useState("")
53 | const [country,setCountry] = useState("")
54 | const [redirect, setRedirect] = useState(false);
55 |
56 |
57 | const submit = async (e: SyntheticEvent) => {
58 | e.preventDefault();
59 |
60 | if (typeof password !== "undefined" && typeof confirmPassword !== "undefined") {
61 |
62 | if (password != confirmPassword) {
63 | alert("Password does not match")
64 | }
65 | }
66 |
67 | const response = await fetch('http://localhost:9000/auth/signup', {
68 | method: 'POST',
69 | headers: {'Content-Type': 'application/json'},
70 | // credentials: 'include',
71 | body: JSON.stringify({
72 | username,
73 | email,
74 | password,
75 | country
76 | })
77 | });
78 | if(response.status === 201) {
79 | setRedirect(true);
80 |
81 | // console.log(accessToken);
82 | // get the logined user info and save it to cache
83 | }
84 |
85 | }
86 |
87 | if (redirect) {
88 |
89 | return ;
90 | }
91 | return (
92 |
93 |
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 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 | );
177 | }
178 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/graphql.ts:
--------------------------------------------------------------------------------
1 | import { ApolloClient, createHttpLink, InMemoryCache } from "@apollo/client";
2 | import { setContext } from '@apollo/client/link/context';
3 | import accessToken from "../accessToken"
4 |
5 | const httpLink = createHttpLink({
6 | uri: `${process.env.REACT_APP_API_URL}/graphql`,
7 | });
8 |
9 | const authLink = setContext((_, { headers }) => {
10 | // get the authentication token from local storage if it exists
11 | const accessToken = localStorage.getItem('accessToken');
12 | // return the headers to the context so httpLink can read them
13 | return {
14 | headers: {
15 | ...headers,
16 | authorization: accessToken ? `Bearer ${accessToken}` : "",
17 | }
18 | }
19 | });
20 |
21 | export const apolloClient = new ApolloClient({
22 | link: authLink.concat(httpLink),
23 | cache: new InMemoryCache(),
24 | // cache: new InMemoryCache().restore(window.__APOLLO_STATE__),
25 | });
26 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/hooks.ts:
--------------------------------------------------------------------------------
1 | import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
2 | import type { RootState, AppDispatch } from './store';
3 |
4 | // Use throughout your app instead of plain `useDispatch` and `useSelector`
5 | export const useAppDispatch = () => useDispatch();
6 | export const useAppSelector: TypedUseSelectorHook = useSelector;
7 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/services/carService/__generated__/GetCars.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | // ====================================================
7 | // GraphQL query operation: GetCars
8 | // ====================================================
9 |
10 | export interface GetCars_cars {
11 | __typename: "Car";
12 | id: string;
13 | name: string;
14 | category: string;
15 | mileage: string;
16 | gearType: string;
17 | gas: string;
18 | thumbnailUrl: string;
19 | dailyPrice: number;
20 | monthlyPrice: number;
21 | }
22 |
23 | export interface GetCars {
24 | cars: GetCars_cars[];
25 | }
26 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/services/carService/__generated__/GetCarsByCategory.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | // ====================================================
7 | // GraphQL query operation: GetCarsByCategory
8 | // ====================================================
9 |
10 | export interface GetCarsByCategory_findByCategory {
11 | __typename: "Car";
12 | id: string;
13 | name: string;
14 | mileage: string;
15 | gearType: string;
16 | gas: string;
17 | thumbnailUrl: string;
18 | dailyPrice: number;
19 | monthlyPrice: number;
20 | }
21 |
22 | export interface GetCarsByCategory {
23 | findByCategory: GetCarsByCategory_findByCategory[];
24 | }
25 |
26 | export interface GetCarsByCategoryVariables {
27 | category: string;
28 | }
29 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/services/carService/findCarByCatagory.ts:
--------------------------------------------------------------------------------
1 | import gql from "graphql-tag";
2 |
3 | export const GET_CARS_BYCATAG = gql`
4 | query GetCarsByCategory($category: String!) {
5 | findByCategory(category:$category) {
6 | id
7 | name
8 | mileage
9 | gearType
10 | gas
11 | thumbnailUrl
12 | dailyPrice
13 | monthlyPrice
14 | }
15 | }
16 | `;
--------------------------------------------------------------------------------
/react-cars-app/src/app/services/carService/index.ts:
--------------------------------------------------------------------------------
1 | import { apolloClient } from "../../graphql";
2 | import { GET_ALL_CARS } from "./queries";
3 | import { GetCars_cars } from "./__generated__/GetCars";
4 |
5 |
6 | class CarService {
7 | public async getCars(): Promise {
8 | const response = await apolloClient
9 | .query({ query: GET_ALL_CARS })
10 | .catch((err) => {
11 | throw err;
12 | });
13 |
14 | if (response && response.data && response.data.cars)
15 | return response.data.cars as GetCars_cars[];
16 |
17 | return [];
18 | }
19 |
20 | // public async findCarByCatag(): Promise {
21 | // const response = await apolloClient
22 | // .query({ query: GET_SUV})
23 | // .catch((err) => {
24 | // throw err;
25 | // });
26 |
27 | // if (response && response.data && response.data.cars)
28 | // return response.data.cars as FindSuv_findSUV[];
29 |
30 | // return [];
31 | // }
32 |
33 | }
34 |
35 | export default new CarService();
36 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/services/carService/queries.ts:
--------------------------------------------------------------------------------
1 | import gql from "graphql-tag";
2 |
3 | export const GET_ALL_CARS = gql`
4 | query GetCars {
5 | cars {
6 | id
7 | name
8 | category
9 | mileage
10 | gearType
11 | gas
12 | thumbnailUrl
13 | dailyPrice
14 | monthlyPrice
15 | }
16 | }
17 | `;
18 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/services/userService/CurreUser.ts:
--------------------------------------------------------------------------------
1 | import gql from "graphql-tag";
2 |
3 | export const ME_QUERY = gql`
4 | query UserNow{
5 | CurrentUser{
6 | id
7 | username
8 | email
9 | country
10 | thumbnailUrl
11 | }
12 | }
13 | `;
14 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/services/userService/SignUp.ts:
--------------------------------------------------------------------------------
1 | import gql from "graphql-tag";
2 |
3 | export const SIGN_UP = gql`
4 | mutation AddUser($SignupData:NewUserInput!){
5 | addNewUser(newUserData:SignupData){
6 | username
7 | email
8 | password
9 | country
10 | }
11 | }
12 | `;
--------------------------------------------------------------------------------
/react-cars-app/src/app/services/userService/__generated__/AddUser.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | import { NewUserInput } from "./../../../../../__generated__/globalTypes";
7 |
8 | // ====================================================
9 | // GraphQL mutation operation: AddUser
10 | // ====================================================
11 |
12 | export interface AddUser_addNewUser {
13 | __typename: "User";
14 | username: string;
15 | email: string;
16 | password: string;
17 | country: string;
18 | }
19 |
20 | export interface AddUser {
21 | addNewUser: AddUser_addNewUser;
22 | }
23 |
24 | export interface AddUserVariables {
25 | SignupData: NewUserInput;
26 | }
27 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/services/userService/__generated__/LoginUser.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | // ====================================================
7 | // GraphQL mutation operation: LoginUser
8 | // ====================================================
9 |
10 | export interface LoginUser_login_user {
11 | __typename: "User";
12 | id: string;
13 | username: string;
14 | email: string;
15 | country: string;
16 | thumbnailUrl: string;
17 | }
18 |
19 | export interface LoginUser_login {
20 | __typename: "LoginResponse";
21 | access_token: string;
22 | user: LoginUser_login_user;
23 | }
24 |
25 | export interface LoginUser {
26 | login: LoginUser_login;
27 | }
28 |
29 | export interface LoginUserVariables {
30 | email: string;
31 | password: string;
32 | }
33 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/services/userService/__generated__/UserNow.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | // ====================================================
7 | // GraphQL query operation: UserNow
8 | // ====================================================
9 |
10 | export interface UserNow_CurrentUser {
11 | __typename: "User";
12 | id: string;
13 | username: string;
14 | email: string;
15 | country: string;
16 | thumbnailUrl: string;
17 | }
18 |
19 | export interface UserNow {
20 | CurrentUser: UserNow_CurrentUser;
21 | }
22 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/services/userService/index.ts:
--------------------------------------------------------------------------------
1 | import { apolloClient } from "../../graphql";
2 | import { LoginUserVariables } from './__generated__/LoginUser';
3 | import {USER_LOGIN} from './userLogin'
4 | import { AddUserVariables, AddUser_addNewUser } from "./__generated__/AddUser";
5 | import { SIGN_UP } from "./SignUp";
6 |
7 | class UserService{
8 |
9 | public async login(variables:LoginUserVariables): Promise{
10 | const response = await apolloClient
11 | .mutate({ mutation: USER_LOGIN,variables})
12 | .catch((err) => {
13 | throw err;
14 | });
15 |
16 | if (response && response.data)
17 | return response.data;
18 |
19 | return "";
20 | };
21 |
22 | public async signup(variables:AddUserVariables): Promise{
23 | const response = await apolloClient
24 | .mutate({ mutation: SIGN_UP,variables})
25 | .catch((err) => {
26 | throw err;
27 | });
28 |
29 | return response.data.user as AddUser_addNewUser;
30 |
31 | };
32 | }
33 |
34 | export default new UserService();
--------------------------------------------------------------------------------
/react-cars-app/src/app/services/userService/userLogin.ts:
--------------------------------------------------------------------------------
1 | import gql from "graphql-tag";
2 |
3 | export const USER_LOGIN = gql`
4 | mutation LoginUser($email: String!, $password:String!) {
5 | login(email:$email, password:$password){
6 | access_token
7 | }
8 | }
9 | `;
10 |
11 |
--------------------------------------------------------------------------------
/react-cars-app/src/app/store.ts:
--------------------------------------------------------------------------------
1 | import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit";
2 | import homePageReducer from "./containers/HomePage/slice";
3 | import reduxLogger from "redux-logger";
4 |
5 | export const store = configureStore({
6 | middleware: (getDefaultMiddleware) =>
7 | getDefaultMiddleware().concat(reduxLogger),
8 | reducer: {
9 | homePage: homePageReducer,
10 |
11 | },
12 | });
13 |
14 | export type AppDispatch = typeof store.dispatch;
15 | export type RootState = ReturnType;
16 | export type AppThunk = ThunkAction<
17 | ReturnType,
18 | RootState,
19 | unknown,
20 | Action
21 | >;
22 |
--------------------------------------------------------------------------------
/react-cars-app/src/assets/images/blob.svg:
--------------------------------------------------------------------------------
1 |
2 |
6 |
--------------------------------------------------------------------------------
/react-cars-app/src/assets/images/car-logo-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daydreamerli/nestjs-graphql-react-fullstack/d6dd1dec1eda1130c162936a254ff3614daae21c/react-cars-app/src/assets/images/car-logo-dark.png
--------------------------------------------------------------------------------
/react-cars-app/src/assets/images/car-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daydreamerli/nestjs-graphql-react-fullstack/d6dd1dec1eda1130c162936a254ff3614daae21c/react-cars-app/src/assets/images/car-logo.png
--------------------------------------------------------------------------------
/react-cars-app/src/assets/images/homepage_design.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daydreamerli/nestjs-graphql-react-fullstack/d6dd1dec1eda1130c162936a254ff3614daae21c/react-cars-app/src/assets/images/homepage_design.png
--------------------------------------------------------------------------------
/react-cars-app/src/assets/images/jeep.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daydreamerli/nestjs-graphql-react-fullstack/d6dd1dec1eda1130c162936a254ff3614daae21c/react-cars-app/src/assets/images/jeep.png
--------------------------------------------------------------------------------
/react-cars-app/src/assets/images/landrover-sport.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daydreamerli/nestjs-graphql-react-fullstack/d6dd1dec1eda1130c162936a254ff3614daae21c/react-cars-app/src/assets/images/landrover-sport.png
--------------------------------------------------------------------------------
/react-cars-app/src/assets/images/mclaren-orange-big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daydreamerli/nestjs-graphql-react-fullstack/d6dd1dec1eda1130c162936a254ff3614daae21c/react-cars-app/src/assets/images/mclaren-orange-big.png
--------------------------------------------------------------------------------
/react-cars-app/src/assets/images/mclaren-orange.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daydreamerli/nestjs-graphql-react-fullstack/d6dd1dec1eda1130c162936a254ff3614daae21c/react-cars-app/src/assets/images/mclaren-orange.png
--------------------------------------------------------------------------------
/react-cars-app/src/assets/images/porche.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daydreamerli/nestjs-graphql-react-fullstack/d6dd1dec1eda1130c162936a254ff3614daae21c/react-cars-app/src/assets/images/porche.png
--------------------------------------------------------------------------------
/react-cars-app/src/generated/graphql.ts:
--------------------------------------------------------------------------------
1 | export type Maybe = T | null;
2 | export type Exact = { [K in keyof T]: T[K] };
3 | export type MakeOptional = Omit & { [SubKey in K]?: Maybe };
4 | export type MakeMaybe = Omit & { [SubKey in K]: Maybe };
5 | /** All built-in and custom scalars, mapped to their actual values */
6 | export type Scalars = {
7 | ID: string;
8 | String: string;
9 | Boolean: boolean;
10 | Int: number;
11 | Float: number;
12 | };
13 |
14 | export type Car = {
15 | __typename?: 'Car';
16 | category: Scalars['String'];
17 | dailyPrice: Scalars['Float'];
18 | driveTrain: Scalars['String'];
19 | gas: Scalars['String'];
20 | gearType: Scalars['String'];
21 | id: Scalars['String'];
22 | mileage: Scalars['String'];
23 | monthlyPrice: Scalars['Float'];
24 | name: Scalars['String'];
25 | quantity: Scalars['Float'];
26 | thumbnailUrl: Scalars['String'];
27 | year: Scalars['String'];
28 | };
29 |
30 | export type LoginResponse = {
31 | __typename?: 'LoginResponse';
32 | access_token: Scalars['String'];
33 | user: User;
34 | };
35 |
36 | export type Mutation = {
37 | __typename?: 'Mutation';
38 | UpdateUserPass: User;
39 | addNewCar: Car;
40 | addNewOrder: Order;
41 | addNewUser: User;
42 | deleteAllOrders: Scalars['Boolean'];
43 | deleteAllUsers: Scalars['Boolean'];
44 | deleteOne: Scalars['Boolean'];
45 | deleteUser: Scalars['Boolean'];
46 | login: LoginResponse;
47 | updateCar: Car;
48 | updateQuantity: Car;
49 | updateUserInfo: User;
50 | };
51 |
52 |
53 | export type MutationUpdateUserPassArgs = {
54 | currPass: Scalars['String'];
55 | newPass: Scalars['String'];
56 | };
57 |
58 |
59 | export type MutationAddNewCarArgs = {
60 | newCarData: NewCarInput;
61 | };
62 |
63 |
64 | export type MutationAddNewOrderArgs = {
65 | carsId: Scalars['String'];
66 | newOrderData: NewOrderInput;
67 | };
68 |
69 |
70 | export type MutationAddNewUserArgs = {
71 | newUserData: NewUserInput;
72 | };
73 |
74 |
75 | export type MutationDeleteOneArgs = {
76 | id: Scalars['String'];
77 | };
78 |
79 |
80 | export type MutationLoginArgs = {
81 | email: Scalars['String'];
82 | password: Scalars['String'];
83 | };
84 |
85 |
86 | export type MutationUpdateCarArgs = {
87 | name: Scalars['String'];
88 | updateData: UpdateCarInput;
89 | };
90 |
91 |
92 | export type MutationUpdateQuantityArgs = {
93 | name: Scalars['String'];
94 | orderQuantity: OrderQuantityInput;
95 | };
96 |
97 |
98 | export type MutationUpdateUserInfoArgs = {
99 | updateData: UpdateUserInput;
100 | };
101 |
102 | export type NewCarInput = {
103 | category: Scalars['String'];
104 | dailyPrice: Scalars['Int'];
105 | driveTrain: Scalars['String'];
106 | gas: Scalars['String'];
107 | gearType: Scalars['String'];
108 | mileage: Scalars['String'];
109 | monthlyPrice: Scalars['Int'];
110 | name: Scalars['String'];
111 | quantity: Scalars['Int'];
112 | thumbnailUrl: Scalars['String'];
113 | year: Scalars['String'];
114 | };
115 |
116 | export type NewOrderInput = {
117 | amount: Scalars['Int'];
118 | duration: Scalars['Int'];
119 | endDate: Scalars['String'];
120 | orderedCars: Scalars['String'];
121 | ownerId: Scalars['String'];
122 | startDate: Scalars['String'];
123 | };
124 |
125 | export type NewUserInput = {
126 | country?: Maybe;
127 | email: Scalars['String'];
128 | password: Scalars['String'];
129 | thumbnailUrl?: Maybe;
130 | username: Scalars['String'];
131 | };
132 |
133 | export type Order = {
134 | __typename?: 'Order';
135 | amount: Scalars['Float'];
136 | duration: Scalars['Float'];
137 | endDate: Scalars['String'];
138 | id: Scalars['String'];
139 | orderedCars: Scalars['String'];
140 | startDate: Scalars['String'];
141 | };
142 |
143 | export type OrderQuantityInput = {
144 | orderNumber: Scalars['Int'];
145 | };
146 |
147 | export type Query = {
148 | __typename?: 'Query';
149 | CheckAvailable: Scalars['String'];
150 | CurrentUser: User;
151 | cars: Array;
152 | findByCategory: Array;
153 | findByDrivetrain: Array;
154 | findByName: Car;
155 | findOrderCars: Array;
156 | finduserByEmail: User;
157 | finduserById: User;
158 | getAllOrders: Array;
159 | getAllUsers: Array;
160 | getCarOrders: Array;
161 | getUserOrders: Array;
162 | };
163 |
164 |
165 | export type QueryCheckAvailableArgs = {
166 | name: Scalars['String'];
167 | };
168 |
169 |
170 | export type QueryFindByCategoryArgs = {
171 | category: Scalars['String'];
172 | };
173 |
174 |
175 | export type QueryFindByDrivetrainArgs = {
176 | driveTrain: Scalars['String'];
177 | };
178 |
179 |
180 | export type QueryFindByNameArgs = {
181 | name: Scalars['String'];
182 | };
183 |
184 |
185 | export type QueryFindOrderCarsArgs = {
186 | ordersId: Scalars['String'];
187 | };
188 |
189 |
190 | export type QueryFinduserByEmailArgs = {
191 | email: Scalars['String'];
192 | };
193 |
194 |
195 | export type QueryFinduserByIdArgs = {
196 | id: Scalars['String'];
197 | };
198 |
199 |
200 | export type QueryGetCarOrdersArgs = {
201 | carsId: Scalars['String'];
202 | };
203 |
204 |
205 | export type QueryGetUserOrdersArgs = {
206 | ownerId: Scalars['String'];
207 | };
208 |
209 | export type UpdateCarInput = {
210 | dailyPrice: Scalars['Int'];
211 | mileage?: Maybe;
212 | monthlyPrice: Scalars['Int'];
213 | thumbnailUrl?: Maybe;
214 | };
215 |
216 | export type UpdateUserInput = {
217 | country?: Maybe;
218 | thumbnailUrl?: Maybe;
219 | username: Scalars['String'];
220 | };
221 |
222 | export type User = {
223 | __typename?: 'User';
224 | country: Scalars['String'];
225 | email: Scalars['String'];
226 | id: Scalars['String'];
227 | password: Scalars['String'];
228 | thumbnailUrl: Scalars['String'];
229 | username: Scalars['String'];
230 | };
231 |
--------------------------------------------------------------------------------
/react-cars-app/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@500;700;800;900&display=swap");
6 |
7 | html,
8 | body,
9 | #root {
10 | width: 100%;
11 | height: 100%;
12 | }
13 |
14 | body {
15 | margin: 0;
16 | font-family: "Poppins", -apple-system, BlinkMacSystemFont, "Segoe UI",
17 | "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
18 | "Helvetica Neue", sans-serif;
19 | -webkit-font-smoothing: antialiased;
20 | -moz-osx-font-smoothing: grayscale;
21 | }
22 |
23 | code {
24 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
25 | monospace;
26 | }
27 |
--------------------------------------------------------------------------------
/react-cars-app/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import "./index.css";
4 | import App from "./App";
5 | import { store } from "./app/store";
6 | import { Provider } from "react-redux";
7 | import * as serviceWorker from "./serviceWorker";
8 | import { ApolloProvider } from "@apollo/client";
9 | import { apolloClient } from "./app/graphql";
10 | import { setContext } from '@apollo/client/link/context';
11 |
12 | ReactDOM.render(
13 |
14 |
15 |
16 |
17 |
18 |
19 | ,
20 | document.getElementById("root")
21 | );
22 |
23 | // If you want your app to work offline and load faster, you can change
24 | // unregister() to register() below. Note this comes with some pitfalls.
25 | // Learn more about service workers: https://bit.ly/CRA-PWA
26 | serviceWorker.unregister();
27 |
--------------------------------------------------------------------------------
/react-cars-app/src/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/react-cars-app/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/react-cars-app/src/serviceWorker.ts:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | type Config = {
24 | onSuccess?: (registration: ServiceWorkerRegistration) => void;
25 | onUpdate?: (registration: ServiceWorkerRegistration) => void;
26 | };
27 |
28 | export function register(config?: Config) {
29 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
30 | // The URL constructor is available in all browsers that support SW.
31 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
32 | if (publicUrl.origin !== window.location.origin) {
33 | // Our service worker won't work if PUBLIC_URL is on a different origin
34 | // from what our page is served on. This might happen if a CDN is used to
35 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
36 | return;
37 | }
38 |
39 | window.addEventListener('load', () => {
40 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
41 |
42 | if (isLocalhost) {
43 | // This is running on localhost. Let's check if a service worker still exists or not.
44 | checkValidServiceWorker(swUrl, config);
45 |
46 | // Add some additional logging to localhost, pointing developers to the
47 | // service worker/PWA documentation.
48 | navigator.serviceWorker.ready.then(() => {
49 | console.log(
50 | 'This web app is being served cache-first by a service ' +
51 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
52 | );
53 | });
54 | } else {
55 | // Is not localhost. Just register service worker
56 | registerValidSW(swUrl, config);
57 | }
58 | });
59 | }
60 | }
61 |
62 | function registerValidSW(swUrl: string, config?: Config) {
63 | navigator.serviceWorker
64 | .register(swUrl)
65 | .then((registration) => {
66 | registration.onupdatefound = () => {
67 | const installingWorker = registration.installing;
68 | if (installingWorker == null) {
69 | return;
70 | }
71 | installingWorker.onstatechange = () => {
72 | if (installingWorker.state === 'installed') {
73 | if (navigator.serviceWorker.controller) {
74 | // At this point, the updated precached content has been fetched,
75 | // but the previous service worker will still serve the older
76 | // content until all client tabs are closed.
77 | console.log(
78 | 'New content is available and will be used when all ' +
79 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
80 | );
81 |
82 | // Execute callback
83 | if (config && config.onUpdate) {
84 | config.onUpdate(registration);
85 | }
86 | } else {
87 | // At this point, everything has been precached.
88 | // It's the perfect time to display a
89 | // "Content is cached for offline use." message.
90 | console.log('Content is cached for offline use.');
91 |
92 | // Execute callback
93 | if (config && config.onSuccess) {
94 | config.onSuccess(registration);
95 | }
96 | }
97 | }
98 | };
99 | };
100 | })
101 | .catch((error) => {
102 | console.error('Error during service worker registration:', error);
103 | });
104 | }
105 |
106 | function checkValidServiceWorker(swUrl: string, config?: Config) {
107 | // Check if the service worker can be found. If it can't reload the page.
108 | fetch(swUrl, {
109 | headers: { 'Service-Worker': 'script' },
110 | })
111 | .then((response) => {
112 | // Ensure service worker exists, and that we really are getting a JS file.
113 | const contentType = response.headers.get('content-type');
114 | if (
115 | response.status === 404 ||
116 | (contentType != null && contentType.indexOf('javascript') === -1)
117 | ) {
118 | // No service worker found. Probably a different app. Reload the page.
119 | navigator.serviceWorker.ready.then((registration) => {
120 | registration.unregister().then(() => {
121 | window.location.reload();
122 | });
123 | });
124 | } else {
125 | // Service worker found. Proceed as normal.
126 | registerValidSW(swUrl, config);
127 | }
128 | })
129 | .catch(() => {
130 | console.log(
131 | 'No internet connection found. App is running in offline mode.'
132 | );
133 | });
134 | }
135 |
136 | export function unregister() {
137 | if ('serviceWorker' in navigator) {
138 | navigator.serviceWorker.ready
139 | .then((registration) => {
140 | registration.unregister();
141 | })
142 | .catch((error) => {
143 | console.error(error.message);
144 | });
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/react-cars-app/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/react-cars-app/src/typings/car.ts:
--------------------------------------------------------------------------------
1 | export interface ICar {
2 | name: string;
3 | dailyPrice: number;
4 | monthlyPrice: number;
5 | mileage: string;
6 | gas: string;
7 | gearType: string;
8 | thumbnailSrc: string;
9 | }
10 |
--------------------------------------------------------------------------------
/react-cars-app/src/typings/index.ts:
--------------------------------------------------------------------------------
1 |
2 | import { IHomePageState } from "../app/containers/HomePage/type";
3 | import { ILoginPageState } from "../app/containers/UserLogin/type";
4 |
5 |
6 | export interface IRootAppState {
7 | homePage: IHomePageState;
8 | loginPage:ILoginPageState;
9 | }
10 |
--------------------------------------------------------------------------------
/react-cars-app/src/typings/react-burger-menu.d.ts:
--------------------------------------------------------------------------------
1 | declare module "react-burger-menu";
2 |
--------------------------------------------------------------------------------
/react-cars-app/src/typings/react-carousel.d.ts:
--------------------------------------------------------------------------------
1 | declare module "@brainhubeu/react-carousel";
2 |
--------------------------------------------------------------------------------
/react-cars-app/src/typings/user.ts:
--------------------------------------------------------------------------------
1 | export interface IUser {
2 | username: string;
3 | password: string;
4 | email: string;
5 | country: string;
6 |
7 | }
--------------------------------------------------------------------------------
/react-cars-app/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | purge: ["./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"],
3 | darkMode: false, // or 'media' or 'class'
4 | theme: {
5 | extend: {},
6 | screens: {
7 | sm: "640px",
8 | // => @media (min-width: 640px) { ... }
9 |
10 | md: "768px",
11 | // => @media (min-width: 768px) { ... }
12 |
13 | lg: "1024px",
14 | // => @media (min-width: 1024px) { ... }
15 |
16 | xl: "1280px",
17 | // => @media (min-width: 1280px) { ... }
18 |
19 | "2xl": "1536px",
20 | // => @media (min-width: 1536px) { ... }
21 | },
22 | },
23 | variants: {
24 | extend: {},
25 | },
26 | plugins: [],
27 | };
28 |
--------------------------------------------------------------------------------
/react-cars-app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src/**/*"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------