87 | >(({ className, ...props }, ref) => (
88 | [role=checkbox]]:translate-y-[2px]",
92 | className
93 | )}
94 | {...props}
95 | />
96 | ))
97 | TableCell.displayName = "TableCell"
98 |
99 | const TableCaption = React.forwardRef<
100 | HTMLTableCaptionElement,
101 | React.HTMLAttributes
102 | >(({ className, ...props }, ref) => (
103 |
108 | ))
109 | TableCaption.displayName = "TableCaption"
110 |
111 | export {
112 | Table,
113 | TableHeader,
114 | TableBody,
115 | TableFooter,
116 | TableHead,
117 | TableRow,
118 | TableCell,
119 | TableCaption,
120 | }
121 |
--------------------------------------------------------------------------------
/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Textarea = React.forwardRef<
6 | HTMLTextAreaElement,
7 | React.ComponentProps<"textarea">
8 | >(({ className, ...props }, ref) => {
9 | return (
10 |
18 | )
19 | })
20 | Textarea.displayName = "Textarea"
21 |
22 | export { Textarea }
23 |
--------------------------------------------------------------------------------
/components/ui/toaster.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { useToast } from "@/hooks/use-toast"
4 | import {
5 | Toast,
6 | ToastClose,
7 | ToastDescription,
8 | ToastProvider,
9 | ToastTitle,
10 | ToastViewport,
11 | } from "@/components/ui/toast"
12 |
13 | export function Toaster() {
14 | const { toasts } = useToast()
15 |
16 | return (
17 |
18 | {toasts.map(function ({ id, title, description, action, ...props }) {
19 | return (
20 |
21 |
22 | {title && {title} }
23 | {description && (
24 | {description}
25 | )}
26 |
27 | {action}
28 |
29 |
30 | )
31 | })}
32 |
33 |
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/emails/index.tsx:
--------------------------------------------------------------------------------
1 | import { Resend } from 'resend'
2 | import PurchaseReceiptEmail from './purchase-receipt'
3 | import { IOrder } from '@/lib/db/models/order.model'
4 | import AskReviewOrderItemsEmail from './ask-review-order-items'
5 | import { SENDER_EMAIL, SENDER_NAME } from '@/lib/constants'
6 |
7 | const resend = new Resend(process.env.RESEND_API_KEY as string)
8 |
9 | export const sendPurchaseReceipt = async ({ order }: { order: IOrder }) => {
10 | await resend.emails.send({
11 | from: `${SENDER_NAME} <${SENDER_EMAIL}>`,
12 | to: (order.user as { email: string }).email,
13 | subject: 'Order Confirmation',
14 | react: ,
15 | })
16 | }
17 |
18 | export const sendAskReviewOrderItems = async ({ order }: { order: IOrder }) => {
19 | const oneDayFromNow = new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString()
20 |
21 | await resend.emails.send({
22 | from: `${SENDER_NAME} <${SENDER_EMAIL}>`,
23 | to: (order.user as { email: string }).email,
24 | subject: 'Review your order items',
25 | react: ,
26 | scheduledAt: oneDayFromNow,
27 | })
28 | }
29 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { dirname } from "path";
2 | import { fileURLToPath } from "url";
3 | import { FlatCompat } from "@eslint/eslintrc";
4 |
5 | const __filename = fileURLToPath(import.meta.url);
6 | const __dirname = dirname(__filename);
7 |
8 | const compat = new FlatCompat({
9 | baseDirectory: __dirname,
10 | });
11 |
12 | const eslintConfig = [
13 | ...compat.extends("next/core-web-vitals", "next/typescript"),
14 | ];
15 |
16 | export default eslintConfig;
17 |
--------------------------------------------------------------------------------
/hooks/use-browsing-history.ts:
--------------------------------------------------------------------------------
1 | import { create } from 'zustand'
2 | import { persist } from 'zustand/middleware'
3 | type BrowsingHistory = {
4 | products: { id: string; category: string }[]
5 | }
6 | const initialState: BrowsingHistory = {
7 | products: [],
8 | }
9 |
10 | export const browsingHistoryStore = create()(
11 | persist(() => initialState, {
12 | name: 'browsingHistoryStore',
13 | })
14 | )
15 |
16 | export default function useBrowsingHistory() {
17 | const { products } = browsingHistoryStore()
18 | return {
19 | products,
20 | addItem: (product: { id: string; category: string }) => {
21 | const index = products.findIndex((p) => p.id === product.id)
22 | if (index !== -1) products.splice(index, 1) // Remove duplicate if it exists
23 | products.unshift(product) // Add id to the start
24 |
25 | if (products.length > 10) products.pop() // Remove excess items if length exceeds 10
26 |
27 | browsingHistoryStore.setState({
28 | products,
29 | })
30 | },
31 |
32 | clear: () => {
33 | browsingHistoryStore.setState({
34 | products: [],
35 | })
36 | },
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/hooks/use-cart-sidebar.ts:
--------------------------------------------------------------------------------
1 | import { usePathname } from 'next/navigation'
2 | import useDeviceType from './use-device-type'
3 | import useCartStore from './use-cart-store'
4 | import { i18n } from '@/i18n-config'
5 |
6 | const locales = i18n.locales
7 | .filter((locale) => locale.code !== 'en-US')
8 | .map((locale) => locale.code)
9 |
10 | const isNotInPaths = (s: string) => {
11 | const localePattern = `/(?:${locales.join('|')})` // Match locales
12 | const pathsPattern = `^(?:${localePattern})?(?:/$|/cart$|/checkout$|/sign-in$|/sign-up$|/order(?:/.*)?$|/account(?:/.*)?$|/admin(?:/.*)?$)?$`
13 | return !new RegExp(pathsPattern).test(s)
14 | }
15 |
16 | function useCartSidebar() {
17 | const {
18 | cart: { items },
19 | } = useCartStore()
20 | const deviceType = useDeviceType()
21 | const currentPath = usePathname()
22 |
23 | return (
24 | items.length > 0 && deviceType === 'desktop' && isNotInPaths(currentPath)
25 | )
26 | }
27 |
28 | export default useCartSidebar
29 |
--------------------------------------------------------------------------------
/hooks/use-device-type.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 |
3 | function useDeviceType() {
4 | const [deviceType, setDeviceType] = useState('unknown')
5 |
6 | useEffect(() => {
7 | const handleResize = () => {
8 | setDeviceType(window.innerWidth <= 768 ? 'mobile' : 'desktop')
9 | }
10 |
11 | handleResize() // Set initial value
12 | window.addEventListener('resize', handleResize)
13 |
14 | return () => window.removeEventListener('resize', handleResize)
15 | }, [])
16 |
17 | return deviceType
18 | }
19 |
20 | export default useDeviceType
21 |
--------------------------------------------------------------------------------
/hooks/use-is-mounted.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react'
2 |
3 | function useIsMounted() {
4 | const [isMounted, setIsMounted] = useState(false)
5 |
6 | useEffect(() => {
7 | setIsMounted(true)
8 | }, [])
9 |
10 | return isMounted
11 | }
12 |
13 | export default useIsMounted
14 |
--------------------------------------------------------------------------------
/hooks/use-setting-store.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 |
3 | import data from '@/lib/data'
4 | import { ClientSetting, SiteCurrency } from '@/types'
5 | import { create } from 'zustand'
6 |
7 | interface SettingState {
8 | setting: ClientSetting
9 | setSetting: (newSetting: ClientSetting) => void
10 | getCurrency: () => SiteCurrency
11 | setCurrency: (currency: string) => void
12 | }
13 |
14 | const useSettingStore = create((set, get) => ({
15 | setting: {
16 | ...data.settings[0],
17 | currency: data.settings[0].defaultCurrency,
18 | } as ClientSetting,
19 | setSetting: (newSetting: ClientSetting) => {
20 | set({
21 | setting: {
22 | ...newSetting,
23 | currency: newSetting.currency || get().setting.currency,
24 | },
25 | })
26 | },
27 | getCurrency: () => {
28 | return (
29 | get().setting.availableCurrencies.find(
30 | (c) => c.code === get().setting.currency
31 | ) || data.settings[0].availableCurrencies[0]
32 | )
33 | },
34 | setCurrency: async (currency: string) => {
35 | set({ setting: { ...get().setting, currency } })
36 | },
37 | }))
38 |
39 | export default useSettingStore
40 |
--------------------------------------------------------------------------------
/i18n-config.ts:
--------------------------------------------------------------------------------
1 | export const i18n = {
2 | locales: [
3 | { code: 'en-US', name: 'English', icon: '🇺🇸' },
4 | { code: 'fr', name: 'Français', icon: '🇫🇷' },
5 | { code: 'ar', name: 'العربية', icon: '🇸🇦' },
6 | ],
7 | defaultLocale: 'en-US',
8 | }
9 |
10 | export const getDirection = (locale: string) => {
11 | return locale === 'ar' ? 'rtl' : 'ltr'
12 | }
13 | export type I18nConfig = typeof i18n
14 | export type Locale = I18nConfig['locales'][number]
15 |
--------------------------------------------------------------------------------
/i18n/request.ts:
--------------------------------------------------------------------------------
1 | import { getRequestConfig } from 'next-intl/server'
2 | import { routing } from './routing'
3 |
4 | export default getRequestConfig(async ({ requestLocale }) => {
5 | // This typically corresponds to the `[locale]` segment
6 | let locale = await requestLocale
7 |
8 | // Ensure that the incoming locale is valid
9 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
10 | if (!locale || !routing.locales.includes(locale as any)) {
11 | locale = routing.defaultLocale
12 | }
13 |
14 | return {
15 | locale,
16 | messages: (await import(`../messages/${locale}.json`)).default,
17 | }
18 | })
19 |
--------------------------------------------------------------------------------
/i18n/routing.ts:
--------------------------------------------------------------------------------
1 | import { i18n } from '@/i18n-config'
2 | import { createNavigation } from 'next-intl/navigation'
3 | import { defineRouting } from 'next-intl/routing'
4 |
5 | export const routing = defineRouting({
6 | locales: i18n.locales.map((locale) => locale.code),
7 | defaultLocale: 'en-US',
8 | localePrefix: 'as-needed',
9 | pathnames: {
10 | // If all locales use the same pathname, a single
11 | // external path can be used for all locales
12 | },
13 | })
14 |
15 | export const { Link, redirect, usePathname, useRouter } =
16 | createNavigation(routing)
17 |
--------------------------------------------------------------------------------
/lessons/01-install-ai-tools-and-vscode-extensions.md:
--------------------------------------------------------------------------------
1 | # 01-install-ai-tools-and-vscode-extensions
2 |
3 | 1. install codeium extensions
4 | 2. create codeium account
5 | 3. use codeium in vscode
6 | 4. install vscode extensions
7 | 1. eslint
8 | 2. prettier
9 | 3. vscode-icons
10 | 4. tailwindcss
11 | 5. es7+ react snippets
12 | 6. pretty typescript errors
13 |
--------------------------------------------------------------------------------
/lessons/02-create-next-app.md:
--------------------------------------------------------------------------------
1 | # 02-create-next-app
2 |
3 | 1. open vs code
4 | 2. based on docs at https://nextjs.org/docs/app/getting-started/installation run
5 | 3. npx create-next-app@latest
6 | app name? nextjs-amazona
7 | 4. cd nextjs-amazona
8 | 5. npm run dev
9 | 6. based on docs at https://ui.shadcn.com/docs/installation/next run
10 | 7. npx shadcn@latest init
11 | 8. npx shadcn@latest add button
12 | 9. copy yellow theme from https://ui.shadcn.com/themes
13 | 10. paste it in global.css
14 |
15 | ```css
16 | @layer base {
17 | :root {
18 | --background: 0 0% 100%;
19 | --foreground: 20 14.3% 4.1%;
20 | --card: 0 0% 100%;
21 | --card-foreground: 20 14.3% 4.1%;
22 | --popover: 0 0% 100%;
23 | --popover-foreground: 20 14.3% 4.1%;
24 | --primary: 47.9 95.8% 53.1%;
25 | --primary-foreground: 26 83.3% 14.1%;
26 | --secondary: 60 4.8% 95.9%;
27 | --secondary-foreground: 24 9.8% 10%;
28 | --muted: 60 4.8% 95.9%;
29 | --muted-foreground: 25 5.3% 44.7%;
30 | --accent: 60 4.8% 95.9%;
31 | --accent-foreground: 24 9.8% 10%;
32 | --destructive: 0 84.2% 60.2%;
33 | --destructive-foreground: 60 9.1% 97.8%;
34 | --border: 20 5.9% 90%;
35 | --input: 20 5.9% 90%;
36 | --ring: 20 14.3% 4.1%;
37 | --radius: 0.5rem;
38 | --chart-1: 12 76% 61%;
39 | --chart-2: 173 58% 39%;
40 | --chart-3: 197 37% 24%;
41 | --chart-4: 43 74% 66%;
42 | --chart-5: 27 87% 67%;
43 | }
44 |
45 | .dark {
46 | --background: 20 14.3% 4.1%;
47 | --foreground: 60 9.1% 97.8%;
48 | --card: 20 14.3% 4.1%;
49 | --card-foreground: 60 9.1% 97.8%;
50 | --popover: 20 14.3% 4.1%;
51 | --popover-foreground: 60 9.1% 97.8%;
52 | --primary: 47.9 95.8% 53.1%;
53 | --primary-foreground: 26 83.3% 14.1%;
54 | --secondary: 12 6.5% 15.1%;
55 | --secondary-foreground: 60 9.1% 97.8%;
56 | --muted: 12 6.5% 15.1%;
57 | --muted-foreground: 24 5.4% 63.9%;
58 | --accent: 12 6.5% 15.1%;
59 | --accent-foreground: 60 9.1% 97.8%;
60 | --destructive: 0 62.8% 30.6%;
61 | --destructive-foreground: 60 9.1% 97.8%;
62 | --border: 12 6.5% 15.1%;
63 | --input: 12 6.5% 15.1%;
64 | --ring: 35.5 91.7% 32.9%;
65 | --chart-1: 220 70% 50%;
66 | --chart-2: 160 60% 45%;
67 | --chart-3: 30 80% 55%;
68 | --chart-4: 280 65% 60%;
69 | --chart-5: 340 75% 55%;
70 | }
71 | }
72 | ```
73 |
74 | 11. replace content in main element in page.tsx with
75 |
76 | ```tsx
77 | import { Button } from '@/components/ui/button'
78 | ...
79 | Button
80 | ```
81 |
82 | 12. publish code to github
83 | 13. deploy github repo to vercel
84 | 14. go to https://nextjs-amazona.vercel.app
85 |
--------------------------------------------------------------------------------
/lessons/08-create-best-selling-slider.md:
--------------------------------------------------------------------------------
1 | # 08-create-best-selling-slider
2 |
3 | 1. app/(home)/page.tsx
4 |
5 | ```tsx
6 | const bestSellingProducts = await getProductsByTag({ tag: 'best-seller' })
7 | return (
8 |
9 |
10 |
15 |
16 |
17 | )
18 | ```
19 |
20 | 2. commit changes and push to GitHub
21 | 3. go to https://nextjs-amazona.vercel.app
22 |
--------------------------------------------------------------------------------
/lessons/16-signin-with-google.md:
--------------------------------------------------------------------------------
1 | # 16-signin-with-google
2 |
3 | ## update .env.local
4 |
5 | - open https://console.cloud.google.com/apis/credentials
6 | - create a new OAuth 2.0 Client ID ans get its credentials
7 |
8 | ```txt
9 | AUTH_GOOGLE_ID=xx.apps.googleusercontent.com
10 | AUTH_GOOGLE_SECRET=xx
11 | ```
12 |
13 | ## update auth.ts
14 |
15 | ```ts
16 | import Google from 'next-auth/providers/google'
17 | Google({
18 | allowDangerousEmailAccountLinking: true,
19 | }),
20 | ```
21 |
22 | ## update lib/actions/user.actions.ts
23 |
24 | ```ts
25 | -
26 | export const SignInWithGoogle = async () => {
27 | await signIn('google')
28 | }
29 | ```
30 |
31 | ## create app/(auth)/sign-in/google-signin-form.tsx
32 |
33 | ```ts
34 | 'use client'
35 | import { useFormStatus } from 'react-dom'
36 |
37 | import { Button } from '@/components/ui/button'
38 | import { SignInWithGoogle } from '@/lib/actions/user.actions'
39 |
40 | export function GoogleSignInForm() {
41 | const SignInButton = () => {
42 | const { pending } = useFormStatus()
43 | return (
44 |
45 | {pending ? 'Redirecting to Google...' : 'Sign In with Google'}
46 |
47 | )
48 | }
49 | return (
50 |
53 | )
54 | }
55 | ```
56 |
57 | ## update app/(auth)/sign-in/page.tsx
58 |
59 | ```ts
60 | import { GoogleSignInForm } from './google-signin-form'
61 |
62 |
63 |
64 |
65 | ```
66 |
67 | ## npm run build
68 |
69 | ## update env variables on vercel
70 |
71 | ## commit changes and push to GitHub
72 |
73 | ## go to https://nextjs-amazona.vercel.app
74 |
--------------------------------------------------------------------------------
/lib/actions/setting.actions.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 | import { ISettingInput } from '@/types'
3 | import data from '../data'
4 | import Setting from '../db/models/setting.model'
5 | import { connectToDatabase } from '../db'
6 | import { formatError } from '../utils'
7 | import { cookies } from 'next/headers'
8 |
9 | const globalForSettings = global as unknown as {
10 | cachedSettings: ISettingInput | null
11 | }
12 | export const getNoCachedSetting = async (): Promise => {
13 | await connectToDatabase()
14 | const setting = await Setting.findOne()
15 | return JSON.parse(JSON.stringify(setting)) as ISettingInput
16 | }
17 |
18 | export const getSetting = async (): Promise => {
19 | if (!globalForSettings.cachedSettings) {
20 | console.log('hit db')
21 | await connectToDatabase()
22 | const setting = await Setting.findOne().lean()
23 | globalForSettings.cachedSettings = setting
24 | ? JSON.parse(JSON.stringify(setting))
25 | : data.settings[0]
26 | }
27 | return globalForSettings.cachedSettings as ISettingInput
28 | }
29 |
30 | export const updateSetting = async (newSetting: ISettingInput) => {
31 | try {
32 | await connectToDatabase()
33 | const updatedSetting = await Setting.findOneAndUpdate({}, newSetting, {
34 | upsert: true,
35 | new: true,
36 | }).lean()
37 | globalForSettings.cachedSettings = JSON.parse(
38 | JSON.stringify(updatedSetting)
39 | ) // Update the cache
40 | return {
41 | success: true,
42 | message: 'Setting updated successfully',
43 | }
44 | } catch (error) {
45 | return { success: false, message: formatError(error) }
46 | }
47 | }
48 |
49 | // Server action to update the currency cookie
50 | export const setCurrencyOnServer = async (newCurrency: string) => {
51 | 'use server'
52 | const cookiesStore = await cookies()
53 | cookiesStore.set('currency', newCurrency)
54 |
55 | return {
56 | success: true,
57 | message: 'Currency updated successfully',
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/lib/actions/web-page.actions.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 |
3 | import { revalidatePath } from 'next/cache'
4 |
5 | import { connectToDatabase } from '@/lib/db'
6 | import WebPage, { IWebPage } from '@/lib/db/models/web-page.model'
7 | import { formatError } from '@/lib/utils'
8 |
9 | import { WebPageInputSchema, WebPageUpdateSchema } from '../validator'
10 | import { z } from 'zod'
11 |
12 | // CREATE
13 | export async function createWebPage(data: z.infer) {
14 | try {
15 | const webPage = WebPageInputSchema.parse(data)
16 | await connectToDatabase()
17 | await WebPage.create(webPage)
18 | revalidatePath('/admin/web-pages')
19 | return {
20 | success: true,
21 | message: 'WebPage created successfully',
22 | }
23 | } catch (error) {
24 | return { success: false, message: formatError(error) }
25 | }
26 | }
27 |
28 | // UPDATE
29 | export async function updateWebPage(data: z.infer) {
30 | try {
31 | const webPage = WebPageUpdateSchema.parse(data)
32 | await connectToDatabase()
33 | await WebPage.findByIdAndUpdate(webPage._id, webPage)
34 | revalidatePath('/admin/web-pages')
35 | return {
36 | success: true,
37 | message: 'WebPage updated successfully',
38 | }
39 | } catch (error) {
40 | return { success: false, message: formatError(error) }
41 | }
42 | }
43 | // DELETE
44 | export async function deleteWebPage(id: string) {
45 | try {
46 | await connectToDatabase()
47 | const res = await WebPage.findByIdAndDelete(id)
48 | if (!res) throw new Error('WebPage not found')
49 | revalidatePath('/admin/web-pages')
50 | return {
51 | success: true,
52 | message: 'WebPage deleted successfully',
53 | }
54 | } catch (error) {
55 | return { success: false, message: formatError(error) }
56 | }
57 | }
58 |
59 | // GET ALL
60 | export async function getAllWebPages() {
61 | await connectToDatabase()
62 | const webPages = await WebPage.find()
63 | return JSON.parse(JSON.stringify(webPages)) as IWebPage[]
64 | }
65 | export async function getWebPageById(webPageId: string) {
66 | await connectToDatabase()
67 | const webPage = await WebPage.findById(webPageId)
68 | return JSON.parse(JSON.stringify(webPage)) as IWebPage
69 | }
70 |
71 | // GET ONE PAGE BY SLUG
72 | export async function getWebPageBySlug(slug: string) {
73 | await connectToDatabase()
74 | const webPage = await WebPage.findOne({ slug, isPublished: true })
75 | if (!webPage) throw new Error('WebPage not found')
76 | return JSON.parse(JSON.stringify(webPage)) as IWebPage
77 | }
78 |
--------------------------------------------------------------------------------
/lib/constants.ts:
--------------------------------------------------------------------------------
1 | export const SENDER_NAME = process.env.SENDER_NAME || 'support'
2 | export const SENDER_EMAIL = process.env.SENDER_EMAIL || 'onboarding@resend.dev'
3 |
4 | export const USER_ROLES = ['Admin', 'User']
5 | export const COLORS = ['Gold', 'Green', 'Red']
6 | export const THEMES = ['Light', 'Dark', 'System']
7 |
--------------------------------------------------------------------------------
/lib/db/client.ts:
--------------------------------------------------------------------------------
1 | // This approach is taken from https://github.com/vercel/next.js/tree/canary/examples/with-mongodb
2 | import { MongoClient, ServerApiVersion } from 'mongodb'
3 |
4 | if (!process.env.MONGODB_URI) {
5 | throw new Error('Invalid/Missing environment variable: "MONGODB_URI"')
6 | }
7 |
8 | const uri = process.env.MONGODB_URI
9 | const options = {
10 | serverApi: {
11 | version: ServerApiVersion.v1,
12 | strict: true,
13 | deprecationErrors: true,
14 | },
15 | }
16 |
17 | let client: MongoClient
18 |
19 | if (process.env.NODE_ENV === 'development') {
20 | // In development mode, use a global variable so that the value
21 | // is preserved across module reloads caused by HMR (Hot Module Replacement).
22 | const globalWithMongo = global as typeof globalThis & {
23 | _mongoClient?: MongoClient
24 | }
25 |
26 | if (!globalWithMongo._mongoClient) {
27 | globalWithMongo._mongoClient = new MongoClient(uri, options)
28 | }
29 | client = globalWithMongo._mongoClient
30 | } else {
31 | // In production mode, it's best to not use a global variable.
32 | client = new MongoClient(uri, options)
33 | }
34 |
35 | // Export a module-scoped MongoClient. By doing this in a
36 | // separate module, the client can be shared across functions.
37 | export default client
38 |
--------------------------------------------------------------------------------
/lib/db/index.ts:
--------------------------------------------------------------------------------
1 | import mongoose from 'mongoose'
2 |
3 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
4 | const cached = (global as any).mongoose || { conn: null, promise: null }
5 |
6 | export const connectToDatabase = async (
7 | MONGODB_URI = process.env.MONGODB_URI
8 | ) => {
9 | if (cached.conn) return cached.conn
10 |
11 | if (!MONGODB_URI) throw new Error('MONGODB_URI is missing')
12 |
13 | cached.promise = cached.promise || mongoose.connect(MONGODB_URI)
14 |
15 | cached.conn = await cached.promise
16 |
17 | return cached.conn
18 | }
19 |
--------------------------------------------------------------------------------
/lib/db/models/order.model.ts:
--------------------------------------------------------------------------------
1 | import { IOrderInput } from '@/types'
2 | import { Document, Model, model, models, Schema } from 'mongoose'
3 |
4 | export interface IOrder extends Document, IOrderInput {
5 | _id: string
6 | createdAt: Date
7 | updatedAt: Date
8 | }
9 |
10 | const orderSchema = new Schema(
11 | {
12 | user: {
13 | type: Schema.Types.ObjectId as unknown as typeof String,
14 | ref: 'User',
15 | required: true,
16 | },
17 | items: [
18 | {
19 | product: {
20 | type: Schema.Types.ObjectId,
21 | ref: 'Product',
22 | required: true,
23 | },
24 | clientId: { type: String, required: true },
25 | name: { type: String, required: true },
26 | slug: { type: String, required: true },
27 | image: { type: String, required: true },
28 | category: { type: String, required: true },
29 | price: { type: Number, required: true },
30 | countInStock: { type: Number, required: true },
31 | quantity: { type: Number, required: true },
32 | size: { type: String },
33 | color: { type: String },
34 | },
35 | ],
36 | shippingAddress: {
37 | fullName: { type: String, required: true },
38 | street: { type: String, required: true },
39 | city: { type: String, required: true },
40 | postalCode: { type: String, required: true },
41 | country: { type: String, required: true },
42 | province: { type: String, required: true },
43 | phone: { type: String, required: true },
44 | },
45 | expectedDeliveryDate: { type: Date, required: true },
46 | paymentMethod: { type: String, required: true },
47 | paymentResult: { id: String, status: String, email_address: String },
48 | itemsPrice: { type: Number, required: true },
49 | shippingPrice: { type: Number, required: true },
50 | taxPrice: { type: Number, required: true },
51 | totalPrice: { type: Number, required: true },
52 | isPaid: { type: Boolean, required: true, default: false },
53 | paidAt: { type: Date },
54 | isDelivered: { type: Boolean, required: true, default: false },
55 | deliveredAt: { type: Date },
56 | createdAt: { type: Date, default: Date.now },
57 | },
58 | {
59 | timestamps: true,
60 | }
61 | )
62 |
63 | const Order =
64 | (models.Order as Model) || model('Order', orderSchema)
65 |
66 | export default Order
67 |
--------------------------------------------------------------------------------
/lib/db/models/product.model.ts:
--------------------------------------------------------------------------------
1 | import { Document, Model, model, models, Schema } from 'mongoose'
2 | import { IProductInput } from '@/types'
3 |
4 | export interface IProduct extends Document, IProductInput {
5 | _id: string
6 | createdAt: Date
7 | updatedAt: Date
8 | }
9 |
10 | const productSchema = new Schema(
11 | {
12 | name: {
13 | type: String,
14 | required: true,
15 | },
16 | slug: {
17 | type: String,
18 | required: true,
19 | unique: true,
20 | },
21 | category: {
22 | type: String,
23 | required: true,
24 | },
25 | images: [String],
26 | brand: {
27 | type: String,
28 | required: true,
29 | },
30 | description: {
31 | type: String,
32 | trim: true,
33 | },
34 | price: {
35 | type: Number,
36 | required: true,
37 | },
38 | listPrice: {
39 | type: Number,
40 | required: true,
41 | },
42 | countInStock: {
43 | type: Number,
44 | required: true,
45 | },
46 | tags: { type: [String], default: ['new arrival'] },
47 | colors: { type: [String], default: ['White', 'Red', 'Black'] },
48 | sizes: { type: [String], default: ['S', 'M', 'L'] },
49 | avgRating: {
50 | type: Number,
51 | required: true,
52 | default: 0,
53 | },
54 | numReviews: {
55 | type: Number,
56 | required: true,
57 | default: 0,
58 | },
59 | ratingDistribution: [
60 | {
61 | rating: {
62 | type: Number,
63 | required: true,
64 | },
65 | count: {
66 | type: Number,
67 | required: true,
68 | },
69 | },
70 | ],
71 | numSales: {
72 | type: Number,
73 | required: true,
74 | default: 0,
75 | },
76 | isPublished: {
77 | type: Boolean,
78 | required: true,
79 | default: false,
80 | },
81 | reviews: [
82 | {
83 | type: Schema.Types.ObjectId,
84 | ref: 'Review',
85 | default: [],
86 | },
87 | ],
88 | },
89 | {
90 | timestamps: true,
91 | }
92 | )
93 |
94 | const Product =
95 | (models.Product as Model) ||
96 | model('Product', productSchema)
97 |
98 | export default Product
99 |
--------------------------------------------------------------------------------
/lib/db/models/review.model.ts:
--------------------------------------------------------------------------------
1 | import { IReviewInput } from '@/types'
2 | import { Document, Model, model, models, Schema } from 'mongoose'
3 |
4 | export interface IReview extends Document, IReviewInput {
5 | _id: string
6 | createdAt: Date
7 | updatedAt: Date
8 | }
9 | const reviewSchema = new Schema(
10 | {
11 | user: {
12 | type: Schema.Types.ObjectId as unknown as typeof String,
13 | ref: 'User',
14 | },
15 | isVerifiedPurchase: {
16 | type: Boolean,
17 | required: true,
18 | default: false,
19 | },
20 | product: {
21 | type: Schema.Types.ObjectId as unknown as typeof String,
22 | ref: 'Product',
23 | },
24 | rating: {
25 | type: Number,
26 | required: true,
27 | min: 1,
28 | max: 5,
29 | },
30 | title: {
31 | type: String,
32 | required: true,
33 | },
34 | comment: {
35 | type: String,
36 | required: true,
37 | },
38 | },
39 | {
40 | timestamps: true,
41 | }
42 | )
43 |
44 | const Review =
45 | (models.Review as Model) || model('Review', reviewSchema)
46 |
47 | export default Review
48 |
--------------------------------------------------------------------------------
/lib/db/models/user.model.ts:
--------------------------------------------------------------------------------
1 | import { IUserInput } from '@/types'
2 | import { Document, Model, model, models, Schema } from 'mongoose'
3 |
4 | export interface IUser extends Document, IUserInput {
5 | _id: string
6 | createdAt: Date
7 | updatedAt: Date
8 | }
9 |
10 | const userSchema = new Schema(
11 | {
12 | email: { type: String, required: true, unique: true },
13 | name: { type: String, required: true },
14 | role: { type: String, required: true, default: 'User' },
15 | password: { type: String },
16 | image: { type: String },
17 | emailVerified: { type: Boolean, default: false },
18 | },
19 | {
20 | timestamps: true,
21 | }
22 | )
23 |
24 | const User = (models.User as Model) || model('User', userSchema)
25 |
26 | export default User
27 |
--------------------------------------------------------------------------------
/lib/db/models/web-page.model.ts:
--------------------------------------------------------------------------------
1 | import { IWebPageInput } from '@/types'
2 | import { Document, Model, model, models, Schema } from 'mongoose'
3 |
4 | export interface IWebPage extends Document, IWebPageInput {
5 | _id: string
6 | createdAt: Date
7 | updatedAt: Date
8 | }
9 |
10 | const webPageSchema = new Schema(
11 | {
12 | title: {
13 | type: String,
14 | required: true,
15 | },
16 | slug: {
17 | type: String,
18 | required: true,
19 | unique: true,
20 | },
21 | content: {
22 | type: String,
23 | required: true,
24 | },
25 | isPublished: {
26 | type: Boolean,
27 | required: true,
28 | default: false,
29 | },
30 | },
31 | {
32 | timestamps: true,
33 | }
34 | )
35 |
36 | const WebPage =
37 | (models.WebPage as Model) ||
38 | model('WebPage', webPageSchema)
39 |
40 | export default WebPage
41 |
--------------------------------------------------------------------------------
/lib/paypal.ts:
--------------------------------------------------------------------------------
1 | const base = process.env.PAYPAL_API_URL || 'https://api-m.sandbox.paypal.com'
2 |
3 | export const paypal = {
4 | createOrder: async function createOrder(price: number) {
5 | const accessToken = await generateAccessToken()
6 | const url = `${base}/v2/checkout/orders`
7 | const response = await fetch(url, {
8 | method: 'post',
9 | headers: {
10 | 'Content-Type': 'application/json',
11 | Authorization: `Bearer ${accessToken}`,
12 | },
13 | body: JSON.stringify({
14 | intent: 'CAPTURE',
15 | purchase_units: [
16 | {
17 | amount: {
18 | currency_code: 'USD',
19 | value: price,
20 | },
21 | },
22 | ],
23 | }),
24 | })
25 | return handleResponse(response)
26 | },
27 | capturePayment: async function capturePayment(orderId: string) {
28 | const accessToken = await generateAccessToken()
29 | const url = `${base}/v2/checkout/orders/${orderId}/capture`
30 | const response = await fetch(url, {
31 | method: 'post',
32 | headers: {
33 | 'Content-Type': 'application/json',
34 | Authorization: `Bearer ${accessToken}`,
35 | },
36 | })
37 |
38 | return handleResponse(response)
39 | },
40 | }
41 |
42 | async function generateAccessToken() {
43 | const { PAYPAL_CLIENT_ID, PAYPAL_APP_SECRET } = process.env
44 | const auth = Buffer.from(PAYPAL_CLIENT_ID + ':' + PAYPAL_APP_SECRET).toString(
45 | 'base64'
46 | )
47 | const response = await fetch(`${base}/v1/oauth2/token`, {
48 | method: 'post',
49 | body: 'grant_type=client_credentials',
50 | headers: {
51 | Authorization: `Basic ${auth}`,
52 | },
53 | })
54 |
55 | const jsonData = await handleResponse(response)
56 | return jsonData.access_token
57 | }
58 |
59 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
60 | async function handleResponse(response: any) {
61 | if (response.status === 200 || response.status === 201) {
62 | return response.json()
63 | }
64 |
65 | const errorMessage = await response.text()
66 | throw new Error(errorMessage)
67 | }
68 |
--------------------------------------------------------------------------------
/lib/uploadthing.ts:
--------------------------------------------------------------------------------
1 | import {
2 | generateUploadButton,
3 | generateUploadDropzone,
4 | } from '@uploadthing/react'
5 | import type { OurFileRouter } from '@/app/api/uploadthing/core'
6 | export const UploadButton = generateUploadButton()
7 | export const UploadDropzone = generateUploadDropzone()
8 |
--------------------------------------------------------------------------------
/middleware.ts:
--------------------------------------------------------------------------------
1 | import createMiddleware from 'next-intl/middleware'
2 | import { routing } from './i18n/routing'
3 |
4 | import NextAuth from 'next-auth'
5 | import authConfig from './auth.config'
6 |
7 | const publicPages = [
8 | '/',
9 | '/search',
10 | '/sign-in',
11 | '/sign-up',
12 | '/cart',
13 | '/cart/(.*)',
14 | '/product/(.*)',
15 | '/page/(.*)',
16 | // (/secret requires auth)
17 | ]
18 |
19 | const intlMiddleware = createMiddleware(routing)
20 | const { auth } = NextAuth(authConfig)
21 |
22 | export default auth((req) => {
23 | const publicPathnameRegex = RegExp(
24 | `^(/(${routing.locales.join('|')}))?(${publicPages
25 | .flatMap((p) => (p === '/' ? ['', '/'] : p))
26 | .join('|')})/?$`,
27 | 'i'
28 | )
29 | const isPublicPage = publicPathnameRegex.test(req.nextUrl.pathname)
30 |
31 | if (isPublicPage) {
32 | // return NextResponse.next()
33 | return intlMiddleware(req)
34 | } else {
35 | if (!req.auth) {
36 | const newUrl = new URL(
37 | `/sign-in?callbackUrl=${
38 | encodeURIComponent(req.nextUrl.pathname) || '/'
39 | }`,
40 | req.nextUrl.origin
41 | )
42 | return Response.redirect(newUrl)
43 | } else {
44 | return intlMiddleware(req)
45 | }
46 | }
47 | })
48 |
49 | export const config = {
50 | // Skip all paths that should not be internationalized
51 | matcher: ['/((?!api|_next|.*\\..*).*)'],
52 | }
53 |
--------------------------------------------------------------------------------
/next.config.ts:
--------------------------------------------------------------------------------
1 | import type { NextConfig } from 'next'
2 | import withNextIntl from 'next-intl/plugin'
3 |
4 | const nextConfig: NextConfig = withNextIntl()({
5 | /* config options here */
6 | images: {
7 | remotePatterns: [
8 | {
9 | protocol: 'https',
10 | hostname: 'utfs.io',
11 | port: '',
12 | },
13 | ],
14 | },
15 | })
16 |
17 | export default nextConfig
18 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nextjs-amazona",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "email": "email dev",
7 | "seed": "npx tsx ./lib/db/seed.ts",
8 | "dev": "next dev --turbopack",
9 | "build": "next build",
10 | "start": "next start",
11 | "lint": "next lint"
12 | },
13 | "dependencies": {
14 | "@auth/mongodb-adapter": "^3.7.4",
15 | "@hookform/resolvers": "^3.9.1",
16 | "@paypal/react-paypal-js": "^8.7.0",
17 | "@radix-ui/react-alert-dialog": "^1.1.4",
18 | "@radix-ui/react-checkbox": "^1.1.3",
19 | "@radix-ui/react-collapsible": "^1.1.2",
20 | "@radix-ui/react-dialog": "^1.1.4",
21 | "@radix-ui/react-dropdown-menu": "^2.1.4",
22 | "@radix-ui/react-label": "^2.1.1",
23 | "@radix-ui/react-popover": "^1.1.4",
24 | "@radix-ui/react-progress": "^1.1.1",
25 | "@radix-ui/react-radio-group": "^1.2.2",
26 | "@radix-ui/react-scroll-area": "^1.2.2",
27 | "@radix-ui/react-select": "^2.1.3",
28 | "@radix-ui/react-separator": "^1.1.1",
29 | "@radix-ui/react-slot": "^1.1.1",
30 | "@radix-ui/react-toast": "^1.2.3",
31 | "@react-email/components": "^0.0.31",
32 | "@stripe/react-stripe-js": "^3.1.1",
33 | "@stripe/stripe-js": "^5.4.0",
34 | "@uploadthing/react": "^7.1.3",
35 | "bcryptjs": "^2.4.3",
36 | "class-variance-authority": "^0.7.1",
37 | "clsx": "^2.1.1",
38 | "date-fns": "^4.1.0",
39 | "embla-carousel-autoplay": "^8.5.1",
40 | "embla-carousel-react": "^8.5.1",
41 | "lucide-react": "^0.468.0",
42 | "mongodb": "^6.12.0",
43 | "mongoose": "^8.9.0",
44 | "next": "15.1.0",
45 | "next-auth": "^5.0.0-beta.25",
46 | "next-intl": "^3.26.3",
47 | "next-themes": "^0.4.4",
48 | "query-string": "^9.1.1",
49 | "react": "^19.0.0",
50 | "react-day-picker": "^8.10.1",
51 | "react-dom": "^19.0.0",
52 | "react-hook-form": "^7.54.2",
53 | "react-intersection-observer": "^9.14.0",
54 | "react-markdown": "^9.0.1",
55 | "react-markdown-editor-lite": "^1.3.4",
56 | "react-medium-image-zoom": "^5.2.12",
57 | "recharts": "^2.15.0",
58 | "resend": "^4.0.1",
59 | "stripe": "^17.5.0",
60 | "tailwind-merge": "^2.5.5",
61 | "tailwindcss-animate": "^1.0.7",
62 | "uploadthing": "^7.4.1",
63 | "vaul": "^1.1.2",
64 | "zod": "^3.24.1",
65 | "zustand": "^5.0.2"
66 | },
67 | "devDependencies": {
68 | "@eslint/eslintrc": "^3",
69 | "@types/bcryptjs": "^2.4.6",
70 | "@types/node": "^20",
71 | "@types/react": "^19",
72 | "@types/react-dom": "^19",
73 | "eslint": "^9",
74 | "eslint-config-next": "15.1.0",
75 | "postcss": "^8",
76 | "react-email": "^3.0.4",
77 | "tailwindcss": "^3.4.1",
78 | "typescript": "^5"
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('postcss-load-config').Config} */
2 | const config = {
3 | plugins: {
4 | tailwindcss: {},
5 | },
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/public/images/app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/app.png
--------------------------------------------------------------------------------
/public/images/banner1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/banner1.jpg
--------------------------------------------------------------------------------
/public/images/banner2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/banner2.jpg
--------------------------------------------------------------------------------
/public/images/banner3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/banner3.jpg
--------------------------------------------------------------------------------
/public/images/jeans.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/jeans.jpg
--------------------------------------------------------------------------------
/public/images/p11-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p11-1.jpg
--------------------------------------------------------------------------------
/public/images/p11-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p11-2.jpg
--------------------------------------------------------------------------------
/public/images/p12-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p12-1.jpg
--------------------------------------------------------------------------------
/public/images/p12-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p12-2.jpg
--------------------------------------------------------------------------------
/public/images/p12-3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p12-3.jpg
--------------------------------------------------------------------------------
/public/images/p12-4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p12-4.jpg
--------------------------------------------------------------------------------
/public/images/p13-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p13-1.jpg
--------------------------------------------------------------------------------
/public/images/p13-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p13-2.jpg
--------------------------------------------------------------------------------
/public/images/p14-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p14-1.jpg
--------------------------------------------------------------------------------
/public/images/p14-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p14-2.jpg
--------------------------------------------------------------------------------
/public/images/p15-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p15-1.jpg
--------------------------------------------------------------------------------
/public/images/p15-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p15-2.jpg
--------------------------------------------------------------------------------
/public/images/p16-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p16-1.jpg
--------------------------------------------------------------------------------
/public/images/p16-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p16-2.jpg
--------------------------------------------------------------------------------
/public/images/p21-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p21-1.jpg
--------------------------------------------------------------------------------
/public/images/p21-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p21-2.jpg
--------------------------------------------------------------------------------
/public/images/p22-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p22-1.jpg
--------------------------------------------------------------------------------
/public/images/p22-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p22-2.jpg
--------------------------------------------------------------------------------
/public/images/p23-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p23-1.jpg
--------------------------------------------------------------------------------
/public/images/p23-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p23-2.jpg
--------------------------------------------------------------------------------
/public/images/p24-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p24-1.jpg
--------------------------------------------------------------------------------
/public/images/p24-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p24-2.jpg
--------------------------------------------------------------------------------
/public/images/p25-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p25-1.jpg
--------------------------------------------------------------------------------
/public/images/p25-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p25-2.jpg
--------------------------------------------------------------------------------
/public/images/p26-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p26-1.jpg
--------------------------------------------------------------------------------
/public/images/p26-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p26-2.jpg
--------------------------------------------------------------------------------
/public/images/p31-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p31-1.jpg
--------------------------------------------------------------------------------
/public/images/p31-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p31-2.jpg
--------------------------------------------------------------------------------
/public/images/p32-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p32-1.jpg
--------------------------------------------------------------------------------
/public/images/p32-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p32-2.jpg
--------------------------------------------------------------------------------
/public/images/p33-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p33-1.jpg
--------------------------------------------------------------------------------
/public/images/p33-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p33-2.jpg
--------------------------------------------------------------------------------
/public/images/p34-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p34-1.jpg
--------------------------------------------------------------------------------
/public/images/p34-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p34-2.jpg
--------------------------------------------------------------------------------
/public/images/p35-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p35-1.jpg
--------------------------------------------------------------------------------
/public/images/p35-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p35-2.jpg
--------------------------------------------------------------------------------
/public/images/p36-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p36-1.jpg
--------------------------------------------------------------------------------
/public/images/p36-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p36-2.jpg
--------------------------------------------------------------------------------
/public/images/p41-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p41-1.jpg
--------------------------------------------------------------------------------
/public/images/p41-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p41-2.jpg
--------------------------------------------------------------------------------
/public/images/p42-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p42-1.jpg
--------------------------------------------------------------------------------
/public/images/p42-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p42-2.jpg
--------------------------------------------------------------------------------
/public/images/p43-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p43-1.jpg
--------------------------------------------------------------------------------
/public/images/p43-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p43-2.jpg
--------------------------------------------------------------------------------
/public/images/p44-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p44-1.jpg
--------------------------------------------------------------------------------
/public/images/p44-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p44-2.jpg
--------------------------------------------------------------------------------
/public/images/p45-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p45-1.jpg
--------------------------------------------------------------------------------
/public/images/p45-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p45-2.jpg
--------------------------------------------------------------------------------
/public/images/p46-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p46-1.jpg
--------------------------------------------------------------------------------
/public/images/p46-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/p46-2.jpg
--------------------------------------------------------------------------------
/public/images/shoes.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/shoes.jpg
--------------------------------------------------------------------------------
/public/images/t-shirts.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/t-shirts.jpg
--------------------------------------------------------------------------------
/public/images/wrist-watches.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basir/nextjs-amazona/c07e0b2738c8fadc6a6c8699b1aa9fe77d9546c3/public/images/wrist-watches.jpg
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'tailwindcss'
2 | import { withUt } from 'uploadthing/tw'
3 |
4 | const config: Config = withUt({
5 | darkMode: ['class'],
6 | content: [
7 | './pages/**/*.{js,ts,jsx,tsx,mdx}',
8 | './components/**/*.{js,ts,jsx,tsx,mdx}',
9 | './app/**/*.{js,ts,jsx,tsx,mdx}',
10 | ],
11 | theme: {
12 | extend: {
13 | colors: {
14 | background: 'hsl(var(--background))',
15 | foreground: 'hsl(var(--foreground))',
16 | card: {
17 | DEFAULT: 'hsl(var(--card))',
18 | foreground: 'hsl(var(--card-foreground))',
19 | },
20 | popover: {
21 | DEFAULT: 'hsl(var(--popover))',
22 | foreground: 'hsl(var(--popover-foreground))',
23 | },
24 | primary: {
25 | DEFAULT: 'hsl(var(--primary))',
26 | foreground: 'hsl(var(--primary-foreground))',
27 | },
28 | secondary: {
29 | DEFAULT: 'hsl(var(--secondary))',
30 | foreground: 'hsl(var(--secondary-foreground))',
31 | },
32 | muted: {
33 | DEFAULT: 'hsl(var(--muted))',
34 | foreground: 'hsl(var(--muted-foreground))',
35 | },
36 | accent: {
37 | DEFAULT: 'hsl(var(--accent))',
38 | foreground: 'hsl(var(--accent-foreground))',
39 | },
40 | destructive: {
41 | DEFAULT: 'hsl(var(--destructive))',
42 | foreground: 'hsl(var(--destructive-foreground))',
43 | },
44 | border: 'hsl(var(--border))',
45 | input: 'hsl(var(--input))',
46 | ring: 'hsl(var(--ring))',
47 | chart: {
48 | '1': 'hsl(var(--chart-1))',
49 | '2': 'hsl(var(--chart-2))',
50 | '3': 'hsl(var(--chart-3))',
51 | '4': 'hsl(var(--chart-4))',
52 | '5': 'hsl(var(--chart-5))',
53 | },
54 | },
55 | borderRadius: {
56 | lg: 'var(--radius)',
57 | md: 'calc(var(--radius) - 2px)',
58 | sm: 'calc(var(--radius) - 4px)',
59 | },
60 | },
61 | },
62 | plugins: [require('tailwindcss-animate')],
63 | }) satisfies Config
64 |
65 | export default config
66 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------
/types/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | CarouselSchema,
3 | CartSchema,
4 | DeliveryDateSchema,
5 | OrderInputSchema,
6 | OrderItemSchema,
7 | PaymentMethodSchema,
8 | ProductInputSchema,
9 | ReviewInputSchema,
10 | SettingInputSchema,
11 | ShippingAddressSchema,
12 | SiteCurrencySchema,
13 | SiteLanguageSchema,
14 | UserInputSchema,
15 | UserNameSchema,
16 | UserSignInSchema,
17 | UserSignUpSchema,
18 | WebPageInputSchema,
19 | } from '@/lib/validator'
20 | import { z } from 'zod'
21 |
22 | export type IReviewInput = z.infer
23 | export type IReviewDetails = IReviewInput & {
24 | _id: string
25 | createdAt: string
26 | user: {
27 | name: string
28 | }
29 | }
30 | export type IProductInput = z.infer
31 |
32 | export type Data = {
33 | settings: ISettingInput[]
34 | webPages: IWebPageInput[]
35 | users: IUserInput[]
36 | products: IProductInput[]
37 | reviews: {
38 | title: string
39 | rating: number
40 | comment: string
41 | }[]
42 | headerMenus: {
43 | name: string
44 | href: string
45 | }[]
46 | carousels: {
47 | image: string
48 | url: string
49 | title: string
50 | buttonCaption: string
51 | isPublished: boolean
52 | }[]
53 | }
54 | // Order
55 | export type IOrderInput = z.infer
56 | export type IOrderList = IOrderInput & {
57 | _id: string
58 | user: {
59 | name: string
60 | email: string
61 | }
62 | createdAt: Date
63 | }
64 | export type OrderItem = z.infer
65 | export type Cart = z.infer
66 | export type ShippingAddress = z.infer
67 |
68 | // user
69 | export type IUserInput = z.infer
70 | export type IUserSignIn = z.infer
71 | export type IUserSignUp = z.infer
72 | export type IUserName = z.infer
73 |
74 | // webpage
75 | export type IWebPageInput = z.infer
76 |
77 | // setting
78 | export type ICarousel = z.infer
79 | export type ISettingInput = z.infer
80 | export type ClientSetting = ISettingInput & {
81 | currency: string
82 | }
83 | export type SiteLanguage = z.infer
84 | export type SiteCurrency = z.infer
85 | export type PaymentMethod = z.infer
86 | export type DeliveryDate = z.infer
87 |
--------------------------------------------------------------------------------