Interio - Full Stack
Introduction
Interio is a platform for interior designers to share work, get inspiration, and
showcase designs
Key features include user registration, design posting with images, tag-based
design browsing, and personal collections
The app structure includes pages for home, designs, profile, and shot uploads
Server actions handle vendor and shot-related operations, including
authentication and CRUD functions
The frontend UI is responsive and includes features like design browsing,
profile editing, and shot uploads
User profiles display work history, skills, and design collections
The project emphasizes a clean, modern design with a focus on showcasing
interior design work
Technologies Used
Nextjs 14 (App Router)
Tailwind CSS with shadcn/ui
Server Actions
TypeScript
React Hook Forms
Uploadthing
Interio - Full Stack 1
MongoDB
Uploadthing
Project Setup
Create a new directory for the project.
Sign up to the uploadthing website and get the UPLOADTHING_SECRET and
UPLOADTHING_APP_ID.
Paste the below command to initialize a nextjs project. (you may also use
npm/yarn/bun)
pnpx create-next-app@latest
Exchange the [Link] file to install all dependencies.
{
"name": "interio",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,cs
},
"dependencies": {
"@hookform/resolvers": "^3.9.0",
"@portabletext/react": "^3.1.0",
"@radix-ui/react-alert-dialog": "^1.1.1",
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.1",
Interio - Full Stack 2
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@reduxjs/toolkit": "^2.2.7",
"@sanity/client": "^6.21.1",
"@sanity/image-url": "^1.0.2",
"@sanity/vision": "^3.53.0",
"@types/node": "22.1.0",
"@types/react": "18.3.3",
"@types/react-dom": "18.3.0",
"@uploadthing/react": "^6.7.2",
"autoprefixer": "10.4.20",
"axios": "^1.7.3",
"bcryptjs": "^2.4.3",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"eslint": "9.8.0",
"eslint-config-next": "14.2.5",
"eslint-plugin-unicorn": "^55.0.0",
"framer-motion": "^11.3.24",
"joi": "^17.13.3",
"jsonwebtoken": "^9.0.2",
"lucide-react": "^0.426.0",
"mongoose": "^8.5.2",
"next": "14.2.5",
"next-themes": "^0.3.0",
"postcss": "8.4.41",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-hook-form": "^7.52.2",
"react-icons": "^5.2.1",
"react-redux": "^9.1.2",
"sanity": "^3.53.0",
"styled-components": "^6.1.12",
"tailwind-merge": "^2.4.0",
"tailwind-scrollbar": "^3.1.0",
Interio - Full Stack 3
"tailwindcss": "3.4.9",
"tailwindcss-animate": "^1.0.7",
"typescript": "5.5.4",
"uploadthing": "^6.13.2",
"vaul": "^0.9.1"
},
"devDependencies": {
"@ianvs/prettier-plugin-sort-imports": "^4.3.1",
"@tailwindcss/typography": "^0.5.14",
"@types/bcryptjs": "^2.4.6",
"@types/jsonwebtoken": "^9.0.6",
"@typescript-eslint/eslint-plugin": "^8.0.1",
"eslint-config-prettier": "^9.1.0",
"prettier": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.5"
}
}
App Structure
├── app
│ ├── [Link]
│ ├── [Link]
│ ├── shots
│ │ └── [Link]
│ ├── profile
│ │ └── [Link]
├── components
│ ├── ui
│ │ ├── [Link]
│ │ ├── [Link]
│ │ ├── [Link]
│ │ └── ...
│ ├── [Link]
│ ├── [Link]
Interio - Full Stack 4
│ └── ...
├── context
│ ├── [Link]
│ ├── [Link]
│ └── [Link]
├── lib
│ ├── [Link]
│ ├── actions
│ │ ├── [Link]
│ │ └── [Link]
│ ├── models
│ │ ├── [Link]
│ │ ├── [Link]
│ │ └── [Link]
│ └── [Link]
├── styles
│ └── [Link]
├── [Link]
├── [Link]
├── [Link]
└── [Link]
Environment Variables (.env)
JWT_SECRET=
MONGODB_URL=
UPLOADTHING_SECRET=
UPLOADTHING_APP_ID=
Some utility functions
Below are some utility functions that will assist in various tasks across the project:
import jwt, { JwtPayload } from "jsonwebtoken"
import { generateComponents } from "@uploadthing/react"
Interio - Full Stack 5
import type { OurFileRouter } from "../app/api/uploadthing/co
const JWT_SECRET = [Link].JWT_SECRET
const getIdByToken = async (token: any) => {
try {
if (!JWT_SECRET) {
throw new Error("JWT_SECRET is not defined")
}
const decoded = [Link]([Link], JWT_SECRET) as st
if (typeof decoded === "string") {
throw new Error("Invalid token")
}
return decoded._id
} catch (error) {
throw new Error(`No token : ${error}`)
}
}
export default getIdByToken
export function getErrorMessage(error: unknown): string {
let message: string
if (error instanceof Error) {
message = [Link]
} else if (error && typeof error === "object" && "message"
message = String([Link])
} else if (typeof error === "string") {
message = error
} else {
message = "Something went wrong"
}
return message
}
export function formatDateInDMY(inputDate: string): string {
Interio - Full Stack 6
const date = new Date(inputDate)
if (isNaN([Link]())) return ""
const options: [Link] = {
day: "2-digit",
month: "short",
year: "numeric",
}
return [Link](undefined, options)
}
export function formatDMY(date: string, weekday?: boolean) {
const options: {
year: string
month: string
day: string
weekday?: string
} = { year: "numeric", month: "long", day: "numeric" }
if (weekday) {
[Link] = "short"
}
// @ts-ignore -d s
return new Date(date).toLocaleDateString(undefined, options
}
export const { UploadButton, UploadDropzone, Uploader } = gen
Types
Types used across the project.
export type OwnerType = {
name: string
follower: string[]
following: string[]
Interio - Full Stack 7
likedshot?: string[]
email: string
_id: string
}
export type shotData = {
title: string
category: string
description: string
tags: string[]
images: { title: string; url: string; _id: string }[]
_id: string
owner: OwnerType
}
export type shotDataArr = shotData[]
export type messageProp = {
sender: string
content: string
readBy: string[]
createdAt: string
}
export type vendorType = {
_id: string
name: string
follower: string[]
following: string[]
likedShot: string[]
}
export type reduxVendor = {
vendor: string
v_id: string
Interio - Full Stack 8
token: string
}
APIs Setup
1. MongoDB connection in the nextjs environment.
import mongoose from "mongoose"
let isConnected = false // Variable to track the connection s
export const connectToDB = async () => {
// Set strict query mode for Mongoose to prevent unknown fi
[Link]("strictQuery", true)
if (![Link].MONGODB_URL) return [Link]("Missing M
// If the connection is already established, return without
if (isConnected) {
[Link]("MongoDB connection already established")
return
}
try {
await [Link]([Link].MONGODB_URL)
isConnected = true // Set the connection status to true
[Link]("MongoDB connected")
} catch (error) {
[Link](error)
}
}
2. Database Schema Design
Interio - Full Stack 9
Vendor
import bcrypt from "bcryptjs"
import jwt from "jsonwebtoken"
import mongoose from "mongoose"
import { IVendor, IVendorModel } from "./types/vendor-type
[Link]("strictQuery", true)
const vendorSchema = new [Link]<IVendor, IVendorM
{
type: {
type: String,
default: "vendor",
},
socketId: {
type: String,
},
name: {
type: String,
trim: true,
required: true,
},
profilePic: {
type: String,
default: null,
},
username: {
type: String,
trim: true,
required: true,
unique: true,
},
email: {
type: String,
Interio - Full Stack 10
unique: true,
trim: true,
required: true,
},
contact: {
type: Number,
},
password: {
type: String,
trim: true,
required: true,
},
address: {
type: String,
trim: true,
default: null,
},
biography: {
type: String,
trim: true,
default: null,
},
workHistory: [
{
title: String,
company: String,
location: String,
from: String,
to: String,
},
],
lookingFor: [
{
title: String,
location: String,
},
Interio - Full Stack 11
],
ownShot: [[Link]],
likedShot: [[Link]],
shotCollections: [[Link]],
socialLinks: [
{
platform: String,
url: String,
},
],
skills: [String],
follower: [String],
following: [String],
resetToken: String,
expireToken: Date,
otp: {
type: String,
default: null,
},
otpExpireIn: {
type: Date,
expireAfterSeconds: 150,
default: null,
},
tokens: [
{
token: {
type: String,
required: true,
},
},
],
},
{
timestamps: true,
}
Interio - Full Stack 12
)
[Link]("Shot", {
ref: "shotModel",
localField: "_id",
foreignField: "owner",
})
[Link] = function () {
const vendor = this
const vendorObject = [Link]()
delete [Link]
delete [Link]
delete [Link]
return vendorObject
}
[Link] = async function ()
const vendor = this
const JWT_SECRET = [Link].JWT_SECRET || null
if (!JWT_SECRET) {
throw new Error("JWT secret not found")
}
const token = [Link]({ _id: vendor._id.toString() }, J
expiresIn: "1h",
})
[Link] = [Link]({ token })
await [Link]()
return token
}
[Link] = async (email, pas
const vendor = await [Link]({
email,
})
Interio - Full Stack 13
if (!vendor) {
throw new Error("Invalid vendor name or Sign up first
}
const isMatch = await [Link](password, [Link]
if (!isMatch) {
throw new Error("Invalid Password!")
}
return vendor
}
[Link]("save", async function (next) {
const vendor = this
if ([Link]("password")) {
[Link] = await [Link]([Link], 8
}
next()
})
const Vendor: IVendorModel = ([Link] as IV
export default Vendor
Shots
import mongoose from "mongoose"
[Link]("strictQuery", true)
const shotSchema = new [Link](
{
title: {
type: String,
trim: true,
required: true,
},
Interio - Full Stack 14
category: {
type: String,
trim: true,
required: true,
lowercase: true,
index: true,
},
description: {
type: String,
required: true,
trim: true,
},
tags: {
type: [String],
lowercase: true,
},
images: [
{
title: String,
url: String,
},
],
owner: {
type: String,
requied: true,
ref: "Vendor",
},
like: [[Link]],
comments: [
{
v_id: String,
comment: String,
},
],
view: {
type: Number,
Interio - Full Stack 15
default: 0,
},
},
{
timestamps: true,
}
)
const Shot = [Link] || [Link]("Shot"
export default Shot
Collection
import mongoose from "mongoose"
[Link]("strictQuery", true)
const collectionSchema = new [Link](
{
cname: {
type: String,
trim: true,
required: true,
},
shots: [[Link]],
owner: {
type: String,
requied: true,
ref: "Vendor",
},
},
{
timestamps: true,
}
Interio - Full Stack 16
)
const Collection = [Link] || mongoose
export default Collection
3. Server Actions
Vendor Actions
"use server"
import { cookies } from "next/headers"
import getIdByToken, { getErrorMessage } from "@/utils/hel
import SHOT from "../model/[Link]"
import VENDOR from "../model/[Link]"
import { connectToDB } from "../mongoose"
//-------------GET ALL VENDOR-------------//
export const getAllVendors = async () => {
try {
connectToDB()
const vendor = await [Link]({})
return { vendor }
} catch (error) {
return {
error: getErrorMessage(error),
}
}
}
//-------------NEW VENDOR-------------//
export const addVendor = async (body: any) => {
try {
connectToDB()
Interio - Full Stack 17
body["username"] = [Link]("@")[0]
const vendor = new VENDOR(body)
await [Link]()
const token = await [Link]()
cookies().set("token", token)
return { name: vendor?.name, _id: vendor._id.toString(
} catch (error) {
return {
error: getErrorMessage(error),
}
}
}
//-------------LOGIN VENDOR-------------//
export const vendorLogin = async (body: any) => {
try {
connectToDB()
const vendor = await [Link]([Link]
if (!vendor) {
throw new Error("Invalid Attempt, vendor not Found!"
}
const token = await [Link]()
cookies().set("token", token)
return { name: vendor?.name, _id: vendor?._id.toString
} catch (error) {
return {
error: getErrorMessage(error),
}
}
}
//-------------LOGOUT VENDOR-------------//
export const vendorLogout = async () => {
try {
const cookieStore = cookies()
Interio - Full Stack 18
const token = [Link]("token")
const _id = await getIdByToken(token)
if (!_id) {
return { message: "Token Expired", code: 500 }
}
const vendor = await [Link]({ _id })
if (!vendor) {
throw new Error("Invalid Attempt, vendor not Found!"
}
cookies().delete("token")
[Link] = [Link]((tok) => {
return [Link] !== token?.value
})
await [Link]()
return { message: "Logged Out!" }
} catch (error) {
return {
error: getErrorMessage(error),
}
}
}
//-------------REVALIDATE VENDOR-------------//
export const revalidateVendor = async () => {
try {
connectToDB()
const cookieStore = cookies()
const prevToken = [Link]("token")
if (!prevToken) {
return { message: "Token Not Found", code: 500 }
}
const _id = await getIdByToken(prevToken)
Interio - Full Stack 19
if (!_id) {
return { message: "Token Expired", code: 500 }
}
const vendor = await [Link]({ _id }).select("n
if (!vendor) {
return { message: "Vendor Not Found", code: 500 }
}
[Link] = [Link]((tok) => {
return [Link] !== prevToken?.value
})
const token = await [Link]()
await [Link]()
cookies().set("token", token)
return { name: vendor?.name, _id: vendor._id.toString(
} catch (error) {
return {
error: getErrorMessage(error),
}
}
}
//-------------GET COMPLETE VENDOR-------------//
export const getVendorProfile = async () => {
try {
connectToDB()
const cookieStore = cookies()
const token = [Link]("token")
if (!token) throw new Error("Token Not Found")
const _id = await getIdByToken(token)
if (!_id) throw new Error("Token Expired")
const vendor = await [Link]({ _id }).select("-
if (!vendor) {
throw Error("NO vendor data FOUND")
}
Interio - Full Stack 20
return { vendor }
} catch (error) {
return {
error: getErrorMessage(error),
}
}
}
//-------------GET VENDOR TABS-------------//
export const getTabs = async (tab: string) => {
try {
connectToDB()
const cookieStore = cookies()
const token = [Link]("token")
if (!token) return
const _id = await getIdByToken(token)
if (!_id) return
let selectWord = "shotCollections"
let tabArr: string | any[] = []
if (tab === "liked-shot") {
selectWord = "likedShot"
}
if (tab === "work") {
selectWord = "ownShot"
}
const vdr = await [Link]({ _id }).select(selec
if (!vdr) {
throw Error("NO vendor data FOUND")
}
if (selectWord === "shotCollections") {
// const collection = await [Link](vend
// tabArr = [Link];
} else if (selectWord === "likedShot") {
tabArr = [Link]
Interio - Full Stack 21
} else {
tabArr = [Link]
}
if ([Link] == 0) {
return { shots: [] }
} else {
const shots = await [Link]({ _id: { $in: tabArr }
if (!shots) {
throw Error("Unable to get shots!")
}
return { shots }
}
} catch (error) {
return {
error: getErrorMessage(error),
}
}
}
//-------------UPDATE VENDOR-------------//
export const updateVendor = async (body: any) => {
try {
connectToDB()
const cookieStore = cookies()
const token = [Link]("token")
if (!token) return
const _id = await getIdByToken(token)
if (!_id) return
const vendor: any = await [Link]({ _id })
if (!vendor) {
throw Error("NO vendor data FOUND")
}
const updates = [Link](body)
[Link]((update) => {
vendor[update] = body[update]
Interio - Full Stack 22
})
await [Link]()
return { vendor }
} catch (error) {
return {
error: getErrorMessage(error),
}
}
}
Shot Actions
"use server"
// import COLLECTION from '../model/collectionModel';
import { getErrorMessage } from "@/utils/helper"
import SHOT from "../model/[Link]"
import VENDOR from "../model/[Link]"
import { connectToDB } from "../mongoose"
//-------------NEW SHOT-------------//
export const addShot = async (body: any) => {
try {
connectToDB()
const shot = await new SHOT(body)
await [Link]()
const vendor = await [Link]({ _id: [Link]
if (!shot || !vendor) {
throw new Error("Unable to add shot!")
}
[Link](shot._id)
Interio - Full Stack 23
await [Link]()
return { shot }
} catch (error) {
return {
error: getErrorMessage(error),
}
}
}
//-------------GET ALL SHOT-------------//
export const getShot = async (type: string, limit = 10) =>
try {
connectToDB()
let where = {}
if (type) {
//@ts-ignore - typescript is not recognizing the reg
const types = type?.split(",").map((t) => new RegExp
where = { tags: { $in: types } }
}
const shots = await [Link](where)
.limit(limit)
.select("title category description tags images owne
.populate("owner", "name follower following likedsho
.sort("-createdAt")
if (!shots) {
throw Error("NO SHOT FOUND")
}
return { shots }
} catch (error) {
return {
error: getErrorMessage(error),
}
}
}
Interio - Full Stack 24
//-------------GET SHOT BY ID-------------//
export const getShotById = async (id: string) => {
try {
connectToDB()
const shot = await [Link](id)
.select("title category description tags images owne
.populate("owner", "name profilePic email")
if (!shot) {
throw Error("NO SHOT FOUND")
}
return shot
} catch (error) {
return {
error: getErrorMessage(error),
}
}
}
4. Nextjs API routes
/vendor/[Link]
import { NextResponse } from "next/server"
import { getVendorProfile, updateVendor } from "@/lib/acti
import { IVendor } from "@/lib/model/types/vendor-type"
export async function GET() {
const { vendor, error } = await getVendorProfile()
if (error) {
return [Link]()
}
return [Link](vendor)
}
Interio - Full Stack 25
export async function POST(request: Request) {
const body = await [Link]()
const { vendor, error } = (await updateVendor(body)) as
if (error) {
return [Link]()
}
return [Link](vendor)
}
/shot/[Link]
import { NextRequest, NextResponse } from "next/server"
import { addShot } from "@/lib/actions/[Link]"
export async function POST(req: NextRequest) {
try {
const body = await [Link]()
if (!body || typeof body !== "object") {
return [Link]()
}
const shot = await addShot(body)
if (!shot) {
return [Link]()
}
return [Link](shot)
} catch (error) {
[Link](error)
return [Link]()
}
}
/shot/[shotId]/[Link]
Interio - Full Stack 26
import { NextRequest, NextResponse } from "next/server"
import { getShotById } from "@/lib/actions/[Link]"
export async function GET(request: NextRequest, { params }
try {
const shotId = [Link]
const shot = await getShotById(shotId)
if (!shot) return [Link]({ shot: {} })
return [Link](shot)
} catch (error) {
return [Link]({ error })
}
}
/uploadthing/[Link]
import { createNextRouteHandler } from "uploadthing/next"
import { ourFileRouter } from "./core"
// Export routes for Next App Router
export const { GET, POST } = createNextRouteHandler({
router: ourFileRouter,
})
// ALSO Make a separate [Link] file in the same folder
import { createUploadthing, type FileRouter } from "upload
const f = createUploadthing()
export const ourFileRouter = {
Interio - Full Stack 27
imageUploader: f({ image: { maxFileSize: "4MB" } }).onUp
return { data }
}),
} satisfies FileRouter
export type OurFileRouter = typeof ourFileRouter
Frontend(UI) Setup
Here, It has mainly 5-6 pages (routes).
├── /home
│ ├── /designs
│ │ ├── /upload
│ │ └── /[shotId]
│ ├── /profile
│ │ └── /edit
Home Page
[Link]
import type { NextPage } from "next"
import Image from "next/image"
import Link from "next/link"
import { SHOTDATA } from "@/utils/dummy"
import { getShot } from "@/lib/actions/[Link]"
import Hero from "@/components/hero"
import IconList from "@/components/motion/icon-list"
import RectangleCard from "@/components/rectangle-card"
const Home: NextPage = async () => {
Interio - Full Stack 28
const { shots } = await getShot("", 10)
return (
<section className="bg-dark">
<Hero />
<IconList />
{/* SHOTS SECTION */}
<h1 className="text-gray pt-16 text-center text-4xl fon
<p className="text-gray mt-4 text-center">
Upload Interior Design Shots Or Get Inspired <br /> B
</p>
<div className="padding py-16 text-2xl sm:text-4xl">
{[Link]((_, i) => (
<Link
href={`/designs?type=${_.type}`}
className="border-gray group flex items-center j
key={i}
>
<h3 className="text-gray group-hover:text-white s
<Image
src={"/[Link]"}
alt="arrow icon"
height={50}
width={50}
priority
className="hidden bg-primary p-2 md:group-hove
/>
</Link>
))}
</div>
{/* WEBSITE TEMPLATE IMAGES */}
<Image src={"/[Link]"} alt="music bg" height={700} wi
Interio - Full Stack 29
<Image src={"/[Link]"} alt="music bg" height={350} w
{/* SHOT LISTS */}
<h1 className="padding text-gray pt-16 text-center text
OVER <span className="text-primary">205+</span> Shots
</h1>
<div className="padding grid grid-cols-2 gap-4 md:grid-
{shots &&
[Link]((_, i) => (
<div key={i}>
<span className=" absolute m-2 rounded-2xl bg-p
<Link href={`/designs/${_._id}`}>
<Image
src={_.images[0].url}
height={250}
width={250}
alt={"man"}
className="h-40 rounded object-fill hover:c
/>
</Link>
{/* <p>{_.title}</p> */}
</div>
))}
</div>
<p className="border-gray m-auto mt-6 w-3/4 border-b pb
<Link href={"/designs"} className="rounded-md bg-prim
Check More
</Link>
</p>
<div className="my-16 grid grid-cols-5 justify-items-st
<div className="hidden lg:col-span-2 lg:block ">
<Image src={"/[Link]"} height={500} width={500} a
</div>
<div className="padding col-span-5 mr-4 sm:mr-8 lg:co
Interio - Full Stack 30
<h1 className="text-gray pt-8 text-4xl font-bold
WHAT OUR
<span className="text-primary"> USERS</span>
<br /> SAY ABOUT US
</h1>
<p className="text-gray my-6">
Our users are our strength. We do every thing pos
</p>
<div className="card text-gray my-4 p-4 ">
<p>
Interio Design is a true treasure trove for int
inspirations, from sleek modernism to cozy rust
curated galleries provide endless ideas. Whethe
for endless creative sparks!
</p>
<h6 className="mt-4 font-semibold text-primary">S
<p>Interior Designer</p>
</div>
</div>
</div>
<RectangleCard />
{/* FOOTER */}
<div className="z-0 flex h-auto flex-col items-center j
<Link href="/">
<Image src={"/[Link]"} alt="interio logo" heig
</Link>
<p className="px-2 md:px-6 lg:px-10">
Modern Designs | Minimal Designs | Luxurious Design
Terms & Conditions Privacy Policy | About us
</p>
💖 </p>
<p>Handcrafted by Interio © {new Date().getFullYear()
<p>Made with
</div>
</section>
Interio - Full Stack 31
)
}
export default Home
[Link]
import { TailwindIndicator } from "../components/tailwind-ind
import "./[Link]"
import { Metadata } from "next"
import { Roboto } from "next/font/google"
import ReduxProvider, { QueryProvider } from "@/utils/provide
import { NextSSRPlugin } from "@uploadthing/react/next-ssr-pl
import { extractRouterConfig } from "uploadthing/server"
import { Toaster } from "@/components/ui/sonner"
import { ourFileRouter } from "./api/uploadthing/core"
import ExtraComponents from "./extra"
const roboto = Roboto({
subsets: ["latin"],
weight: "500",
})
export const metadata: Metadata = {
title: "Interior Design | Home",
description: "Interior Design Shots, Get Inspired By Other
}
export default function RootLayout({ children }: { children:
return (
<html lang="en">
Interio - Full Stack 32
<body className={[Link]}>
<QueryProvider>
<ReduxProvider>
{children}
<ExtraComponents />
</ReduxProvider>
</QueryProvider>
<Toaster />
<NextSSRPlugin routerConfig={extractRouterConfig(ourF
<TailwindIndicator />
</body>
</html>
)
}
Design Pages
/design/[Link]
import { shotData } from "@/types"
import { getShot } from "@/lib/actions/[Link]"
import ShotCard from "./shot-card"
type Props = {
searchParams: { type: string }
}
export default async function Designs({ searchParams }: Props
const type = [Link]
const shots: shotData[] = []
try {
const data = await getShot(type)
if (data) {
[Link](...([Link] as shotData[]))
}
} catch (error) {
[Link](error)
Interio - Full Stack 33
}
return (
<>
{shots?.length > 0 ? (
<div className="grid grid-cols-1 justify-items-center
{[Link]((shot: shotData) => (
<ShotCard key={shot._id} shot={shot} />
))}
</div>
) : (
<p className="text-center">Nothing to show!</p>
)}
</>
)
}
// SHOT CARD COMPONENT
import React from "react"
import Image from "next/image"
import Link from "next/link"
import { shotData } from "@/types"
import { Icons } from "@/components/icons"
export default function ShotCard({ shot }: { shot: shotData }
return (
<div key={shot._id} className="mb-2 sm:mb-4">
<Link href={`/designs/${shot._id}`}>
<Image
src={[Link][0].url}
alt="l1img"
height={350}
quality={100}
Interio - Full Stack 34
width={370}
className="duration-400 h-52 w-80 cursor-pointer ro
/>
</Link>
<div className="text-gray flex justify-between px-2 py-
<span className="text-xs md:text-sm">
<[Link] className="inline" /> 1.1k
</span>
<p className="flex gap-2">
<span className="text-xs md:text-sm">
<[Link] className="inline" /> 1.1k
</span>
<span className="text-xs md:text-sm">
<[Link] className="inline" /> 1.1k
</span>
</p>
</div>
</div>
)
}
/design/[shotId]/[Link]
import Image from "next/image"
import { shotData } from "@/types"
import { getShot, getShotById } from "@/lib/actions/[Link]
import { Icons } from "@/components/icons"
import ShotCard from "../shot-card"
export type Props = {
params: {
shotId: string
}
Interio - Full Stack 35
}
const ShotId = async ({ params }: Props) => {
const shot = await getShotById([Link])
const moreShot = await getShot("", 4)
if (!shot || [Link]) return <p className="text-center">
return (
<>
<Image
src={shot?.images[0]?.url ? [Link][0].url : "/gr
className="h-[700px] w-[1200px] rounded"
alt="bed"
height={500}
width={1400}
/>
<div className="my-8 flex justify-between">
<div className="flex gap-x-4">
<Image src={[Link] || "/[Link]"} h
<div>
<h1>{shot?.title}</h1>
<p className="text-gray text-xs">{shot?.owner?.na
</div>
</div>
<div className="flex gap-x-4 ">
<button className="cborder bg-trans rounded px-4 py
<button className="bg-trans rounded border border-p
<Image src={"/[Link]"} alt="heart-icon" heigh
</button>
</div>
</div>
<div className="my-8 flex items-center justify-center g
<button className="cborder bg-trans rounded px-4 py-2
<[Link] />
Interio - Full Stack 36
</button>
<button className="cborder bg-trans rounded px-4 py-2
<[Link] />
</button>
<button className="cborder bg-trans rounded px-4 py-2
<[Link] />
</button>
</div>
<p className="border-gray my-8 w-full border-[0.5px]" /
<p>{shot?.description}</p>
<h2>I am available for new projects</h2>
📪
<p className="my-4">
Email:
<a href={`[Link]
<span className="text-primary"> {shot?.owner?.email
</a>
</p>
<p className="border-gray my-8 w-full border-[0.5px]" /
{/* {shot?.owner?._id === vendor.v_id && (
<div className='my-8 flex items-center justify-ce
<button className='cborder rounded bg-trans px-
Edit
</button>
<button className='cborder rounded bg-trans px-
Edit Details
</button>
<button className='cborder rounded bg-trans px-
Delete
</button>
</div>
)} */}
<div className="flex flex-col gap-y-4">
<div className="flex justify-between">
Interio - Full Stack 37
<p>Similar Shots</p>
<p className="cursor-pointer text-primary hover:und
</div>
<div className="flex gap-4 overflow-x-auto ">
{moreShot &&
[Link] &&
[Link] > 0 &&
[Link]((shot) => <ShotCard key={shot
</div>
</div>
</>
)
}
export default ShotId
/design/upload/[Link]
"use client"
import React, { useState } from "react"
import { useRouter } from "next/navigation"
import { TAGS } from "@/utils/dummy"
import { UploadDropzone } from "@/utils/uploadthing"
import { joiResolver } from "@hookform/resolvers/joi"
import axios from "axios"
import Joi from "joi"
import { SubmitHandler, useForm } from "react-hook-form"
import { toast } from "sonner"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import { Form, FormControl, FormField, FormItem, FormLabel, F
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
Interio - Full Stack 38
interface IFormInput {
description: string
shotUrl: string
tags: string[]
title: string
}
const schema = [Link]({
description: [Link]().min(200).max(1000).required().mes
"[Link]": `Description cannot be empty`,
"[Link]": `Please provide a description for your sh
"[Link]": `Description should have a minimum length o
"[Link]": `Description should have a maximum length o
}),
shotUrl: [Link]().required().messages({
"[Link]": `Shot Url cannot be empty`,
"[Link]": `Please provide a url for your shot. It w
}),
title: [Link]().required().messages({
"[Link]": `Title cannot be empty`,
"[Link]": `Please provide a title for your shot. It
}),
tags: [Link]().items([Link]()).required().messages({
"[Link]": `Please provide a tags for your shot. It
}),
})
const Upload = () => {
const router = useRouter()
const [loading, setLoading] = useState<boolean>(false)
const form = useForm<IFormInput>({
resolver: joiResolver(schema),
})
const onSubmit: SubmitHandler<IFormInput> = async (val) =>
Interio - Full Stack 39
setLoading(true)
[Link]("Uploading Shot...")
try {
const { data } = await [Link]("/api/shots", {
role: "vendor",
description: [Link],
images: {
title: "Hotel Room",
url: [Link],
},
tags: [Link],
category: "Furniture",
title: [Link],
owner: [Link]("v_id"),
})
if (!data) {
return
}
[Link]("Shot uploaded successfully")
[Link](`/designs/${[Link]._id}`)
} catch (error) {
[Link]("Shot upload failed")
} finally {
setLoading(false)
}
}
function addTags(tag: string, tags: string[]) {
if (tags) {
return [...tags, tag]
} else {
return [tag]
}
}
return (
Interio - Full Stack 40
<>
<h1 className="mt-2 text-center">What have you been wor
<Form {...form}>
<form onSubmit={[Link](onSubmit)} classNam
<FormField
control={[Link]}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Title</FormLabel>
<FormControl>
<Input placeholder="enter title" {...field}
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={[Link]}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>Description</FormLabel>
<FormControl>
<Textarea rows={12} placeholder="Tell us a
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<p className="text-muted-foreground">Upload Shot</p
<UploadDropzone
endpoint="imageUploader"
className="h-60 w-full rounded bg-background px-4
onClientUploadComplete={(res) => {
Interio - Full Stack 41
[Link]("shotUrl", res[0].url)
setLoading(false)
}}
onUploadBegin={() => {
setLoading(true)
}}
onUploadError={(error: Error) => {
[Link]([Link])
}}
config={{
mode: "auto",
}}
/>
<FormField
control={[Link]}
name="tags"
render={() => (
<FormItem>
<FormLabel className="mb-2 text-base">Tags</F
<div className="flex gap-2">
{[Link]((tag) => (
<FormField
key={[Link]}
control={[Link]}
name="tags"
render={({ field }) => {
return (
<FormItem key={[Link]} className="f
<FormControl>
<Checkbox
checked={[Link]?.include
onCheckedChange={(checked) =>
return checked
? [Link](addTags(
: [Link]([Link]
}}
Interio - Full Stack 42
/>
</FormControl>
<FormLabel className="font-normal
</FormItem>
)
}}
/>
))}
</div>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" className="w-full" disabled={
Upload Shot
</Button>
</form>
</Form>
</>
)
}
export default Upload
/[Link]
"use client"
import type { ReactElement } from "react"
import Image from "next/image"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { useAppDispatch, useAppSelector } from "@/context/hoo
import { isLogin, setLogout, togglePanel, vendor as vd } from
Interio - Full Stack 43
import { EXCLUDE_PATHS, START_WITH_PATHS } from "@/utils/dumm
import clsx from "clsx"
import { toast } from "sonner"
import { vendorLogout } from "@/lib/actions/[Link]"
import { Icons } from "@/components/icons"
import DesignNav from "./design-nav"
type Props = {
children?: [Link]
way?: "without" | "with"
}
export default function LeftSideBar({ children, way }: Props)
const dispatch = useAppDispatch()
const vendor = useAppSelector(vd)
const pathname = usePathname()
const loginStatus = useAppSelector(isLogin)
const logoutHandler = async () => {
try {
const data = await vendorLogout()
if ([Link]) {
return [Link]([Link])
}
[Link]("Logout Successfully!")
dispatch(setLogout())
} catch (error: any) {
[Link](error?.message)
[Link]("Some Error!", error)
}
}
if ([Link]("/blogs/add")) return <>{children}<
return (
Interio - Full Stack 44
<>
<aside className="fixed hidden h-screen md:flex">
<div className="flex h-screen w-[60px] flex-col items
<Link href="/">
<Image src={"/[Link]"} alt="interio logo" he
</Link>
<Link href="/designs">
<[Link] />
</Link>
<Link href="/profile">
<[Link] />
</Link>
<button onClick={() => dispatch(togglePanel("invite
<[Link] />
</button>
<button onClick={() => dispatch(togglePanel("collec
<[Link] />
</button>
<p className="border-gray w-[2vw] border"> </p>
<button title="Coming Soon!">
<[Link] />
</button>
<div className="cursor-pointer">{loginStatus && <Ic
{loginStatus && (
<Link href="/profile" className="absolute bottom-
<Image
src={vendor?.profilePic || "/[Link]"}
alt="interio logo"
height={40}
width={40}
className="h-10 w-10 rounded-full object-fill
/>
</Link>
)}
Interio - Full Stack 45
</div>
<div className="bluebg hidden h-screen w-[220px] flex
<div className="mt-3 w-[200px]">
{/* ?.split(' ')[0] */}
<h3 className="font-sm font-medium">Hello {vendor
<p className="text-light text-xs">Check out your
</div>
<Link
href="/designs"
className={clsx(
{ "bg-secondary/70 text-white": pathname == "/d
"flex w-[200px] items-center gap-x-2 rounded-lg
)}
>
<[Link] size={24} />
<div>
<h3 className="font-sm font-medium">10k+</h3>
<p className="text-light text-xs">Inspirations
</div>
</Link>
<div
className={clsx(
{ "bg-secondary text-white": pathname == "/suit
"flex w-[200px] items-center gap-x-2 rounded-lg
)}
>
<[Link] size={24} />
<div>
<h3 className="font-sm font-medium">123+</h3>
<p className="text-light text-xs">
Find Work <br /> (coming soon)
</p>
</div>
</div>
<div
Interio - Full Stack 46
className={clsx(
{ "bg-secondary text-white": pathname == "/user
"flex w-[200px] items-center gap-x-2 rounded-lg
)}
>
<[Link] size={24} />
<div>
<h3 className="font-sm font-medium">104+</h3>
<p className="text-light text-xs">
Hire Designer <br /> (coming soon)
</p>
</div>
</div>
<Link
href={`/profile/chat?v_id=${vendor._id}`}
className={clsx(
{
"bg-secondary text-white": pathname == "/prof
"pointer-events-none": !loginStatus,
},
"pointer-events-none flex w-[200px] items-cente
)}
>
<[Link] size={24} />
<div>
{loginStatus ? (
<>
<h3 className="font-sm font-medium">{"5+"}<
<p className="text-light text-xs">Project m
</>
) : (
<p className="text-light text-sm">Login to ch
)}
</div>
</Link>
</div>
Interio - Full Stack 47
</aside>
<div
className={clsx(
{
"px-4 pt-4 text-white sm:px-8 md:pt-8 xl:px-10 "
},
"px-4 py-8 text-white sm:px-8 md:ml-[65px] md:pt-8
)}
>
{!EXCLUDE_PATHS.includes(pathname) && !START_WITH_PAT
{children}
</div>
</>
)
}
Profile Pages
profile/[Link]
import { formatDateInDMY } from "@/utils/helper"
import { getVendorProfile } from "@/lib/actions/[Link]
import { Icons } from "@/components/icons"
const User = async () => {
const { vendor, error } = await getVendorProfile()
if (error || !vendor)
return (
<div className="text-center">
<h1 className="text-red-500">Some error at our end!</
<p className="text-gray">{error}</p>
</div>
)
Interio - Full Stack 48
return (
<section className="space-y-6">
<div>
<h1 className="mb-1 text-xl text-primary underline de
<p className="text-sm text-foreground">{vendor?.biogr
</div>
<div>
<h1 className="mb-1 text-xl text-primary underline de
<div className="text-gray mt-2 flex flex-wrap gap-2">
{vendor?.[Link]((_, i) => (
<span className="rounded-3xl bg-secondary px-4 py
{_}
</span>
))}
</div>
</div>
<div>
<h1 className="mb-1 text-xl text-primary underline de
{[Link] > 0 ? (
vendor?.[Link]((_, i) => (
<div className="mb-4 grid grid-cols-2 items-end j
<div>
<h2 className="mb-1 text-base">{_.title}</h2>
<h3 className="flex items-center gap-2 lg:ml-
<[Link] size={24} className="
</h3>
</div>
<p className="flex items-center gap-1">
<[Link] size={24} className="text-whi
</p>
<p className="flex items-center gap-1">
<[Link] size={24} className="text-whi
</p>
<p className="flex items-center gap-1">
<[Link] size={24} className="text-whi
Interio - Full Stack 49
{formatDateInDMY(_.to) || "Now"}
</p>
</div>
))
) : (
<p className="text-foreground">No work history foun
)}
<div>
<h1 className="mb-1 text-xl text-primary underline
{[Link] > 0 ? (
<div className="text-foreground">
{vendor?.[Link]((_, i) => (
<div key={i} className="mb-2">
<h2 className="mb-1 text-base">{_.title}</h
<h3 className="flex items-center gap-2 lg:m
<[Link] size={24} className="text
</h3>
</div>
))}
</div>
) : (
<p className="text-foreground">No looking for fou
)}
</div>
</div>
</section>
)
}
export default User
profile/edit/[Link]
Interio - Full Stack 50
"use client"
import { useEffect, useState } from "react"
import Image from "next/image"
import Link from "next/link"
import { useRouter } from "next/navigation"
import { TAGS } from "@/utils/dummy"
import { joiResolver } from "@hookform/resolvers/joi"
import axios from "axios"
import Joi from "joi"
import { SubmitHandler, useForm } from "react-hook-form"
import { toast } from "sonner"
import { Button, buttonVariants } from "@/components/ui/butto
import { Checkbox } from "@/components/ui/checkbox"
import { Form, FormControl, FormField, FormItem, FormLabel, F
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
import { Icons } from "@/components/icons"
interface IFormInput {
name: string
profilePic: string
email: string
contact: number
address: string
biography: string
workHistory: object
lookingFor: object
skills: string[]
}
const schema = [Link]({
name: [Link]().max(50).required().messages({
"[Link]": "Name must be at most {#limit} characters l
Interio - Full Stack 51
"[Link]": "Name is required",
}),
profilePic: [Link](),
contact: [Link]().min(8).max(12).messages({
"[Link]": "Contact number should be atleast 8 digitd"
"[Link]": "Name must be at most 12 digits long",
}),
email: [Link]()
.email({ tlds: { allow: false } })
.required()
.messages({
"[Link]": "Email is required",
"[Link]": "Email is required",
}),
address: [Link](),
biography: [Link]().max(500).messages({
"[Link]": "Bio must be at most {#limit} characters lo
}),
skills: [Link]().items([Link]()).min(1).required().m
"[Link]": "Select atleast 1 skill.",
"[Link]": "Select atleast 1 skill.",
}),
// workHistory: [Link]().items({
// title: [Link](),
// company: [Link](),
// location: [Link](),
// from: [Link](),
// to: [Link](),
// }),
// lookingFor: [Link]().items({
// title: [Link](),
// location: [Link](),
// }),
})
const Edit = () => {
Interio - Full Stack 52
const [vendor, setVendor] = useState<IFormInput>()
const [loading, setLoading] = useState<boolean>(false)
const router = useRouter()
const form = useForm<IFormInput>({
resolver: joiResolver(schema),
defaultValues: vendor,
})
const getVendor = async () => {
try {
const data = await [Link](`/api/vendor`)
if ([Link] !== 200) throw new Error("Something wen
const vendor = [Link]
setVendor(vendor)
if (vendor) {
[Link]("name", [Link])
[Link]("address", [Link])
[Link]("email", [Link])
[Link]("skills", [Link])
// [Link]("workHistory", [Link])
// [Link]("lookingFor", [Link])
// [Link] && [Link]("contact", vendor
// [Link] && [Link]("biography", ven
// [Link] && [Link]("profilePic", v
}
} catch (error) {
[Link](error)
}
}
useEffect(() => {
getVendor()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const onSubmit: SubmitHandler<IFormInput> = async (val) =>
setLoading(true)
Interio - Full Stack 53
try {
// [Link]((value) => value !== "")
// remove repeated skills
[Link] = [Link]((skill, index) => {
return [Link](skill) === index
})
const data = await [Link](`/api/vendor`, val)
[Link](data)
[Link]("Profile updated successful")
[Link]("/profile")
} catch (error) {
[Link]("Update failed")
} finally {
setLoading(false)
}
}
return (
<>
<Form {...form}>
<form onSubmit={[Link](onSubmit)} classNam
<div className="relative -mt-16 flex items-center
<div className="flex items-center gap-4">
<Image
src={[Link]("profilePic") || "/user.p
alt="user"
width={80}
height={80}
className="h-12 w-12 rounded-full md:h-20 md
/>
<[Link] size={24} />
</div>
<Link
className={buttonVariants({
Interio - Full Stack 54
variant: "outline",
})}
href={"/profile"}
>
Cancel Edit
</Link>
</div>
<div className="grid gap-4 md:grid-cols-3">
<FormField
control={[Link]}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input type="name" {...field} defaultValu
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={[Link]}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input type="email" {...field} defaultVal
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
Interio - Full Stack 55
control={[Link]}
name="contact"
render={({ field }) => (
<FormItem>
<FormLabel>Contact</FormLabel>
<FormControl>
<Input type="number" {...field} defaultVa
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<FormField
control={[Link]}
name="address"
render={({ field }) => (
<FormItem>
<FormLabel>Address</FormLabel>
<FormControl>
<Textarea {...field} defaultValue={vendor?
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={[Link]}
name="biography"
render={({ field }) => (
<FormItem>
<FormLabel>Biography</FormLabel>
<FormControl>
<Textarea {...field} defaultValue={vendor?
</FormControl>
<FormMessage />
Interio - Full Stack 56
</FormItem>
)}
/>
<FormField
control={[Link]}
name="skills"
render={() => (
<FormItem>
<FormLabel className="mb-2 text-base">Skills<
<div className="flex gap-2">
{[Link]((tag) => (
<FormField
key={[Link]}
control={[Link]}
name="skills"
render={({ field }) => {
return (
<FormItem key={[Link]} className="f
<FormControl>
<Checkbox
checked={[Link]?.include
onCheckedChange={(checked) =>
return checked
? [Link](addSkill
: [Link]([Link]
}}
/>
</FormControl>
<FormLabel className="font-normal
</FormItem>
)
}}
/>
))}
</div>
<FormMessage />
Interio - Full Stack 57
</FormItem>
)}
/>
<Button type="submit" className="w-full" disabled={
Save
</Button>
</form>
</Form>
</>
)
}
export default Edit
function addSkill(tag: string, tags: string[]) {
if (tags) {
return [...tags, tag]
} else {
return [tag]
}
}
profile/[tab]/[Link]
import { shotData } from "@/types"
import { getTabs } from "@/lib/actions/[Link]"
import ShotCard from "@/app/(designs)/designs/shot-card"
const Profile = async ({ params }: { params: { tab: string }
const { shots, error } = (await getTabs([Link])) as { s
if (!shots || [Link] == 0 || error) return <div class
Interio - Full Stack 58
return (
<>
<div className="grid grid-cols-2 justify-items-center g
{/* @ts-ignore - d */}
{shots && [Link]((shot: shotData) => <ShotCard key
</div>
</>
)
}
export default Profile
/[Link]
"use client"
import type { ReactElement } from "react"
import Image from "next/image"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { useAppDispatch } from "@/context/hook"
import { isLogin, togglePanel, vendor as vd } from "@/context
import { useSelector } from "react-redux"
import { toast } from "sonner"
import { Button, buttonVariants } from "@/components/ui/butto
import LeftSideBar from "@/app/(designs)/layout"
export default function Layout({ children }: { children: Reac
const pathname = usePathname()
const loginStatus = useSelector(isLogin)
const vendor = useSelector(vd)
const dispatch = useAppDispatch()
if (!loginStatus) {
Interio - Full Stack 59
[Link]("Signup/Signin for checking profile!")
}
return (
<LeftSideBar>
<>
<section className="flex-col">
<Image key={2} src={"/[Link]"} alt="cover_ima
{loginStatus && pathname !== "/profile/edit" && (
<div className="relative -mt-16 flex items-cente
<div className="flex items-center gap-4">
<Image
src={vendor?.profilePic || "/[Link]"}
alt="user"
width={80}
height={80}
className="h-12 w-12 rounded-full md:h-20 m
/>
<div>
<h1>{vendor?.name}</h1>
<p className="text-gray text-xs">0 Follower
</div>
</div>
<Link
className={buttonVariants({
variant: pathname == "/profile/edit" ? "out
})}
href={pathname == "/profile/edit" ? "/profile
>
{pathname == "/profile/edit" ? "Cancel Edit"
</Link>
</div>
)}
</section>
{loginStatus ? (
<>
{!(pathname == "/profile/edit") && (
Interio - Full Stack 60
<main>
<header className="text-gray my-2 grid grid-c
<Link
href={"/profile"}
className={`${buttonVariants({
variant: "secondary",
})} ${pathname == "/profile" ? "bg-second
>
About me
</Link>
<Link
href={"/profile/work"}
className={`${buttonVariants({
variant: "secondary",
})} ${pathname == "/profile/work" ? "bg-s
>
Work
</Link>
<Link
href={"/profile/liked-shot"}
className={`${buttonVariants({
variant: "secondary",
})} ${pathname == "/profile/liked-shot" ?
>
Liked Shots
</Link>
<Link
href={"/profile/collection"}
className={`${buttonVariants({
variant: "secondary",
})} ${pathname == "/profile/collection" ?
`}
>
Collections
</Link>
Interio - Full Stack 61
</header>
<p className="border-gray mb-4 w-full border-
</main>
)}
{children}
</>
) : (
<div className="flex flex-col items-center justify-
<h1 className="text-gray mt-8 text-center">
Login/Signup to access messages! or Back to{" "
<Link href="/designs" className="text-primary u
Designs
</Link>{" "}
</h1>
<div className="mt-4 flex gap-4">
<Button className="mr-4 rounded bg-primary px-4
Sign up
</Button>
<Button className="rounded bg-secondary px-4 py
Sign in
</Button>
</div>
</div>
)}
</>
</LeftSideBar>
)
}
LeftSideBar - component
"use client"
import type { ReactElement } from "react"
import Image from "next/image"
Interio - Full Stack 62
import Link from "next/link"
import { usePathname } from "next/navigation"
import { useAppDispatch, useAppSelector } from "@/context/hoo
import { isLogin, setLogout, togglePanel, vendor as vd } from
import { EXCLUDE_PATHS, START_WITH_PATHS } from "@/utils/dumm
import clsx from "clsx"
import { toast } from "sonner"
import { vendorLogout } from "@/lib/actions/[Link]"
import { Icons } from "@/components/icons"
import DesignNav from "./design-nav"
type Props = {
children?: [Link]
way?: "without" | "with"
}
export default function LeftSideBar({ children, way }: Props)
const dispatch = useAppDispatch()
const vendor = useAppSelector(vd)
const pathname = usePathname()
const loginStatus = useAppSelector(isLogin)
const logoutHandler = async () => {
try {
const data = await vendorLogout()
if ([Link]) {
return [Link]([Link])
}
[Link]("Logout Successfully!")
dispatch(setLogout())
} catch (error: any) {
[Link](error?.message)
[Link]("Some Error!", error)
}
Interio - Full Stack 63
}
if ([Link]("/blogs/add")) return <>{children}<
return (
<>
<aside className="fixed hidden h-screen md:flex">
<div className="flex h-screen w-[60px] flex-col items
<Link href="/">
<Image src={"/[Link]"} alt="interio logo" he
</Link>
<Link href="/designs">
<[Link] />
</Link>
<Link href="/profile">
<[Link] />
</Link>
<button onClick={() => dispatch(togglePanel("invite
<[Link] />
</button>
<button onClick={() => dispatch(togglePanel("collec
<[Link] />
</button>
<p className="border-gray w-[2vw] border"> </p>
<button title="Coming Soon!">
<[Link] />
</button>
<div className="cursor-pointer">{loginStatus && <Ic
{loginStatus && (
<Link href="/profile" className="absolute bottom-
<Image
src={vendor?.profilePic || "/[Link]"}
alt="interio logo"
height={40}
width={40}
Interio - Full Stack 64
className="h-10 w-10 rounded-full object-fill
/>
</Link>
)}
</div>
<div className="bluebg hidden h-screen w-[220px] flex
<div className="mt-3 w-[200px]">
{/* ?.split(' ')[0] */}
<h3 className="font-sm font-medium">Hello {vendor
<p className="text-light text-xs">Check out your
</div>
<Link
href="/designs"
className={clsx(
{ "bg-secondary/70 text-white": pathname == "/d
"flex w-[200px] items-center gap-x-2 rounded-lg
)}
>
<[Link] size={24} />
<div>
<h3 className="font-sm font-medium">10k+</h3>
<p className="text-light text-xs">Inspirations
</div>
</Link>
<div
className={clsx(
{ "bg-secondary text-white": pathname == "/suit
"flex w-[200px] items-center gap-x-2 rounded-lg
)}
>
<[Link] size={24} />
<div>
<h3 className="font-sm font-medium">123+</h3>
<p className="text-light text-xs">
Find Work <br /> (coming soon)
Interio - Full Stack 65
</p>
</div>
</div>
<div
className={clsx(
{ "bg-secondary text-white": pathname == "/user
"flex w-[200px] items-center gap-x-2 rounded-lg
)}
>
<[Link] size={24} />
<div>
<h3 className="font-sm font-medium">104+</h3>
<p className="text-light text-xs">
Hire Designer <br /> (coming soon)
</p>
</div>
</div>
<Link
href={`/profile/chat?v_id=${vendor._id}`}
className={clsx(
{
"bg-secondary text-white": pathname == "/prof
"pointer-events-none": !loginStatus,
},
"pointer-events-none flex w-[200px] items-cente
)}
>
<[Link] size={24} />
<div>
{loginStatus ? (
<>
<h3 className="font-sm font-medium">{"5+"}<
<p className="text-light text-xs">Project m
</>
) : (
<p className="text-light text-sm">Login to ch
Interio - Full Stack 66
)}
</div>
</Link>
</div>
</aside>
<div
className={clsx(
{
"px-4 pt-4 text-white sm:px-8 md:pt-8 xl:px-10 "
},
"px-4 py-8 text-white sm:px-8 md:ml-[65px] md:pt-8
)}
>
{!EXCLUDE_PATHS.includes(pathname) && !START_WITH_PAT
{children}
</div>
</>
)
}
Context Setup
[Link]
import { useDispatch, useSelector } from "react-redux"
import type { TypedUseSelectorHook } from "react-redux"
import type { AppDispatch, RootState } from "./store"
// Use throughout your app instead of plain `useDispatch` and
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState>
[Link]
Interio - Full Stack 67
import { configureStore } from "@reduxjs/toolkit"
import themeReducer from "./theme"
export const store = configureStore({
reducer: {
theme: themeReducer,
},
})
// Infer the `RootState` and `AppDispatch` types from the sto
export type RootState = ReturnType<typeof [Link]>
// Inferred type: {posts: PostsState, comments: CommentsState
export type AppDispatch = typeof [Link]
[Link]
import { createSlice } from "@reduxjs/toolkit"
import type { RootState } from "./store"
export interface themeState {
sidebar: boolean
isLogin: boolean
showPanel: boolean
panelFor: string
vendor: {
name?: string
_id?: string
token?: string
profilePic?: string
}
}
const initialState: themeState = {
Interio - Full Stack 68
sidebar: false,
isLogin: false,
showPanel: false,
panelFor: "",
vendor: {
name: "",
_id: "",
token: "",
profilePic: "",
},
}
export const themeSlice = createSlice({
name: "theme",
initialState,
reducers: {
togglePanel: (state, actions) => {
const panelType = [Link]
if (panelType === "HIDE") {
[Link] = false
[Link] = ""
} else {
[Link] = true
[Link] = panelType
}
},
showSidebar: (state) => {
[Link] = true
},
hideSidebar: (state) => {
[Link] = false
},
setLogin: (state, actions) => {
[Link] = true
Interio - Full Stack 69
const { name, _id, token, profilePic } = actions?.paylo
[Link] = name
[Link]._id = _id
[Link] = token
[Link] = profilePic
if (name && _id) {
[Link]("vendor", name)
[Link]("v_id", _id)
}
},
setLogout: (state) => {
[Link] = false
[Link]("vendor")
[Link]("v_id")
},
},
})
// Action creators are generated for each case reducer functi
export const { showSidebar, hideSidebar, setLogin, setLogout,
export const sidebar = (state: RootState) => [Link]
export const isLogin = (state: RootState) => [Link]
export const panelFor = (state: RootState) => [Link]
export const showPanel = (state: RootState) => [Link]
export const vendor = (state: RootState) => [Link]
export default [Link]
Conclusion
This project documentation provides a comprehensive overview of the Interio Full
Stack application, detailing its various components and functionalities. From user
profiles to vendor interactions, the documentation covers all essential aspects to
ensure a smooth and efficient development process.
Interio - Full Stack 70
By following the guidelines and code examples provided, you can effectively
contribute to and enhance the application's features. Continued collaboration and
adherence to best practices will be key to the project's success.
Useful Resources
1. [Link]
2. [Link]
3. [Link]
4. [Link]
5. [Link]
6. [Link]
7. [Link]
8. [Link]
Interio - Full Stack 71