diff --git a/.gitignore b/.gitignore index 4be9d59..ad6fe12 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,13 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. +#env files +.env +.env.local + +# ignore sqlite db file +prisma/db +prisma/migrations/** + # dependencies /node_modules /.pnp @@ -18,6 +26,7 @@ # misc .DS_Store *.pem +.vscode # debug npm-debug.log* diff --git a/README.md b/README.md index b8d379e..9ff8b67 100644 --- a/README.md +++ b/README.md @@ -1,181 +1,28 @@ ## Jamstack ECommerce Next -Jamstack ECommerce Next provides a way to quickly get up and running with a fully configurable ECommerce site using Next.js. +### Important! -Out of the box, the site uses completely static data coming from a provider at `providers/inventoryProvider.js`. You can update this provider to fetch data from any real API by changing the call in the `getInventory` function. +This is a fork from [Nader's Jamstack Ecommerce Next](https://github.com/jamstack-cms/jamstack-ecommerce). He has done all the work with setting up and building the UI among other things. This fork shows you how you can add a backend using Prisma using any supported database dialect. -![Home](example-images/1.png) +## Walkthrough/Tuitorial -### Live preview +If you prefer a walkthrough, I have written a [detailed blog post here](https://joeynimu.com/how-to-build-a-full-stack-jamstack-e-commerce-app-with-nextjs-and-prisma) -Click [here](https://www.jamstackecommerce.dev/) to see a live preview. +## Getting Started -
- Other Jamstack ECommerce pages +### Clone and Install Dependancies -### Category view -![Category view](example-images/2.png) +Start by cloning and installing dependancies. -### Item view -![Item view](example-images/3.png) +- `$ git clone https://github.com/joeynimu/jamstack-ecommerce.git` -### Cart view -![Cart view](example-images/4.png) +- `$ yarn` if you are using yarn or `$ npm install` if you are using npm -### Admin panel -![Admin panel](example-images/5.png) -
+- `$ yarn push-db`: Creates the db structure +- `$ yarn seed-db`: Adds some sample data for you to start with +- `$ yarn dev`: Runs the NexJS application that reads data from your database +- `$ yarn db-studio`: Opens Prisma Studio where you can view and edit your database -### Getting started +### Backend Details -1. Clone the project - -```sh -$ git clone https://github.com/jamstack-cms/jamstack-ecommerce.git -``` - -2. Install the dependencies: - -```sh -$ yarn - -# or - -$ npm install -``` - -3. Run the project - -```sh -$ npm run dev - -# or to build - -$ npm run build -``` - -## Deploy to Vercel - -Use the [Vercel CLI](https://vercel.com/download) - -```sh -vercel -``` - -## Deploy to AWS - -```sh -npx serverless -``` - -## About the project - -### Tailwind - -This project is styled using Tailwind. To learn more how this works, check out the Tailwind documentation [here](https://tailwindcss.com/docs). - -### Components - -The main files, components, and images you may want to change / modify are: - -__Logo__ - public/logo.png -__Button, ListItem, etc..__ - components -__Form components__ - components/formComponents -__Context (state)__ - context/mainContext.js -__Pages (admin, cart, checkout, index)__ - pages -__Templates (category view, single item view, inventory views)__ - templates - -### How it works - -As it is set up, inventory is fetched from a local hard coded array of inventory items. This can easily be configured to instead be fetched from a remote source like Shopify or another CMS or data source by changing the inventory provider. - -#### Configuring inventory provider - -Update __utils/inventoryProvider.js__ with your own inventory provider. - -#### Download images at build time - -If you change the provider to fetch images from a remote source, you may choose to also download the images locally at build time to improve performance. Here is an example of some code that should work for this use case: - -```javascript -import fs from 'fs' -import axios from 'axios' -import path from 'path' - -function getImageKey(url) { - const split = url.split('/') - const key = split[split.length - 1] - const keyItems = key.split('?') - const imageKey = keyItems[0] - return imageKey -} - -function getPathName(url, pathName = 'downloads') { - let reqPath = path.join(__dirname, '..') - let key = getImageKey(url) - key = key.replace(/%/g, "") - const rawPath = `${reqPath}/public/${pathName}/${key}` - return rawPath -} - -async function downloadImage (url) { - return new Promise(async (resolve, reject) => { - const path = getPathName(url) - const writer = fs.createWriteStream(path) - const response = await axios({ - url, - method: 'GET', - responseType: 'stream' - }) - response.data.pipe(writer) - writer.on('finish', resolve) - writer.on('error', reject) - }) -} - -export default downloadImage -``` - -You can use this function to map over the inventory data after fetching and replace the image paths with a reference to the location of the downloaded images, probably would look something like this: - -```javascript -await Promise.all( - inventory.map(async (item, index) => { - try { - const relativeUrl = `../downloads/${item.image}` - if (!fs.existsSync(`${__dirname}/public/downloads/${item.image}`)) { - await downloadImage(image) - } - inventory[index].image = relativeUrl - } catch (err) { - console.log('error downloading image: ', err) - } - }) -) -``` - -### Updating with Auth / Admin panel - -1. Update __pages/admin.js__ with sign up, sign, in, sign out, and confirm sign in methods. - -2. Update __components/ViewInventory.js__ with methods to interact with the actual inventory API. - -3. Update __components/formComponents/AddInventory.js__ with methods to add item to actual inventory API. - -### Roadmap - -- Full product and category search -- Auto dropdown navigation for large number of categories -- Ability to add more / more configurable metadata to item details -- Themeing + dark mode -- Optional user account / profiles out of the box -- Make Admin Panel responsive -- Have an idea or a request? Submit [an issue](https://github.com/jamstack-cms/jamstack-ecommerce/issues) or [a pull request](https://github.com/jamstack-cms/jamstack-ecommerce/pulls)! - -### Other considerations - -#### Server-side processing of payments - -To see an example of how to process payments server-side with stripe, check out the [Lambda function in the snippets folder](https://github.com/jamstack-cms/jamstack-ecommerce/blob/next/snippets/lambda.js). - -Also, consider verifying totals by passing in an array of IDs into the function, calculating the total on the server, then comparing the totals to check and make sure they match. \ No newline at end of file +By default, the project is configured to use `sqlite` but you can change this to point to a `PostgreSQL` db instance or any other supported dialect. diff --git a/lib/prisma-client.ts b/lib/prisma-client.ts new file mode 100644 index 0000000..bfc339e --- /dev/null +++ b/lib/prisma-client.ts @@ -0,0 +1,4 @@ +import { PrismaClient } from "@prisma/client" + +const prisma = new PrismaClient() +export default prisma diff --git a/next-env.d.ts b/next-env.d.ts new file mode 100644 index 0000000..7b7aa2c --- /dev/null +++ b/next-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/package.json b/package.json index 1911a9d..8413f6e 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,17 @@ "scripts": { "dev": "next dev", "build": "next build", - "start": "next start" + "start": "next start", + "generate": "npx prisma generate", + "push-db": "npx prisma db push --preview-feature", + "seed-db": "npx prisma db seed --preview-feature", + "db-studio": "npx prisma studio" }, "dependencies": { + "@prisma/client": "^2.21.2", "@stripe/react-stripe-js": "^1.1.2", "@stripe/stripe-js": "^1.11.0", + "@types/react": "^17.0.2", "autoprefixer": "^10.1.0", "next": "10.0.4", "postcss": "^8.2.2", @@ -19,5 +25,11 @@ "react-toastify": "^6.2.0", "tailwindcss": "^2.0.2", "uuid": "^8.3.2" + }, + "devDependencies": { + "@types/node": "^14.14.41", + "prisma": "^2.21.2", + "ts-node": "^9.1.1", + "typescript": "^4.2.4" } } diff --git a/pages/categories.js b/pages/categories.js deleted file mode 100644 index 58d7e92..0000000 --- a/pages/categories.js +++ /dev/null @@ -1,74 +0,0 @@ -import Head from 'next/head' -import { titleIfy , slugify } from '../utils/helpers' -import { DisplayMedium } from '../components' -import CartLink from '../components/CartLink' -import { fetchInventory } from '../utils/inventoryProvider' - -function Categories ({ categories = [] }) { - return ( - <> -
- - - Jamstack ECommerce - All Categories - - - -
-

All categories

-
-
- - {/*
*/} -
- { - categories.map((category, index) => ( - - )) - } -
-
-
- - ) -} - -export async function getStaticProps() { - const inventory = await fetchInventory() - const inventoryCategories = inventory.reduce((acc, next) => { - const categories = next.categories - categories.forEach(c => { - const index = acc.findIndex(item => item.name === c) - if (index !== -1) { - const item = acc[index] - item.itemCount = item.itemCount + 1 - acc[index] = item - } else { - const item = { - name: c, - image: next.image, - itemCount: 1 - } - acc.push(item) - } - }) - return acc - }, []) - - return { - props: { - categories: inventoryCategories - } - } -} - -export default Categories \ No newline at end of file diff --git a/pages/categories.tsx b/pages/categories.tsx new file mode 100644 index 0000000..9c1b6c0 --- /dev/null +++ b/pages/categories.tsx @@ -0,0 +1,71 @@ +import Head from "next/head" +import { titleIfy, slugify } from "../utils/helpers" +import { DisplayMedium } from "../components" +import CartLink from "../components/CartLink" +import prisma from "../lib/prisma-client" +import { FC } from "react" +import { Category, Product } from "@prisma/client" + +type categoryType = Category & { + products: Product[] +} + +type Props = { + categories: categoryType[] +} + +const Categories: FC = ({ categories = [] }) => { + return ( + <> +
+ + + Jamstack ECommerce - All Categories + + + +
+

All categories

+
+
+ {/*
*/} +
+ {categories.map((category, index) => ( + + ))} +
+
+
+ + ) +} + +export async function getStaticProps() { + const categories = await prisma.category.findMany({ + include: { + products: true, + }, + }) + + return { + props: { + categories, + }, + revalidate: 1, + } +} + +export default Categories diff --git a/pages/category/[name].js b/pages/category/[name].js index 2ee4775..5effcaa 100644 --- a/pages/category/[name].js +++ b/pages/category/[name].js @@ -1,69 +1,78 @@ -import Head from 'next/head' -import ListItem from '../../components/ListItem' -import { titleIfy, slugify } from '../../utils/helpers' -import fetchCategories from '../../utils/categoryProvider' -import inventoryForCategory from '../../utils/inventoryForCategory' -import CartLink from '../../components/CartLink' +import Head from "next/head" +import ListItem from "../../components/ListItem" +import { titleIfy, slugify } from "../../utils/helpers" +import fetchCategories from "../../utils/categoryProvider" +import CartLink from "../../components/CartLink" +import prismaClient from "../../lib/prisma-client" -const Category = (props) => { - const { inventory, title } = props +const Category = ({ inventory, title }) => { return ( <> Jamstack ECommerce - {title} - +
-
-
+
+

{titleIfy(title)}

-
- { - inventory.map((item, index) => { - return ( - - ) - }) - } +
+ {inventory.map((item, index) => { + return ( + + ) + })}
-
+
) } -export async function getStaticPaths () { +export async function getStaticPaths() { const categories = await fetchCategories() - const paths = categories.map(category => { - return { params: { name: slugify(category) }} + const paths = categories.map((category) => { + return { params: { name: slugify(category) } } }) return { paths, - fallback: false + fallback: false, } } -export async function getStaticProps ({ params }) { - const category = params.name.replace(/-/g," ") - const inventory = await inventoryForCategory(category) +export async function getStaticProps({ params }) { + const category = params.name.replace(/-/g, " ") + const products = await prismaClient.product.findMany({ + where: { + categories: { + some: { name: category }, + }, + }, + }) + return { props: { - inventory, - title: category - } + inventory: products, + title: category, + }, + revalidate: 1, } } -export default Category \ No newline at end of file +export default Category diff --git a/pages/index.js b/pages/index.js deleted file mode 100644 index 55d6a86..0000000 --- a/pages/index.js +++ /dev/null @@ -1,133 +0,0 @@ -import Head from 'next/head' -import { Center, Footer, Tag, Showcase, DisplaySmall, DisplayMedium } from '../components' -import { titleIfy, slugify } from '../utils/helpers' -import { fetchInventory } from '../utils/inventoryProvider' -import CartLink from '../components/CartLink' - -const Home = ({ inventoryData = [], categories: categoryData = [] }) => { - const inventory = inventoryData.slice(0, 4) - const categories = categoryData.slice(0, 2) - - return ( - <> - -
- - Jamstack ECommerce - - - -
-
- -
-
-
-
- -
-
-
-
-
- - -
-
-

Trending Now

-

Find the perfect piece or accessory to finish off your favorite room in the house.

-
-
- - - - - - - -
- - ) -} - -export async function getStaticProps() { - const inventory = await fetchInventory() - - const inventoryCategorized = inventory.reduce((acc, next) => { - const categories = next.categories - categories.forEach(c => { - const index = acc.findIndex(item => item.name === c) - if (index !== -1) { - const item = acc[index] - item.itemCount = item.itemCount + 1 - acc[index] = item - } else { - const item = { - name: c, - image: next.image, - itemCount: 1 - } - acc.push(item) - } - }) - return acc - }, []) - - return { - props: { - inventoryData: inventory, - categories: inventoryCategorized - } - } -} - -export default Home \ No newline at end of file diff --git a/pages/index.tsx b/pages/index.tsx new file mode 100644 index 0000000..0201228 --- /dev/null +++ b/pages/index.tsx @@ -0,0 +1,142 @@ +import Head from "next/head" +import { + Center, + Footer, + Tag, + Showcase, + DisplaySmall, + DisplayMedium, +} from "../components" +import { FC } from "react" +import { titleIfy, slugify } from "../utils/helpers" +import CartLink from "../components/CartLink" +import prismaClient from "../lib/prisma-client" +import { Category, Product } from "@prisma/client" + +type inventoryDataType = Product & { + categories: Category[] +} + +type categoryDataType = Category & { + products: Product[] +} + +type Props = { + inventoryData: inventoryDataType[] | [] + categories: categoryDataType[] | [] +} + +const Home: FC = ({ + inventoryData = [], + categories: categoryData = [], +}) => { + const inventory = inventoryData.slice(0, 4) + const categories = categoryData.slice(0, 2) + + console.log({ inventory, categories }) + + return ( + <> + +
+ + Jamstack ECommerce + + + +
+
+ +
+
+
+
+ +
+
+
+
+
+ + +
+
+

Trending Now

+

+ Find the perfect piece or accessory to finish off your favorite room + in the house. +

+
+
+ + + + + + + +
+ + ) +} + +export async function getStaticProps() { + const categories = await prismaClient.category.findMany({ + include: { + products: true, + }, + }) + + const inventoryData = await prismaClient.product.findMany({ + include: { + categories: true, + }, + }) + + return { + props: { + inventoryData, + categories, + }, + revalidate: 1, + } +} + +export default Home diff --git a/pages/product/[name].js b/pages/product/[name].js index ed25b14..82d1d2a 100644 --- a/pages/product/[name].js +++ b/pages/product/[name].js @@ -1,26 +1,34 @@ -import { useState } from 'react' -import Head from 'next/head' -import Button from '../../components/Button' -import Image from '../../components/Image' -import QuantityPicker from '../../components/QuantityPicker' -import { fetchInventory } from '../../utils/inventoryProvider' -import { slugify } from '../../utils/helpers' -import CartLink from '../../components/CartLink' -import { SiteContext, ContextProviderComponent } from '../../context/mainContext' +import { useState } from "react" +import Head from "next/head" +import Button from "../../components/Button" +import Image from "../../components/Image" +import QuantityPicker from "../../components/QuantityPicker" +import { fetchInventory } from "../../utils/inventoryProvider" +import { slugify } from "utils/helpers" +import CartLink from "../../components/CartLink" +import { + SiteContext, + ContextProviderComponent, +} from "../../context/mainContext" +import prismaClient from "lib/prisma-client" const ItemView = (props) => { const [numberOfitems, updateNumberOfItems] = useState(1) const { product } = props - const { price, image, name, description } = product - const { context: { addToCart }} = props + const { price, image, name, description, currentInventory } = product + const { + context: { addToCart }, + } = props - function addItemToCart (product) { + function addItemToCart(product) { product["quantity"] = numberOfitems addToCart(product) } function increment() { - updateNumberOfItems(numberOfitems + 1) + if (numberOfitems < currentInventory) { + updateNumberOfItems(numberOfitems + 1) + } } function decrement() { @@ -34,24 +42,24 @@ const ItemView = (props) => { Jamstack ECommerce - {name} - + -
-
-
+
+
+
Inventory item
-
-

{name}

-

${price}

-

{description}

+
+

+ {name} +

+

${price}

+

{description}

{ ) } -export async function getStaticPaths () { - const inventory = await fetchInventory() - const paths = inventory.map(item => { - return { params: { name: slugify(item.name) }} +export async function getStaticPaths() { + const products = await prismaClient.product.findMany() + const paths = products.map((product) => { + return { params: { name: slugify(product.name) } } }) return { paths, - fallback: false + fallback: false, } } -export async function getStaticProps ({ params }) { - const name = params.name.replace(/-/g," ") - const inventory = await fetchInventory() - const product = inventory.find(item => slugify(item.name) === slugify(name)) - +export async function getStaticProps({ params }) { + const name = params.name.replace(/-/g, " ") + const product = await prismaClient.product.findFirst({ + where: { + name: { + contains: name, + }, + }, + }) return { props: { product, - } + }, + revalidate: 1, } } @@ -97,12 +110,10 @@ function ItemViewWithContext(props) { return ( - { - context => - } + {(context) => } ) } -export default ItemViewWithContext \ No newline at end of file +export default ItemViewWithContext diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..e7a82da --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,29 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +datasource db { + provider = "sqlite" + url = "file:./db" +} + +generator client { + provider = "prisma-client-js" +} + +model Category { + id Int @id + name String + products Product[] @relation(references: [id]) + image String? +} + +model Product { + id Int @id + name String + price Float + image String + brand String + categories Category[] @relation(references: [id]) + currentInventory Int + description String +} \ No newline at end of file diff --git a/prisma/seed.ts b/prisma/seed.ts new file mode 100644 index 0000000..ad76eb3 --- /dev/null +++ b/prisma/seed.ts @@ -0,0 +1,55 @@ +import { PrismaClient } from "@prisma/client" +import { categories, products } from "../utils/data" +const prisma = new PrismaClient() + +async function main() { + // creates categories + await Promise.all( + categories.map(({ name, id, image }) => + prisma.category.upsert({ + where: { id }, + update: {}, + create: { name, id, image }, + }) + ) + ) + await Promise.all( + products.map( + ({ + categories, + id, + name, + price, + image, + description, + brand, + currentInventory, + }) => + prisma.product.upsert({ + where: { id }, + update: {}, + create: { + id, + name, + price, + image, + description, + brand, + currentInventory, + categories: { + connect: categories.map((id) => ({ id })), + }, + }, + }) + ) + ) +} + +main() + .then(() => console.log(`Seeded data successfully`)) + .catch((e) => console.error(`Failed to seed data, ${e}`)) + .finally(async () => { + await prisma.$disconnect() + }) + +export default main diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..0f601cd --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "commonjs", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve" + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +} diff --git a/utils/data.ts b/utils/data.ts new file mode 100644 index 0000000..ae252f4 --- /dev/null +++ b/utils/data.ts @@ -0,0 +1,277 @@ +interface ProductInterface { + id: number + name: string + categories: number[] + price: number + image: string + description: string + currentInventory: number + brand?: string +} + +interface CategoryInterface { + id?: number + image?: string + name: string +} + +export const categories: CategoryInterface[] = [ + { + id: 1, + name: "new arrivals", + image: "/products/couch1.png", + }, + { id: 2, name: "sofas", image: "/products/couch5.png" }, + { + id: 3, + name: "living room", + image: "/products/couch5.png", + }, + { + id: 4, + name: "on sale", + image: "/products/couch8.png", + }, + { + id: 5, + name: "chairs", + image: "/products/chair1.png", + }, +] + +export const products: ProductInterface[] = [ + { + id: 1, + categories: [1], + name: "Timber Gray Sofa", + price: 1000, + image: "/products/couch1.png", + description: + "Stay a while. The Timber charme chocolat sofa is set atop an oak trim and flaunts fluffy leather back and seat cushions. Over time, this brown leather sofa’s full-aniline upholstery will develop a worn-in vintage look. Snuggle up with your cutie (animal or human) and dive into a bowl of popcorn. This sofa is really hard to leave. Natural color variations, wrinkles and creases are part of the unique characteristics of this leather. It will develop a relaxed vintage look with regular use.", + brand: "Jason Bourne", + currentInventory: 4, + }, + { + id: 2, + categories: [2, 3], + name: "Carmel Brown Sofa", + price: 1000, + image: "/products/couch5.png", + description: + "Stay a while. The Timber charme chocolat sofa is set atop an oak trim and flaunts fluffy leather back and seat cushions. Over time, this brown leather sofa’s full-aniline upholstery will develop a worn-in vintage look. Snuggle up with your cutie (animal or human) and dive into a bowl of popcorn. This sofa is really hard to leave. Natural color variations, wrinkles and creases are part of the unique characteristics of this leather. It will develop a relaxed vintage look with regular use.", + brand: "Jason Bourne", + currentInventory: 2, + }, + { + id: 3, + categories: [1, 2], + name: "Mod Leather Sofa", + price: 800, + image: "/products/couch6.png", + description: + "Easy to love. The Sven in birch ivory looks cozy and refined, like a sweater that a fancy lady wears on a coastal vacation. This ivory loveseat has a tufted bench seat, loose back pillows and bolsters, solid walnut legs, and is ready to make your apartment the adult oasis you dream of. Nestle it with plants, an ottoman, an accent chair, or 8 dogs. Your call.", + brand: "Jason Bourne", + currentInventory: 8, + }, + { + id: 4, + categories: [1, 2], + name: "Thetis Gray Love Seat", + price: 900, + image: "/products/couch7.png", + description: + "You know your dad’s incredible vintage bomber jacket? The Nirvana dakota tan leather sofa is that jacket, but in couch form. With super-plush down-filled cushions, a corner-blocked wooden frame, and a leather patina that only gets better with age, the Nirvana will have you looking cool and feeling peaceful every time you take a seat. Looks pretty great with a sheepskin throw, if we may say so. With use, this leather will become softer and more wrinkled and the cushions will take on a lived-in look, like your favorite leather jacket.", + brand: "Jason Bourne", + currentInventory: 10, + }, + { + id: 5, + categories: [4, 2], + name: "Sven Tan Matte", + price: 1200, + image: "/products/couch8.png", + description: + "You don’t have to go outside to be rugged. The Cigar rawhide sofa features a sturdy corner-blocked wooden frame and raw seams for that Malboro-person look. This brown leather sofa is cozy in a cottage, cabin, or a condo. And the leather (the leather!) becomes more beautiful with use: subtle character markings such as insect bites, healed scars, and grain variation reflects a real vintage. Saddle up and pass the remote.", + brand: "Jason Bourne", + currentInventory: 7, + }, + { + id: 6, + categories: [4, 2], + name: "Otis Malt Sofa", + price: 500, + image: "/products/couch9.png", + description: + "You don’t have to go outside to be rugged. The Cigar rawhide sofa features a sturdy corner-blocked wooden frame and raw seams for that Malboro-person look. This brown leather sofa is cozy in a cottage, cabin, or a condo. And the leather (the leather!) becomes more beautiful with use: subtle character markings such as insect bites, healed scars, and grain variation reflects a real vintage. Saddle up and pass the remote.", + brand: "Jason Bourne", + currentInventory: 13, + }, + { + id: 7, + categories: [4, 2], + name: "Ceni Brown 3 Seater", + price: 650, + image: "/products/couch10.png", + description: + "You don’t have to go outside to be rugged. The Cigar rawhide sofa features a sturdy corner-blocked wooden frame and raw seams for that Malboro-person look. This brown leather sofa is cozy in a cottage, cabin, or a condo. And the leather (the leather!) becomes more beautiful with use: subtle character markings such as insect bites, healed scars, and grain variation reflects a real vintage. Saddle up and pass the remote.", + brand: "Jason Bourne", + currentInventory: 9, + }, + { + id: 8, + categories: [2, 3], + name: "Jameson Jack Lounger", + price: 1230, + image: "/products/couch11.png", + description: + "You don’t have to go outside to be rugged. The Cigar rawhide sofa features a sturdy corner-blocked wooden frame and raw seams for that Malboro-person look. This brown leather sofa is cozy in a cottage, cabin, or a condo. And the leather (the leather!) becomes more beautiful with use: subtle character markings such as insect bites, healed scars, and grain variation reflects a real vintage. Saddle up and pass the remote.", + brand: "Jason Bourne", + currentInventory: 24, + }, + + { + id: 9, + categories: [2], + name: "Galaxy Blue Sofa", + price: 800, + image: "/products/couch2.png", + description: + "Easy to love. The Sven in birch ivory looks cozy and refined, like a sweater that a fancy lady wears on a coastal vacation. This ivory loveseat has a tufted bench seat, loose back pillows and bolsters, solid walnut legs, and is ready to make your apartment the adult oasis you dream of. Nestle it with plants, an ottoman, an accent chair, or 8 dogs. Your call.", + brand: "Jason Bourne", + currentInventory: 43, + }, + { + id: 10, + categories: [1, 2], + name: "Markus Green Love Seat", + price: 900, + image: "/products/couch3.png", + description: + "You know your dad’s incredible vintage bomber jacket? The Nirvana dakota tan leather sofa is that jacket, but in couch form. With super-plush down-filled cushions, a corner-blocked wooden frame, and a leather patina that only gets better with age, the Nirvana will have you looking cool and feeling peaceful every time you take a seat. Looks pretty great with a sheepskin throw, if we may say so. With use, this leather will become softer and more wrinkled and the cushions will take on a lived-in look, like your favorite leather jacket.", + brand: "Jason Bourne", + currentInventory: 2, + }, + { + id: 11, + categories: [4, 2], + name: "Dabit Matte Black", + price: 1200, + image: "/products/couch4.png", + description: + "You don’t have to go outside to be rugged. The Cigar rawhide sofa features a sturdy corner-blocked wooden frame and raw seams for that Malboro-person look. This brown leather sofa is cozy in a cottage, cabin, or a condo. And the leather (the leather!) becomes more beautiful with use: subtle character markings such as insect bites, healed scars, and grain variation reflects a real vintage. Saddle up and pass the remote.", + brand: "Jason Bourne", + currentInventory: 14, + }, + + { + id: 12, + categories: [4, 5], + name: "Embrace Blue", + price: 300, + image: "/products/chair1.png", + description: + "You don’t have to go outside to be rugged. The Cigar rawhide sofa features a sturdy corner-blocked wooden frame and raw seams for that Malboro-person look. This brown leather sofa is cozy in a cottage, cabin, or a condo. And the leather (the leather!) becomes more beautiful with use: subtle character markings such as insect bites, healed scars, and grain variation reflects a real vintage. Saddle up and pass the remote.", + brand: "Jason Bourne", + currentInventory: 12, + }, + { + id: 13, + categories: [4, 5], + name: "Nord Lounger", + price: 825, + image: "/products/chair2.png", + description: + "You don’t have to go outside to be rugged. The Cigar rawhide sofa features a sturdy corner-blocked wooden frame and raw seams for that Malboro-person look. This brown leather sofa is cozy in a cottage, cabin, or a condo. And the leather (the leather!) becomes more beautiful with use: subtle character markings such as insect bites, healed scars, and grain variation reflects a real vintage. Saddle up and pass the remote.", + brand: "Jason Bourne", + currentInventory: 13, + }, + { + id: 14, + categories: [4, 5], + name: "Ceni Matte Oranve", + price: 720, + image: "/products/chair3.png", + description: + "You don’t have to go outside to be rugged. The Cigar rawhide sofa features a sturdy corner-blocked wooden frame and raw seams for that Malboro-person look. This brown leather sofa is cozy in a cottage, cabin, or a condo. And the leather (the leather!) becomes more beautiful with use: subtle character markings such as insect bites, healed scars, and grain variation reflects a real vintage. Saddle up and pass the remote.", + brand: "Jason Bourne", + currentInventory: 33, + }, + { + id: 15, + categories: [4, 5], + name: "Abisko Green Recliner", + price: 2000, + image: "/products/chair4.png", + description: + "You don’t have to go outside to be rugged. The Cigar rawhide sofa features a sturdy corner-blocked wooden frame and raw seams for that Malboro-person look. This brown leather sofa is cozy in a cottage, cabin, or a condo. And the leather (the leather!) becomes more beautiful with use: subtle character markings such as insect bites, healed scars, and grain variation reflects a real vintage. Saddle up and pass the remote.", + brand: "Jason Bourne", + currentInventory: 23, + }, + { + id: 16, + categories: [4, 5], + name: "Denim on Denim Single", + price: 1100, + image: "/products/chair5.png", + description: + "You don’t have to go outside to be rugged. The Cigar rawhide sofa features a sturdy corner-blocked wooden frame and raw seams for that Malboro-person look. This brown leather sofa is cozy in a cottage, cabin, or a condo. And the leather (the leather!) becomes more beautiful with use: subtle character markings such as insect bites, healed scars, and grain variation reflects a real vintage. Saddle up and pass the remote.", + brand: "Jason Bourne", + currentInventory: 13, + }, + { + id: 17, + categories: [4, 5], + name: "Levo Tan Lounge Chair", + price: 600, + image: "/products/chair6.png", + description: + "You don’t have to go outside to be rugged. The Cigar rawhide sofa features a sturdy corner-blocked wooden frame and raw seams for that Malboro-person look. This brown leather sofa is cozy in a cottage, cabin, or a condo. And the leather (the leather!) becomes more beautiful with use: subtle character markings such as insect bites, healed scars, and grain variation reflects a real vintage. Saddle up and pass the remote.", + brand: "Jason Bourne", + currentInventory: 15, + }, + + { + id: 18, + categories: [4, 5], + name: "Anime Tint Recliner", + price: 775, + image: "/products/chair7.png", + description: + "You don’t have to go outside to be rugged. The Cigar rawhide sofa features a sturdy corner-blocked wooden frame and raw seams for that Malboro-person look. This brown leather sofa is cozy in a cottage, cabin, or a condo. And the leather (the leather!) becomes more beautiful with use: subtle character markings such as insect bites, healed scars, and grain variation reflects a real vintage. Saddle up and pass the remote.", + brand: "Jason Bourne", + currentInventory: 44, + }, + { + id: 19, + categories: [4, 5], + name: "Josh Jones Red Chair", + price: 1200, + image: "/products/chair8.png", + description: + "You don’t have to go outside to be rugged. The Cigar rawhide sofa features a sturdy corner-blocked wooden frame and raw seams for that Malboro-person look. This brown leather sofa is cozy in a cottage, cabin, or a condo. And the leather (the leather!) becomes more beautiful with use: subtle character markings such as insect bites, healed scars, and grain variation reflects a real vintage. Saddle up and pass the remote.", + brand: "Jason Bourne", + currentInventory: 17, + }, + { + id: 20, + categories: [4, 5], + name: "Black Sand Lounge", + price: 1600, + image: "/products/chair9.png", + description: + "You don’t have to go outside to be rugged. The Cigar rawhide sofa features a sturdy corner-blocked wooden frame and raw seams for that Malboro-person look. This brown leather sofa is cozy in a cottage, cabin, or a condo. And the leather (the leather!) becomes more beautiful with use: subtle character markings such as insect bites, healed scars, and grain variation reflects a real vintage. Saddle up and pass the remote.", + brand: "Jason Bourne", + currentInventory: 28, + }, + { + id: 21, + categories: [4, 5], + name: "Mint Beige Workchair", + price: 550, + image: "/products/chair10.png", + description: + "You don’t have to go outside to be rugged. The Cigar rawhide sofa features a sturdy corner-blocked wooden frame and raw seams for that Malboro-person look. This brown leather sofa is cozy in a cottage, cabin, or a condo. And the leather (the leather!) becomes more beautiful with use: subtle character markings such as insect bites, healed scars, and grain variation reflects a real vintage. Saddle up and pass the remote.", + brand: "Jason Bourne", + currentInventory: 31, + }, +] diff --git a/yarn.lock b/yarn.lock index b7c36f8..5428120 100644 --- a/yarn.lock +++ b/yarn.lock @@ -160,6 +160,23 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/context-base/-/context-base-0.14.0.tgz#c67fc20a4d891447ca1a855d7d70fa79a3533001" integrity sha512-sDOAZcYwynHFTbLo6n8kIbLiVF3a3BLkrmehJUyEbT9F+Smbi47kLGS2gG2g0fjBLR/Lr1InPD7kXL7FaTqEkw== +"@prisma/client@^2.21.2": + version "2.21.2" + resolved "https://registry.yarnpkg.com/@prisma/client/-/client-2.21.2.tgz#ca8489832da1d61add429390210be4d7896e5e29" + integrity sha512-UjkOXYpxLuHyoMDsP2m0LTcxhrjQa1dEOLFe3aDrO/BLrs/2yUxyPdtwSKxizRXFzuXSGkKIK225vcjZRuMpAg== + dependencies: + "@prisma/engines-version" "2.21.0-36.e421996c87d5f3c8f7eeadd502d4ad402c89464d" + +"@prisma/engines-version@2.21.0-36.e421996c87d5f3c8f7eeadd502d4ad402c89464d": + version "2.21.0-36.e421996c87d5f3c8f7eeadd502d4ad402c89464d" + resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-2.21.0-36.e421996c87d5f3c8f7eeadd502d4ad402c89464d.tgz#b749bae4173eb766dafc298aaa7d883c2dbe555b" + integrity sha512-9/fE1gdPWmjbMjXUJjrTMt848TsgEnSjZCcJ1wu9OAcRlAKKJBLehftqC3gSEShDijvMYgeTdGU5snMpwmv4vg== + +"@prisma/engines@2.21.0-36.e421996c87d5f3c8f7eeadd502d4ad402c89464d": + version "2.21.0-36.e421996c87d5f3c8f7eeadd502d4ad402c89464d" + resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-2.21.0-36.e421996c87d5f3c8f7eeadd502d4ad402c89464d.tgz#aafed60c9506bc766e49ea60b9f8ce7da2385bc6" + integrity sha512-L57tvSoom2GDWDqik4wrAUBvLTAv5MTm2OOzNMBKsv0w5cX7ONoZ8KnGQN+csmdJpQVBs93dIvIBm72OO+l/9Q== + "@stripe/react-stripe-js@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@stripe/react-stripe-js/-/react-stripe-js-1.1.2.tgz#a7f5ef5b4d7dc7fa723501b706644414cfe6dcba" @@ -177,6 +194,24 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw== +"@types/node@^14.14.41": + version "14.14.41" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.41.tgz#d0b939d94c1d7bd53d04824af45f1139b8c45615" + integrity sha512-dueRKfaJL4RTtSa7bWeTK1M+VH+Gns73oCgzvYfHZywRCoPSd8EkXBL0mZ9unPTveBn+D9phZBaxuzpwjWkW0g== + +"@types/prop-types@*": + version "15.7.3" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" + integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== + +"@types/react@^17.0.2": + version "17.0.2" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.2.tgz#3de24c4efef902dd9795a49c75f760cbe4f7a5a8" + integrity sha512-Xt40xQsrkdvjn1EyWe1Bc0dJLcil/9x2vAuW7ya+PuQip4UYUaXyhzWmAbwRsdMgwOFHpfp7/FFZebDU6Y8VHA== + dependencies: + "@types/prop-types" "*" + csstype "^3.0.2" + "@webassemblyjs/ast@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" @@ -469,6 +504,11 @@ are-we-there-yet@~1.1.2: delegates "^1.0.0" readable-stream "^2.0.6" +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + arity-n@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/arity-n/-/arity-n-1.0.4.tgz#d9e76b11733e08569c0847ae7b39b2860b30b745" @@ -1134,6 +1174,11 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: safe-buffer "^5.0.1" sha.js "^2.4.8" +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + cross-fetch@3.0.6: version "3.0.6" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.0.6.tgz#3a4040bc8941e653e0e9cf17f29ebcd177d3365c" @@ -1344,6 +1389,11 @@ didyoumean@^1.2.1: resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.1.tgz#e92edfdada6537d484d73c0172fd1eba0c4976ff" integrity sha1-6S7f2tplN9SE1zwBcv0eugxJdv8= +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -2379,6 +2429,11 @@ make-dir@^3.0.2: dependencies: semver "^6.0.0" +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + map-cache@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" @@ -3114,6 +3169,13 @@ pretty-hrtime@^1.0.3: resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE= +prisma@^2.21.2: + version "2.21.2" + resolved "https://registry.yarnpkg.com/prisma/-/prisma-2.21.2.tgz#a73b4cbe92a884aa98b317684d6741871b5e94a5" + integrity sha512-Ux9ovDIUHsMNLGLtuo6BBKCuuBVLpZmhM2LXF+VBUQvsbmsVfp3u5CRyHGEqaZqMibYQJISy7YZYF/RgozHKkQ== + dependencies: + "@prisma/engines" "2.21.0-36.e421996c87d5f3c8f7eeadd502d4ad402c89464d" + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -3675,7 +3737,7 @@ source-map-resolve@^0.5.0, source-map-resolve@^0.5.2: source-map-url "^0.4.0" urix "^0.1.0" -source-map-support@~0.5.12, source-map-support@~0.5.19: +source-map-support@^0.5.17, source-map-support@~0.5.12, source-map-support@~0.5.19: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== @@ -4058,6 +4120,18 @@ traverse@0.6.6: resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137" integrity sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc= +ts-node@^9.1.1: + version "9.1.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-9.1.1.tgz#51a9a450a3e959401bda5f004a72d54b936d376d" + integrity sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg== + dependencies: + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + source-map-support "^0.5.17" + yn "3.1.1" + ts-pnp@^1.1.6: version "1.2.0" resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92" @@ -4100,6 +4174,11 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +typescript@^4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961" + integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg== + union-value@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" @@ -4339,6 +4418,11 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"