├── public ├── favicon.ico ├── hero-bg.png ├── hero-bg-lg.png ├── favicon │ ├── favicon.ico │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── mstile-150x150.png │ ├── apple-touch-icon.png │ ├── android-chrome-192x192.png │ ├── android-chrome-256x256.png │ ├── browserconfig.xml │ ├── site.webmanifest │ └── safari-pinned-tab.svg ├── fonts │ ├── Quicksand.ttf │ └── RibeyeMarrow.ttf ├── social-preview.png ├── icons │ ├── arrow-right.svg │ ├── award-right.svg │ └── award-left.svg ├── vercel.svg ├── data.json └── roomMap.json ├── postcss.config.js ├── next.config.js ├── next-env.d.ts ├── renovate.json ├── src ├── utils │ ├── ga.js │ └── index.ts ├── pages │ ├── api │ │ └── hello.ts │ ├── components │ │ ├── Logo │ │ │ └── index.tsx │ │ ├── Category │ │ │ └── index.tsx │ │ ├── Banner │ │ │ └── index.tsx │ │ └── Card │ │ │ └── index.tsx │ ├── _app.tsx │ ├── _document.tsx │ └── index.tsx ├── const.ts ├── styles │ └── global.css └── types.ts ├── tailwind.config.js ├── .gitignore ├── tsconfig.json ├── README.md ├── .github └── workflows │ ├── ci.yml │ └── sync_data.yml ├── LICENSE.md ├── scripts ├── updateList.js └── generateCatMap.js └── package.json /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mayandev/ikea-low-price/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/hero-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mayandev/ikea-low-price/HEAD/public/hero-bg.png -------------------------------------------------------------------------------- /public/hero-bg-lg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mayandev/ikea-low-price/HEAD/public/hero-bg-lg.png -------------------------------------------------------------------------------- /public/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mayandev/ikea-low-price/HEAD/public/favicon/favicon.ico -------------------------------------------------------------------------------- /public/fonts/Quicksand.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mayandev/ikea-low-price/HEAD/public/fonts/Quicksand.ttf -------------------------------------------------------------------------------- /public/social-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mayandev/ikea-low-price/HEAD/public/social-preview.png -------------------------------------------------------------------------------- /public/fonts/RibeyeMarrow.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mayandev/ikea-low-price/HEAD/public/fonts/RibeyeMarrow.ttf -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mayandev/ikea-low-price/HEAD/public/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mayandev/ikea-low-price/HEAD/public/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | reactStrictMode: true, 3 | images: { 4 | domains: ['www.ikea.cn'], 5 | } 6 | }; -------------------------------------------------------------------------------- /public/favicon/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mayandev/ikea-low-price/HEAD/public/favicon/mstile-150x150.png -------------------------------------------------------------------------------- /public/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mayandev/ikea-low-price/HEAD/public/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicon/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mayandev/ikea-low-price/HEAD/public/favicon/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/favicon/android-chrome-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mayandev/ikea-low-price/HEAD/public/favicon/android-chrome-256x256.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"], 3 | "semanticCommits": true, 4 | "stabilityDays": 3, 5 | "prCreation": "not-pending", 6 | "labels": ["type: dependencies"], 7 | "packageRules": [ 8 | { 9 | "packageNames": ["node"], 10 | "enabled": false 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /public/favicon/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/utils/ga.js: -------------------------------------------------------------------------------- 1 | // log the pageview with their URL 2 | export const pageview = (url) => { 3 | window.gtag("config", process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS, { 4 | page_path: url, 5 | }); 6 | }; 7 | 8 | // log specific events happening. 9 | export const event = ({ action, params }) => { 10 | window.gtag("event", action, params); 11 | }; 12 | -------------------------------------------------------------------------------- /src/pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import type { NextApiRequest, NextApiResponse } from "next"; 3 | 4 | type Data = { 5 | name: string; 6 | }; 7 | 8 | export default function handler( 9 | req: NextApiRequest, 10 | res: NextApiResponse 11 | ) { 12 | res.status(200).json({ name: `John Doe` }); 13 | } 14 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: ['./src/**/*.{js,ts,jsx,tsx}'], 3 | theme: { 4 | fontFamily: { 5 | logo: ['RibeyeMarrow', 'system-ui'], 6 | }, 7 | extend: { 8 | colors: { 9 | 'ikea-blue': '#1a52bf', 10 | 'ikea-yellow': '#FBD914', 11 | 'ikea-orange': '#b76d32', 12 | 'dark-black': '#35363a', 13 | }, 14 | }, 15 | }, 16 | plugins: [require('tailwind-scrollbar-hide')], 17 | }; 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # dependencies 3 | /node_modules 4 | /.pnp 5 | .pnp.js 6 | 7 | # testing 8 | /coverage 9 | 10 | # next.js 11 | /.next/ 12 | /out/ 13 | 14 | # production 15 | /build 16 | 17 | # misc 18 | .DS_Store 19 | *.pem 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 | 32 | # vercel 33 | .vercel 34 | 35 | # js map 36 | *.js.map -------------------------------------------------------------------------------- /public/icons/arrow-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/favicon/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "IKEA", 3 | "short_name": "IKEA", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-256x256.png", 12 | "sizes": "256x256", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /src/pages/components/Logo/index.tsx: -------------------------------------------------------------------------------- 1 | export default function Logo() { 2 | return ( 3 |
4 |
5 | 6 | 7 |
8 |

9 | IKEA 10 |

11 |
12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /public/favicon/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.14, written by Peter Selinger 2001-2017 9 | 10 | 12 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "baseUrl": ".", 17 | "paths": { 18 | "@/*": ["./src/*"], 19 | "@/public/*": ["./public/*"] 20 | }, 21 | "incremental": true 22 | }, 23 | "exclude": ["node_modules"], 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "src/utils/ga.js"] 25 | } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ikea-low-price 2 | 3 | [![Netlify Status](https://api.netlify.com/api/v1/badges/db931ab6-7a62-4775-95d7-b0dec76beb12/deploy-status)](https://app.netlify.com/sites/ikea-lp/deploys) 4 | 5 | ![social privew image](./public/social-preview.png) 6 | 7 | Showcasing IKEA's low-priced items.Product data will sync every week. 8 | 9 | 一个宜家低价清单,数据每周自动同步。 10 | 11 | ## Contact 12 | 13 | - Open an [issue](https://github.com/Mayandev/ikea-low-price/issues) if you have any question about this app. 14 | - DM me on [twitter](https://twitter.com/phillzou) is also welcome. 15 | 16 | Buy Me a Coffee at ko-fi.com 17 | 18 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Check PR 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | run-ci: 7 | env: 8 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 9 | 10 | name: Run Type Check & Linters 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v3 16 | with: 17 | fetch-depth: 0 18 | 19 | - name: Set up Node 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: 14 23 | 24 | - name: Install dependencies (with cache) 25 | uses: bahmutov/npm-install@v1 26 | 27 | - name: Check types 28 | run: yarn type-check 29 | 30 | - name: Check linting 31 | run: yarn lint 32 | 33 | - name: Check commits messages 34 | uses: wagoid/commitlint-github-action@v4 35 | -------------------------------------------------------------------------------- /src/const.ts: -------------------------------------------------------------------------------- 1 | import { RoomType } from "./types"; 2 | 3 | export const CategoryMap = { 4 | all: "全部商品", 5 | "living-room": "客厅", 6 | "dining-room": "餐厅", 7 | kitchen: "厨房", 8 | bedroom: "卧室", 9 | bathroom: "浴室", 10 | "childrens-room": "儿童房", 11 | "home-office": "书房和办公室", 12 | hallway: "门厅", 13 | outdoor: "户外", 14 | laundry: "洗衣房", 15 | balcony: "阳台", 16 | other: "其他", 17 | } as { [key in RoomType]: string }; 18 | 19 | export const CategoryColorMap = { 20 | all: "#4b82ee", 21 | "living-room": "#ffd800", 22 | "dining-room": "#1a52bf", 23 | kitchen: "#5de04e", 24 | bedroom: "#4ee0a7", 25 | bathroom: "#4eb3e0", 26 | "childrens-room": "#4e8ce0", 27 | "home-office": "#4e62e0", 28 | hallway: "#884ee0", 29 | outdoor: "#e04ed6", 30 | laundry: "#349f44", 31 | balcony: "#cf7474", 32 | other: "#1a52bf", 33 | } as { [key in RoomType]: string }; 34 | -------------------------------------------------------------------------------- /src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { AppProps } from "next/app"; 2 | import { useRouter } from "next/router"; 3 | import { useEffect } from "react"; 4 | 5 | import * as ga from "@/utils/ga"; 6 | 7 | import "@/styles/global.css"; 8 | 9 | export default function MyApp({ Component, pageProps }: AppProps) { 10 | const router = useRouter(); 11 | 12 | useEffect(() => { 13 | const handleRouteChange = (url: string) => { 14 | ga.pageview(url); 15 | }; 16 | // When the component is mounted, subscribe to router changes 17 | // and log those page views 18 | router.events.on(`routeChangeComplete`, handleRouteChange); 19 | // If the component is unmounted, unsubscribe 20 | // from the event with the `off` method 21 | return () => { 22 | router.events.off(`routeChangeComplete`, handleRouteChange); 23 | }; 24 | }, [router.events]); 25 | return ; 26 | } 27 | -------------------------------------------------------------------------------- /src/styles/global.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @font-face { 6 | font-family: "RibeyeMarrow"; 7 | src: url("/fonts/RibeyeMarrow.ttf"); 8 | font-display: swap; 9 | font-style: bold; 10 | font-weight: 700; 11 | } 12 | 13 | @font-face { 14 | font-family: "Quicksand"; 15 | src: url("/fonts/Quicksand.ttf"); 16 | font-display: swap; 17 | font-style: bold; 18 | font-weight: 700; 19 | } 20 | 21 | html, 22 | body { 23 | padding: 0; 24 | margin: 0; 25 | font-family: "Quicksand", -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 26 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 27 | background-color: #f2f2f2; 28 | } 29 | 30 | a { 31 | color: inherit; 32 | text-decoration: none; 33 | } 34 | 35 | * { 36 | box-sizing: border-box; 37 | } 38 | 39 | @media (prefers-color-scheme: dark) { 40 | body { 41 | background-color: #35363a; 42 | } 43 | } -------------------------------------------------------------------------------- /src/pages/components/Category/index.tsx: -------------------------------------------------------------------------------- 1 | import { CategoryMap } from "@/const"; 2 | import { ProductContext } from "@/pages"; 3 | import { RoomType } from "@/types"; 4 | import { useContext } from "react"; 5 | 6 | export default function Category() { 7 | const { setCategory, category: room } = useContext(ProductContext); 8 | 9 | return ( 10 |
11 | {Object.keys(CategoryMap).map((key) => ( 12 | 22 | ))} 23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 João Pedro Schmitz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import NextDocument, { Html, Head, Main, NextScript } from "next/document"; 2 | import React from "react"; 3 | 4 | class Document extends NextDocument { 5 | render() { 6 | return ( 7 | 8 | 9 | 10 | {/* Google Analytics */} 11 |