├── .eslintrc.json
├── app
├── globals.css
├── favicon.ico
├── admin
│ ├── page.tsx
│ ├── order
│ │ └── page.tsx
│ ├── layout.tsx
│ ├── create
│ │ └── page.tsx
│ └── manage
│ │ └── page.tsx
├── cart
│ └── page.tsx
├── components
│ ├── WarningText.tsx
│ ├── containers
│ │ ├── PageContainer.tsx
│ │ └── AuthContainer.tsx
│ ├── footer
│ │ └── Footer.tsx
│ ├── navbar
│ │ ├── HamburgerMenu.tsx
│ │ ├── Logo.tsx
│ │ ├── Search.tsx
│ │ ├── CardCount.tsx
│ │ ├── Navbar.tsx
│ │ └── User.tsx
│ ├── general
│ │ ├── Avatar.tsx
│ │ ├── Heading.tsx
│ │ ├── Checkbox.tsx
│ │ ├── ChoiceInput.tsx
│ │ ├── Input.tsx
│ │ ├── Button.tsx
│ │ └── Counter.tsx
│ ├── home
│ │ ├── Banner.tsx
│ │ ├── Products.tsx
│ │ ├── Category.tsx
│ │ └── ProductCard.tsx
│ ├── admin
│ │ ├── AdminSidebarItem.tsx
│ │ ├── AdminSidebar.tsx
│ │ ├── ManageClient.tsx
│ │ └── CreateForm.tsx
│ ├── detail
│ │ ├── Comment.tsx
│ │ └── DetailClient.tsx
│ ├── auth
│ │ ├── LoginClient.tsx
│ │ └── RegisterClient.tsx
│ └── cart
│ │ └── CartClient.tsx
├── page.tsx
├── login
│ └── page.tsx
├── register
│ └── page.tsx
├── product
│ └── [productId]
│ │ └── page.tsx
├── api
│ ├── register
│ │ └── route.ts
│ └── product
│ │ ├── [id]
│ │ └── route.ts
│ │ └── route.ts
├── actions
│ ├── getProductsId.ts
│ ├── getCurrentUser.ts
│ └── getProducts.ts
└── layout.tsx
├── public
├── hepsi.jpeg
├── vercel.svg
└── next.svg
├── postcss.config.js
├── utils
├── TextClip.tsx
└── Products.tsx
├── next.config.js
├── provider
└── CartProvider.tsx
├── libs
├── prismadb.ts
└── firebase.ts
├── .gitignore
├── tailwind.config.ts
├── tsconfig.json
├── package.json
├── README.md
├── pages
└── api
│ └── auth
│ └── [...nextauth].ts
├── prisma
└── schema.prisma
└── hooks
└── useCart.tsx
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 |
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/berkantkaya/next13-youtube-content/HEAD/app/favicon.ico
--------------------------------------------------------------------------------
/public/hepsi.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/berkantkaya/next13-youtube-content/HEAD/public/hepsi.jpeg
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/app/admin/page.tsx:
--------------------------------------------------------------------------------
1 |
2 | const Admin = () => {
3 | return (
4 |
Admin
5 | )
6 | }
7 |
8 | export default Admin
--------------------------------------------------------------------------------
/app/admin/order/page.tsx:
--------------------------------------------------------------------------------
1 |
2 | const Order = () => {
3 | return (
4 | Order
5 | )
6 | }
7 |
8 | export default Order
--------------------------------------------------------------------------------
/utils/TextClip.tsx:
--------------------------------------------------------------------------------
1 |
2 | const textClip = (text: string) => {
3 | if(text.length < 20) return text
4 | return text.substring(0, 15) + "..."
5 | }
6 |
7 | export default textClip
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | images: {
4 | domains: ["firebasestorage.googleapis.com"]
5 | }
6 | }
7 |
8 | module.exports = nextConfig
9 |
--------------------------------------------------------------------------------
/app/cart/page.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import CartClient from '../components/cart/CartClient'
3 |
4 | const Cart = () => {
5 | return (
6 |
7 |
8 |
9 | )
10 | }
11 |
12 | export default Cart
--------------------------------------------------------------------------------
/app/components/WarningText.tsx:
--------------------------------------------------------------------------------
1 |
2 | const WarningText = ({text}: {text: string}) => {
3 | return (
4 | {text}
5 | )
6 | }
7 |
8 | export default WarningText
--------------------------------------------------------------------------------
/app/components/containers/PageContainer.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const PageContainer = ({children}: {children: React.ReactNode}) => {
4 | return (
5 | {children}
6 | )
7 | }
8 |
9 | export default PageContainer
--------------------------------------------------------------------------------
/app/components/footer/Footer.tsx:
--------------------------------------------------------------------------------
1 |
2 | const Footer = () => {
3 | return (
4 | Burasını keyfine göre düzenle...
5 | )
6 | }
7 |
8 | export default Footer
--------------------------------------------------------------------------------
/provider/CartProvider.tsx:
--------------------------------------------------------------------------------
1 | import { CartContextProvider } from "@/hooks/useCart"
2 |
3 | const CartProvider = ({children}:{children:React.ReactNode}) => {
4 | return (
5 | {children}
6 | )
7 | }
8 |
9 | export default CartProvider
--------------------------------------------------------------------------------
/app/components/containers/AuthContainer.tsx:
--------------------------------------------------------------------------------
1 |
2 | const AuthContainer = ({children}: {children: React.ReactNode}) => {
3 | return (
4 | {children}
5 | )
6 | }
7 |
8 | export default AuthContainer
--------------------------------------------------------------------------------
/app/components/navbar/HamburgerMenu.tsx:
--------------------------------------------------------------------------------
1 | import {RxHamburgerMenu} from 'react-icons/rx'
2 | const HamburgerMenu = () => {
3 | return (
4 |
5 |
6 |
7 | )
8 | }
9 |
10 | export default HamburgerMenu
--------------------------------------------------------------------------------
/app/components/general/Avatar.tsx:
--------------------------------------------------------------------------------
1 | import {RxAvatar} from 'react-icons/rx'
2 | interface AvatarProps {
3 | image?: string
4 | }
5 | const Avatar:React.FC = ({image}) => {
6 | if(image) return
7 | return
8 | }
9 |
10 | export default Avatar
--------------------------------------------------------------------------------
/libs/prismadb.ts:
--------------------------------------------------------------------------------
1 | import { PrismaClient } from "@prisma/client";
2 |
3 |
4 |
5 | declare global {
6 | var prisma : PrismaClient | undefined
7 | }
8 |
9 | const client = globalThis.prisma || new PrismaClient();
10 |
11 | if(process.env.NODE_ENV !== "production") globalThis.prisma = client;
12 |
13 | export default client;
--------------------------------------------------------------------------------
/app/page.tsx:
--------------------------------------------------------------------------------
1 | import Banner from "./components/home/Banner";
2 | import Category from "./components/home/Category";
3 | import Products from "./components/home/Products";
4 |
5 | export default function Home() {
6 | return (
7 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/app/admin/layout.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import AdminSidebar from '../components/admin/AdminSidebar'
3 |
4 | const AdminLayout = ({children}:{children:React.ReactNode}) => {
5 | return (
6 |
10 | )
11 | }
12 |
13 | export default AdminLayout
--------------------------------------------------------------------------------
/app/login/page.tsx:
--------------------------------------------------------------------------------
1 | import { getCurrentUser } from "../actions/getCurrentUser";
2 | import LoginClient from "../components/auth/LoginClient"
3 |
4 | const Login = async() => {
5 | const currentUser = await getCurrentUser();
6 | return (
7 |
8 |
9 |
10 | )
11 | }
12 |
13 | export default Login
--------------------------------------------------------------------------------
/app/components/general/Heading.tsx:
--------------------------------------------------------------------------------
1 |
2 |
3 | interface HeadingProps {
4 | center?: boolean
5 | text: string
6 | }
7 | const Heading:React.FC = ({center, text}) => {
8 | return (
9 | {text}
10 | )
11 | }
12 |
13 | export default Heading
--------------------------------------------------------------------------------
/app/components/navbar/Logo.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { useRouter } from "next/navigation"
4 |
5 | const Logo = () => {
6 | const router = useRouter()
7 | return (
8 | router.push('/')} className="bg-orange-700 px-2 py-1 rounded-md text-lg md:text-2xl cursor-pointer">Burada.com
9 | )
10 | }
11 |
12 | export default Logo
--------------------------------------------------------------------------------
/app/components/home/Banner.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image"
2 |
3 | const Banner = () => {
4 | return (
5 |
10 | )
11 | }
12 |
13 | export default Banner
--------------------------------------------------------------------------------
/app/components/navbar/Search.tsx:
--------------------------------------------------------------------------------
1 |
2 | const Search = () => {
3 | return (
4 |
5 |
6 |
7 |
8 | )
9 | }
10 |
11 | export default Search
--------------------------------------------------------------------------------
/app/register/page.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import RegisterClient from '../components/auth/RegisterClient'
3 | import { getCurrentUser } from '../actions/getCurrentUser';
4 |
5 | const Register = async() => {
6 | const currentUser = await getCurrentUser();
7 | return (
8 |
9 |
10 |
11 | )
12 | }
13 |
14 | export default Register
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/app/product/[productId]/page.tsx:
--------------------------------------------------------------------------------
1 | import DetailClient from '@/app/components/detail/DetailClient';
2 | import { products } from '@/utils/Products';
3 | import React from 'react'
4 |
5 | type DetailProps = {
6 | productId? : string
7 | }
8 |
9 | const Detail = ({params}: {params: DetailProps}) => {
10 |
11 | const {productId} = params;
12 |
13 | const product = products.find(product => product.id == productId)
14 |
15 | return (
16 |
17 |
18 |
19 | )
20 | }
21 |
22 | export default Detail
--------------------------------------------------------------------------------
/app/components/general/Checkbox.tsx:
--------------------------------------------------------------------------------
1 | import { FieldValues, UseFormRegister } from "react-hook-form"
2 |
3 | interface CheckboxProps {
4 | id: string
5 | register: UseFormRegister
6 | label: string
7 | }
8 | const Checkbox:React.FC = ({id, register, label}) => {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 | )
16 | }
17 |
18 | export default Checkbox
--------------------------------------------------------------------------------
/app/api/register/route.ts:
--------------------------------------------------------------------------------
1 | import bcrypt from "bcrypt"
2 | import prisma from '@/libs/prismadb'
3 | import { NextResponse } from "next/server";
4 |
5 |
6 | export async function POST(request:Request) {
7 | const body = await request.json();
8 | const {name, email, password} = body;
9 |
10 | const hashedPassword = await bcrypt.hash(password, 10);
11 |
12 | const user = await prisma.user.create({
13 | data: {
14 | name,
15 | email,
16 | hashedPassword
17 | }
18 | })
19 |
20 | return NextResponse.json(user)
21 | }
--------------------------------------------------------------------------------
/libs/firebase.ts:
--------------------------------------------------------------------------------
1 | // Import the functions you need from the SDKs you need
2 | import { initializeApp } from "firebase/app";
3 | // TODO: Add SDKs for Firebase products that you want to use
4 | // https://firebase.google.com/docs/web/setup#available-libraries
5 |
6 | // Your web app's Firebase configuration
7 | const firebaseConfig = {
8 | apiKey: "",
9 | authDomain: "",
10 | projectId: "",
11 | storageBucket: "",
12 | messagingSenderId: "",
13 | appId: ""
14 | };
15 |
16 | // Initialize Firebase
17 | const firebaseApp = initializeApp(firebaseConfig);
18 |
19 | export default firebaseApp;
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'tailwindcss'
2 |
3 | const config: Config = {
4 | content: [
5 | './pages/**/*.{js,ts,jsx,tsx,mdx}',
6 | './components/**/*.{js,ts,jsx,tsx,mdx}',
7 | './app/**/*.{js,ts,jsx,tsx,mdx}',
8 | ],
9 | theme: {
10 | extend: {
11 | backgroundImage: {
12 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
13 | 'gradient-conic':
14 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
15 | },
16 | },
17 | },
18 | plugins: [],
19 | }
20 | export default config
21 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/components/admin/AdminSidebarItem.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link"
2 | import { IconType } from "react-icons"
3 |
4 |
5 | interface AdminSidebarItem {
6 | selected?: boolean
7 | name: string,
8 | icon: IconType,
9 | url: string
10 | }
11 | const AdminSidebarItem:React.FC = ({selected,name,icon:Icon,url}) => {
12 | return (
13 |
14 |
15 | {name}
16 |
17 | )
18 | }
19 |
20 | export default AdminSidebarItem
--------------------------------------------------------------------------------
/app/api/product/[id]/route.ts:
--------------------------------------------------------------------------------
1 | import prisma from '@/libs/prismadb'
2 | import { getCurrentUser } from "@/app/actions/getCurrentUser";
3 | import { NextResponse } from "next/server";
4 |
5 |
6 |
7 | export async function DELETE(
8 | request: Request, {params} : {params: {id : string}}
9 | ) {
10 |
11 | const currentUser = await getCurrentUser();
12 |
13 | if(!currentUser || currentUser.role !== "ADMIN"){
14 | return NextResponse.error()
15 | }
16 |
17 | const product = await prisma.product.delete({
18 | where: {
19 | id: params.id
20 | }
21 | })
22 | return NextResponse.json(product)
23 | }
--------------------------------------------------------------------------------
/app/components/navbar/CardCount.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import UseCart from '@/hooks/useCart'
3 | import { useRouter } from 'next/navigation'
4 | import {MdShoppingBasket} from 'react-icons/md'
5 | const CardCount = () => {
6 | const {cartPrdcts} = UseCart()
7 | const router = useRouter();
8 | return (
9 | router.push('/cart')} className="hidden md:flex relative">
10 |
11 |
{cartPrdcts?.length}
12 |
13 | )
14 | }
15 |
16 | export default CardCount
--------------------------------------------------------------------------------
/app/admin/create/page.tsx:
--------------------------------------------------------------------------------
1 | import { getCurrentUser } from "@/app/actions/getCurrentUser"
2 | import WarningText from "@/app/components/WarningText"
3 | import CreateForm from "@/app/components/admin/CreateForm"
4 | import AuthContainer from "@/app/components/containers/AuthContainer"
5 |
6 | const CreateProduct = async() => {
7 | const currentUser = await getCurrentUser()
8 |
9 | if(!currentUser || currentUser.role !== "ADMIN"){
10 | return (
11 |
12 | )
13 | }
14 | return (
15 |
16 |
17 |
18 | )
19 | }
20 |
21 | export default CreateProduct
--------------------------------------------------------------------------------
/app/components/general/ChoiceInput.tsx:
--------------------------------------------------------------------------------
1 | import { IconType } from "react-icons"
2 |
3 | interface ChoiceInputProps {
4 | text: string
5 | icon: IconType
6 | onClick: (value: string) => void
7 | selected?: boolean
8 | }
9 | const ChoiceInput:React.FC = ({text, icon: Icon , onClick, selected}) => {
10 | return (
11 | onClick(text)} className={`my-3 cursor-pointer px-4 py-2 rounded-md flex items-center gap-2 justify-center h-16 border ${selected ? "border-black" : "border-gray-200"}`}>
12 |
13 | {text}
14 |
15 | )
16 | }
17 |
18 | export default ChoiceInput
--------------------------------------------------------------------------------
/app/components/navbar/Navbar.tsx:
--------------------------------------------------------------------------------
1 | import { getCurrentUser } from "@/app/actions/getCurrentUser"
2 | import CardCount from "./CardCount"
3 | import HamburgerMenu from "./HamburgerMenu"
4 | import Logo from "./Logo"
5 | import Search from "./Search"
6 | import User from "./User"
7 |
8 | const Navbar = async () => {
9 | const currentUser = await getCurrentUser();
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | )
19 | }
20 |
21 | export default Navbar
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
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 |
--------------------------------------------------------------------------------
/app/components/general/Input.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { FieldErrors, FieldValues, UseFormRegister } from "react-hook-form"
4 |
5 | interface InputProps{
6 | id: string
7 | placeholder: string
8 | disabled?: boolean
9 | type: string
10 | required?: boolean
11 | register: UseFormRegister
12 | errors: FieldErrors
13 | }
14 | const Input:React.FC = ({
15 | id, placeholder, disabled, type, required, register, errors
16 | }) => {
17 | return (
18 |
19 | )
20 | }
21 |
22 | export default Input
--------------------------------------------------------------------------------
/app/actions/getProductsId.ts:
--------------------------------------------------------------------------------
1 | import prisma from '@/libs/prismadb'
2 | import { products } from './../../utils/Products';
3 | interface IParams {
4 | productId?: string
5 | }
6 | export default async function getProductsId(params:IParams) {
7 | const {productId} = params;
8 | const product = await prisma.product.findUnique({
9 | where: {
10 | id: productId
11 | },
12 | include:{
13 | reviews:{
14 | include:{
15 | user: true
16 | },
17 | orderBy:{
18 | createdDate: 'desc'
19 | }
20 | }
21 | }
22 | })
23 | if(!product){
24 | return null
25 | }
26 | return product
27 | }
--------------------------------------------------------------------------------
/app/components/general/Button.tsx:
--------------------------------------------------------------------------------
1 | import { IconType } from "react-icons";
2 |
3 |
4 | interface ButtonProps {
5 | text: string
6 | onClick : (e: React.MouseEvent) => void;
7 | small?: boolean
8 | outline? : boolean
9 | icon?: IconType
10 | disabled?: boolean
11 | }
12 | const Button:React.FC = ({text, onClick, small, outline, disabled, icon:Icon}) => {
13 | return (
14 |
18 |
19 | )
20 | }
21 |
22 | export default Button
--------------------------------------------------------------------------------
/app/components/general/Counter.tsx:
--------------------------------------------------------------------------------
1 | import { CardProductProps } from "../detail/DetailClient";
2 |
3 | interface CounterProps {
4 | cardProduct : CardProductProps,
5 | increaseFunc: () => void;
6 | decreaseFunc: () => void;
7 | }
8 | const Counter:React.FC = ({cardProduct, increaseFunc, decreaseFunc}) => {
9 |
10 | const buttonStyle = "w-8 h-8 border flex items-center justify-center text-lg rounded-md"
11 | return (
12 |
13 |
-
14 |
{cardProduct?.quantity}
15 |
+
16 |
17 | )
18 | }
19 |
20 | export default Counter
--------------------------------------------------------------------------------
/app/components/detail/Comment.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import {RxAvatar} from 'react-icons/rx'
3 | import Avatar from "../general/Avatar"
4 | import { Rating } from '@mui/material'
5 |
6 | const Comment = ({prd}: {prd : any}) => {
7 |
8 | return (
9 |
10 | {/*
*/}
11 |
12 |
13 |
14 |
{prd?.user?.name}
15 |
16 |
17 |
18 | {prd.comment} asdasd asdasdasd asdasdas dasdasdasd
19 |
20 | )
21 | }
22 |
23 | export default Comment
--------------------------------------------------------------------------------
/app/admin/manage/page.tsx:
--------------------------------------------------------------------------------
1 | import { getCurrentUser } from "@/app/actions/getCurrentUser"
2 | import getProducts from "@/app/actions/getProducts"
3 | import WarningText from "@/app/components/WarningText"
4 | import ManageClient from "@/app/components/admin/ManageClient"
5 | import AuthContainer from "@/app/components/containers/AuthContainer"
6 |
7 | const Manage = async() => {
8 | const products = await getProducts({category: null})
9 | const currentUser = await getCurrentUser()
10 |
11 | if(!currentUser || currentUser.role !== "ADMIN"){
12 | return (
13 |
14 | )
15 | }
16 | return (
17 |
18 |
19 |
20 | )
21 | }
22 |
23 | export default Manage
--------------------------------------------------------------------------------
/app/components/home/Products.tsx:
--------------------------------------------------------------------------------
1 | import Heading from "../general/Heading"
2 | import ProductCard from "./ProductCard"
3 | import getProducts, { IProductParams } from "@/app/actions/getProducts"
4 | import WarningText from "../WarningText"
5 | import { products } from "@/utils/Products"
6 |
7 |
8 | const Products = () => {
9 |
10 | if(products.length == 0){
11 | return
12 | }
13 | return (
14 |
15 |
16 |
17 | {
18 | products.map(product => (
19 |
20 | ))
21 | }
22 |
23 |
24 | )
25 | }
26 |
27 | export default Products
--------------------------------------------------------------------------------
/app/api/product/route.ts:
--------------------------------------------------------------------------------
1 | import { getCurrentUser } from '@/app/actions/getCurrentUser';
2 | import prisma from '@/libs/prismadb'
3 | import { NextResponse } from "next/server";
4 |
5 |
6 | export async function POST(request:Request) {
7 | const currentUser = await getCurrentUser();
8 |
9 | if(!currentUser || currentUser.role !== "ADMIN"){
10 | return NextResponse.error()
11 | }
12 | const body = await request.json();
13 | const {name, description, brand, category, price, inStock, image} = body;
14 |
15 |
16 | const product = await prisma.product.create({
17 | data: {
18 | name,
19 | description,
20 | brand,
21 | category,
22 | price: parseFloat(price),
23 | inStock,
24 | image
25 | }
26 | })
27 |
28 | return NextResponse.json(product)
29 | }
--------------------------------------------------------------------------------
/app/components/home/Category.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | const Category = () => {
3 | const categoryList = [
4 | {
5 | name: "Ayakkabı"
6 | },
7 | {
8 | name: "Ayakkabı"
9 | },
10 | {
11 | name: "Ayakkabı"
12 | },
13 | {
14 | name: "Ayakkabı"
15 | },
16 | {
17 | name: "Ayakkabı"
18 | },
19 | {
20 | name: "Ayakkabı"
21 | },
22 | {
23 | name: "Ayakkabı"
24 | },
25 | ]
26 | return (
27 |
28 | {
29 | categoryList.map((category, index) => (
30 |
{category.name}
31 | ))
32 | }
33 |
34 | )
35 | }
36 |
37 | export default Category
--------------------------------------------------------------------------------
/app/actions/getCurrentUser.ts:
--------------------------------------------------------------------------------
1 | import { authOptions } from "@/pages/api/auth/[...nextauth]";
2 | import { getServerSession } from "next-auth";
3 | import prisma from '@/libs/prismadb'
4 |
5 |
6 | export async function getSession() {
7 | return await getServerSession(authOptions)
8 | }
9 |
10 | export async function getCurrentUser() {
11 | try {
12 | const session = await getSession();
13 |
14 | if (!session?.user?.email) {
15 | return null
16 | }
17 | const currentUser = await prisma.user.findUnique({
18 | where: {
19 | email: session?.user?.email
20 | }
21 | })
22 | if (!currentUser) {
23 | return null
24 | }
25 |
26 | return {
27 | ...currentUser,
28 | createdAt: currentUser.createdAt.toISOString(),
29 | updatedAt: currentUser.updateAt.toISOString(),
30 | emailVerified: currentUser.emailVerified?.toISOString() || null
31 | }
32 |
33 |
34 | } catch (error: any) {
35 | return null
36 | }
37 | }
--------------------------------------------------------------------------------
/app/components/home/ProductCard.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import textClip from "@/utils/TextClip"
3 | import { Rating } from "@mui/material"
4 | import Image from "next/image"
5 | import { useRouter } from "next/navigation"
6 |
7 |
8 | const ProductCard = ({product}: {product: any}) => {
9 | const router = useRouter()
10 |
11 | let productRating = product?.reviews?.reduce((acc: number, item: any) => acc + item.rating, 0) / product?.reviews?.length
12 | return (
13 | router.push(`product/${product.id}`)} className="w-[240px] cursor-pointer flex flex-col flex-1 shadow-lg p-2 rounded-md">
14 |
15 |
16 |
17 |
18 |
{textClip(product.name)}
19 |
20 |
{product.price} ₺
21 |
22 |
23 | )
24 | }
25 |
26 | export default ProductCard
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from 'next'
2 | import { Inter } from 'next/font/google'
3 | import './globals.css'
4 | import Navbar from './components/navbar/Navbar'
5 | import Footer from './components/footer/Footer'
6 | import CartProvider from '@/provider/CartProvider'
7 | import { Toaster } from 'react-hot-toast'
8 |
9 | const inter = Inter({ subsets: ['latin'] })
10 |
11 | export const metadata: Metadata = {
12 | title: 'Burada.com',
13 | description: 'Generated by create next app',
14 | }
15 |
16 | export default function RootLayout({
17 | children,
18 | }: {
19 | children: React.ReactNode
20 | }) {
21 | return (
22 |
23 |
24 |
28 |
29 |
30 |
31 | {children}
32 |
33 |
34 |
35 |
36 |
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@auth/prisma-adapter": "^1.0.5",
13 | "@emotion/react": "^11.11.1",
14 | "@emotion/styled": "^11.11.0",
15 | "@mui/material": "^5.14.17",
16 | "@mui/x-data-grid": "^6.18.1",
17 | "@next-auth/prisma-adapter": "^1.0.7",
18 | "@prisma/client": "^5.5.2",
19 | "@types/bcrypt": "^5.0.2",
20 | "axios": "^1.6.1",
21 | "bcrypt": "^5.1.1",
22 | "firebase": "^10.6.0",
23 | "next": "14.0.1",
24 | "next-auth": "^4.24.4",
25 | "react": "^18",
26 | "react-dom": "^18",
27 | "react-hook-form": "^7.48.2",
28 | "react-hot-toast": "^2.4.1",
29 | "react-icons": "^4.11.0"
30 | },
31 | "devDependencies": {
32 | "@types/node": "^20",
33 | "@types/react": "^18",
34 | "@types/react-dom": "^18",
35 | "autoprefixer": "^10.0.1",
36 | "eslint": "^8",
37 | "eslint-config-next": "14.0.1",
38 | "postcss": "^8",
39 | "prisma": "^5.5.2",
40 | "tailwindcss": "^3.3.0",
41 | "typescript": "^5"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/components/admin/AdminSidebar.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import { usePathname } from "next/navigation";
3 | import AdminSidebarItem from "./AdminSidebarItem"
4 | import { MdDashboard, MdBorderOuter,MdOutlineCreate } from "react-icons/md";
5 |
6 | const AdminSidebar = () => {
7 | const pathname = usePathname();
8 | const adminPanel = [
9 | {
10 | name: "Özetler",
11 | icon: MdDashboard,
12 | url: "/admin"
13 | },
14 | {
15 | name: "Ürün Olustur",
16 | icon: MdOutlineCreate,
17 | url: "/admin/create"
18 | },
19 | {
20 | name: "Ürünleri Yönet",
21 | icon: MdOutlineCreate,
22 | url: "/admin/manage"
23 | },
24 | {
25 | name: "Siparişlerim",
26 | icon: MdBorderOuter,
27 | url: "/admin/order"
28 | },
29 | ]
30 | return (
31 |
32 |
33 | {
34 | adminPanel.map((admin,i) => (
35 |
36 | ))
37 | }
38 |
39 |
40 | )
41 | }
42 |
43 | export default AdminSidebar
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 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).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | # or
12 | pnpm dev
13 | # or
14 | bun dev
15 | ```
16 |
17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18 |
19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20 |
21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
22 |
23 | ## Learn More
24 |
25 | To learn more about Next.js, take a look at the following resources:
26 |
27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29 |
30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
31 |
32 | ## Deploy on Vercel
33 |
34 | The 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.
35 |
36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
37 |
--------------------------------------------------------------------------------
/app/actions/getProducts.ts:
--------------------------------------------------------------------------------
1 | import prisma from '@/libs/prismadb'
2 |
3 | export interface IProductParams{
4 | category?: string | null
5 | search?: string | null
6 | }
7 |
8 | export default async function getProducts(params:IProductParams) {
9 | try {
10 | const {category, search} = params;
11 | let searchString = search;
12 | if(!search){
13 | searchString = ""
14 | }
15 | let query: any = {}
16 |
17 | if(category){
18 | query.category = category
19 | }
20 | const products = await prisma.product.findMany({
21 | where:{
22 | ...query,
23 | OR: [
24 | {
25 | name: {
26 | contains: searchString,
27 | mode: 'insensitive'
28 | },
29 | description: {
30 | contains: searchString,
31 | mode: 'insensitive'
32 | },
33 | }
34 | ]
35 | },
36 | include: {
37 | reviews:{
38 | include:{
39 | user: true
40 | },
41 | orderBy:{
42 | createdDate: "desc"
43 | }
44 | }
45 | }
46 | })
47 |
48 | return products;
49 | } catch (error: any) {
50 | throw new Error(error)
51 | }
52 | }
--------------------------------------------------------------------------------
/pages/api/auth/[...nextauth].ts:
--------------------------------------------------------------------------------
1 | import NextAuth, { AuthOptions } from "next-auth"
2 | import GoogleProvider from "next-auth/providers/google"
3 | // import { PrismaAdapter } from "@auth/prisma-adapter"
4 | import { PrismaAdapter } from "@next-auth/prisma-adapter"
5 | import { PrismaClient } from "@prisma/client"
6 | import prisma from '@/libs/prismadb'
7 | import CredentialsProvider from "next-auth/providers/credentials";
8 | import bcrypt from 'bcrypt'
9 |
10 |
11 | export const authOptions : AuthOptions = {
12 | adapter: PrismaAdapter(prisma),
13 | providers: [
14 | GoogleProvider({
15 | clientId: process.env.GOOGLE_CLIENT_ID as string,
16 | clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
17 | }),
18 | CredentialsProvider({
19 | name: "credentials",
20 | credentials: {
21 | email: { label: "email", type: "text"},
22 | password: { label: "password", type: "password" }
23 | },
24 | async authorize(credentials, req) {
25 | if(!credentials?.email || !credentials.password){
26 | throw new Error('Gecersiz mail ya da parola...')
27 | }
28 | const user = await prisma.user.findUnique({
29 | where: {
30 | email: credentials.email
31 | }
32 | })
33 |
34 | if(!user || !user.hashedPassword){
35 | throw new Error('Gecersiz mail ya da parola...')
36 | }
37 |
38 | const comparePassword = await bcrypt.compare(credentials.password, user.hashedPassword)
39 |
40 | if(!comparePassword){
41 | throw new Error('Yanlıs parola...')
42 | }
43 |
44 | return user
45 |
46 | }
47 | })
48 |
49 | ],
50 | pages : {
51 | signIn: "/login"
52 | },
53 | debug: process.env.NODE_ENV === "development",
54 | session: {
55 | strategy: "jwt"
56 | },
57 | secret:process.env.NEXTAUTH_SECRET
58 | }
59 |
60 |
61 | export default NextAuth(authOptions)
--------------------------------------------------------------------------------
/prisma/schema.prisma:
--------------------------------------------------------------------------------
1 | datasource db {
2 | provider = "mongodb"
3 | url = env("DATABASE_URL")
4 | }
5 |
6 | generator client {
7 | provider = "prisma-client-js"
8 | }
9 |
10 | model Account {
11 | id String @id @default(auto()) @map("_id") @db.ObjectId
12 | userId String @db.ObjectId
13 | type String
14 | provider String
15 | providerAccountId String
16 | refresh_token String? @db.String
17 | access_token String? @db.String
18 | expires_at Int?
19 | token_type String?
20 | scope String?
21 | id_token String? @db.String
22 | session_state String?
23 |
24 | user User @relation(fields: [userId], references: [id], onDelete: Cascade)
25 |
26 | @@unique([provider, providerAccountId])
27 | }
28 |
29 | model Product{
30 | id String @id @default(auto()) @map("_id") @db.ObjectId
31 | name String
32 | description String
33 | price Float
34 | brand String
35 | category String
36 | inStock Boolean
37 | image String
38 | reviews Review[]
39 | }
40 |
41 | model Review{
42 | id String @id @default(auto()) @map("_id") @db.ObjectId
43 | userId String @db.ObjectId
44 | productId String @db.ObjectId
45 | rating Int
46 | comment String
47 | createdDate DateTime @default(now())
48 |
49 | product Product @relation(fields: [productId], references: [id])
50 | user User @relation(fields: [userId], references: [id], onDelete: Cascade)
51 |
52 | }
53 |
54 |
55 | model User {
56 | id String @id @default(auto()) @map("_id") @db.ObjectId
57 | name String?
58 | email String? @unique
59 | emailVerified DateTime?
60 | image String?
61 | hashedPassword String?
62 | createdAt DateTime @default(now())
63 | updateAt DateTime @updatedAt
64 | role Role @default(USER)
65 |
66 | accounts Account[]
67 | reviews Review[]
68 | }
69 |
70 | enum Role{
71 | USER
72 | ADMIN
73 | }
74 |
75 |
--------------------------------------------------------------------------------
/app/components/navbar/User.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import { User } from "@prisma/client"
3 | import { useState } from "react"
4 | import {AiOutlineUser} from 'react-icons/ai'
5 | import { signOut } from "next-auth/react"
6 | import { useRouter } from "next/navigation"
7 |
8 | interface UserProps{
9 | currentUser: User | null | undefined
10 | }
11 | const User:React.FC = ({currentUser}) => {
12 | const router = useRouter()
13 | const [openMenu, setOpenMenu] = useState(false)
14 |
15 | console.log(currentUser, "currentUser")
16 |
17 | const menuFunc = (type:any) => {
18 | setOpenMenu(false)
19 | if(type == "logout"){
20 | signOut();
21 | router.push("/login")
22 | }else if(type == "register"){
23 | router.push("/register")
24 | }else{
25 | router.push("/login")
26 | }
27 |
28 | }
29 |
30 | return (
31 |
32 |
setOpenMenu(!openMenu)} className="flex items-center gap-1 cursor-pointer">
33 |
34 |
{currentUser ? currentUser.name : "User"}
35 |
36 | {
37 | openMenu && (
38 |
39 | {
40 | currentUser ? (
41 |
42 |
router.push('/admin')} className="text-slate-600">Admin
43 |
menuFunc("logout")} className="text-slate-600">Logout
44 |
45 | ) : (
46 |
47 |
menuFunc("register")} className="text-slate-600">Register
48 |
menuFunc("login")} className="text-slate-600">Login
49 |
50 | )
51 | }
52 |
53 | )
54 | }
55 |
56 | )
57 | }
58 |
59 | export default User
--------------------------------------------------------------------------------
/app/components/auth/LoginClient.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import { useForm, SubmitHandler, FieldValues } from "react-hook-form"
3 | import {FaGoogle} from 'react-icons/fa'
4 |
5 | import AuthContainer from "../containers/AuthContainer"
6 | import Button from "../general/Button"
7 | import Heading from "../general/Heading"
8 | import Input from "../general/Input"
9 | import Link from "next/link"
10 | import { signIn } from "next-auth/react"
11 | import { useRouter } from "next/navigation"
12 | import toast from "react-hot-toast"
13 | import { User } from "@prisma/client"
14 | import { useEffect } from "react"
15 |
16 | interface LoginClientProps{
17 | currentUser: User | null | undefined
18 | }
19 |
20 | const LoginClient:React.FC = ({currentUser}) => {
21 | const router = useRouter();
22 | const {
23 | register,
24 | handleSubmit,
25 | watch,
26 | formState: { errors },
27 | } = useForm()
28 |
29 | const onSubmit: SubmitHandler = (data) => {
30 | signIn('credentials', {
31 | ...data,
32 | redirect: false
33 | }).then((callback) => {
34 | if(callback?.ok){
35 | router.push('/cart')
36 | router.refresh();
37 | toast.success('Login İşlemi Basarılı...')
38 | }
39 |
40 | if(callback?.error){
41 | toast.error(callback.error)
42 | }
43 | })
44 | }
45 |
46 | useEffect(() => {
47 | if(currentUser){
48 | router.push('/cart')
49 | router.refresh();
50 | }
51 | }, [])
52 | return (
53 |
54 |
55 |
56 |
57 |
58 |
63 |
64 | )
65 | }
66 |
67 | export default LoginClient
--------------------------------------------------------------------------------
/app/components/cart/CartClient.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import UseCart from "@/hooks/useCart"
4 | import PageContainer from "../containers/PageContainer"
5 | import Image from "next/image"
6 | import Button from "../general/Button"
7 | import { CardProductProps } from "../detail/DetailClient"
8 | import Counter from "../general/Counter"
9 |
10 | const CartClient = () => {
11 | const {cartPrdcts, removeFromCart,removeCart,addToBasketIncrease,addToBasketDecrease} = UseCart()
12 |
13 | console.log(cartPrdcts, "cartPrdcts")
14 | if(!cartPrdcts || cartPrdcts.length == 0){
15 | return Sepetinizde ürün bulunmamaktadır...
16 | }
17 |
18 | let cartPrdctsTotal = cartPrdcts.reduce((acc: any, item:CardProductProps) => acc + item.quantity * item.price,0)
19 | return (
20 |
21 |
22 |
23 |
Ürün Resmi
24 |
Ürün Adı
25 |
Ürün Miktarı
26 |
Ürün Fiyatı
27 |
28 |
29 |
30 | {
31 | cartPrdcts.map(cart => (
32 |
33 |
34 |
35 |
36 |
{cart.name}
37 |
38 | addToBasketIncrease(cart)} decreaseFunc={() => addToBasketDecrease(cart)}/>
39 |
40 |
{cart.price} ₺
41 |
42 | removeFromCart(cart)}/>
43 |
44 |
45 | ))
46 | }
47 |
48 |
49 |
removeCart()} className="w-1/5 underline text-sm">Sepeti Sil
50 |
{cartPrdctsTotal} ₺
51 |
52 |
53 |
54 | )
55 | }
56 |
57 | export default CartClient
--------------------------------------------------------------------------------
/app/components/auth/RegisterClient.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import { useForm, SubmitHandler, FieldValues } from "react-hook-form"
3 | import {FaGoogle} from 'react-icons/fa'
4 |
5 | import AuthContainer from "../containers/AuthContainer"
6 | import Button from "../general/Button"
7 | import Heading from "../general/Heading"
8 | import Input from "../general/Input"
9 | import Link from "next/link"
10 | import axios from "axios"
11 | import toast from "react-hot-toast"
12 | import { signIn } from "next-auth/react"
13 | import { useRouter } from "next/navigation"
14 | import { User } from "@prisma/client"
15 | import { useEffect } from "react"
16 |
17 |
18 | interface RegisterClientProps{
19 | currentUser: User | null | undefined
20 | }
21 | const RegisterClient:React.FC = ({currentUser}) => {
22 | const router = useRouter();
23 | const {
24 | register,
25 | handleSubmit,
26 | watch,
27 | formState: { errors },
28 | } = useForm()
29 |
30 | const onSubmit: SubmitHandler = (data) => {
31 | axios.post('/api/register', data).then(() => {
32 | toast.success('Kullanıcı Olusturuldu...')
33 | signIn('credentials', {
34 | email: data.email,
35 | password: data.password,
36 | redirect: false
37 | }).then((callback) => {
38 | if(callback?.ok){
39 | router.push('/cart')
40 | router.refresh();
41 | toast.success('Login İşlemi Basarılı...')
42 | }
43 |
44 | if(callback?.error){
45 | toast.error(callback.error)
46 | }
47 | })
48 | })
49 | }
50 |
51 | useEffect(() => {
52 | if(currentUser){
53 | router.push('/cart')
54 | router.refresh();
55 | }
56 | }, [])
57 | return (
58 |
59 |
69 |
70 | )
71 | }
72 |
73 | export default RegisterClient
--------------------------------------------------------------------------------
/app/components/admin/ManageClient.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import firebaseApp from "@/libs/firebase"
3 | import { DataGrid, GridColDef } from "@mui/x-data-grid"
4 | import { Product } from "@prisma/client"
5 | import axios from "axios"
6 | import { deleteObject, getStorage, ref } from "firebase/storage"
7 | import { useRouter } from "next/navigation"
8 | import { useCallback } from "react"
9 | import toast from "react-hot-toast"
10 |
11 | interface ManageClientProps {
12 | products: Product[]
13 | }
14 | const ManageClient:React.FC = ({products}) => {
15 | const storage = getStorage(firebaseApp)
16 | const router = useRouter()
17 | let rows: any = []
18 |
19 | if(products){
20 | rows = products.map((product) => {
21 | return {
22 | id: product.id,
23 | name: product.name,
24 | price: product.price,
25 | category: product.category,
26 | brand: product.brand,
27 | inStock: product.inStock,
28 | image: product.image
29 | }
30 | })
31 | }
32 |
33 | const columns: GridColDef[] = [
34 | {field: "id", headerName: "ID", width: 200},
35 | {field: "name", headerName: "Name", width: 150},
36 | {field: "price", headerName: "Price", width: 100},
37 | {field: "category", headerName: "Category", width: 100},
38 | {field: "brand", headerName: "Brand", width: 100},
39 | {field: "inStock",
40 | headerName: "Brand",
41 | width: 100,
42 | renderCell: (params) => {
43 | return (
44 |
45 | {params.row.inStock == true ? "Stokta Mevcut" : "Stokta Mevcut Değil"}
46 |
47 | )
48 | }
49 | },
50 | {field: "actions",
51 | headerName: "Action",
52 | width: 100,
53 | renderCell: (params) => {
54 | return (
55 | handleDelete(params.row.id, params.row.image)} className="mx-4 text-red-500 cursor-pointer ">
56 | Sil
57 |
58 | )
59 | }
60 | },
61 | ]
62 |
63 | const handleDelete = useCallback(async (id: string, image: any) => {
64 | toast.success('sildirme işlemi icin bekleyin...')
65 | const handleDeleteImg = async() => {
66 | try {
67 | const imageRef = ref(storage, image)
68 | await deleteObject(imageRef)
69 | } catch (error) {
70 | return console.log("bir hata mevcut", error)
71 | }
72 | }
73 | await handleDeleteImg();
74 | axios.delete(`/api/product/${id}`)
75 | .then(() => {
76 | toast.success('sildirme işlemi basarılı')
77 | router.refresh();
78 | })
79 | .catch((error: any) => {
80 | console.log(error)
81 | })
82 | }, [])
83 | return (
84 |
85 |
96 |
97 | )
98 | }
99 |
100 | export default ManageClient
--------------------------------------------------------------------------------
/app/components/detail/DetailClient.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import Image from "next/image"
4 | import PageContainer from "../containers/PageContainer"
5 | import Counter from "../general/Counter"
6 | import { useEffect, useState } from "react"
7 | import { Rating } from "@mui/material"
8 | import Button from "../general/Button"
9 | import Comment from "./Comment"
10 | import Heading from "../general/Heading"
11 | import UseCart from "@/hooks/useCart"
12 |
13 |
14 | export type CardProductProps = {
15 | id: string
16 | name: string
17 | description: string
18 | price: number
19 | quantity: number
20 | image: string
21 | inStock: boolean
22 | }
23 |
24 | const DetailClient = ({product}: {product: any}) => {
25 |
26 | const {productCartQty, addToBasket, cartPrdcts} = UseCart();
27 | const [displayButton, setDisplayButton] = useState(false)
28 |
29 | const [cardProduct, setCardProduct] = useState({
30 | id: product.id,
31 | name: product.name,
32 | description: product.description,
33 | price: product.price,
34 | quantity: 1,
35 | image: product.image,
36 | inStock: product.inStock,
37 | })
38 |
39 |
40 | useEffect(() => {
41 | setDisplayButton(false)
42 | let controlDisplay: any = cartPrdcts?.findIndex(cart => cart.id == product.id)
43 | if(controlDisplay > -1){
44 | setDisplayButton(true)
45 | }
46 | },[cartPrdcts])
47 |
48 | const increaseFunc = () => {
49 | if(cardProduct.quantity == 10) return
50 | setCardProduct(prev => ({...prev, quantity: prev.quantity + 1 }))
51 | }
52 | const decreaseFunc = () => {
53 | if(cardProduct.quantity == 1) return
54 | setCardProduct(prev => ({...prev, quantity: prev.quantity - 1 }))
55 | }
56 |
57 | let productRating = product?.reviews?.reduce((acc: number, item: any) => acc + item.rating, 0) / product?.reviews?.length
58 |
59 | return (
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
{product?.name}
68 |
69 |
{product?.description}
70 |
71 |
STOK DURUMU:
72 | {
73 | product.inStock ?
Ürün Stokta Mevcut
:
Ürün Stokta Bulunmamakta
74 | }
75 |
76 |
{product.price} ₺
77 |
78 | {
79 | displayButton ? <>
80 |
{}}/>
81 | > : <>
82 |
83 | addToBasket(cardProduct)}/>
84 | >
85 | }
86 |
87 |
88 |
89 |
90 |
91 |
92 | {
93 | product?.reviews?.map((prd: any) => (
94 |
95 | ))
96 | }
97 |
98 |
99 |
100 | )
101 | }
102 |
103 | export default DetailClient
--------------------------------------------------------------------------------
/hooks/useCart.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import { CardProductProps } from "@/app/components/detail/DetailClient";
3 | import { createContext, useCallback, useContext, useEffect, useState } from "react";
4 | import toast from "react-hot-toast";
5 |
6 | interface CartContextProps {
7 | productCartQty: number
8 | cartPrdcts: CardProductProps[] | null
9 | addToBasket: (product: CardProductProps) => void
10 | addToBasketIncrease: (product: CardProductProps) => void
11 | addToBasketDecrease: (product: CardProductProps) => void
12 | removeFromCart: (product: CardProductProps) => void
13 | removeCart: () => void
14 | }
15 | const CartContext = createContext(null)
16 |
17 |
18 | interface Props{
19 | [propName: string]: any
20 | }
21 | export const CartContextProvider = (props: Props) => {
22 | const [productCartQty, setProductCartQty] = useState(0)
23 | const [cartPrdcts, setCartPrdcts] = useState(null)
24 |
25 |
26 | useEffect(() => {
27 | let getItem: any = localStorage.getItem('cart')
28 | let getItemParse: CardProductProps[] | null = JSON.parse(getItem)
29 | setCartPrdcts(getItemParse)
30 | },[])
31 |
32 | const addToBasketIncrease = useCallback((product: CardProductProps) => {
33 | let updatedCart;
34 | if(product.quantity == 10){
35 | return toast.error('Daha fazla ekleyemezsin...')
36 | }
37 | if(cartPrdcts){
38 | updatedCart = [...cartPrdcts];
39 | const existingItem = cartPrdcts.findIndex(item => item.id === product.id)
40 |
41 | if(existingItem > -1){
42 | updatedCart[existingItem].quantity = ++updatedCart[existingItem].quantity
43 | }
44 | setCartPrdcts(updatedCart)
45 | localStorage.setItem('cart', JSON.stringify(updatedCart))
46 | }
47 | }, [cartPrdcts])
48 |
49 | const addToBasketDecrease = useCallback((product: CardProductProps) => {
50 | let updatedCart;
51 | if(product.quantity == 1){
52 | return toast.error('Daha az ekleyemezsin...')
53 | }
54 | if(cartPrdcts){
55 | updatedCart = [...cartPrdcts];
56 | const existingItem = cartPrdcts.findIndex(item => item.id === product.id)
57 |
58 | if(existingItem > -1){
59 | updatedCart[existingItem].quantity = --updatedCart[existingItem].quantity
60 | }
61 | setCartPrdcts(updatedCart)
62 | localStorage.setItem('cart', JSON.stringify(updatedCart))
63 | }
64 | }, [cartPrdcts])
65 |
66 | const removeCart = useCallback(() => {
67 | setCartPrdcts(null)
68 | toast.success('Sepet Temizlendi...')
69 | localStorage.setItem('cart', JSON.stringify(null))
70 | }, [])
71 |
72 | const addToBasket = useCallback((product: CardProductProps) => {
73 | setCartPrdcts(prev => {
74 | let updatedCart;
75 | if(prev){
76 | updatedCart = [...prev,product]
77 | }else{
78 | updatedCart = [product]
79 | }
80 | toast.success('Ürün Sepete Eklendi...')
81 | localStorage.setItem('cart', JSON.stringify(updatedCart))
82 | return updatedCart
83 | })
84 | }, [cartPrdcts])
85 |
86 | const removeFromCart = useCallback((product: CardProductProps) => {
87 | if(cartPrdcts){
88 | const filteredProducts = cartPrdcts.filter(cart => cart.id !== product.id)
89 |
90 | setCartPrdcts(filteredProducts)
91 | toast.success('Ürün Sepetten Silindi...')
92 | localStorage.setItem('cart', JSON.stringify(filteredProducts))
93 | }
94 | }, [cartPrdcts])
95 |
96 | let value = {
97 | productCartQty,
98 | addToBasket,
99 | cartPrdcts,
100 | removeFromCart,
101 | removeCart,
102 | addToBasketIncrease,
103 | addToBasketDecrease
104 | }
105 | return (
106 |
107 | )
108 | }
109 |
110 |
111 | const UseCart = () => {
112 | const context = useContext(CartContext)
113 | if(context == null){
114 | throw new Error('Bir hata durumu mevcut')
115 | }
116 | return context
117 | }
118 |
119 | export default UseCart
--------------------------------------------------------------------------------
/app/components/admin/CreateForm.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import { FaComputer } from "react-icons/fa6";
3 | import { GiBallerinaShoes } from "react-icons/gi";
4 | import { FaTabletAlt } from "react-icons/fa";
5 | import { CiMicrophoneOn } from "react-icons/ci";
6 | import { getStorage, ref, uploadBytesResumable, getDownloadURL } from "firebase/storage";
7 |
8 |
9 |
10 |
11 | import { FieldValues, SubmitHandler, useForm } from "react-hook-form"
12 | import Heading from "../general/Heading"
13 | import Input from "../general/Input"
14 | import Checkbox from "../general/Checkbox"
15 | import ChoiceInput from "../general/ChoiceInput";
16 | import Button from "../general/Button";
17 | import { useState } from "react";
18 | import firebaseApp from "@/libs/firebase";
19 | import toast from "react-hot-toast";
20 | import axios from "axios";
21 | import { useRouter } from "next/navigation";
22 |
23 | const CreateForm = () => {
24 | const [img, setImg] = useState(null)
25 | const router = useRouter();
26 |
27 | const categoryList = [
28 | {
29 | name: "Bilgisayar",
30 | icon: FaComputer
31 | },
32 | {
33 | name: "Ayakkabı",
34 | icon: GiBallerinaShoes
35 | },
36 | {
37 | name: "Tablet",
38 | icon: FaTabletAlt
39 | },
40 | {
41 | name: "Mikrofon",
42 | icon: CiMicrophoneOn
43 | },
44 | {
45 | name: "Ayakkabı1",
46 | icon: FaComputer
47 | },
48 | {
49 | name: "Ayakkabı2",
50 | icon: FaComputer
51 | },
52 | ]
53 |
54 |
55 | const {
56 | register,
57 | handleSubmit,
58 | setValue,
59 | watch,
60 | formState: { errors },
61 | } = useForm({
62 | defaultValues: {
63 | name: "",
64 | description: "",
65 | brand: "",
66 | category: "",
67 | price: "",
68 | image: "",
69 | inStock: false
70 | }
71 | })
72 |
73 | const onSubmit: SubmitHandler = async (data) => {
74 | console.log("data", data)
75 |
76 | let uploadedImg;
77 |
78 | const handleChange = async () => {
79 | toast.success('Yükleme işlemi basarılı !!!')
80 | try {
81 | const storage = getStorage(firebaseApp);
82 | const storageRef = ref(storage, 'images/shop.jpg');
83 |
84 |
85 | const uploadTask = uploadBytesResumable(storageRef, img);
86 | await new Promise((resolve, reject) => {
87 | uploadTask.on('state_changed',
88 | (snapshot) => {
89 | // Observe state change events such as progress, pause, and resume
90 | // Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded
91 | const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
92 | console.log('Upload is ' + progress + '% done');
93 | switch (snapshot.state) {
94 | case 'paused':
95 | console.log('Upload is paused');
96 | break;
97 | case 'running':
98 | console.log('Upload is running');
99 | break;
100 | }
101 | },
102 | (error) => {
103 | reject(error)
104 | },
105 | () => {
106 | getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => {
107 | console.log('File available at', downloadURL);
108 | uploadedImg =downloadURL;
109 | resolve()
110 | }).catch((error) => {
111 | console.log(error)
112 | });
113 | }
114 | );
115 | })
116 |
117 |
118 | } catch (error) {
119 | console.log(error)
120 | }
121 | }
122 | await handleChange()
123 |
124 | let newData = {...data, image: uploadedImg}
125 |
126 | axios.post('/api/product', newData)
127 | .then(() => {
128 | toast.success('Ürün ekleme işlemi basarılı !!!')
129 | router.refresh();
130 |
131 | }).catch((error) => {
132 | console.log(error, "error")
133 | })
134 |
135 | console.log(newData, "NEWDATAAAA")
136 | }
137 |
138 | const category = watch('category')
139 |
140 | const setCustomValue = (id: string, value: any) => {
141 | setValue(id, value, {
142 | shouldDirty: true,
143 | shouldTouch: true,
144 | shouldValidate: true
145 | })
146 | }
147 |
148 | const onChangeFunc = (e: React.ChangeEvent) => {
149 | if (e.target.files && e.target.files.length > 0) {
150 | setImg(e.target.files[0])
151 | }
152 | }
153 |
154 |
155 | return (
156 |
212 | )
213 | }
214 |
215 | export default CreateForm
--------------------------------------------------------------------------------
/utils/Products.tsx:
--------------------------------------------------------------------------------
1 | export const products = [
2 | {
3 | id: "64a654593e91b8e73a351e9b",
4 | name: "iphone 14",
5 | description: "Short description",
6 | price: 2999,
7 | brand: "apple",
8 | category: "Phone",
9 | inStock: true,
10 | image: "https://firebasestorage.googleapis.com/v0/b/ecommerce-shop-cc542.appspot.com/o/products%2F1688622161445-iphone14-white.png?alt=media&token=fe2065e5-fdfe-4a6f-baa6-380b5fad90b8",
11 | reviews: [],
12 | },
13 | {
14 | id: "64a4ebe300900d44bb50628a",
15 | name: "Logitech MX",
16 | description:
17 | "PERFECT STROKE KEYS - Spherically-dished keys match the shape of your fingertips, offering satisfying feedback with every tap\nCOMFORT AND STABILITY - Type with confidence on a keyboard crafted for comfort, stability, and precision",
18 | price: 102.99,
19 | brand: "logitech",
20 | category: "Accesories",
21 | inStock: true,
22 | image: "https://firebasestorage.googleapis.com/v0/b/ecommerce-shop-cc542.appspot.com/o/products%2F1688622161445-iphone14-white.png?alt=media&token=fe2065e5-fdfe-4a6f-baa6-380b5fad90b8",
23 | reviews: [
24 | {
25 | id: "64a65a6158b470c6e06959ee",
26 | userId: "6475af156bad4917456e6e1e",
27 | productId: "64a4ebe300900d44bb50628a",
28 | rating: 5,
29 | comment: "lorem lorem lorem lorem lorem lorem lorem",
30 | createdDate: "2023-07-06T06:08:33.067Z",
31 | user: {
32 | id: "6475af156bad4917456e6e1e",
33 | name: "Berkant Kaya",
34 | email: "example@gmail.com",
35 | emailVerified: null,
36 | image:
37 | "https://lh3.googleusercontent.com/a/AAcHTteOiCtILLBWiAoolIW9PJH-r5825pBDl824_8LD=s96-c",
38 | hashedPassword: null,
39 | createdAt: "2023-05-30T08:08:53.979Z",
40 | updatedAt: "2023-05-30T08:08:53.979Z",
41 | role: "ADMIN",
42 | },
43 | },
44 | {
45 | id: "64a65a6158b470c6e06959ee",
46 | userId: "6475af156bad4917456e6e1e",
47 | productId: "64a4ebe300900d44bb50628a",
48 | rating: 5,
49 | comment: "lorem lorem lorem lorem lorem lorem lorem",
50 | createdDate: "2023-07-06T06:08:33.067Z",
51 | user: {
52 | id: "6475af156bad4917456e6e1e",
53 | name: "Berkant Kaya",
54 | email: "example@gmail.com",
55 | emailVerified: null,
56 | image:
57 | "https://lh3.googleusercontent.com/a/AAcHTteOiCtILLBWiAoolIW9PJH-r5825pBDl824_8LD=s96-c",
58 | hashedPassword: null,
59 | createdAt: "2023-05-30T08:08:53.979Z",
60 | updatedAt: "2023-05-30T08:08:53.979Z",
61 | role: "ADMIN",
62 | },
63 | },
64 | ],
65 | },
66 | {
67 | id: "648437b38c44d52b9542e340",
68 | name: "Apple iPhone 12, 64GB",
69 | description:
70 | 'The product is refurbished, fully functional, and in excellent condition. Backed by the 90-day E~Shop Renewed Guarantee.\n- This pre-owned product has been professionally inspected, tested and cleaned by Amazon qualified vendors. It is not certified by Apple.\n- This product is in "Excellent condition". The screen and body show no signs of cosmetic damage visible from 12 inches away.\n- This product will have a battery that exceeds 80% capacity relative to new.\n- Accessories may not be original, but will be compatible and fully functional. Product may come in generic box.\n- Product will come with a SIM removal tool, a charger and a charging cable. Headphone and SIM card are not included.\n- This product is eligible for a replacement or refund within 90-day of receipt if it does not work as expected.\n- Refurbished phones are not guaranteed to be waterproof.',
71 | price: 40,
72 | brand: "Apple",
73 | category: "Phone",
74 | inStock: true,
75 | image: "https://firebasestorage.googleapis.com/v0/b/ecommerce-shop-cc542.appspot.com/o/products%2F1688622161445-iphone14-white.png?alt=media&token=fe2065e5-fdfe-4a6f-baa6-380b5fad90b8",
76 | reviews: [
77 | {
78 | id: "6499b4887402b0efd394d8f3",
79 | userId: "6499b184b0e9a8c8709821d3",
80 | productId: "648437b38c44d52b9542e340",
81 | rating: 4,
82 | comment:
83 | "Çok güzel bir ürün, tavsiye ederim.",
84 | createdDate: "2023-06-26T15:53:44.483Z",
85 | user: {
86 | id: "6499b184b0e9a8c8709821d3",
87 | name: "Berkant Kaya",
88 | email: "example1@gmail.com",
89 | emailVerified: null,
90 | image:
91 | "https://lh3.googleusercontent.com/a/AAcHTtcuRLwWi1vPKaQOcJlUurlhRAIIq2LgYccE8p32=s96-c",
92 | hashedPassword: null,
93 | createdAt: "2023-06-26T15:40:52.558Z",
94 | updatedAt: "2023-06-26T15:40:52.558Z",
95 | role: "USER",
96 | },
97 | },
98 | {
99 | id: "6499a110efe4e4de451c7edc",
100 | userId: "6475af156bad4917456e6e1e",
101 | productId: "648437b38c44d52b9542e340",
102 | rating: 5,
103 | comment: "lorem lorem lorem lorem lorem lorem",
104 | createdDate: "2023-06-26T14:30:40.998Z",
105 | user: {
106 | id: "6475af156bad4917456e6e1e",
107 | name: "Berkant Kaya",
108 | email: "example@gmail.com",
109 | emailVerified: null,
110 | image:
111 | "https://lh3.googleusercontent.com/a/AAcHTteOiCtILLBWiAoolIW9PJH-r5825pBDl824_8LD=s96-c",
112 | hashedPassword: null,
113 | createdAt: "2023-05-30T08:08:53.979Z",
114 | updatedAt: "2023-05-30T08:08:53.979Z",
115 | role: "ADMIN",
116 | },
117 | },
118 | ],
119 | },
120 | {
121 | id: "64a4e9e77e7299078334019f",
122 | name: "Logitech MX Master ",
123 | description:
124 | "Cross computer control: Game changing capacity to navigate seamlessly on 3 computers, and copy paste text, images, and files from 1 to the other using Logitech flow\nDual connectivity: Use with upto 3 Windows or Mac computers via included Unifying receiver or Bluetooth Smart wireless technology. Gesture button- Yes",
125 | price: 70,
126 | brand: "logitech",
127 | category: "Accesories",
128 | inStock: true,
129 | image: "https://firebasestorage.googleapis.com/v0/b/ecommerce-shop-cc542.appspot.com/o/products%2F1688622161445-iphone14-white.png?alt=media&token=fe2065e5-fdfe-4a6f-baa6-380b5fad90b8",
130 | reviews: [],
131 | },
132 | {
133 | id: "649d775128b6744f0f497040",
134 | name: 'Smart Watch(Answer/Make Call)',
135 | description:
136 | 'Bluetooth Call and Message Reminder: The smart watch is equipped with HD speaker, after connecting to your phone via Bluetooth, you can directly use the smartwatches to answer or make calls, read messages, store contacts, view call history. The smartwatch can set up more message notifications in "GloryFit" APP. You will never miss any calls and messages during meetings, workout and riding.',
137 | price: 50,
138 | brand: "Nerunsa",
139 | category: "Watch",
140 | inStock: true,
141 | image: "https://firebasestorage.googleapis.com/v0/b/ecommerce-shop-cc542.appspot.com/o/products%2F1688622161445-iphone14-white.png?alt=media&token=fe2065e5-fdfe-4a6f-baa6-380b5fad90b8",
142 | reviews: [],
143 | },
144 | ];
--------------------------------------------------------------------------------