├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── api.ts ├── components ├── Page.tsx └── StoreCard.tsx ├── globals.css ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── pages └── index.tsx ├── public ├── favicon.ico └── vercel.svg ├── tsconfig.json └── types.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | insert_final_newline = true 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | trim_trailing_whitespace = true 9 | max_line_length = 80 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | .idea/ 4 | .next/ 5 | .vscode/ 6 | build/ 7 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": false, 4 | "es2021": true, 5 | "node": true 6 | }, 7 | "extends": [ 8 | "plugin:prettier/recommended", 9 | "next", 10 | "next/core-web-vitals" 11 | ], 12 | "plugins": ["react", "prettier", "import", "@typescript-eslint"], 13 | "parser": "@typescript-eslint/parser", 14 | "parserOptions": { 15 | "ecmaFeatures": { 16 | "jsx": true 17 | }, 18 | "ecmaVersion": 12, 19 | "sourceType": "module" 20 | }, 21 | "settings": { 22 | "react": { 23 | "version": "detect" 24 | } 25 | }, 26 | "rules": { 27 | "@next/next/no-server-import-in-page": "off", 28 | "no-console": "warn", 29 | "react/prop-types": "off", 30 | "react/jsx-uses-react": "off", 31 | "react/react-in-jsx-scope": "off", 32 | "prettier/prettier": [ 33 | "warn", 34 | { 35 | "printWidth": 100, 36 | "trailingComma": "all", 37 | "tabWidth": 2, 38 | "semi": true, 39 | "singleQuote": false, 40 | "bracketSpacing": false, 41 | "arrowParens": "always", 42 | "endOfLine":"auto" 43 | } 44 | ], 45 | "@typescript-eslint/no-unused-vars": [ 46 | "warn", 47 | { 48 | "args": "after-used", 49 | "ignoreRestSiblings": false, 50 | "argsIgnorePattern": "^_.*?$" 51 | } 52 | ], 53 | "import/order": ["warn", { 54 | "groups": ["type", "builtin", "object", "external", "internal", "parent", "sibling", "index"], 55 | "pathGroups": [{ 56 | "pattern": "~/**", 57 | "group": "external", 58 | "position": "after" 59 | }], 60 | "newlines-between": "always" 61 | }], 62 | "react/self-closing-comp": "warn", 63 | "react/jsx-sort-props": [ 64 | "warn", 65 | { 66 | "callbacksLast": true, 67 | "shorthandFirst": true, 68 | "noSortAlphabetically": false, 69 | "reservedFirst": true 70 | } 71 | ], 72 | "padding-line-between-statements": [ 73 | "warn", 74 | {"blankLine": "always", "prev": "*", "next": "return"}, 75 | {"blankLine": "always", "prev": ["const", "let", "var"], "next": "*"}, 76 | {"blankLine": "any", "prev": ["const", "let", "var"], "next": ["const", "let", "var"]} 77 | ] 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /.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 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": false, 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll.eslint": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Next.js workshop 2 | Estamos haciendo una aplicación para la ciudad de Mar del Plata, Buenos Aires, Argentina. Nos dieron estas especificaciones: 3 | 4 | * Tenemos que mostrar en una lista todos los locales y tiendas destacadas de la ciudad. 5 | * Cada tienda debe tener su página dedicada. 6 | * En la página de detalle debemos mostrar un mapa, una foto y los datos de la tienda. 7 | 8 | También nos pasaron estos requerimientos técnicos: 9 | * Debemos usar incremental static regeneration y debemos pre-renderizar todas las tiendas en build time. 10 | * Debemos tener una api pública para que la gente pueda consumir la lista de tiendas y tiendas específicas. 11 | * Debemos tener una ruta `/near` que nos muestre la tienda más cercana. 12 | 13 | También unos puntos extra: 14 | * Crear un diseño nuevo. 15 | * Consumir los datos de una base de datos real (Google Sheets, MongoDB, etc.). 16 | * Implementar la misma aplicación para la ciudad en la que vivís para ayudar a pequeños comerciantes. 17 | 18 | El cliente nos pidió que despleguemos nuestra aplicación y le demos un link para verla! 19 | 20 | --- 21 | Slides: https://docs.google.com/presentation/d/1dEt_8I0Urru3w09yseA64T_3b4COIh09DEKtymojryU/edit#slide=id.g1087c820d6f_0_1 22 | -------------------------------------------------------------------------------- /api.ts: -------------------------------------------------------------------------------- 1 | import {Store} from "./types"; 2 | 3 | const MOCK = [ 4 | { 5 | id: "manolo-rivadavia", 6 | title: "Manolo Rivadavia", 7 | description: "Los churros más icónicos de Mar del Plata", 8 | image: 9 | "https://lh5.googleusercontent.com/p/AF1QipPNwL6yRmKi-Hkc08DSbJkM0Pfd3VOzBhjR5Mnw=w203-h114-k-no", 10 | link: "http://churrosmanolo.com/es/", 11 | location: { 12 | map: "https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d25147.79033508727!2d-57.5473714!3d-38.001392!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x9584dc4acea437cf%3A0x3d0b14007abe7d25!2sManolo!5e0!3m2!1ses!2sar!4v1640036194553!5m2!1ses!2sar", 13 | address: "CMA, Rivadavia 2371", 14 | city: "Quilmes", 15 | lat: -38.001392, 16 | lng: -57.5473714, 17 | }, 18 | }, 19 | { 20 | id: "manolo-ramos", 21 | title: "Manolo Ramos", 22 | description: "Los churros más icónicos de Mar del Plata", 23 | image: 24 | "https://lh5.googleusercontent.com/p/AF1QipPNwL6yRmKi-Hkc08DSbJkM0Pfd3VOzBhjR5Mnw=w203-h114-k-no", 25 | link: "http://churrosmanolo.com/es/", 26 | location: { 27 | map: "https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d25147.79033508727!2d-57.5473714!3d-38.001392!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x9584dc4acea437cf%3A0x3d0b14007abe7d25!2sManolo!5e0!3m2!1ses!2sar!4v1640036194553!5m2!1ses!2sar", 28 | address: "Av. Patricio Peralta Ramos 4900", 29 | city: "Quilmes", 30 | lat: -38.0127326, 31 | lng: -57.5596429, 32 | }, 33 | }, 34 | { 35 | id: "manolo-alem", 36 | title: "Manolo Alem", 37 | description: "Los churros más icónicos de Mar del Plata", 38 | image: 39 | "https://lh5.googleusercontent.com/p/AF1QipPNwL6yRmKi-Hkc08DSbJkM0Pfd3VOzBhjR5Mnw=w203-h114-k-no", 40 | link: "http://churrosmanolo.com/es/", 41 | location: { 42 | map: "https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d25147.79033508727!2d-57.5473714!3d-38.001392!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x9584dc4acea437cf%3A0x3d0b14007abe7d25!2sManolo!5e0!3m2!1ses!2sar!4v1640036194553!5m2!1ses!2sar", 43 | address: "Leandro N. Alem 3980", 44 | city: "Quilmes", 45 | lat: -38.0297605, 46 | lng: -57.5549408, 47 | }, 48 | }, 49 | ]; 50 | 51 | const api = { 52 | list: async (): Promise => MOCK, 53 | fetch: async (id: string): Promise => { 54 | const store = MOCK.find((store) => store.id === id); 55 | 56 | if (!store) { 57 | throw new Error("Store not found"); 58 | } 59 | 60 | return store; 61 | }, 62 | near: async (city: string): Promise => { 63 | return MOCK.find((store) => store.location.city === city) || MOCK[0]; 64 | }, 65 | }; 66 | 67 | export default api; 68 | -------------------------------------------------------------------------------- /components/Page.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Image from "next/image"; 3 | 4 | interface Props { 5 | children: React.ReactNode; 6 | } 7 | 8 | const Page: React.FC = ({children}) => { 9 | return ( 10 |
18 |
25 |
33 |

Welcome to Mar del Plata!

34 |

Encontrá los mejores locales de Mar del Plata

35 |
36 |
48 | {children} 49 |
50 |
51 | 52 | 74 |
75 | ); 76 | }; 77 | 78 | export default Page; 79 | -------------------------------------------------------------------------------- /components/StoreCard.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Image from "next/image"; 3 | 4 | import {Store} from "../types"; 5 | 6 | interface Props { 7 | store: Store; 8 | } 9 | 10 | const StoreCard: React.FC = ({store}) => { 11 | return ( 12 |
13 | {store.title} 21 |
22 |

{store.title}

23 |

32 | {store.description} 33 |

34 |

{store.location.address}

35 |
36 |
37 | ); 38 | }; 39 | 40 | export default StoreCard; 41 | -------------------------------------------------------------------------------- /globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 0; 5 | font-family: 'Open Sans', -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 7 | } 8 | 9 | a { 10 | color: inherit; 11 | text-decoration: none; 12 | } 13 | 14 | * { 15 | box-sizing: border-box; 16 | margin: 0; 17 | } 18 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | module.exports = { 3 | reactStrictMode: true, 4 | images: { 5 | domains: ["lh5.googleusercontent.com"], 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-workshop", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "next": "^12.2.0", 13 | "react": "^18.2.0", 14 | "react-dom": "^18.2.0" 15 | }, 16 | "devDependencies": { 17 | "@types/node": "^18.0.3", 18 | "@types/react": "^18.0.15", 19 | "@typescript-eslint/eslint-plugin": "^5.7.0", 20 | "@typescript-eslint/parser": "^5.7.0", 21 | "eslint": "^8.5.0", 22 | "eslint-config-next": "^12.2.0", 23 | "eslint-config-prettier": "^8.3.0", 24 | "eslint-config-standard": "^16.0.3", 25 | "eslint-plugin-import": "^2.25.3", 26 | "eslint-plugin-node": "^11.1.0", 27 | "eslint-plugin-prettier": "^4.0.0", 28 | "eslint-plugin-promise": "^5.1.0", 29 | "eslint-plugin-react": "^7.27.1", 30 | "eslint-plugin-react-hooks": "^4.3.0", 31 | "prettier": "^2.5.1", 32 | "typescript": "4.5.4" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import type {NextPage} from "next"; 2 | 3 | const Home: NextPage = () => { 4 | return
Hello future
; 5 | }; 6 | 7 | export default Home; 8 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goncy/nextjs-initial-workshop/5e7645c9709f8b98740d722aac1dfeb8e05267c3/public/favicon.ico -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /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": true, 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 | "incremental": true 21 | }, 22 | "include": [ 23 | "next-env.d.ts", 24 | "**/*.ts", 25 | "**/*.tsx" 26 | ], 27 | "exclude": [ 28 | "node_modules" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /types.ts: -------------------------------------------------------------------------------- 1 | export interface Store { 2 | id: string; 3 | title: string; 4 | image: string; 5 | description: string; 6 | location: { 7 | address: string; 8 | map: string; 9 | lat: number; 10 | lng: number; 11 | }; 12 | } 13 | --------------------------------------------------------------------------------