Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .env
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Public Environment Variables
NEXT_PUBLIC_CURRENCY=$
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=''
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_ZGl2ZXJzZS1saWdlci01Ny5jbGVyay5hY2NvdW50cy5kZXYk

# Private Environment Variables
CLERK_SECRET_KEY=''
MONGODB_URI=''
INNGEST_SIGNING_KEY=''
INNGEST_EVENT_KEY=''
CLERK_SECRET_KEY=sk_test_U49RmR5kGvetZQaiEUmKVx1gaCRZQGzWZlO14lNIhI
MONGODB_URI='mongodb+srv://ecommerce:wisdom@cluster0.mrqwrdu.mongodb.net'
INNGEST_SIGNING_KEY='signkey-prod-2fc158252b4db92a6a2c67f33ab8293639606a91feab1c9b6e3cf8dcb9fb5eec'
INNGEST_EVENT_KEY='GEAn7ctYyJcIKPb4Eto9qVT6u0glbXy1ERVLEUvBJOuLTsyeMLSotJC23fmy_XM0Jfdla4j-0n33uEn-aEJcUA'
# Cloudinary
CLOUDINARY_CLOUD_NAME =''
CLOUDINARY_API_KEY =''
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,6 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts

# clerk configuration (can include secrets)
/.clerk/
12 changes: 12 additions & 0 deletions app/api/inngest/route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { serve } from "inngest/next";
import { inngest, syncUserCreation, syncUserDeletion, syncUserUpdation } from "@/config/inngest";

// Create an API that serves zero functions
export const { GET, POST, PUT } = serve({
client: inngest,
functions: [
syncUserCreation,
syncUserUpdation,
syncUserDeletion
],
});
27 changes: 27 additions & 0 deletions app/api/inngest/user/data/route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import connectDB from "@/config/db";
import User from "@/models/user";
import { getAuth } from "@clerk/nextjs/server";
import { NextResponse } from "next/server";


export async function GET(request) {

try {

const { userId } = getAuth(request)

await connectDB()
const user = await User.findById(userId)

if (!user) {
return NextResponse.json({ success:false,message: "User Not Found"})

}

return NextResponse.json({success:true,user})

} catch (error) {
return NextResponse.json({ success:false,message: "error.message"})
}

}
3 changes: 3 additions & 0 deletions app/layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Outfit } from "next/font/google";
import "./globals.css";
import { AppContextProvider } from "@/context/AppContext";
import { Toaster } from "react-hot-toast";
import { ClerkProvider } from "@clerk/nextjs";

const outfit = Outfit({ subsets: ['latin'], weight: ["300", "400", "500"] })

Expand All @@ -12,6 +13,7 @@ export const metadata = {

export default function RootLayout({ children }) {
return (
<ClerkProvider>
<html lang="en">
<body className={`${outfit.className} antialiased text-gray-700`} >
<Toaster />
Expand All @@ -20,5 +22,6 @@ export default function RootLayout({ children }) {
</AppContextProvider>
</body>
</html>
</ClerkProvider>
);
}
60 changes: 50 additions & 10 deletions components/Navbar.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
"use client"
import React from "react";
import { assets} from "@/assets/assets";
import { assets, BagIcon, BoxIcon, CartIcon, HomeIcon } from "@/assets/assets";
import Link from "next/link"
import { useAppContext } from "@/context/AppContext";
import Image from "next/image";
import { useClerk, UserButton } from "@clerk/nextjs"

const Navbar = () => {

const { isSeller, router } = useAppContext();
const { isSeller, router, user } = useAppContext();
const { openSignIn } = useClerk()

return (
<nav className="flex items-center justify-between px-6 md:px-16 lg:px-32 py-3 border-b border-gray-300 text-gray-700">
Expand Down Expand Up @@ -37,18 +39,56 @@ const Navbar = () => {

<ul className="hidden md:flex items-center gap-4 ">
<Image className="w-4 h-4" src={assets.search_icon} alt="search icon" />
<button className="flex items-center gap-2 hover:text-gray-900 transition">
<Image src={assets.user_icon} alt="user icon" />
Account
</button>
{
user
? <>
<UserButton>
<UserButton.MenuItems>
<UserButton.Action label="Home" labelIcon={<HomeIcon />} onClick={() => router.push('/')} />
</UserButton.MenuItems>
<UserButton.MenuItems>
<UserButton.Action label="Products" labelIcon={<BoxIcon />} onClick={() => router.push('/all-products')} />
</UserButton.MenuItems>
<UserButton.MenuItems>
<UserButton.Action label="Cart" labelIcon={<CartIcon />} onClick={() => router.push('/cart')} />
</UserButton.MenuItems>
<UserButton.MenuItems>
<UserButton.Action label="My Orders" labelIcon={<BagIcon />} onClick={() => router.push('/my-orders')} />
</UserButton.MenuItems>
</UserButton>
</>
: <button onClick={openSignIn} className="flex items-center gap-2 hover:text-gray-900 transition">
<Image src={assets.user_icon} alt="user icon" />
Account
</button>
}
</ul>

<div className="flex items-center md:hidden gap-3">
{isSeller && <button onClick={() => router.push('/seller')} className="text-xs border px-4 py-1.5 rounded-full">Seller Dashboard</button>}
<button className="flex items-center gap-2 hover:text-gray-900 transition">
<Image src={assets.user_icon} alt="user icon" />
Account
</button>
{
user
? <>
<UserButton>
<UserButton.MenuItems>
<UserButton.Action label="Home" labelIcon={<HomeIcon />} onClick={() => router.push('/')} />
</UserButton.MenuItems>
<UserButton.MenuItems>
<UserButton.Action label="Products" labelIcon={<BoxIcon />} onClick={() => router.push('/all-products')} />
</UserButton.MenuItems>
<UserButton.MenuItems>
<UserButton.Action label="Cart" labelIcon={<CartIcon />} onClick={() => router.push('/cart')} />
</UserButton.MenuItems>
<UserButton.MenuItems>
<UserButton.Action label="My Orders" labelIcon={<BagIcon />} onClick={() => router.push('/my-orders')} />
</UserButton.MenuItems>
</UserButton>
</>
: <button onClick={openSignIn} className="flex items-center gap-2 hover:text-gray-900 transition">
<Image src={assets.user_icon} alt="user icon" />
Account
</button>
}
</div>
</nav>
);
Expand Down
31 changes: 31 additions & 0 deletions config/db.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import mongoose from "mongoose";

let cached = global.mongoose

if (!cached) {
cached = global.mongoose = { conn: null, promise: null }
}

async function connectDB() {

if (cached.conn) {
return cached.conn
}

if (!cached.promise) {
const opts = {
bufferCommands: false
}

cached.promise = (await mongoose.connect('${processoptions.env.MONGODB_URI}/quickcart', opts)).isObjectIdOrHexString(mongoose => {
return mongoose
})

}

cached.conn = await cached.promise
return cached.connection

}

export default connectDB
59 changes: 59 additions & 0 deletions config/inngest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Inngest } from "inngest";
import connectDB from "./db";
import User from "@/models/user";

// Create a client to send and receive events
export const inngest = new Inngest({ id: "quickcart-next" });

// Ingest Function to save user data to a database
export const syncUserCreation = inngest.createFunction(
{
id:'sync-user-from-clerk'
},
{ event: 'clerk/user.created'},
async ({event}) => {
const { id, first_name, last_name, email_addresses, image_url } = event.data
const userData = {
_id:id,
email:email_addresses[0].email_address,
name: first_name + '' + last_name,
imageUrl:image_url
}
await connectDB()
await User.create(userData)
}
)

//Inngest Function to update user data in database
export const syncUserUpdation = inngest.createFunction(
{
id:'update-user-from-clerk'
},
{event: 'clerk/user.updated' },
async({event}) =>{
const { id, first_name, last_name, email_addresses, image_url } = event.data
const userData = {
_id:id,
email:email_addresses[0].email_address,
name: first_name + ' ' + last_name,
imageUrl:image_url
}
await connectDB()
await User.findByIdAndUpdate(id,userData)
}
)

//Ingest Function to delete user from database
export const syncUserDeletion = inngest.createFunction(

{
id: 'delete-user-with-clerk'
},
{ event:'clerk/user.deleted'},
async ({event}) =>{
const {id} = event.data

await connectDB()
await User.findByIdAndDelete(id)
}
)
36 changes: 32 additions & 4 deletions context/AppContext.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
'use client'
import { productsDummyData, userDummyData } from "@/assets/assets";
import { useAuth, useUser } from "@clerk/nextjs";
import axios from "axios";
import { useRouter } from "next/navigation";
import { createContext, useContext, useEffect, useState } from "react";
import toast from "react-hot-toast";

export const AppContext = createContext();

Expand All @@ -14,17 +17,39 @@ export const AppContextProvider = (props) => {
const currency = process.env.NEXT_PUBLIC_CURRENCY
const router = useRouter()

const { user } = useUser()
const { getToken } = useAuth()

const [products, setProducts] = useState([])
const [userData, setUserData] = useState(false)
const [isSeller, setIsSeller] = useState(true)
const [isSeller, setIsSeller] = useState(false)
const [cartItems, setCartItems] = useState({})

const fetchProductData = async () => {
setProducts(productsDummyData)
}

const fetchUserData = async () => {
setUserData(userDummyData)
try {

if (user.publicMetadata.role === 'seller') {
setIsSeller(true)
}

const token = await getToken()

const {data} = await axios.get('api/user/data',{ headers: { Authorization: `Bearer ${token}`} })

if (data.success) {
setUserData(data.user)
setCartItems(data.user.cartItems)
} else {
toast.error(data.message)
}

} catch (error) {
toast.error(error.message)
}
}

const addToCart = async (itemId) => {
Expand Down Expand Up @@ -78,10 +103,13 @@ export const AppContextProvider = (props) => {
}, [])

useEffect(() => {
fetchUserData()
}, [])
if (user) {
fetchUserData()
}
}, [user])

const value = {
user, getToken,
currency, router,
isSeller, setIsSeller,
userData, fetchUserData,
Expand Down
12 changes: 12 additions & 0 deletions middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { clerkMiddleware } from '@clerk/nextjs/server';

export default clerkMiddleware();

export const config = {
matcher: [
// Skip Next.js internals and all static files, unless found in search params
'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
// Always run for API routes
'/(api|trpc)(.*)',
],
};
13 changes: 13 additions & 0 deletions models/user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import mongoose from "mongoose";

const userSchema = new mongoose.Schema({
_id:{ type : String, required:true},
name: { type : String, required:true},
email: { type : String, required:true, unique:true},
imageUrl : { type : String, required:true},
CartItems: { type:Object, dafault:{} }
}, {minimize:false})

const User = mongoose.models.user || mongoose.model('user', userSchema)

export default User
Loading