[
  {
    "path": ".eslintrc.json",
    "content": "{\n  \"extends\": \"next/core-web-vitals\"\n}\n"
  },
  {
    "path": ".gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.js\n.yarn/install-state.gz\n\n# testing\n/coverage\n\n# next.js\n/.next/\n/out/\n\n# production\n/build\n\n# misc\n.env\n.idea\n.DS_Store\n*.pem\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# local env files\n.env*.local\n\n# vercel\n.vercel\n\n# typescript\n*.tsbuildinfo\nnext-env.d.ts\n"
  },
  {
    "path": "README.md",
    "content": "This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).\n\n## Getting Started\n\nFirst, run the development server:\n\n```bash\nnpm run dev\n# or\nyarn dev\n# or\npnpm dev\n# or\nbun dev\n```\n\nOpen [http://localhost:3000](http://localhost:3000) with your browser to see the result.\n\nYou can start editing the page by modifying `app/page.js`. The page auto-updates as you edit the file.\n\nThis project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.\n\n## Learn More\n\nTo learn more about Next.js, take a look at the following resources:\n\n- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.\n- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.\n\nYou can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!\n\n## Deploy on Vercel\n\nThe easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.\n\nCheck out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.\n"
  },
  {
    "path": "jsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  }\n}\n"
  },
  {
    "path": "next.config.js",
    "content": "/** @type {import('next').NextConfig} */\nconst nextConfig = {\n  images: {\n    remotePatterns: [\n      {\n        protocol: 'https',\n        hostname: '*.googleusercontent.com',\n      },\n      {\n        protocol: 'https',\n        hostname: 'dawid-food-ordering.s3.amazonaws.com',\n      },\n    ]\n  }\n}\n\nmodule.exports = nextConfig\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"food-ordering-app\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev\",\n    \"build\": \"next build\",\n    \"start\": \"next start\",\n    \"lint\": \"next lint\"\n  },\n  \"dependencies\": {\n    \"@auth/mongodb-adapter\": \"^2.0.3\",\n    \"@aws-sdk/client-s3\": \"^3.438.0\",\n    \"bcrypt\": \"^5.1.1\",\n    \"micro\": \"^10.0.1\",\n    \"mongodb\": \"^6.2.0\",\n    \"mongoose\": \"^7.6.3\",\n    \"next\": \"14.0.0\",\n    \"next-auth\": \"^4.24.4\",\n    \"react\": \"^18\",\n    \"react-dom\": \"^18\",\n    \"react-flying-item\": \"^1.1.2\",\n    \"react-hot-toast\": \"^2.4.1\",\n    \"stripe\": \"^14.3.0\",\n    \"uniqid\": \"^5.4.0\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"20.8.9\",\n    \"@types/react\": \"18.2.33\",\n    \"autoprefixer\": \"^10\",\n    \"eslint\": \"^8\",\n    \"eslint-config-next\": \"14.0.0\",\n    \"postcss\": \"^8\",\n    \"tailwindcss\": \"^3\",\n    \"typescript\": \"5.2.2\"\n  }\n}\n"
  },
  {
    "path": "postcss.config.js",
    "content": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n}\n"
  },
  {
    "path": "src/app/api/auth/[...nextauth]/route.js",
    "content": "import clientPromise from \"@/libs/mongoConnect\";\nimport {UserInfo} from \"@/models/UserInfo\";\nimport bcrypt from \"bcrypt\";\nimport * as mongoose from \"mongoose\";\nimport {User} from '@/models/User';\nimport NextAuth, {getServerSession} from \"next-auth\";\nimport CredentialsProvider from \"next-auth/providers/credentials\";\nimport GoogleProvider from \"next-auth/providers/google\";\nimport { MongoDBAdapter } from \"@auth/mongodb-adapter\"\n\nexport const authOptions = {\n  secret: process.env.SECRET,\n  adapter: MongoDBAdapter(clientPromise),\n  providers: [\n    GoogleProvider({\n      clientId: process.env.GOOGLE_CLIENT_ID,\n      clientSecret: process.env.GOOGLE_CLIENT_SECRET,\n    }),\n    CredentialsProvider({\n      name: 'Credentials',\n      id: 'credentials',\n      credentials: {\n        username: { label: \"Email\", type: \"email\", placeholder: \"test@example.com\" },\n        password: { label: \"Password\", type: \"password\" },\n      },\n      async authorize(credentials, req) {\n        const email = credentials?.email;\n        const password = credentials?.password;\n\n        mongoose.connect(process.env.MONGO_URL);\n        const user = await User.findOne({email});\n        const passwordOk = user && bcrypt.compareSync(password, user.password);\n\n        if (passwordOk) {\n          return user;\n        }\n\n        return null\n      }\n    })\n  ],\n};\n\nexport async function isAdmin() {\n  const session = await getServerSession(authOptions);\n  const userEmail = session?.user?.email;\n  if (!userEmail) {\n    return false;\n  }\n  const userInfo = await UserInfo.findOne({email:userEmail});\n  if (!userInfo) {\n    return false;\n  }\n  return userInfo.admin;\n}\n\nconst handler = NextAuth(authOptions);\n\nexport { handler as GET, handler as POST }"
  },
  {
    "path": "src/app/api/categories/route.js",
    "content": "import {isAdmin} from \"@/app/api/auth/[...nextauth]/route\";\nimport {Category} from \"@/models/Category\";\nimport mongoose from \"mongoose\";\n\nexport async function POST(req) {\n  mongoose.connect(process.env.MONGO_URL);\n  const {name} = await req.json();\n  if (await isAdmin()) {\n    const categoryDoc = await Category.create({name});\n    return Response.json(categoryDoc);\n  } else {\n    return Response.json({});\n  }\n}\n\nexport async function PUT(req) {\n  mongoose.connect(process.env.MONGO_URL);\n  const {_id, name} = await req.json();\n  if (await isAdmin()) {\n    await Category.updateOne({_id}, {name});\n  }\n  return Response.json(true);\n}\n\nexport async function GET() {\n  mongoose.connect(process.env.MONGO_URL);\n  return Response.json(\n    await Category.find()\n  );\n}\n\nexport async function DELETE(req) {\n  mongoose.connect(process.env.MONGO_URL);\n  const url = new URL(req.url);\n  const _id = url.searchParams.get('_id');\n  if (await isAdmin()) {\n    await Category.deleteOne({_id});\n  }\n  return Response.json(true);\n}"
  },
  {
    "path": "src/app/api/checkout/route.js",
    "content": "import {authOptions} from \"@/app/api/auth/[...nextauth]/route\";\nimport {MenuItem} from \"@/models/MenuItem\";\nimport {Order} from \"@/models/Order\";\nimport mongoose from \"mongoose\";\nimport {getServerSession} from \"next-auth\";\nconst stripe = require('stripe')(process.env.STRIPE_SK);\n\nexport async function POST(req) {\n  mongoose.connect(process.env.MONGO_URL);\n\n  const {cartProducts, address} = await req.json();\n  const session = await getServerSession(authOptions);\n  const userEmail = session?.user?.email;\n\n  const orderDoc = await Order.create({\n    userEmail,\n    ...address,\n    cartProducts,\n    paid: false,\n  });\n\n  const stripeLineItems = [];\n  for (const cartProduct of cartProducts) {\n\n    const productInfo = await MenuItem.findById(cartProduct._id);\n\n    let productPrice = productInfo.basePrice;\n    if (cartProduct.size) {\n      const size = productInfo.sizes\n        .find(size => size._id.toString() === cartProduct.size._id.toString());\n      productPrice += size.price;\n    }\n    if (cartProduct.extras?.length > 0) {\n      for (const cartProductExtraThing of cartProduct.extras) {\n        const productExtras = productInfo.extraIngredientPrices;\n        const extraThingInfo = productExtras\n          .find(extra => extra._id.toString() === cartProductExtraThing._id.toString());\n        productPrice += extraThingInfo.price;\n      }\n    }\n\n    const productName = cartProduct.name;\n\n    stripeLineItems.push({\n      quantity: 1,\n      price_data: {\n        currency: 'USD',\n        product_data: {\n          name: productName,\n        },\n        unit_amount: productPrice * 100,\n      },\n    });\n  }\n\n  const stripeSession = await stripe.checkout.sessions.create({\n    line_items: stripeLineItems,\n    mode: 'payment',\n    customer_email: userEmail,\n    success_url: process.env.NEXTAUTH_URL + 'orders/' + orderDoc._id.toString() + '?clear-cart=1',\n    cancel_url: process.env.NEXTAUTH_URL + 'cart?canceled=1',\n    metadata: {orderId:orderDoc._id.toString()},\n    payment_intent_data: {\n      metadata:{orderId:orderDoc._id.toString()},\n    },\n    shipping_options: [\n      {\n        shipping_rate_data: {\n          display_name: 'Delivery fee',\n          type: 'fixed_amount',\n          fixed_amount: {amount: 500, currency: 'USD'},\n        },\n      }\n    ],\n  });\n\n  return Response.json(stripeSession.url);\n}"
  },
  {
    "path": "src/app/api/menu-items/route.js",
    "content": "import {isAdmin} from \"@/app/api/auth/[...nextauth]/route\";\nimport {MenuItem} from \"@/models/MenuItem\";\nimport mongoose from \"mongoose\";\n\nexport async function POST(req) {\n  mongoose.connect(process.env.MONGO_URL);\n  const data = await req.json();\n  if (await isAdmin()) {\n    const menuItemDoc = await MenuItem.create(data);\n    return Response.json(menuItemDoc);\n  } else {\n    return Response.json({});\n  }\n}\n\nexport async function PUT(req) {\n  mongoose.connect(process.env.MONGO_URL);\n  if (await isAdmin()) {\n    const {_id, ...data} = await req.json();\n    await MenuItem.findByIdAndUpdate(_id, data);\n  }\n  return Response.json(true);\n}\n\nexport async function GET() {\n  mongoose.connect(process.env.MONGO_URL);\n  return Response.json(\n    await MenuItem.find()\n  );\n}\n\nexport async function DELETE(req) {\n  mongoose.connect(process.env.MONGO_URL);\n  const url = new URL(req.url);\n  const _id = url.searchParams.get('_id');\n  if (await isAdmin()) {\n    await MenuItem.deleteOne({_id});\n  }\n  return Response.json(true);\n}"
  },
  {
    "path": "src/app/api/orders/route.js",
    "content": "import {authOptions, isAdmin} from \"@/app/api/auth/[...nextauth]/route\";\nimport {Order} from \"@/models/Order\";\nimport mongoose from \"mongoose\";\nimport {getServerSession} from \"next-auth\";\n\nexport async function GET(req) {\n  mongoose.connect(process.env.MONGO_URL);\n\n  const session = await getServerSession(authOptions);\n  const userEmail = session?.user?.email;\n  const admin = await isAdmin();\n\n  const url = new URL(req.url);\n  const _id = url.searchParams.get('_id');\n  if (_id) {\n    return Response.json( await Order.findById(_id) );\n  }\n\n\n  if (admin) {\n    return Response.json( await Order.find() );\n  }\n\n  if (userEmail) {\n    return Response.json( await Order.find({userEmail}) );\n  }\n\n}"
  },
  {
    "path": "src/app/api/profile/route.js",
    "content": "import {authOptions} from \"@/app/api/auth/[...nextauth]/route\";\nimport {User} from \"@/models/User\";\nimport {UserInfo} from \"@/models/UserInfo\";\nimport mongoose from \"mongoose\";\nimport {getServerSession} from \"next-auth\";\n\nexport async function PUT(req) {\n  mongoose.connect(process.env.MONGO_URL);\n  const data = await req.json();\n  const {_id, name, image, ...otherUserInfo} = data;\n\n  let filter = {};\n  if (_id) {\n    filter = {_id};\n  } else {\n    const session = await getServerSession(authOptions);\n    const email = session.user.email;\n    filter = {email};\n  }\n\n  const user = await User.findOne(filter);\n  await User.updateOne(filter, {name, image});\n  await UserInfo.findOneAndUpdate({email:user.email}, otherUserInfo, {upsert:true});\n\n  return Response.json(true);\n}\n\nexport async function GET(req) {\n  mongoose.connect(process.env.MONGO_URL);\n\n  const url = new URL(req.url);\n  const _id = url.searchParams.get('_id');\n\n  let filterUser = {};\n  if (_id) {\n    filterUser = {_id};\n  } else {\n    const session = await getServerSession(authOptions);\n    const email = session?.user?.email;\n    if (!email) {\n      return Response.json({});\n    }\n    filterUser = {email};\n  }\n\n  const user = await User.findOne(filterUser).lean();\n  const userInfo = await UserInfo.findOne({email:user.email}).lean();\n\n  return Response.json({...user, ...userInfo});\n\n}"
  },
  {
    "path": "src/app/api/register/route.js",
    "content": "import {User} from \"@/models/User\";\nimport bcrypt from \"bcrypt\";\nimport mongoose from \"mongoose\";\n\nexport async function POST(req) {\n  const body = await req.json();\n  mongoose.connect(process.env.MONGO_URL);\n  const pass = body.password;\n  if (!pass?.length || pass.length < 5) {\n    new Error('password must be at least 5 characters');\n  }\n\n  const notHashedPassword = pass;\n  const salt = bcrypt.genSaltSync(10);\n  body.password = bcrypt.hashSync(notHashedPassword, salt);\n\n  const createdUser = await User.create(body);\n  return Response.json(createdUser);\n}"
  },
  {
    "path": "src/app/api/upload/route.js",
    "content": "import {PutObjectCommand, S3Client} from \"@aws-sdk/client-s3\";\nimport uniqid from 'uniqid';\n\nexport async function POST(req) {\n  const data =  await req.formData();\n  if (data.get('file')) {\n    // upload the file\n    const file = data.get('file');\n\n    const s3Client = new S3Client({\n      region: 'us-east-1',\n      credentials: {\n        accessKeyId: process.env.MY_AWS_ACCESS_KEY,\n        secretAccessKey: process.env.MY_AWS_SECRET_KEY,\n      },\n    });\n\n    const ext = file.name.split('.').slice(-1)[0];\n    const newFileName = uniqid() + '.' + ext;\n\n    const chunks = [];\n    for await (const chunk of file.stream()) {\n      chunks.push(chunk);\n    }\n    const buffer = Buffer.concat(chunks);\n\n    const bucket = 'dawid-food-ordering';\n    await s3Client.send(new PutObjectCommand({\n      Bucket: bucket,\n      Key: newFileName,\n      ACL: 'public-read',\n      ContentType: file.type,\n      Body: buffer,\n    }));\n\n\n    const link = 'https://'+bucket+'.s3.amazonaws.com/'+newFileName;\n    return Response.json(link);\n  }\n  return Response.json(true);\n}"
  },
  {
    "path": "src/app/api/users/route.js",
    "content": "import {isAdmin} from \"@/app/api/auth/[...nextauth]/route\";\nimport {User} from \"@/models/User\";\nimport mongoose from \"mongoose\";\n\nexport async function GET() {\n  mongoose.connect(process.env.MONGO_URL);\n  if (await isAdmin()) {\n    const users = await User.find();\n    return Response.json(users);\n  } else {\n    return Response.json([]);\n  }\n}"
  },
  {
    "path": "src/app/api/webhook/route.js",
    "content": "import {Order} from \"@/models/Order\";\n\nconst stripe = require('stripe')(process.env.STRIPE_SK);\n\nexport async function POST(req) {\n  const sig = req.headers.get('stripe-signature');\n  let event;\n\n  try {\n    const reqBuffer = await req.text();\n    const signSecret = process.env.STRIPE_SIGN_SECRET;\n    event = stripe.webhooks.constructEvent(reqBuffer, sig, signSecret);\n  } catch (e) {\n    console.error('stripe error');\n    console.log(e);\n    return Response.json(e, {status: 400});\n  }\n\n  if (event.type === 'checkout.session.completed') {\n    console.log(event);\n    const orderId = event?.data?.object?.metadata?.orderId;\n    const isPaid = event?.data?.object?.payment_status === 'paid';\n    if (isPaid) {\n      await Order.updateOne({_id:orderId}, {paid:true});\n    }\n  }\n\n  return Response.json('ok', {status: 200});\n}"
  },
  {
    "path": "src/app/cart/page.js",
    "content": "'use client';\nimport {CartContext, cartProductPrice} from \"@/components/AppContext\";\nimport Trash from \"@/components/icons/Trash\";\nimport AddressInputs from \"@/components/layout/AddressInputs\";\nimport SectionHeaders from \"@/components/layout/SectionHeaders\";\nimport CartProduct from \"@/components/menu/CartProduct\";\nimport {useProfile} from \"@/components/UseProfile\";\nimport Image from \"next/image\";\nimport {useContext, useEffect, useState} from \"react\";\nimport toast from \"react-hot-toast\";\n\nexport default function CartPage() {\n  const {cartProducts,removeCartProduct} = useContext(CartContext);\n  const [address, setAddress] = useState({});\n  const {data:profileData} = useProfile();\n\n  useEffect(() => {\n    if (typeof window !== 'undefined') {\n      if (window.location.href.includes('canceled=1')) {\n        toast.error('Payment failed 😔');\n      }\n    }\n  }, []);\n\n  useEffect(() => {\n    if (profileData?.city) {\n      const {phone, streetAddress, city, postalCode, country} = profileData;\n      const addressFromProfile = {\n        phone,\n        streetAddress,\n        city,\n        postalCode,\n        country\n      };\n      setAddress(addressFromProfile);\n    }\n  }, [profileData]);\n\n  let subtotal = 0;\n  for (const p of cartProducts) {\n    subtotal += cartProductPrice(p);\n  }\n  function handleAddressChange(propName, value) {\n    setAddress(prevAddress => ({...prevAddress, [propName]:value}));\n  }\n  async function proceedToCheckout(ev) {\n    ev.preventDefault();\n    // address and shopping cart products\n\n    const promise = new Promise((resolve, reject) => {\n      fetch('/api/checkout', {\n        method: 'POST',\n        headers: {'Content-Type':'application/json'},\n        body: JSON.stringify({\n          address,\n          cartProducts,\n        }),\n      }).then(async (response) => {\n        if (response.ok) {\n          resolve();\n          window.location = await response.json();\n        } else {\n          reject();\n        }\n      });\n    });\n\n    await toast.promise(promise, {\n      loading: 'Preparing your order...',\n      success: 'Redirecting to payment...',\n      error: 'Something went wrong... Please try again later',\n    })\n  }\n\n  if (cartProducts?.length === 0) {\n    return (\n      <section className=\"mt-8 text-center\">\n        <SectionHeaders mainHeader=\"Cart\" />\n        <p className=\"mt-4\">Your shopping cart is empty 😔</p>\n      </section>\n    );\n  }\n\n  return (\n    <section className=\"mt-8\">\n      <div className=\"text-center\">\n        <SectionHeaders mainHeader=\"Cart\" />\n      </div>\n      <div className=\"mt-8 grid gap-8 grid-cols-2\">\n        <div>\n          {cartProducts?.length === 0 && (\n            <div>No products in your shopping cart</div>\n          )}\n          {cartProducts?.length > 0 && cartProducts.map((product, index) => (\n            <CartProduct\n              key={index}\n              product={product}\n              onRemove={removeCartProduct}\n            />\n          ))}\n          <div className=\"py-2 pr-16 flex justify-end items-center\">\n            <div className=\"text-gray-500\">\n              Subtotal:<br />\n              Delivery:<br />\n              Total:\n            </div>\n            <div className=\"font-semibold pl-2 text-right\">\n              ${subtotal}<br />\n              $5<br />\n              ${subtotal + 5}\n            </div>\n          </div>\n        </div>\n        <div className=\"bg-gray-100 p-4 rounded-lg\">\n          <h2>Checkout</h2>\n          <form onSubmit={proceedToCheckout}>\n            <AddressInputs\n              addressProps={address}\n              setAddressProp={handleAddressChange}\n            />\n            <button type=\"submit\">Pay ${subtotal+5}</button>\n          </form>\n        </div>\n      </div>\n    </section>\n  );\n}"
  },
  {
    "path": "src/app/categories/page.js",
    "content": "'use client';\nimport DeleteButton from \"@/components/DeleteButton\";\nimport UserTabs from \"@/components/layout/UserTabs\";\nimport {useEffect, useState} from \"react\";\nimport {useProfile} from \"@/components/UseProfile\";\nimport toast from \"react-hot-toast\";\n\nexport default function CategoriesPage() {\n\n  const [categoryName, setCategoryName] = useState('');\n  const [categories, setCategories] = useState([]);\n  const {loading:profileLoading, data:profileData} = useProfile();\n  const [editedCategory, setEditedCategory] = useState(null);\n\n  useEffect(() => {\n    fetchCategories();\n  }, []);\n\n  function fetchCategories() {\n    fetch('/api/categories').then(res => {\n      res.json().then(categories => {\n        setCategories(categories);\n      });\n    });\n  }\n\n  async function handleCategorySubmit(ev) {\n    ev.preventDefault();\n    const creationPromise = new Promise(async (resolve, reject) => {\n      const data = {name:categoryName};\n      if (editedCategory) {\n        data._id = editedCategory._id;\n      }\n      const response = await fetch('/api/categories', {\n        method: editedCategory ? 'PUT' : 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify(data),\n      });\n      setCategoryName('');\n      fetchCategories();\n      setEditedCategory(null);\n      if (response.ok)\n        resolve();\n      else\n        reject();\n    });\n    await toast.promise(creationPromise, {\n      loading: editedCategory\n                 ? 'Updating category...'\n                 : 'Creating your new category...',\n      success: editedCategory ? 'Category updated' : 'Category created',\n      error: 'Error, sorry...',\n    });\n  }\n\n  async function handleDeleteClick(_id) {\n    const promise = new Promise(async (resolve, reject) => {\n      const response = await fetch('/api/categories?_id='+_id, {\n        method: 'DELETE',\n      });\n      if (response.ok) {\n        resolve();\n      } else {\n        reject();\n      }\n    });\n\n    await toast.promise(promise, {\n      loading: 'Deleting...',\n      success: 'Deleted',\n      error: 'Error',\n    });\n\n    fetchCategories();\n  }\n\n  if (profileLoading) {\n    return 'Loading user info...';\n  }\n\n  if (!profileData.admin) {\n    return 'Not an admin';\n  }\n\n  return (\n    <section className=\"mt-8 max-w-2xl mx-auto\">\n      <UserTabs isAdmin={true} />\n      <form className=\"mt-8\" onSubmit={handleCategorySubmit}>\n        <div className=\"flex gap-2 items-end\">\n          <div className=\"grow\">\n            <label>\n              {editedCategory ? 'Update category' : 'New category name'}\n              {editedCategory && (\n                <>: <b>{editedCategory.name}</b></>\n              )}\n            </label>\n            <input type=\"text\"\n                   value={categoryName}\n                   onChange={ev => setCategoryName(ev.target.value)}\n            />\n          </div>\n          <div className=\"pb-2 flex gap-2\">\n            <button className=\"border border-primary\" type=\"submit\">\n              {editedCategory ? 'Update' : 'Create'}\n            </button>\n            <button\n              type=\"button\"\n              onClick={() => {\n                setEditedCategory(null);\n                setCategoryName('');\n              }}>\n              Cancel\n            </button>\n          </div>\n        </div>\n      </form>\n      <div>\n        <h2 className=\"mt-8 text-sm text-gray-500\">Existing categories</h2>\n        {categories?.length > 0 && categories.map(c => (\n          <div\n            key={c._id}\n            className=\"bg-gray-100 rounded-xl p-2 px-4 flex gap-1 mb-1 items-center\">\n            <div className=\"grow\">\n              {c.name}\n            </div>\n            <div className=\"flex gap-1\">\n              <button type=\"button\"\n                      onClick={() => {\n                        setEditedCategory(c);\n                        setCategoryName(c.name);\n                      }}\n              >\n                Edit\n              </button>\n              <DeleteButton\n                label=\"Delete\"\n                onDelete={() => handleDeleteClick(c._id)} />\n            </div>\n          </div>\n        ))}\n      </div>\n    </section>\n  );\n}"
  },
  {
    "path": "src/app/globals.css",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\nsection.hero{\n    @apply block md:grid;\n    grid-template-columns: .4fr .6fr;\n}\n\nselect,\ninput[type=\"email\"],\ninput[type=\"password\"],\ninput[type=\"tel\"],\ninput[type=\"text\"] {\n    @apply block w-full mb-2 rounded-xl;\n    @apply border p-2 border-gray-300 bg-gray-100;\n}\ninput[type=\"email\"]:disabled,\ninput[type=\"password\"]:disabled,\ninput[type=\"tel\"]:disabled,\ninput[type=\"text\"]:disabled {\n    @apply bg-gray-300 border-0 cursor-not-allowed text-gray-500;\n}\nlabel{\n    @apply text-gray-500 text-sm leading-tight;\n}\nlabel + input{\n    margin-top: -2px;\n}\nbutton, .button{\n    @apply flex w-full justify-center gap-2 text-gray-700 font-semibold;\n    @apply border border-gray-300 rounded-xl px-6 py-2;\n}\nbutton[type=\"submit\"], .submit, button.primary{\n    @apply border-primary bg-primary text-white;\n}\nbutton[type=\"submit\"]:disabled, .submit:disabled{\n    @apply cursor-not-allowed bg-red-400;\n}\ndiv.tabs > * {\n    @apply bg-gray-300 text-gray-700 rounded-full py-2 px-4;\n}\ndiv.tabs > *.active{\n    @apply bg-primary text-white;\n}\n\n.flying-button-parent button{\n    @apply border-primary bg-primary text-white rounded-full;\n}"
  },
  {
    "path": "src/app/layout.js",
    "content": "import {AppProvider} from \"@/components/AppContext\";\nimport Header from \"@/components/layout/Header\";\nimport { Roboto } from 'next/font/google'\nimport './globals.css'\nimport {Toaster} from \"react-hot-toast\";\n\nconst roboto = Roboto({ subsets: ['latin'], weight: ['400', '500', '700'] })\n\nexport const metadata = {\n  title: 'Create Next App',\n  description: 'Generated by create next app',\n}\n\nexport default function RootLayout({ children }) {\n  return (\n    <html lang=\"en\" className=\"scroll-smooth\">\n      <body className={roboto.className}>\n        <main className=\"max-w-4xl mx-auto p-4\">\n          <AppProvider>\n            <Toaster />\n            <Header />\n            {children}\n            <footer className=\"border-t p-8 text-center text-gray-500 mt-16\">\n              &copy; 2023 All rights reserved\n            </footer>\n          </AppProvider>\n        </main>\n      </body>\n    </html>\n  )\n}\n"
  },
  {
    "path": "src/app/login/page.js",
    "content": "'use client';\nimport {signIn} from \"next-auth/react\";\nimport Image from \"next/image\";\nimport {useState} from \"react\";\n\nexport default function LoginPage() {\n  const [email, setEmail] = useState('');\n  const [password, setPassword] = useState('');\n  const [loginInProgress, setLoginInProgress] = useState(false);\n\n  async function handleFormSubmit(ev) {\n    ev.preventDefault();\n    setLoginInProgress(true);\n\n    await signIn('credentials', {email, password, callbackUrl: '/'});\n\n    setLoginInProgress(false);\n  }\n  return (\n    <section className=\"mt-8\">\n      <h1 className=\"text-center text-primary text-4xl mb-4\">\n        Login\n      </h1>\n      <form className=\"max-w-xs mx-auto\" onSubmit={handleFormSubmit}>\n        <input type=\"email\" name=\"email\" placeholder=\"email\" value={email}\n               disabled={loginInProgress}\n               onChange={ev => setEmail(ev.target.value)} />\n        <input type=\"password\" name=\"password\" placeholder=\"password\" value={password}\n               disabled={loginInProgress}\n               onChange={ev => setPassword(ev.target.value)}/>\n        <button disabled={loginInProgress} type=\"submit\">Login</button>\n        <div className=\"my-4 text-center text-gray-500\">\n          or login with provider\n        </div>\n        <button type=\"button\" onClick={() => signIn('google', {callbackUrl: '/'})}\n                className=\"flex gap-4 justify-center\">\n          <Image src={'/google.png'} alt={''} width={24} height={24} />\n          Login with google\n        </button>\n      </form>\n    </section>\n  );\n}"
  },
  {
    "path": "src/app/menu/page.js",
    "content": "'use client';\nimport SectionHeaders from \"@/components/layout/SectionHeaders\";\nimport MenuItem from \"@/components/menu/MenuItem\";\nimport {useEffect, useState} from \"react\";\n\nexport default function MenuPage() {\n  const [categories, setCategories] = useState([]);\n  const [menuItems, setMenuItems] = useState([]);\n  useEffect(() => {\n    fetch('/api/categories').then(res => {\n      res.json().then(categories => setCategories(categories))\n    });\n    fetch('/api/menu-items').then(res => {\n      res.json().then(menuItems => setMenuItems(menuItems));\n    });\n  }, []);\n  return (\n    <section className=\"mt-8\">\n      {categories?.length > 0 && categories.map(c => (\n        <div key={c._id}>\n          <div className=\"text-center\">\n            <SectionHeaders mainHeader={c.name} />\n          </div>\n          <div className=\"grid sm:grid-cols-3 gap-4 mt-6 mb-12\">\n            {menuItems.filter(item => item.category === c._id).map(item => (\n              <MenuItem key={item._id} {...item} />\n            ))}\n          </div>\n        </div>\n      ))}\n    </section>\n  );\n}"
  },
  {
    "path": "src/app/menu-items/edit/[id]/page.js",
    "content": "'use client';\nimport DeleteButton from \"@/components/DeleteButton\";\nimport Left from \"@/components/icons/Left\";\nimport EditableImage from \"@/components/layout/EditableImage\";\nimport MenuItemForm from \"@/components/layout/MenuItemForm\";\nimport UserTabs from \"@/components/layout/UserTabs\";\nimport {useProfile} from \"@/components/UseProfile\";\nimport Link from \"next/link\";\nimport {redirect, useParams} from \"next/navigation\";\nimport {useEffect, useState} from \"react\";\nimport toast from \"react-hot-toast\";\n\nexport default function EditMenuItemPage() {\n\n  const {id} = useParams();\n\n  const [menuItem, setMenuItem] = useState(null);\n  const [redirectToItems, setRedirectToItems] = useState(false);\n  const {loading, data} = useProfile();\n\n  useEffect(() => {\n    fetch('/api/menu-items').then(res => {\n      res.json().then(items => {\n        const item = items.find(i => i._id === id);\n        setMenuItem(item);\n      });\n    })\n  }, []);\n\n  async function handleFormSubmit(ev, data) {\n    ev.preventDefault();\n    data = {...data, _id:id};\n    const savingPromise = new Promise(async (resolve, reject) => {\n      const response = await fetch('/api/menu-items', {\n        method: 'PUT',\n        body: JSON.stringify(data),\n        headers: { 'Content-Type': 'application/json' },\n      });\n      if (response.ok)\n        resolve();\n      else\n        reject();\n    });\n\n    await toast.promise(savingPromise, {\n      loading: 'Saving this tasty item',\n      success: 'Saved',\n      error: 'Error',\n    });\n\n    setRedirectToItems(true);\n  }\n\n  async function handleDeleteClick() {\n    const promise = new Promise(async (resolve, reject) => {\n      const res = await fetch('/api/menu-items?_id='+id, {\n        method: 'DELETE',\n      });\n      if (res.ok)\n        resolve();\n      else\n        reject();\n    });\n\n    await toast.promise(promise, {\n      loading: 'Deleting...',\n      success: 'Deleted',\n      error: 'Error',\n    });\n\n    setRedirectToItems(true);\n  }\n\n  if (redirectToItems) {\n    return redirect('/menu-items');\n  }\n\n  if (loading) {\n    return 'Loading user info...';\n  }\n\n  if (!data.admin) {\n    return 'Not an admin.';\n  }\n\n  return (\n    <section className=\"mt-8\">\n      <UserTabs isAdmin={true} />\n      <div className=\"max-w-2xl mx-auto mt-8\">\n        <Link href={'/menu-items'} className=\"button\">\n          <Left />\n          <span>Show all menu items</span>\n        </Link>\n      </div>\n      <MenuItemForm menuItem={menuItem} onSubmit={handleFormSubmit} />\n      <div className=\"max-w-md mx-auto mt-2\">\n        <div className=\"max-w-xs ml-auto pl-4\">\n          <DeleteButton\n            label=\"Delete this menu item\"\n            onDelete={handleDeleteClick}\n          />\n        </div>\n      </div>\n    </section>\n  );\n}"
  },
  {
    "path": "src/app/menu-items/new/page.js",
    "content": "'use client';\nimport Left from \"@/components/icons/Left\";\nimport Right from \"@/components/icons/Right\";\nimport EditableImage from \"@/components/layout/EditableImage\";\nimport MenuItemForm from \"@/components/layout/MenuItemForm\";\nimport UserTabs from \"@/components/layout/UserTabs\";\nimport {useProfile} from \"@/components/UseProfile\";\nimport Link from \"next/link\";\nimport {redirect} from \"next/navigation\";\nimport {useState} from \"react\";\nimport toast from \"react-hot-toast\";\n\nexport default function NewMenuItemPage() {\n\n  const [redirectToItems, setRedirectToItems] = useState(false);\n  const {loading, data} = useProfile();\n\n  async function handleFormSubmit(ev, data) {\n    ev.preventDefault();\n    const savingPromise = new Promise(async (resolve, reject) => {\n      const response = await fetch('/api/menu-items', {\n        method: 'POST',\n        body: JSON.stringify(data),\n        headers: { 'Content-Type': 'application/json' },\n      });\n      if (response.ok)\n        resolve();\n      else\n        reject();\n    });\n\n    await toast.promise(savingPromise, {\n      loading: 'Saving this tasty item',\n      success: 'Saved',\n      error: 'Error',\n    });\n\n    setRedirectToItems(true);\n  }\n\n  if (redirectToItems) {\n    return redirect('/menu-items');\n  }\n\n  if (loading) {\n    return 'Loading user info...';\n  }\n\n  if (!data.admin) {\n    return 'Not an admin.';\n  }\n\n  return (\n    <section className=\"mt-8\">\n      <UserTabs isAdmin={true} />\n      <div className=\"max-w-2xl mx-auto mt-8\">\n        <Link href={'/menu-items'} className=\"button\">\n          <Left />\n          <span>Show all menu items</span>\n        </Link>\n      </div>\n      <MenuItemForm menuItem={null} onSubmit={handleFormSubmit} />\n    </section>\n  );\n}"
  },
  {
    "path": "src/app/menu-items/page.js",
    "content": "'use client';\nimport Right from \"@/components/icons/Right\";\nimport UserTabs from \"@/components/layout/UserTabs\";\nimport {useProfile} from \"@/components/UseProfile\";\nimport Image from \"next/image\";\nimport Link from \"next/link\";\nimport {useEffect, useState} from \"react\";\n\nexport default function MenuItemsPage() {\n\n  const [menuItems, setMenuItems] = useState([]);\n  const {loading, data} = useProfile();\n\n  useEffect(() => {\n    fetch('/api/menu-items').then(res => {\n      res.json().then(menuItems => {\n        setMenuItems(menuItems);\n      });\n    })\n  }, []);\n\n  if (loading) {\n    return 'Loading user info...';\n  }\n\n  if (!data.admin) {\n    return 'Not an admin.';\n  }\n\n  return (\n    <section className=\"mt-8 max-w-2xl mx-auto\">\n      <UserTabs isAdmin={true} />\n      <div className=\"mt-8\">\n        <Link\n          className=\"button flex\"\n          href={'/menu-items/new'}>\n          <span>Crete new menu item</span>\n          <Right />\n        </Link>\n      </div>\n      <div>\n        <h2 className=\"text-sm text-gray-500 mt-8\">Edit menu item:</h2>\n        <div className=\"grid grid-cols-3 gap-2\">\n          {menuItems?.length > 0 && menuItems.map(item => (\n            <Link\n              key={item._id}\n              href={'/menu-items/edit/'+item._id}\n              className=\"bg-gray-200 rounded-lg p-4\"\n            >\n              <div className=\"relative\">\n                <Image\n                  className=\"rounded-md\"\n                  src={item.image} alt={''} width={200} height={200} />\n              </div>\n              <div className=\"text-center\">\n                {item.name}\n              </div>\n            </Link>\n          ))}\n        </div>\n      </div>\n    </section>\n  );\n}"
  },
  {
    "path": "src/app/orders/[id]/page.js",
    "content": "'use client';\nimport {CartContext, cartProductPrice} from \"@/components/AppContext\";\nimport AddressInputs from \"@/components/layout/AddressInputs\";\nimport SectionHeaders from \"@/components/layout/SectionHeaders\";\nimport CartProduct from \"@/components/menu/CartProduct\";\nimport {useParams} from \"next/navigation\";\nimport {useContext, useEffect, useState} from \"react\";\n\nexport default function OrderPage() {\n  const {clearCart} = useContext(CartContext);\n  const [order, setOrder] = useState();\n  const [loadingOrder, setLoadingOrder] = useState(true);\n  const {id} = useParams();\n  useEffect(() => {\n    if (typeof window.console !== \"undefined\") {\n      if (window.location.href.includes('clear-cart=1')) {\n        clearCart();\n      }\n    }\n    if (id) {\n      setLoadingOrder(true);\n      fetch('/api/orders?_id='+id).then(res => {\n        res.json().then(orderData => {\n          setOrder(orderData);\n          setLoadingOrder(false);\n        });\n      })\n    }\n  }, []);\n\n  let subtotal = 0;\n  if (order?.cartProducts) {\n    for (const product of order?.cartProducts) {\n      subtotal += cartProductPrice(product);\n    }\n  }\n\n  return (\n    <section className=\"max-w-2xl mx-auto mt-8\">\n      <div className=\"text-center\">\n        <SectionHeaders mainHeader=\"Your order\" />\n        <div className=\"mt-4 mb-8\">\n          <p>Thanks for your order.</p>\n          <p>We will call you when your order will be on the way.</p>\n        </div>\n      </div>\n      {loadingOrder && (\n        <div>Loading order...</div>\n      )}\n      {order && (\n        <div className=\"grid md:grid-cols-2 md:gap-16\">\n          <div>\n            {order.cartProducts.map(product => (\n              <CartProduct key={product._id} product={product} />\n            ))}\n            <div className=\"text-right py-2 text-gray-500\">\n              Subtotal:\n              <span className=\"text-black font-bold inline-block w-8\">${subtotal}</span>\n              <br />\n              Delivery:\n              <span className=\"text-black font-bold inline-block w-8\">$5</span>\n              <br />\n              Total:\n              <span className=\"text-black font-bold inline-block w-8\">\n                ${subtotal + 5}\n              </span>\n            </div>\n          </div>\n          <div>\n            <div className=\"bg-gray-100 p-4 rounded-lg\">\n              <AddressInputs\n                disabled={true}\n                addressProps={order}\n              />\n            </div>\n          </div>\n        </div>\n      )}\n    </section>\n  );\n}"
  },
  {
    "path": "src/app/orders/page.js",
    "content": "'use client';\nimport SectionHeaders from \"@/components/layout/SectionHeaders\";\nimport UserTabs from \"@/components/layout/UserTabs\";\nimport {useProfile} from \"@/components/UseProfile\";\nimport {dbTimeForHuman} from \"@/libs/datetime\";\nimport Link from \"next/link\";\nimport {useEffect, useState} from \"react\";\n\nexport default function OrdersPage() {\n  const [orders, setOrders] = useState([]);\n  const [loadingOrders, setLoadingOrders] = useState(true);\n  const {loading, data:profile} = useProfile();\n\n  useEffect(() => {\n    fetchOrders();\n  }, []);\n\n  function fetchOrders() {\n    setLoadingOrders(true);\n    fetch('/api/orders').then(res => {\n      res.json().then(orders => {\n        setOrders(orders.reverse());\n        setLoadingOrders(false);\n      })\n    })\n  }\n\n  return (\n    <section className=\"mt-8 max-w-2xl mx-auto\">\n      <UserTabs isAdmin={profile.admin} />\n      <div className=\"mt-8\">\n        {loadingOrders && (\n          <div>Loading orders...</div>\n        )}\n        {orders?.length > 0 && orders.map(order => (\n          <div\n            key={order._id}\n            className=\"bg-gray-100 mb-2 p-4 rounded-lg flex flex-col md:flex-row items-center gap-6\">\n            <div className=\"grow flex flex-col md:flex-row items-center gap-6\">\n              <div>\n                <div className={\n                  (order.paid ? 'bg-green-500' : 'bg-red-400')\n                  + ' p-2 rounded-md text-white w-24 text-center'\n                }>\n                  {order.paid ? 'Paid' : 'Not paid'}\n                </div>\n              </div>\n              <div className=\"grow\">\n                <div className=\"flex gap-2 items-center mb-1\">\n                  <div className=\"grow\">{order.userEmail}</div>\n                  <div className=\"text-gray-500 text-sm\">{dbTimeForHuman(order.createdAt)}</div>\n                </div>\n                <div className=\"text-gray-500 text-xs\">\n                  {order.cartProducts.map(p => p.name).join(', ')}\n                </div>\n              </div>\n            </div>\n            <div className=\"justify-end flex gap-2 items-center whitespace-nowrap\">\n              <Link href={\"/orders/\"+order._id} className=\"button\">\n                Show order\n              </Link>\n            </div>\n          </div>\n        ))}\n      </div>\n    </section>\n  );\n}"
  },
  {
    "path": "src/app/page.js",
    "content": "import Header from \"@/components/layout/Header\";\nimport Hero from \"@/components/layout/Hero\";\nimport HomeMenu from \"@/components/layout/HomeMenu\";\nimport SectionHeaders from \"@/components/layout/SectionHeaders\";\n\nexport default function Home() {\n  return (\n    <>\n      <Hero />\n      <HomeMenu />\n      <section className=\"text-center my-16\" id=\"about\">\n        <SectionHeaders\n          subHeader={'Our story'}\n          mainHeader={'About us'}\n        />\n        <div className=\"text-gray-500 max-w-md mx-auto mt-4 flex flex-col gap-4\">\n          <p>\n            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Magni minima odit recusandae. Illum ipsa non repudiandae? Eum ipsam iste quos suscipit tempora? Aperiam esse fugiat inventore laboriosam officiis quam rem!\n          </p>\n          <p>At consectetur delectus ducimus est facere iure molestias obcaecati quaerat vitae voluptate? Aspernatur dolor explicabo iste minus molestiae pariatur provident quibusdam saepe?</p>\n          <p>Laborum molestias neque nulla obcaecati odio quia quod reprehenderit sit vitae voluptates? Eos, tenetur.</p>\n        </div>\n      </section>\n      <section className=\"text-center my-8\" id=\"contact\">\n        <SectionHeaders\n          subHeader={'Don\\'t hesitate'}\n          mainHeader={'Contact us'}\n        />\n        <div className=\"mt-8\">\n          <a className=\"text-4xl underline text-gray-500\" href=\"tel:+46738123123\">\n            +46 738 123 123\n          </a>\n        </div>\n      </section>\n    </>\n  )\n}\n"
  },
  {
    "path": "src/app/profile/page.js",
    "content": "'use client';\nimport EditableImage from \"@/components/layout/EditableImage\";\nimport InfoBox from \"@/components/layout/InfoBox\";\nimport SuccessBox from \"@/components/layout/SuccessBox\";\nimport UserForm from \"@/components/layout/UserForm\";\nimport UserTabs from \"@/components/layout/UserTabs\";\nimport {useSession} from \"next-auth/react\";\nimport Image from \"next/image\";\nimport Link from \"next/link\";\nimport {redirect} from \"next/navigation\";\nimport {useEffect, useState} from \"react\";\nimport toast from \"react-hot-toast\";\n\nexport default function ProfilePage() {\n  const session = useSession();\n\n  const [user, setUser] = useState(null);\n  const [isAdmin, setIsAdmin] = useState(false);\n  const [profileFetched, setProfileFetched] = useState(false);\n  const {status} = session;\n\n  useEffect(() => {\n    if (status === 'authenticated') {\n      fetch('/api/profile').then(response => {\n        response.json().then(data => {\n          setUser(data);\n          setIsAdmin(data.admin);\n          setProfileFetched(true);\n        })\n      });\n    }\n  }, [session, status]);\n\n  async function handleProfileInfoUpdate(ev, data) {\n    ev.preventDefault();\n\n    const savingPromise = new Promise(async (resolve, reject) => {\n      const response = await fetch('/api/profile', {\n        method: 'PUT',\n        headers: {'Content-Type': 'application/json'},\n        body: JSON.stringify(data),\n      });\n      if (response.ok)\n        resolve()\n      else\n        reject();\n    });\n\n    await toast.promise(savingPromise, {\n      loading: 'Saving...',\n      success: 'Profile saved!',\n      error: 'Error',\n    });\n\n  }\n\n  if (status === 'loading' || !profileFetched) {\n    return 'Loading...';\n  }\n\n  if (status === 'unauthenticated') {\n    return redirect('/login');\n  }\n\n  return (\n    <section className=\"mt-8\">\n      <UserTabs isAdmin={isAdmin} />\n      <div className=\"max-w-2xl mx-auto mt-8\">\n        <UserForm user={user} onSave={handleProfileInfoUpdate} />\n      </div>\n    </section>\n  );\n}"
  },
  {
    "path": "src/app/register/page.js",
    "content": "\"use client\";\nimport {signIn} from \"next-auth/react\";\nimport Image from \"next/image\";\nimport Link from \"next/link\";\nimport {useState} from \"react\";\n\nexport default function RegisterPage() {\n  const [email, setEmail] = useState('');\n  const [password, setPassword] = useState('');\n  const [creatingUser, setCreatingUser] = useState(false);\n  const [userCreated, setUserCreated] = useState(false);\n  const [error, setError] = useState(false);\n  async function handleFormSubmit(ev) {\n    ev.preventDefault();\n    setCreatingUser(true);\n    setError(false);\n    setUserCreated(false);\n    const response = await fetch('/api/register', {\n      method: 'POST',\n      body: JSON.stringify({email, password}),\n      headers: {'Content-Type': 'application/json'},\n    });\n    if (response.ok) {\n      setUserCreated(true);\n    }\n    else {\n      setError(true);\n    }\n    setCreatingUser(false);\n  }\n  return (\n    <section className=\"mt-8\">\n      <h1 className=\"text-center text-primary text-4xl mb-4\">\n        Register\n      </h1>\n      {userCreated && (\n        <div className=\"my-4 text-center\">\n          User created.<br />\n          Now you can{' '}\n          <Link className=\"underline\" href={'/login'}>Login &raquo;</Link>\n        </div>\n      )}\n      {error && (\n        <div className=\"my-4 text-center\">\n          An error has occurred.<br />\n          Please try again later\n        </div>\n      )}\n      <form className=\"block max-w-xs mx-auto\" onSubmit={handleFormSubmit}>\n        <input type=\"email\" placeholder=\"email\" value={email}\n               disabled={creatingUser}\n               onChange={ev => setEmail(ev.target.value)} />\n        <input type=\"password\" placeholder=\"password\" value={password}\n               disabled={creatingUser}\n                onChange={ev => setPassword(ev.target.value)}/>\n        <button type=\"submit\" disabled={creatingUser}>\n          Register\n        </button>\n        <div className=\"my-4 text-center text-gray-500\">\n          or login with provider\n        </div>\n        <button\n          onClick={() => signIn('google', {callbackUrl:'/'})}\n          className=\"flex gap-4 justify-center\">\n          <Image src={'/google.png'} alt={''} width={24} height={24} />\n          Login with google\n        </button>\n        <div className=\"text-center my-4 text-gray-500 border-t pt-4\">\n          Existing account?{' '}\n          <Link className=\"underline\" href={'/login'}>Login here &raquo;</Link>\n        </div>\n      </form>\n    </section>\n  );\n}"
  },
  {
    "path": "src/app/users/[id]/page.js",
    "content": "'use client';\nimport UserForm from \"@/components/layout/UserForm\";\nimport UserTabs from \"@/components/layout/UserTabs\";\nimport {useProfile} from \"@/components/UseProfile\";\nimport {useParams} from \"next/navigation\";\nimport {useEffect, useState} from \"react\";\nimport toast from \"react-hot-toast\";\n\nexport default function EditUserPage() {\n  const {loading, data} = useProfile();\n  const [user, setUser] = useState(null);\n  const {id} = useParams();\n\n  useEffect(() => {\n    fetch('/api/profile?_id='+id).then(res => {\n      res.json().then(user => {\n        setUser(user);\n      });\n    })\n  }, []);\n\n  async function handleSaveButtonClick(ev, data) {\n    ev.preventDefault();\n    const promise = new Promise(async (resolve, reject) => {\n      const res = await fetch('/api/profile', {\n        method: 'PUT',\n        headers: {'Content-Type': 'application/json'},\n        body: JSON.stringify({...data,_id:id}),\n      });\n      if (res.ok)\n        resolve();\n      else\n        reject();\n    });\n\n    await toast.promise(promise, {\n      loading: 'Saving user...',\n      success: 'User saved',\n      error: 'An error has occurred while saving the user',\n    });\n  }\n\n  if (loading) {\n    return 'Loading user profile...';\n  }\n\n  if (!data.admin) {\n    return 'Not an admin';\n  }\n\n  return (\n    <section className=\"mt-8 mx-auto max-w-2xl\">\n      <UserTabs isAdmin={true} />\n      <div className=\"mt-8\">\n        <UserForm user={user} onSave={handleSaveButtonClick} />\n      </div>\n    </section>\n  );\n}"
  },
  {
    "path": "src/app/users/page.js",
    "content": "'use client';\nimport UserTabs from \"@/components/layout/UserTabs\";\nimport {useProfile} from \"@/components/UseProfile\";\nimport Link from \"next/link\";\nimport {useEffect, useState} from \"react\";\n\nexport default function UsersPage() {\n\n  const [users, setUsers] = useState([]);\n  const {loading,data} = useProfile();\n\n  useEffect(() => {\n    fetch('/api/users').then(response => {\n      response.json().then(users => {\n        setUsers(users);\n      });\n    })\n  }, []);\n\n  if (loading) {\n    return 'Loading user info...';\n  }\n\n  if (!data.admin) {\n    return 'Not an admin';\n  }\n\n  return (\n    <section className=\"max-w-2xl mx-auto mt-8\">\n      <UserTabs isAdmin={true} />\n      <div className=\"mt-8\">\n        {users?.length > 0 && users.map(user => (\n          <div\n            key={user._id}\n            className=\"bg-gray-100 rounded-lg mb-2 p-1 px-4 flex items-center gap-4\">\n            <div className=\"grid grid-cols-2 md:grid-cols-3 gap-4 grow\">\n              <div className=\"text-gray-900\">\n                {!!user.name && (<span>{user.name}</span>)}\n                {!user.name && (<span className=\"italic\">No name</span>)}\n              </div>\n              <span className=\"text-gray-500\">{user.email}</span>\n            </div>\n            <div>\n              <Link className=\"button\" href={'/users/'+user._id}>\n                Edit\n              </Link>\n            </div>\n          </div>\n        ))}\n      </div>\n    </section>\n  );\n}"
  },
  {
    "path": "src/components/AppContext.js",
    "content": "'use client';\nimport {SessionProvider} from \"next-auth/react\";\nimport {createContext, useEffect, useState} from \"react\";\nimport toast from \"react-hot-toast\";\n\nexport const CartContext = createContext({});\n\nexport function cartProductPrice(cartProduct) {\n  let price = cartProduct.basePrice;\n  if (cartProduct.size) {\n    price += cartProduct.size.price;\n  }\n  if (cartProduct.extras?.length > 0) {\n    for (const extra of cartProduct.extras) {\n      price += extra.price;\n    }\n  }\n  return price;\n}\n\nexport function AppProvider({children}) {\n  const [cartProducts,setCartProducts] = useState([]);\n\n  const ls = typeof window !== 'undefined' ? window.localStorage : null;\n\n  useEffect(() => {\n    if (ls && ls.getItem('cart')) {\n      setCartProducts( JSON.parse( ls.getItem('cart') ) );\n    }\n  }, []);\n\n  function clearCart() {\n    setCartProducts([]);\n    saveCartProductsToLocalStorage([]);\n  }\n\n  function removeCartProduct(indexToRemove) {\n    setCartProducts(prevCartProducts => {\n      const newCartProducts = prevCartProducts\n        .filter((v,index) => index !== indexToRemove);\n      saveCartProductsToLocalStorage(newCartProducts);\n      return newCartProducts;\n    });\n    toast.success('Product removed');\n  }\n\n  function saveCartProductsToLocalStorage(cartProducts) {\n    if (ls) {\n      ls.setItem('cart', JSON.stringify(cartProducts));\n    }\n  }\n\n  function addToCart(product, size=null, extras=[]) {\n    setCartProducts(prevProducts => {\n      const cartProduct = {...product, size, extras};\n      const newProducts = [...prevProducts, cartProduct];\n      saveCartProductsToLocalStorage(newProducts);\n      return newProducts;\n    });\n  }\n\n  return (\n    <SessionProvider>\n      <CartContext.Provider value={{\n        cartProducts, setCartProducts,\n        addToCart, removeCartProduct, clearCart,\n      }}>\n        {children}\n      </CartContext.Provider>\n    </SessionProvider>\n  );\n}"
  },
  {
    "path": "src/components/DeleteButton.js",
    "content": "import {useState} from \"react\";\n\nexport default function DeleteButton({label,onDelete}) {\n  const [showConfirm, setShowConfirm] = useState(false);\n\n  if (showConfirm) {\n    return (\n      <div className=\"fixed bg-black/80 inset-0 flex items-center h-full justify-center\">\n        <div className=\"bg-white p-4 rounded-lg\">\n          <div>Are you sure you want to delete?</div>\n          <div className=\"flex gap-2 mt-1\">\n            <button type=\"button\" onClick={() => setShowConfirm(false)}>\n              Cancel\n            </button>\n            <button\n              onClick={() => {\n                onDelete();\n                setShowConfirm(false);\n              }}\n              type=\"button\"\n              className=\"primary\">\n              Yes,&nbsp;delete!\n            </button>\n          </div>\n        </div>\n      </div>\n    );\n  }\n\n  return (\n    <button type=\"button\" onClick={() => setShowConfirm(true)}>\n      {label}\n    </button>\n  );\n}"
  },
  {
    "path": "src/components/UseProfile.js",
    "content": "import {useEffect, useState} from \"react\";\n\nexport function useProfile() {\n  const [data, setData] = useState(false);\n  const [loading, setLoading] = useState(true);\n  useEffect(() => {\n    setLoading(true);\n    fetch('/api/profile').then(response => {\n      response.json().then(data => {\n        setData(data);\n        setLoading(false);\n      });\n    })\n  }, []);\n\n  return {loading, data};\n}"
  },
  {
    "path": "src/components/icons/Bars2.js",
    "content": "export default function Bars2({className=\"w-6 h-6\"}) {\n  return (\n    <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" strokeWidth={1.5} stroke=\"currentColor\" className={className}>\n      <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M3.75 9h16.5m-16.5 6.75h16.5\" />\n    </svg>\n  );\n}"
  },
  {
    "path": "src/components/icons/ChevronDown.js",
    "content": "export default function ChevronDown({className=\"w-6 h-6\"}) {\n  return (\n    <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" strokeWidth={1.5} stroke=\"currentColor\" className={className}>\n      <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M19.5 8.25l-7.5 7.5-7.5-7.5\" />\n    </svg>\n\n  );\n}"
  },
  {
    "path": "src/components/icons/ChevronUp.js",
    "content": "export default function ChevronUp({className=\"w-6 h-6\"}) {\n  return (\n    <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" strokeWidth={1.5} stroke=\"currentColor\" className={className}>\n      <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M4.5 15.75l7.5-7.5 7.5 7.5\" />\n    </svg>\n  );\n}"
  },
  {
    "path": "src/components/icons/Left.js",
    "content": "export default function Left({className=\"w-6 h-6\"}) {\n  return (\n    <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" strokeWidth={1.5} stroke=\"currentColor\" className={className}>\n      <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M11.25 9l-3 3m0 0l3 3m-3-3h7.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z\" />\n    </svg>\n\n  );\n}"
  },
  {
    "path": "src/components/icons/Plus.js",
    "content": "export default function Plus({className=\"w-6 h-6\"}) {\n  return (\n    <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" strokeWidth={1.5} stroke=\"currentColor\" className={className}>\n      <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M12 4.5v15m7.5-7.5h-15\" />\n    </svg>\n  );\n}"
  },
  {
    "path": "src/components/icons/Right.js",
    "content": "export default function Right({className=\"w-6 h-6\"}) {\n  return (\n    <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" strokeWidth={1.5} stroke=\"currentColor\" className={className}>\n      <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M12.75 15l3-3m0 0l-3-3m3 3h-7.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z\" />\n    </svg>\n  );\n}"
  },
  {
    "path": "src/components/icons/ShoppingCart.js",
    "content": "export default function ShoppingCart({className = \"w-6 h-6\"}) {\n  return (\n    <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" strokeWidth={1.5} stroke=\"currentColor\" className={className}>\n      <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M2.25 3h1.386c.51 0 .955.343 1.087.835l.383 1.437M7.5 14.25a3 3 0 00-3 3h15.75m-12.75-3h11.218c1.121-2.3 2.1-4.684 2.924-7.138a60.114 60.114 0 00-16.536-1.84M7.5 14.25L5.106 5.272M6 20.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm12.75 0a.75.75 0 11-1.5 0 .75.75 0 011.5 0z\" />\n    </svg>\n  );\n}"
  },
  {
    "path": "src/components/icons/Trash.js",
    "content": "export default function Trash({className=\"w-6 h-6\"}) {\n  return (\n    <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" strokeWidth={1.5} stroke=\"currentColor\" className={className}>\n      <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0\" />\n    </svg>\n\n  );\n}"
  },
  {
    "path": "src/components/layout/AddressInputs.js",
    "content": "export default function AddressInputs({addressProps,setAddressProp,disabled=false}) {\n  const {phone, streetAddress, postalCode, city, country} = addressProps;\n  return (\n    <>\n      <label>Phone</label>\n      <input\n        disabled={disabled}\n        type=\"tel\" placeholder=\"Phone number\"\n        value={phone || ''} onChange={ev => setAddressProp('phone', ev.target.value)} />\n      <label>Street address</label>\n      <input\n        disabled={disabled}\n        type=\"text\" placeholder=\"Street address\"\n        value={streetAddress || ''} onChange={ev => setAddressProp('streetAddress', ev.target.value)}\n      />\n      <div className=\"grid grid-cols-2 gap-2\">\n        <div>\n          <label>Postal code</label>\n          <input\n            disabled={disabled}\n            type=\"text\" placeholder=\"Postal code\"\n            value={postalCode || ''} onChange={ev => setAddressProp('postalCode', ev.target.value)}\n          />\n        </div>\n        <div>\n          <label>City</label>\n          <input\n            disabled={disabled}\n            type=\"text\" placeholder=\"City\"\n            value={city || ''} onChange={ev => setAddressProp('city', ev.target.value)}\n          />\n        </div>\n      </div>\n      <label>Country</label>\n      <input\n        disabled={disabled}\n        type=\"text\" placeholder=\"Country\"\n        value={country || ''} onChange={ev => setAddressProp('country', ev.target.value)}\n      />\n    </>\n  );\n}"
  },
  {
    "path": "src/components/layout/EditableImage.js",
    "content": "import Image from \"next/image\";\nimport toast from \"react-hot-toast\";\n\nexport default function EditableImage({link, setLink}) {\n\n  async function handleFileChange(ev) {\n    const files = ev.target.files;\n    if (files?.length === 1) {\n      const data = new FormData;\n      data.set('file', files[0]);\n\n      const uploadPromise = fetch('/api/upload', {\n        method: 'POST',\n        body: data,\n      }).then(response => {\n        if (response.ok) {\n          return response.json().then(link => {\n            setLink(link);\n          })\n        }\n        throw new Error('Something went wrong');\n      });\n\n      await toast.promise(uploadPromise, {\n        loading: 'Uploading...',\n        success: 'Upload complete',\n        error: 'Upload error',\n      });\n    }\n  }\n\n  return (\n    <>\n      {link && (\n        <Image className=\"rounded-lg w-full h-full mb-1\" src={link} width={250} height={250} alt={'avatar'} />\n      )}\n      {!link && (\n        <div className=\"text-center bg-gray-200 p-4 text-gray-500 rounded-lg mb-1\">\n          No image\n        </div>\n      )}\n      <label>\n        <input type=\"file\" className=\"hidden\" onChange={handleFileChange} />\n        <span className=\"block border border-gray-300 rounded-lg p-2 text-center cursor-pointer\">Change image</span>\n      </label>\n    </>\n  );\n}"
  },
  {
    "path": "src/components/layout/Header.js",
    "content": "'use client';\nimport {CartContext} from \"@/components/AppContext\";\nimport Bars2 from \"@/components/icons/Bars2\";\nimport ShoppingCart from \"@/components/icons/ShoppingCart\";\nimport {signOut, useSession} from \"next-auth/react\";\nimport Link from \"next/link\";\nimport {useContext, useState} from \"react\";\n\nfunction AuthLinks({status, userName}) {\n  if (status === 'authenticated') {\n    return (\n      <>\n        <Link href={'/profile'} className=\"whitespace-nowrap\">\n          Hello, {userName}\n        </Link>\n        <button\n          onClick={() => signOut()}\n          className=\"bg-primary rounded-full text-white px-8 py-2\">\n          Logout\n        </button>\n      </>\n    );\n  }\n  if (status === 'unauthenticated') {\n    return (\n      <>\n        <Link href={'/login'}>Login</Link>\n        <Link href={'/register'} className=\"bg-primary rounded-full text-white px-8 py-2\">\n          Register\n        </Link>\n      </>\n    );\n  }\n}\n\nexport default function Header() {\n  const session = useSession();\n  const status = session?.status;\n  const userData = session.data?.user;\n  let userName = userData?.name || userData?.email;\n  const {cartProducts} = useContext(CartContext);\n  const [mobileNavOpen, setMobileNavOpen] = useState(false);\n  if (userName && userName.includes(' ')) {\n    userName = userName.split(' ')[0];\n  }\n  return (\n    <header>\n      <div className=\"flex items-center md:hidden justify-between\">\n        <Link className=\"text-primary font-semibold text-2xl\" href={'/'}>\n          ST PIZZA\n        </Link>\n        <div className=\"flex gap-8 items-center\">\n          <Link href={'/cart'} className=\"relative\">\n            <ShoppingCart />\n            {cartProducts?.length > 0 && (\n              <span className=\"absolute -top-2 -right-4 bg-primary text-white text-xs py-1 px-1 rounded-full leading-3\">\n            {cartProducts.length}\n          </span>\n            )}\n          </Link>\n          <button\n            className=\"p-1 border\"\n            onClick={() => setMobileNavOpen(prev => !prev)}>\n            <Bars2 />\n          </button>\n        </div>\n      </div>\n      {mobileNavOpen && (\n        <div\n          onClick={() => setMobileNavOpen(false)}\n          className=\"md:hidden p-4 bg-gray-200 rounded-lg mt-2 flex flex-col gap-2 text-center\">\n          <Link href={'/'}>Home</Link>\n          <Link href={'/menu'}>Menu</Link>\n          <Link href={'/#about'}>About</Link>\n          <Link href={'/#contact'}>Contact</Link>\n          <AuthLinks status={status} userName={userName} />\n        </div>\n      )}\n      <div className=\"hidden md:flex items-center justify-between\">\n        <nav className=\"flex items-center gap-8 text-gray-500 font-semibold\">\n          <Link className=\"text-primary font-semibold text-2xl\" href={'/'}>\n            ST PIZZA\n          </Link>\n          <Link href={'/'}>Home</Link>\n          <Link href={'/menu'}>Menu</Link>\n          <Link href={'/#about'}>About</Link>\n          <Link href={'/#contact'}>Contact</Link>\n        </nav>\n        <nav className=\"flex items-center gap-4 text-gray-500 font-semibold\">\n          <AuthLinks status={status} userName={userName} />\n          <Link href={'/cart'} className=\"relative\">\n            <ShoppingCart />\n            {cartProducts?.length > 0 && (\n              <span className=\"absolute -top-2 -right-4 bg-primary text-white text-xs py-1 px-1 rounded-full leading-3\">\n            {cartProducts.length}\n          </span>\n            )}\n          </Link>\n        </nav>\n      </div>\n    </header>\n  );\n}"
  },
  {
    "path": "src/components/layout/Hero.js",
    "content": "import Right from \"@/components/icons/Right\";\nimport Image from \"next/image\";\n\nexport default function Hero() {\n  return (\n    <section className=\"hero md:mt-4\">\n      <div className=\"py-8 md:py-12\">\n        <h1 className=\"text-4xl font-semibold\">\n          Everything<br />\n          is better<br />\n          with a&nbsp;\n          <span className=\"text-primary\">\n            Pizza\n          </span>\n        </h1>\n        <p className=\"my-6 text-gray-500 text-sm\">\n          Pizza is the missing piece that makes every day complete, a simple yet delicious joy in life\n        </p>\n        <div className=\"flex gap-4 text-sm\">\n          <button className=\"flex justify-center bg-primary uppercase flex items-center gap-2 text-white px-4 py-2 rounded-full\">\n            Order now\n            <Right />\n          </button>\n          <button className=\"flex items-center border-0 gap-2 py-2 text-gray-600 font-semibold\">\n            Learn more\n            <Right />\n          </button>\n        </div>\n      </div>\n      <div className=\"relative hidden md:block\">\n        <Image src={'/pizza.png'} layout={'fill'} objectFit={'contain'} alt={'pizza'} />\n      </div>\n    </section>\n  );\n}"
  },
  {
    "path": "src/components/layout/HomeMenu.js",
    "content": "'use client';\nimport SectionHeaders from \"@/components/layout/SectionHeaders\";\nimport MenuItem from \"@/components/menu/MenuItem\";\nimport Image from \"next/image\";\nimport {useEffect, useState} from \"react\";\n\nexport default function HomeMenu() {\n  const [bestSellers, setBestSellers] = useState([]);\n  useEffect(() => {\n    fetch('/api/menu-items').then(res => {\n      res.json().then(menuItems => {\n        setBestSellers(menuItems.slice(-3));\n      });\n    });\n  }, []);\n  return (\n    <section className=\"\">\n      <div className=\"absolute left-0 right-0 w-full justify-start\">\n        <div className=\"absolute left-0 -top-[70px] text-left -z-10\">\n          <Image src={'/sallad1.png'} width={109} height={189}  alt={'sallad'} />\n        </div>\n        <div className=\"absolute -top-[100px] right-0 -z-10\">\n          <Image src={'/sallad2.png'} width={107} height={195} alt={'sallad'} />\n        </div>\n      </div>\n      <div className=\"text-center mb-4\">\n        <SectionHeaders\n          subHeader={'check out'}\n          mainHeader={'Our Best Sellers'} />\n      </div>\n      <div className=\"grid sm:grid-cols-3 gap-4\">\n        {bestSellers?.length > 0 && bestSellers.map(item => (\n          <MenuItem key={item._id} {...item} />\n        ))}\n      </div>\n    </section>\n  );\n}"
  },
  {
    "path": "src/components/layout/InfoBox.js",
    "content": "export default function InfoBox({children}) {\n  return (\n    <div className=\"text-center bg-blue-100 p-4 rounded-lg border border-blue-300\">\n      {children}\n    </div>\n  );\n}"
  },
  {
    "path": "src/components/layout/MenuItemForm.js",
    "content": "import Plus from \"@/components/icons/Plus\";\nimport Trash from \"@/components/icons/Trash\";\nimport EditableImage from \"@/components/layout/EditableImage\";\nimport MenuItemPriceProps from \"@/components/layout/MenuItemPriceProps\";\nimport {useEffect, useState} from \"react\";\n\nexport default function MenuItemForm({onSubmit,menuItem}) {\n  const [image, setImage] = useState(menuItem?.image || '');\n  const [name, setName] = useState(menuItem?.name || '');\n  const [description, setDescription] = useState(menuItem?.description || '');\n  const [basePrice, setBasePrice] = useState(menuItem?.basePrice || '');\n  const [sizes, setSizes] = useState(menuItem?.sizes || []);\n  const [category, setCategory] = useState(menuItem?.category || '');\n  const [categories, setCategories] = useState([]);\n  const [\n    extraIngredientPrices,\n    setExtraIngredientPrices,\n  ] = useState(menuItem?.extraIngredientPrices || []);\n\n  useEffect(() => {\n    fetch('/api/categories').then(res => {\n      res.json().then(categories => {\n        setCategories(categories);\n      });\n    });\n  }, []);\n\n  return (\n    <form\n      onSubmit={ev =>\n        onSubmit(ev, {\n          image,name,description,basePrice,sizes,extraIngredientPrices,category,\n        })\n      }\n      className=\"mt-8 max-w-2xl mx-auto\">\n      <div\n        className=\"md:grid items-start gap-4\"\n        style={{gridTemplateColumns:'.3fr .7fr'}}>\n        <div>\n          <EditableImage link={image} setLink={setImage} />\n        </div>\n        <div className=\"grow\">\n          <label>Item name</label>\n          <input\n            type=\"text\"\n            value={name}\n            onChange={ev => setName(ev.target.value)}\n          />\n          <label>Description</label>\n          <input\n            type=\"text\"\n            value={description}\n            onChange={ev => setDescription(ev.target.value)}\n          />\n          <label>Category</label>\n          <select value={category} onChange={ev => setCategory(ev.target.value)}>\n            {categories?.length > 0 && categories.map(c => (\n              <option key={c._id} value={c._id}>{c.name}</option>\n            ))}\n          </select>\n          <label>Base price</label>\n          <input\n            type=\"text\"\n            value={basePrice}\n            onChange={ev => setBasePrice(ev.target.value)}\n          />\n          <MenuItemPriceProps name={'Sizes'}\n                              addLabel={'Add item size'}\n                              props={sizes}\n                              setProps={setSizes} />\n          <MenuItemPriceProps name={'Extra ingredients'}\n                              addLabel={'Add ingredients prices'}\n                              props={extraIngredientPrices}\n                              setProps={setExtraIngredientPrices}/>\n          <button type=\"submit\">Save</button>\n        </div>\n      </div>\n    </form>\n  );\n}"
  },
  {
    "path": "src/components/layout/MenuItemPriceProps.js",
    "content": "import ChevronDown from \"@/components/icons/ChevronDown\";\nimport ChevronUp from \"@/components/icons/ChevronUp\";\nimport Plus from \"@/components/icons/Plus\";\nimport Trash from \"@/components/icons/Trash\";\nimport {useState} from \"react\";\n\nexport default function MenuItemPriceProps({name,addLabel,props,setProps}) {\n\n  const [isOpen, setIsOpen] = useState(false);\n\n  function addProp() {\n    setProps(oldProps => {\n      return [...oldProps, {name:'', price:0}];\n    });\n  }\n\n  function editProp(ev, index, prop) {\n    const newValue = ev.target.value;\n    setProps(prevSizes => {\n      const newSizes = [...prevSizes];\n      newSizes[index][prop] = newValue;\n      return newSizes;\n    });\n  }\n\n  function removeProp(indexToRemove) {\n    setProps(prev => prev.filter((v,index) => index !== indexToRemove));\n  }\n\n  return (\n    <div className=\"bg-gray-200 p-2 rounded-md mb-2\">\n      <button\n        onClick={() => setIsOpen(prev => !prev)}\n        className=\"inline-flex p-1 border-0 justify-start\"\n        type=\"button\">\n        {isOpen && (\n          <ChevronUp />\n        )}\n        {!isOpen && (\n          <ChevronDown />\n        )}\n        <span>{name}</span>\n        <span>({props?.length})</span>\n      </button>\n      <div className={isOpen ? 'block' : 'hidden'}>\n        {props?.length > 0 && props.map((size,index) => (\n          <div key={index} className=\"flex items-end gap-2\">\n            <div>\n              <label>Name</label>\n              <input type=\"text\"\n                     placeholder=\"Size name\"\n                     value={size.name}\n                     onChange={ev => editProp(ev, index, 'name')}\n              />\n            </div>\n            <div>\n              <label>Extra price</label>\n              <input type=\"text\" placeholder=\"Extra price\"\n                     value={size.price}\n                     onChange={ev => editProp(ev, index, 'price')}\n              />\n            </div>\n            <div>\n              <button type=\"button\"\n                      onClick={() => removeProp(index)}\n                      className=\"bg-white mb-2 px-2\">\n                <Trash />\n              </button>\n            </div>\n          </div>\n        ))}\n        <button\n          type=\"button\"\n          onClick={addProp}\n          className=\"bg-white items-center\">\n          <Plus className=\"w-4 h-4\" />\n          <span>{addLabel}</span>\n        </button>\n      </div>\n    </div>\n  );\n}"
  },
  {
    "path": "src/components/layout/SectionHeaders.js",
    "content": "export default function SectionHeaders({subHeader,mainHeader}) {\n  return (\n    <>\n      <h3 className=\"uppercase text-gray-500 font-semibold leading-4\">\n        {subHeader}\n      </h3>\n      <h2 className=\"text-primary font-bold text-4xl italic\">\n        {mainHeader}\n      </h2>\n    </>\n  );\n}"
  },
  {
    "path": "src/components/layout/SuccessBox.js",
    "content": "export default function SuccessBox({children}) {\n  return (\n    <div className=\"text-center bg-green-100 p-4 rounded-lg border border-green-300\">\n      {children}\n    </div>\n  );\n}"
  },
  {
    "path": "src/components/layout/UserForm.js",
    "content": "'use client';\nimport AddressInputs from \"@/components/layout/AddressInputs\";\nimport EditableImage from \"@/components/layout/EditableImage\";\nimport {useProfile} from \"@/components/UseProfile\";\nimport {useState} from \"react\";\n\nexport default function UserForm({user,onSave}) {\n  const [userName, setUserName] = useState(user?.name || '');\n  const [image, setImage] = useState(user?.image || '');\n  const [phone, setPhone] = useState(user?.phone || '');\n  const [streetAddress, setStreetAddress] = useState(user?.streetAddress || '');\n  const [postalCode, setPostalCode] = useState(user?.postalCode || '');\n  const [city, setCity] = useState(user?.city || '');\n  const [country, setCountry] = useState(user?.country || '');\n  const [admin, setAdmin] = useState(user?.admin || false);\n  const {data:loggedInUserData} = useProfile();\n\n  function handleAddressChange(propName, value) {\n    if (propName === 'phone') setPhone(value);\n    if (propName === 'streetAddress') setStreetAddress(value);\n    if (propName === 'postalCode') setPostalCode(value);\n    if (propName === 'city') setCity(value);\n    if (propName === 'country') setCountry(value);\n  }\n\n  return (\n    <div className=\"md:flex gap-4\">\n      <div>\n        <div className=\"p-2 rounded-lg relative max-w-[120px]\">\n          <EditableImage link={image} setLink={setImage} />\n        </div>\n      </div>\n      <form\n        className=\"grow\"\n        onSubmit={ev =>\n          onSave(ev, {\n            name:userName, image, phone, admin,\n            streetAddress, city, country, postalCode,\n          })\n        }\n      >\n        <label>\n          First and last name\n        </label>\n        <input\n          type=\"text\" placeholder=\"First and last name\"\n          value={userName} onChange={ev => setUserName(ev.target.value)}\n        />\n        <label>Email</label>\n        <input\n          type=\"email\"\n          disabled={true}\n          value={user.email}\n          placeholder={'email'}\n        />\n        <AddressInputs\n          addressProps={{phone, streetAddress, postalCode, city, country}}\n          setAddressProp={handleAddressChange}\n        />\n        {loggedInUserData.admin && (\n          <div>\n            <label className=\"p-2 inline-flex items-center gap-2 mb-2\" htmlFor=\"adminCb\">\n              <input\n                id=\"adminCb\" type=\"checkbox\" className=\"\" value={'1'}\n                checked={admin}\n                onChange={ev => setAdmin(ev.target.checked)}\n              />\n              <span>Admin</span>\n            </label>\n          </div>\n        )}\n        <button type=\"submit\">Save</button>\n      </form>\n    </div>\n  );\n}"
  },
  {
    "path": "src/components/layout/UserTabs.js",
    "content": "'use client';\nimport Link from \"next/link\";\nimport {usePathname} from \"next/navigation\";\n\nexport default function UserTabs({isAdmin}) {\n  const path = usePathname();\n  return (\n    <div className=\"flex mx-auto gap-2 tabs justify-center flex-wrap\">\n      <Link\n        className={path === '/profile' ? 'active' : ''}\n        href={'/profile'}\n      >\n        Profile\n      </Link>\n      {isAdmin && (\n        <>\n          <Link\n            href={'/categories'}\n            className={path === '/categories' ? 'active' : ''}\n          >\n            Categories\n          </Link>\n          <Link\n            href={'/menu-items'}\n            className={path.includes('menu-items') ? 'active' : ''}\n          >\n            Menu Items\n          </Link>\n          <Link\n            className={path.includes('/users') ? 'active' : ''}\n            href={'/users'}\n          >\n            Users\n          </Link>\n        </>\n      )}\n      <Link\n        className={path === '/orders' ? 'active' : ''}\n        href={'/orders'}\n      >\n        Orders\n      </Link>\n    </div>\n  );\n}"
  },
  {
    "path": "src/components/menu/AddToCartButton.js",
    "content": "import FlyingButton from 'react-flying-item'\n\nexport default function AddToCartButton({\n  hasSizesOrExtras, onClick, basePrice, image\n}) {\n  if (!hasSizesOrExtras) {\n    return (\n      <div className=\"flying-button-parent mt-4\">\n        <FlyingButton\n          targetTop={'5%'}\n          targetLeft={'95%'}\n          src={image}>\n          <div onClick={onClick}>\n            Add to cart ${basePrice}\n          </div>\n        </FlyingButton>\n      </div>\n    );\n  }\n  return (\n    <button\n      type=\"button\"\n      onClick={onClick}\n      className=\"mt-4 bg-primary text-white rounded-full px-8 py-2\"\n    >\n      <span>Add to cart (from ${basePrice})</span>\n    </button>\n  );\n}"
  },
  {
    "path": "src/components/menu/CartProduct.js",
    "content": "import {cartProductPrice} from \"@/components/AppContext\";\nimport Trash from \"@/components/icons/Trash\";\nimport Image from \"next/image\";\n\nexport default function CartProduct({product,onRemove}) {\n  return (\n    <div className=\"flex items-center gap-4 border-b py-4\">\n      <div className=\"w-24\">\n        <Image width={240} height={240} src={product.image} alt={''} />\n      </div>\n      <div className=\"grow\">\n        <h3 className=\"font-semibold\">\n          {product.name}\n        </h3>\n        {product.size && (\n          <div className=\"text-sm\">\n            Size: <span>{product.size.name}</span>\n          </div>\n        )}\n        {product.extras?.length > 0 && (\n          <div className=\"text-sm text-gray-500\">\n            {product.extras.map(extra => (\n              <div key={extra.name}>{extra.name} ${extra.price}</div>\n            ))}\n          </div>\n        )}\n      </div>\n      <div className=\"text-lg font-semibold\">\n        ${cartProductPrice(product)}\n      </div>\n      {!!onRemove && (\n        <div className=\"ml-2\">\n          <button\n            type=\"button\"\n            onClick={() => onRemove(index)}\n            className=\"p-2\">\n            <Trash />\n          </button>\n        </div>\n      )}\n    </div>\n  );\n}"
  },
  {
    "path": "src/components/menu/MenuItem.js",
    "content": "import {CartContext} from \"@/components/AppContext\";\nimport MenuItemTile from \"@/components/menu/MenuItemTile\";\nimport Image from \"next/image\";\nimport {useContext, useState} from \"react\";\nimport FlyingButton from \"react-flying-item\";\nimport toast from \"react-hot-toast\";\n\nexport default function MenuItem(menuItem) {\n  const {\n    image,name,description,basePrice,\n    sizes, extraIngredientPrices,\n  } = menuItem;\n  const [\n    selectedSize, setSelectedSize\n  ] = useState(sizes?.[0] || null);\n  const [selectedExtras, setSelectedExtras] = useState([]);\n  const [showPopup, setShowPopup] = useState(false);\n  const {addToCart} = useContext(CartContext);\n\n  async function handleAddToCartButtonClick() {\n    console.log('add to cart');\n    const hasOptions = sizes.length > 0 || extraIngredientPrices.length > 0;\n    if (hasOptions && !showPopup) {\n      setShowPopup(true);\n      return;\n    }\n    addToCart(menuItem, selectedSize, selectedExtras);\n    await new Promise(resolve => setTimeout(resolve, 1000));\n    console.log('hiding popup');\n    setShowPopup(false);\n  }\n  function handleExtraThingClick(ev, extraThing) {\n    const checked = ev.target.checked;\n    if (checked) {\n      setSelectedExtras(prev => [...prev, extraThing]);\n    } else {\n      setSelectedExtras(prev => {\n        return prev.filter(e => e.name !== extraThing.name);\n      });\n    }\n  }\n\n  let selectedPrice = basePrice;\n  if (selectedSize) {\n    selectedPrice += selectedSize.price;\n  }\n  if (selectedExtras?.length > 0) {\n    for (const extra of selectedExtras) {\n      selectedPrice += extra.price;\n    }\n  }\n\n  return (\n    <>\n      {showPopup && (\n        <div\n          onClick={() => setShowPopup(false)}\n          className=\"fixed inset-0 bg-black/80 flex items-center justify-center\">\n          <div\n            onClick={ev => ev.stopPropagation()}\n            className=\"my-8 bg-white p-2 rounded-lg max-w-md\">\n            <div\n              className=\"overflow-y-scroll p-2\"\n              style={{maxHeight:'calc(100vh - 100px)'}}>\n              <Image\n                src={image}\n                alt={name}\n                width={300} height={200}\n                className=\"mx-auto\" />\n              <h2 className=\"text-lg font-bold text-center mb-2\">{name}</h2>\n              <p className=\"text-center text-gray-500 text-sm mb-2\">\n                {description}\n              </p>\n              {sizes?.length > 0 && (\n                <div className=\"py-2\">\n                  <h3 className=\"text-center text-gray-700\">Pick your size</h3>\n                  {sizes.map(size => (\n                    <label\n                      key={size._id}\n                      className=\"flex items-center gap-2 p-4 border rounded-md mb-1\">\n                      <input\n                        type=\"radio\"\n                        onChange={() => setSelectedSize(size)}\n                        checked={selectedSize?.name === size.name}\n                        name=\"size\"/>\n                      {size.name} ${basePrice + size.price}\n                    </label>\n                  ))}\n                </div>\n              )}\n              {extraIngredientPrices?.length > 0 && (\n                <div className=\"py-2\">\n                  <h3 className=\"text-center text-gray-700\">Any extras?</h3>\n                  {extraIngredientPrices.map(extraThing => (\n                    <label\n                      key={extraThing._id}\n                      className=\"flex items-center gap-2 p-4 border rounded-md mb-1\">\n                      <input\n                        type=\"checkbox\"\n                        onChange={ev => handleExtraThingClick(ev, extraThing)}\n                        checked={selectedExtras.map(e => e._id).includes(extraThing._id)}\n                        name={extraThing.name} />\n                      {extraThing.name} +${extraThing.price}\n                    </label>\n                  ))}\n                </div>\n              )}\n              <FlyingButton\n                targetTop={'5%'}\n                targetLeft={'95%'}\n                src={image}>\n                <div className=\"primary sticky bottom-2\"\n                     onClick={handleAddToCartButtonClick}>\n                  Add to cart ${selectedPrice}\n                </div>\n              </FlyingButton>\n              <button\n                className=\"mt-2\"\n                onClick={() => setShowPopup(false)}>\n                Cancel\n              </button>\n            </div>\n          </div>\n        </div>\n      )}\n      <MenuItemTile\n        onAddToCart={handleAddToCartButtonClick}\n        {...menuItem} />\n    </>\n  );\n}"
  },
  {
    "path": "src/components/menu/MenuItemTile.js",
    "content": "import AddToCartButton from \"@/components/menu/AddToCartButton\";\n\nexport default function MenuItemTile({onAddToCart, ...item}) {\n  const {image, description, name, basePrice,\n    sizes, extraIngredientPrices,\n  } = item;\n  const hasSizesOrExtras = sizes?.length > 0 || extraIngredientPrices?.length > 0;\n  return (\n    <div className=\"bg-gray-200 p-4 rounded-lg text-center\n      group hover:bg-white hover:shadow-md hover:shadow-black/25 transition-all\">\n      <div className=\"text-center\">\n        <img src={image} className=\"max-h-auto max-h-24 block mx-auto\" alt=\"pizza\"/>\n      </div>\n      <h4 className=\"font-semibold text-xl my-3\">{name}</h4>\n      <p className=\"text-gray-500 text-sm line-clamp-3\">\n        {description}\n      </p>\n      <AddToCartButton\n        image={image}\n        hasSizesOrExtras={hasSizesOrExtras}\n        onClick={onAddToCart}\n        basePrice={basePrice}\n      />\n    </div>\n  );\n}"
  },
  {
    "path": "src/libs/datetime.js",
    "content": "export function dbTimeForHuman(str) {\n\n  return str.replace('T', ' ').substring(0, 16);\n}"
  },
  {
    "path": "src/libs/mongoConnect.js",
    "content": "// This approach is taken from https://github.com/vercel/next.js/tree/canary/examples/with-mongodb\nimport { MongoClient } from \"mongodb\"\n\nif (!process.env.MONGO_URL) {\n  throw new Error('Invalid/Missing environment variable: \"MONGODB_URI\"')\n}\n\nconst uri = process.env.MONGO_URL\nconst options = {}\n\nlet client\nlet clientPromise;\n\nif (process.env.NODE_ENV === \"development\") {\n  // In development mode, use a global variable so that the value\n  // is preserved across module reloads caused by HMR (Hot Module Replacement).\n  if (!global._mongoClientPromise) {\n    client = new MongoClient(uri, options)\n    global._mongoClientPromise = client.connect()\n  }\n  clientPromise = global._mongoClientPromise\n} else {\n  // In production mode, it's best to not use a global variable.\n  client = new MongoClient(uri, options)\n  clientPromise = client.connect()\n}\n\n// Export a module-scoped MongoClient promise. By doing this in a\n// separate module, the client can be shared across functions.\nexport default clientPromise"
  },
  {
    "path": "src/models/Category.js",
    "content": "import {model, models, Schema} from \"mongoose\";\n\nconst CategorySchema = new Schema({\n  name: {type:String, required:true},\n}, {timestamps: true});\n\nexport const Category = models?.Category || model('Category', CategorySchema);"
  },
  {
    "path": "src/models/MenuItem.js",
    "content": "import mongoose, {model, models, Schema} from \"mongoose\";\n\nconst ExtraPriceSchema = new Schema({\n  name: String,\n  price: Number,\n});\n\nconst MenuItemSchema = new Schema({\n  image: {type: String},\n  name: {type: String},\n  description: {type: String},\n  category: {type: mongoose.Types.ObjectId},\n  basePrice: {type: Number},\n  sizes: {type:[ExtraPriceSchema]},\n  extraIngredientPrices: {type:[ExtraPriceSchema]},\n}, {timestamps: true});\n\nexport const MenuItem = models?.MenuItem || model('MenuItem', MenuItemSchema);"
  },
  {
    "path": "src/models/Order.js",
    "content": "import {model, models, Schema} from \"mongoose\";\n\nconst OrderSchema = new Schema({\n  userEmail: String,\n  phone: String,\n  streetAddress: String,\n  postalCode: String,\n  city: String,\n  country: String,\n  cartProducts: Object,\n  paid: {type: Boolean, default: false},\n}, {timestamps: true});\n\nexport const Order = models?.Order || model('Order', OrderSchema);"
  },
  {
    "path": "src/models/User.js",
    "content": "import {model, models, Schema} from \"mongoose\";\n\nconst UserSchema = new Schema({\n  name: {type: String},\n  email: {type: String, required: true, unique: true},\n  password: {type: String},\n  image: {type: String},\n}, {timestamps: true});\n\nexport const User = models?.User || model('User', UserSchema);"
  },
  {
    "path": "src/models/UserInfo.js",
    "content": "import {model, models, Schema} from \"mongoose\";\n\nconst UserInfoSchema = new Schema({\n  email: {type: String, required: true},\n  streetAddress: {type: String},\n  postalCode: {type: String},\n  city: {type: String},\n  country: {type: String},\n  phone: {type: String},\n  admin: {type: Boolean, default: false},\n}, {timestamps: true});\n\nexport const UserInfo = models?.UserInfo || model('UserInfo', UserInfoSchema);"
  },
  {
    "path": "tailwind.config.js",
    "content": "/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n  content: [\n    './src/pages/**/*.{js,ts,jsx,tsx,mdx}',\n    './src/components/**/*.{js,ts,jsx,tsx,mdx}',\n    './src/app/**/*.{js,ts,jsx,tsx,mdx}',\n  ],\n  theme: {\n    extend: {\n      colors: {\n        primary: '#f13a01',\n      },\n    },\n  },\n  plugins: [],\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"lib\": [\n      \"dom\",\n      \"dom.iterable\",\n      \"esnext\"\n    ],\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    },\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"strict\": false,\n    \"noEmit\": true,\n    \"incremental\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"jsx\": \"preserve\",\n    \"plugins\": [\n      {\n        \"name\": \"next\"\n      }\n    ],\n    \"forceConsistentCasingInFileNames\": true\n  },\n  \"include\": [\n    \"next-env.d.ts\",\n    \".next/types/**/*.ts\",\n    \"**/*.ts\",\n    \"**/*.tsx\"\n  ],\n  \"exclude\": [\n    \"node_modules\"\n  ]\n}\n"
  }
]