├── next-env.d.ts ├── api ├── Faq.ts └── Car.ts ├── cars.sqlite ├── public ├── favicon.ico ├── photos │ └── cars │ │ ├── mazda-6-2015.jpg │ │ ├── audi-a2-2003-8199.jpg │ │ ├── bmw-320-2009-18000.jpg │ │ ├── bmw-x1-2012-24999.jpg │ │ ├── renault-espace-2004.jpg │ │ ├── seat-leon-2010-13650.jpg │ │ ├── ford-fiesta-2008-5950.jpg │ │ ├── ford-fiesta-2014-9950.jpg │ │ ├── peugeot-3008-2014-17999.jpg │ │ ├── renault-clio-2017-13485.jpg │ │ ├── smart-fortwo-2008-5800.jpg │ │ ├── smart-fortwo-2012-7950.jpg │ │ ├── bmw-116-d-line-urban-2013.jpg │ │ ├── renault-megane-2016-15890.jpg │ │ ├── volkswagen-eos-2008-14700.jpg │ │ ├── mercedes-benz-200-2015-24999.jpg │ │ ├── volkswagen-golf-2013-18300.jpg │ │ ├── volkswagen-tiguan-2007-14299.jpg │ │ ├── mercedes-benz-e250-2011-29800.jpg │ │ ├── smart-fortwo-passion-2003-8888.jpg │ │ └── smart-fortwo-passion-2015-11500.jpg └── vercel.svg ├── src ├── getAsString.ts ├── openDB.ts ├── pages │ ├── api │ │ ├── cars.ts │ │ └── models.ts │ ├── faq.tsx │ ├── _app.tsx │ ├── _document.tsx │ ├── cars.tsx │ ├── car │ │ └── [make] │ │ │ └── [brand] │ │ │ └── [id].tsx │ └── index.tsx ├── database │ ├── getMakes.ts │ ├── getModels.ts │ └── getPaginatedCars.ts └── components │ ├── Nav.tsx │ ├── CarPagination.tsx │ └── CarCard.tsx ├── .gitignore ├── database-test.js ├── tsconfig.json ├── package.json ├── README.md └── migrations └── 001-helloworld.sql /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /api/Faq.ts: -------------------------------------------------------------------------------- 1 | export interface FaqModel { 2 | id: number; 3 | question: string; 4 | answer: string; 5 | } -------------------------------------------------------------------------------- /cars.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmvantunes/youtube-2020-may-building-a-car-trader-app-6/HEAD/cars.sqlite -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmvantunes/youtube-2020-may-building-a-car-trader-app-6/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/photos/cars/mazda-6-2015.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmvantunes/youtube-2020-may-building-a-car-trader-app-6/HEAD/public/photos/cars/mazda-6-2015.jpg -------------------------------------------------------------------------------- /public/photos/cars/audi-a2-2003-8199.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmvantunes/youtube-2020-may-building-a-car-trader-app-6/HEAD/public/photos/cars/audi-a2-2003-8199.jpg -------------------------------------------------------------------------------- /public/photos/cars/bmw-320-2009-18000.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmvantunes/youtube-2020-may-building-a-car-trader-app-6/HEAD/public/photos/cars/bmw-320-2009-18000.jpg -------------------------------------------------------------------------------- /public/photos/cars/bmw-x1-2012-24999.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmvantunes/youtube-2020-may-building-a-car-trader-app-6/HEAD/public/photos/cars/bmw-x1-2012-24999.jpg -------------------------------------------------------------------------------- /public/photos/cars/renault-espace-2004.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmvantunes/youtube-2020-may-building-a-car-trader-app-6/HEAD/public/photos/cars/renault-espace-2004.jpg -------------------------------------------------------------------------------- /public/photos/cars/seat-leon-2010-13650.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmvantunes/youtube-2020-may-building-a-car-trader-app-6/HEAD/public/photos/cars/seat-leon-2010-13650.jpg -------------------------------------------------------------------------------- /public/photos/cars/ford-fiesta-2008-5950.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmvantunes/youtube-2020-may-building-a-car-trader-app-6/HEAD/public/photos/cars/ford-fiesta-2008-5950.jpg -------------------------------------------------------------------------------- /public/photos/cars/ford-fiesta-2014-9950.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmvantunes/youtube-2020-may-building-a-car-trader-app-6/HEAD/public/photos/cars/ford-fiesta-2014-9950.jpg -------------------------------------------------------------------------------- /public/photos/cars/peugeot-3008-2014-17999.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmvantunes/youtube-2020-may-building-a-car-trader-app-6/HEAD/public/photos/cars/peugeot-3008-2014-17999.jpg -------------------------------------------------------------------------------- /public/photos/cars/renault-clio-2017-13485.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmvantunes/youtube-2020-may-building-a-car-trader-app-6/HEAD/public/photos/cars/renault-clio-2017-13485.jpg -------------------------------------------------------------------------------- /public/photos/cars/smart-fortwo-2008-5800.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmvantunes/youtube-2020-may-building-a-car-trader-app-6/HEAD/public/photos/cars/smart-fortwo-2008-5800.jpg -------------------------------------------------------------------------------- /public/photos/cars/smart-fortwo-2012-7950.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmvantunes/youtube-2020-may-building-a-car-trader-app-6/HEAD/public/photos/cars/smart-fortwo-2012-7950.jpg -------------------------------------------------------------------------------- /public/photos/cars/bmw-116-d-line-urban-2013.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmvantunes/youtube-2020-may-building-a-car-trader-app-6/HEAD/public/photos/cars/bmw-116-d-line-urban-2013.jpg -------------------------------------------------------------------------------- /public/photos/cars/renault-megane-2016-15890.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmvantunes/youtube-2020-may-building-a-car-trader-app-6/HEAD/public/photos/cars/renault-megane-2016-15890.jpg -------------------------------------------------------------------------------- /public/photos/cars/volkswagen-eos-2008-14700.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmvantunes/youtube-2020-may-building-a-car-trader-app-6/HEAD/public/photos/cars/volkswagen-eos-2008-14700.jpg -------------------------------------------------------------------------------- /src/getAsString.ts: -------------------------------------------------------------------------------- 1 | export function getAsString(value: string|string[]): string { 2 | if(Array.isArray(value)) { 3 | return value[0]; 4 | } 5 | 6 | return value; 7 | } -------------------------------------------------------------------------------- /public/photos/cars/mercedes-benz-200-2015-24999.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmvantunes/youtube-2020-may-building-a-car-trader-app-6/HEAD/public/photos/cars/mercedes-benz-200-2015-24999.jpg -------------------------------------------------------------------------------- /public/photos/cars/volkswagen-golf-2013-18300.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmvantunes/youtube-2020-may-building-a-car-trader-app-6/HEAD/public/photos/cars/volkswagen-golf-2013-18300.jpg -------------------------------------------------------------------------------- /public/photos/cars/volkswagen-tiguan-2007-14299.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmvantunes/youtube-2020-may-building-a-car-trader-app-6/HEAD/public/photos/cars/volkswagen-tiguan-2007-14299.jpg -------------------------------------------------------------------------------- /public/photos/cars/mercedes-benz-e250-2011-29800.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmvantunes/youtube-2020-may-building-a-car-trader-app-6/HEAD/public/photos/cars/mercedes-benz-e250-2011-29800.jpg -------------------------------------------------------------------------------- /public/photos/cars/smart-fortwo-passion-2003-8888.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmvantunes/youtube-2020-may-building-a-car-trader-app-6/HEAD/public/photos/cars/smart-fortwo-passion-2003-8888.jpg -------------------------------------------------------------------------------- /public/photos/cars/smart-fortwo-passion-2015-11500.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmvantunes/youtube-2020-may-building-a-car-trader-app-6/HEAD/public/photos/cars/smart-fortwo-passion-2015-11500.jpg -------------------------------------------------------------------------------- /src/openDB.ts: -------------------------------------------------------------------------------- 1 | import { open } from 'sqlite' 2 | import sqlite3 from 'sqlite3' 3 | 4 | export async function openDB () { 5 | return open({ 6 | filename: 'cars.sqlite', 7 | driver: sqlite3.Database 8 | }) 9 | } -------------------------------------------------------------------------------- /api/Car.ts: -------------------------------------------------------------------------------- 1 | export interface CarModel { 2 | id: number; 3 | make: string; 4 | model: string; 5 | year: number; 6 | fuelType: string; 7 | kilometers: number; 8 | details: string; 9 | price: number; 10 | photoUrl: string; 11 | } 12 | -------------------------------------------------------------------------------- /src/pages/api/cars.ts: -------------------------------------------------------------------------------- 1 | import { NextApiRequest, NextApiResponse } from 'next'; 2 | import { getPaginatedCars } from '../../database/getPaginatedCars'; 3 | 4 | export default async function cars( 5 | req: NextApiRequest, 6 | res: NextApiResponse 7 | ) { 8 | const cars = await getPaginatedCars(req.query); 9 | res.json(cars); 10 | } 11 | -------------------------------------------------------------------------------- /src/database/getMakes.ts: -------------------------------------------------------------------------------- 1 | import { openDB } from '../openDB'; 2 | 3 | export interface Make { 4 | make: string; 5 | count: number; 6 | } 7 | 8 | export async function getMakes() { 9 | const db = await openDB(); 10 | const makes = await db.all(` 11 | SELECT make, count(*) as count 12 | FROM car 13 | GROUP BY make 14 | `); 15 | return makes; 16 | } 17 | -------------------------------------------------------------------------------- /src/pages/api/models.ts: -------------------------------------------------------------------------------- 1 | import { NextApiRequest, NextApiResponse } from 'next'; 2 | import { getModels } from '../../database/getModels'; 3 | import { getAsString } from '../../getAsString'; 4 | 5 | export default async function models( 6 | req: NextApiRequest, 7 | res: NextApiResponse 8 | ) { 9 | const make = getAsString(req.query.make); 10 | const models = await getModels(make); 11 | res.json(models); 12 | } 13 | -------------------------------------------------------------------------------- /src/database/getModels.ts: -------------------------------------------------------------------------------- 1 | import { openDB } from '../openDB'; 2 | 3 | export interface Model { 4 | model: string; 5 | count: number; 6 | } 7 | 8 | export async function getModels(make: string) { 9 | const db = await openDB(); 10 | const model = await db.all( 11 | ` 12 | SELECT model, count(*) as count 13 | FROM car 14 | WHERE make = @make 15 | GROUP BY model 16 | `, 17 | { '@make': make } 18 | ); 19 | return model; 20 | } 21 | -------------------------------------------------------------------------------- /.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 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | 21 | # debug 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | # local env files 27 | .env.local 28 | .env.development.local 29 | .env.test.local 30 | .env.production.local 31 | -------------------------------------------------------------------------------- /database-test.js: -------------------------------------------------------------------------------- 1 | const sqlite = require('sqlite'); 2 | const sqlite3 = require('sqlite3'); 3 | 4 | async function setup() { 5 | const db = await sqlite.open({ 6 | filename: 'cars.sqlite', 7 | driver: sqlite3.Database, 8 | }); 9 | 10 | await db.migrate({ force: 'last' }); 11 | 12 | const faq = await db.all('SELECT * FROM FAQ ORDER BY createDate DESC'); 13 | console.log('ALL faq', JSON.stringify(faq, null, 2)); 14 | 15 | const cars = await db.all('SELECT * FROM Car'); 16 | console.log('ALL CARS', JSON.stringify(cars, null, 2)); 17 | } 18 | 19 | setup(); 20 | -------------------------------------------------------------------------------- /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 | "strict": false, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve" 20 | }, 21 | "exclude": [ 22 | "node_modules" 23 | ], 24 | "include": [ 25 | "next-env.d.ts", 26 | "**/*.ts", 27 | "**/*.tsx" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "youtube-2020-may-nextjs-part11", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "node ./database-test.js && next dev", 7 | "build": "next build", 8 | "start": "next start" 9 | }, 10 | "dependencies": { 11 | "@material-ui/core": "4.9.14", 12 | "@material-ui/icons": "4.9.1", 13 | "@material-ui/lab": "^4.0.0-alpha.53", 14 | "axios": "0.19.2", 15 | "formik": "2.1.4", 16 | "next": "9.4.2", 17 | "react": "16.13.1", 18 | "react-dom": "16.13.1", 19 | "sqlite": "4.0.8", 20 | "sqlite3": "4.2.0", 21 | "swr": "0.2.1" 22 | }, 23 | "devDependencies": { 24 | "@types/node": "14.0.5", 25 | "@types/react": "16.9.35", 26 | "typescript": "3.9.3" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/Nav.tsx: -------------------------------------------------------------------------------- 1 | import { AppBar, Button, makeStyles, Toolbar, Typography } from '@material-ui/core'; 2 | import Link from 'next/link'; 3 | import React from 'react'; 4 | 5 | const useStyles = makeStyles((theme) => ({ 6 | root: { 7 | flexGrow: 1, 8 | }, 9 | menuButton: { 10 | marginRight: theme.spacing(2), 11 | }, 12 | title: { 13 | flexGrow: 1, 14 | }, 15 | })); 16 | 17 | export function Nav() { 18 | const classes = useStyles(); 19 | 20 | return ( 21 | 22 | 23 | 24 | Car Trader 25 | 26 | 27 | 34 | 35 | 42 | 43 | 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /src/pages/faq.tsx: -------------------------------------------------------------------------------- 1 | import { ExpansionPanel, ExpansionPanelDetails, ExpansionPanelSummary, Typography } from '@material-ui/core'; 2 | import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; 3 | import { GetStaticProps } from 'next'; 4 | import { FaqModel } from '../../api/Faq'; 5 | import { openDB } from '../openDB'; 6 | 7 | interface FaqProps { 8 | faq: FaqModel[]; 9 | } 10 | 11 | export default function Faq({ faq }: FaqProps) { 12 | return ( 13 |
14 | {faq.map((f) => ( 15 | 16 | } 18 | aria-controls="panel1a-content" 19 | id="panel1a-header" 20 | > 21 | {f.question} 22 | 23 | 24 | {f.answer} 25 | 26 | 27 | ))} 28 |
29 | ); 30 | } 31 | 32 | export const getStaticProps: GetStaticProps = async () => { 33 | const db = await openDB(); 34 | const faq = await db.all('SELECT * FROM FAQ ORDER BY createDate DESC'); 35 | return { props: { faq } }; 36 | }; 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/zeit/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | ``` 12 | 13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 14 | 15 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. 16 | 17 | ## Learn More 18 | 19 | To learn more about Next.js, take a look at the following resources: 20 | 21 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 22 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 23 | 24 | You can check out [the Next.js GitHub repository](https://github.com/zeit/next.js/) - your feedback and contributions are welcome! 25 | 26 | ## Deploy on Vercel 27 | 28 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/import?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 29 | 30 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 31 | -------------------------------------------------------------------------------- /src/components/CarPagination.tsx: -------------------------------------------------------------------------------- 1 | import { PaginationRenderItemParams } from '@material-ui/lab'; 2 | import Pagination from '@material-ui/lab/Pagination'; 3 | import PaginationItem from '@material-ui/lab/PaginationItem'; 4 | import Link from 'next/link'; 5 | import { useRouter } from 'next/router'; 6 | import { ParsedUrlQuery } from 'querystring'; 7 | import { forwardRef } from 'react'; 8 | import { getAsString } from '../getAsString'; 9 | 10 | export function CarPagination({ totalPages }: { totalPages: number }) { 11 | const { query } = useRouter(); 12 | 13 | return ( 14 | ( 18 | 24 | )} 25 | /> 26 | ); 27 | } 28 | 29 | export interface MaterialUiLinkProps { 30 | item: PaginationRenderItemParams; 31 | query: ParsedUrlQuery; 32 | } 33 | 34 | const MaterialUiLink = forwardRef( 35 | ({ item, query, ...props }, ref) => ( 36 | 43 | 44 | 45 | ) 46 | ); 47 | -------------------------------------------------------------------------------- /src/database/getPaginatedCars.ts: -------------------------------------------------------------------------------- 1 | import { ParsedUrlQuery } from 'querystring'; 2 | import { CarModel } from '../../api/Car'; 3 | import { getAsString } from '../getAsString'; 4 | import { openDB } from '../openDB'; 5 | 6 | const mainQuery = ` 7 | FROM car 8 | WHERE (@make is NULL OR @make = make) 9 | AND (@model is NULL OR @model = model) 10 | AND (@minPrice is NULL OR @minPrice <= price) 11 | AND (@maxPrice is NULL OR @maxPrice >= price) 12 | `; 13 | 14 | export async function getPaginatedCars(query: ParsedUrlQuery) { 15 | const db = await openDB(); 16 | 17 | const page = getValueNumber(query.page) || 1; 18 | const rowsPerPage = getValueNumber(query.rowsPerPage) || 4; 19 | const offset = (page - 1) * rowsPerPage; 20 | 21 | const dbParams = { 22 | '@make': getValueStr(query.make), 23 | '@model': getValueStr(query.model), 24 | '@minPrice': getValueNumber(query.minPrice), 25 | '@maxPrice': getValueNumber(query.maxPrice), 26 | }; 27 | 28 | const carsPromise = db.all( 29 | `SELECT * ${mainQuery} LIMIT @rowsPerPage OFFSET @offset`, 30 | { 31 | ...dbParams, 32 | '@rowsPerPage': rowsPerPage, 33 | '@offset': offset, 34 | } 35 | ); 36 | 37 | const totalRowsPromise = db.get<{ count: number }>( 38 | `SELECT COUNT(*) as count ${mainQuery}`, 39 | dbParams 40 | ); 41 | 42 | const [cars, totalRows] = await Promise.all([carsPromise, totalRowsPromise]); 43 | 44 | return { cars, totalPages: Math.ceil(totalRows.count / rowsPerPage) }; 45 | } 46 | 47 | function getValueNumber(value: string | string[]) { 48 | const str = getValueStr(value); 49 | const number = parseInt(str); 50 | return isNaN(number) ? null : number; 51 | } 52 | 53 | function getValueStr(value: string | string[]) { 54 | const str = getAsString(value); 55 | return !str || str.toLowerCase() === 'all' ? null : str; 56 | } 57 | -------------------------------------------------------------------------------- /src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Container, CssBaseline } from '@material-ui/core'; 2 | import red from '@material-ui/core/colors/red'; 3 | import { createMuiTheme, ThemeProvider } from '@material-ui/core/styles'; 4 | import axios from 'axios'; 5 | import App from 'next/app'; 6 | import Head from 'next/head'; 7 | import React from 'react'; 8 | import { SWRConfig } from 'swr'; 9 | import { Nav } from '../components/Nav'; 10 | 11 | // Create a theme instance. 12 | export const theme = createMuiTheme({ 13 | palette: { 14 | primary: { 15 | main: '#556cd6', 16 | }, 17 | error: { 18 | main: red.A400, 19 | }, 20 | background: { 21 | default: '#fff', 22 | }, 23 | }, 24 | }); 25 | 26 | export default class MyApp extends App { 27 | componentDidMount() { 28 | // Remove the server-side injected CSS. 29 | const jssStyles = document.querySelector('#jss-server-side'); 30 | if (jssStyles) { 31 | jssStyles.parentElement!.removeChild(jssStyles); 32 | } 33 | } 34 | 35 | render() { 36 | const { Component, pageProps } = this.props; 37 | 38 | return ( 39 | 40 | 41 | Car Trader 42 | 46 | 47 | 48 | {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */} 49 | 50 |