Abhis Synopsys Final
Abhis Synopsys Final
ON
“E-commerce project”
(KCA 451)
Submitted in Partial Fulfillment of the requirements for The
Award of the Degree of
BY
ABHISHEK KUMAR
(2300160140002)
Under the Supervision of
UDIT AGARWAL
(DEAN)
M.C.A DEPARTMENT
(2025)
Table of Contents
Acknowledgement
Certificate
Declaration
1.Introduction
2.Background and Overview
3. Aims and Objectives
a. Technical Objectives
b.Code Architecture
c. Data Management Objectives
d.Educational, Functional, and Technical Objectives
e. Features of E-Commerce
f. Project Screenshots
4. Project Scope
a. User Interface
b.File Management Features
c. File Sharing & Collaboration
d.Authentication & User
Management
e. Technical Implementation
5. Survey of Technologies
6.Frontend
7. Backend and Database
8.Requirement and Analysis
a. Hardware Requirements
b.Software Requirements
c. Functional Requirements
d. Installation Requirements
ABHISHEK KUMAR
(2300160140002)
M.C.A 2nd Year (4th Semester)
Certificate
DATE:
TEACHER:
SIGNATURE:
DEPARTMENT: MCA
ABHISHEK
KUMAR
(2300160140002)
M.C.A 2nd Year (4th Semester)
1
1. Introduction
A website is a collection of interlinked web pages that are hosted on
a server and accessed through the internet using a web browser.
Websites are primarily designed to provide information or
showcase content to visitors. They can range from simple static
pages to complex, dynamic platforms.
A web application (web app) is a software application that runs in a
web browser. Unlike a traditional website, which focuses on
presenting static or dynamic content, a web app is designed for
interactivity and functionality, allowing users to perform specific
tasks.
Key Characteristics of a Web App:
Features of E-Commerce
● User authentication using Appwrite (signup, login, logout)
● Upload various file types (documents, images, videos, audio,
etc.)
● Preview files in a new tab
● - Rename, Delete and Download files from
storage
● - File sharing via unique share links
● - Dashboard with:
○ Total and used storage display
○ Recent uploads
○ File type categorization and summary
● - Global search across all uploaded files and shared
content
● - Sorting options by date, name, or file size
● - Responsive and modern user interface
● - Integration with Appwrite Node SDK
● - Environment configuration via
.env.local
● - Secure access and API communication with
environment variables
● - Clean and reusable component architecture
● - Modern state management and API handling
practices
● - Clean Modern Interface
7
Project Screenshots:
8
1
1
11
4. Project Scope
E-Commerce's functionality encompasses both individual users and
small teams.
a. User Interface
● Dashboard with overview of storage usage and file statistics
● Upload section with drag-and-drop and browse functionality
e. Technical Implementation
● Built using React 19 and Next.js 15 with App Router
● Appwrite Node SDK integration for storage and
authentication
● TailwindCSS and ShadCN for clean, modern UI components
● TypeScript for strong typing and developer safety
● Environment variable management for secure project
configuration
● Optimized routing and lazy loading for performance
● File size and type validation before upload
5. Survey of Technologies
● JavaScript: JavaScript is a scripting programming language
used to create interactions and control over the contents of the
webpage. JS is used here in this project mostly. Although I am
not using pure vanilla JS here instead I am using ReactJS which
is a Library of JavaScript itself.
● React 19: Enables the development of modular,
component-driven UIs with high performance.
● Next.js 15: Provides routing, server-side rendering, and
API routes, making it ideal for scalable applications.
● Appwrite: Serves as the BaaS backend, offering services
like authentication, storage, and databases.
● CSS: Cascading style sheets is used to format and give
the structure to the content of the webpage. It is used to
give the overall look to the webpage.
● TailwindCSS: A utility-first CSS framework that promotes
clean, responsive, and customizable UIs.
13
● ShadCN UI: A modern React component library that speeds
up frontend development.
● TypeScript: Adds type safety to JavaScript, improving
reliability and developer productivity.
● VSCode: It is a Integrated development env for
programming which Includes built-in support for JavaScript,
TypeScript, and Node.js, plus extensions for other
languages and runtimes.
● NPM and NodeJS: Nodejs is a runtime env which is used
to create backends for javascript based webapps, NPM is
used to manage node packages and modules.
14
6. Frontend
The frontend consists of:
● Minimum RAM: 8 GB
● Processor: Multi-core CPU (i5 or higher recommended)
● Storage: At least 10 GB for development and test files
Internet: Stable connection for API integration and
Appwrite services
Software Requirements:
Functional Requirements
- Installation Requirements
- Steps to run E-Commerce locally:
1. Copy the code
2. Install Node.js (version 18+).
3. Create a .env.local file with the following keys:
16
NEXT_PUBLIC_APPWRITE_ENDPOINT="https://cloud.appwrite.i
o/v 1"
NEXT_PUBLIC_APPWRITE_PROJECT="your_project_id"
NEXT_PUBLIC_APPWRITE_DATABASE="your_database_id"
NEXT_PUBLIC_APPWRITE_USERS_COLLECTION="users_colle
cti
on_id"
NEXT_PUBLIC_APPWRITE_FILES_COLLECTION="files_collect
io
n_id"
NEXT_PUBLIC_APPWRITE_BUCKET="bucket_id"
NEXT_APPWRITE_KEY="your_appwrite_key"
npm install
10. Codebase
app/(root)/page.js
import { Route, Routes } from "react-router-dom";
import AuthLayout from "./components/auth/layout";
import AuthLogin from "./pages/auth/login";
import AuthRegister from "./pages/auth/register";
import AdminLayout from "./components/admin-view/layout";
import AdminDashboard from "./pages/admin-view/dashboard";
import AdminProducts from "./pages/admin-view/products";
import AdminOrders from "./pages/admin-view/orders";
import AdminFeatures from "./pages/admin-view/features";
import ShoppingLayout from "./components/shopping-view/layout";
import NotFound from "./pages/not-found";
import ShoppingHome from "./pages/shopping-view/home";
import ShoppingListing from "./pages/shopping-view/listing";
import ShoppingCheckout from "./pages/shopping-view/checkout";
import ShoppingAccount from "./pages/shopping-view/account";
import CheckAuth from "./components/common/check-auth";
import UnauthPage from "./pages/unauth-page";
import { useDispatch, useSelector } from "react-redux";
import { useEffect } from "react";
import { checkAuth } from "./store/auth-slice";
import { Skeleton } from "@/components/ui/skeleton";
import PaypalReturnPage from "./pages/shopping-view/paypal-return";
import PaymentSuccessPage from "./pages/shopping-view/payment-success";
import SearchProducts from "./pages/shopping-view/search";
function App() {
const { user, isAuthenticated, isLoading } = useSelector(
(state) => state.auth
);
const dispatch = useDispatch();
useEffect(() => {
dispatch(checkAuth());
}, [dispatch]);
console.log(isLoading, user);
return (
<div className="flex flex-col overflow-hidden bg-white">
<Routes>
<Route
path="/"
element={
<CheckAuth
isAuthenticated={isAuthenticated}
user={user}
></CheckAuth>
}
/>
<Route
path="/auth"
element={
<CheckAuth isAuthenticated={isAuthenticated} user={user}>
3
<AuthLayout />
</CheckAuth>
}
>
<Route path="login" element={<AuthLogin />} />
<Route path="register" element={<AuthRegister />} />
</Route>
<Route
path="/admin"
element={
<CheckAuth isAuthenticated={isAuthenticated} user={user}>
<AdminLayout />
</CheckAuth>
}
>
<Route path="dashboard" element={<AdminDashboard />} />
<Route path="products" element={<AdminProducts />} />
<Route path="orders" element={<AdminOrders />} />
<Route path="features" element={<AdminFeatures />} />
</Route>
<Route
path="/shop"
element={
<CheckAuth isAuthenticated={isAuthenticated} user={user}>
<ShoppingLayout />
</CheckAuth>
}
>
<Route path="home" element={<ShoppingHome />} />
<Route path="listing" element={<ShoppingListing />} />
<Route path="checkout" element={<ShoppingCheckout />} />
<Route path="account" element={<ShoppingAccount />} />
<Route path="paypal-return" element={<PaypalReturnPage />} />
<Route path="payment-success" element={<PaymentSuccessPage />} />
<Route path="search" element={<SearchProducts />} />
</Route>
<Route path="/unauth-page" element={<UnauthPage />} />
<Route path="*" element={<NotFound />} />
</Routes>
</div>
);
}
auth/login
import CommonForm from "@/components/common/form";
import { useToast } from "@/components/ui/use-toast";
import { loginFormControls } from "@/config";
import { loginUser } from "@/store/auth-slice";
import { useState } from "react";
import { useDispatch } from "react-redux";
import { Link } from "react-router-dom";
const initialState = {
email: "",
password: "",
};
function AuthLogin() {
3
const [formData, setFormData] = useState(initialState);
const dispatch = useDispatch();
const { toast } = useToast();
function onSubmit(event) {
event.preventDefault();
dispatch(loginUser(formData)).then((data) => {
if (data?.payload?.success) {
toast({
title: data?.payload?.message,
});
} else {
toast({
title: data?.payload?.message,
variant: "destructive",
});
}
});
}
return (
<div className="mx-auto w-full max-w-md space-y-6">
<div className="text-center">
<h1 className="text-3xl font-bold tracking-tight text-foreground">
Sign in to your account
</h1>
<p className="mt-2">
Don't have an account
<Link
className="font-medium ml-2 text-primary hover:underline"
to="/auth/register"
>
Register
</Link>
</p>
</div>
<CommonForm
formControls={loginFormControls}
buttonText={"Sign In"}
formData={formData}
setFormData={setFormData}
onSubmit={onSubmit}
/>
</div>
);
}
auth/registreation
import CommonForm from "@/components/common/form";
const initialState = {
userName: "",
email: "",
password: "",
};
function AuthRegister() {
function onSubmit(event) {
event.preventDefault();
dispatch(registerUser(formData)).then((data) => {
if (data?.payload?.success) {
toast({
title: data?.payload?.message,
});
navigate("/auth/login");
} else {
toast({
title: data?.payload?.message,
variant: "destructive",
});
});
}
3
console.log(formData);
return (
<div className="text-center">
</h1>
<p className="mt-2">
<Link
to="/auth/login"
>
Login
</Link>
</p>
</div>
<CommonForm
formControls={registerFormControls}
buttonText={"Sign Up"}
formData={formData}
setFormData={setFormData}
onSubmit={onSubmit}
/>
</div>
);
Shopping_view/account.jsx
function ShoppingAccount() {
return (
<div className="flex flex-col">
<div className="relative h-[300px] w-full overflow-hidden">
<img
src={accImg}
className="h-full w-full object-cover object-center"
/>
</div>
<div className="container mx-auto grid grid-cols-1 gap-8 py-8">
<div className="flex flex-col rounded-lg border bg-background p-6 shadow-sm">
<Tabs defaultValue="orders">
<TabsList>
<TabsTrigger value="orders">Orders</TabsTrigger>
<TabsTrigger value="address">Address</TabsTrigger>
</TabsList>
<TabsContent value="orders">
<ShoppingOrders />
</TabsContent>
<TabsContent value="address">
<Address />
</TabsContent>
</Tabs>
</div>
</div>
</div>
);
}
export default ShoppingAccount;
3
Page/Chackout.jsx
import Address from "@/components/shopping-view/address";
import img from "../../assets/account.jpg";
import { useDispatch, useSelector } from "react-redux";
import UserCartItemsContent from "@/components/shopping-view/cart-items-content";
import { Button } from "@/components/ui/button";
import { useState } from "react";
import { createNewOrder } from "@/store/shop/order-slice";
import { Navigate } from "react-router-dom";
import { useToast } from "@/components/ui/use-toast";
function ShoppingCheckout() {
const { cartItems } = useSelector((state) => state.shopCart);
const { user } = useSelector((state) => state.auth);
const { approvalURL } = useSelector((state) => state.shopOrder);
const [currentSelectedAddress, setCurrentSelectedAddress] = useState(null);
const [isPaymentStart, setIsPaymemntStart] = useState(false);
const dispatch = useDispatch();
const { toast } = useToast();
console.log(currentSelectedAddress, "cartItems");
const totalCartAmount =
cartItems && cartItems.items && cartItems.items.length > 0
? cartItems.items.reduce(
(sum, currentItem) =>
sum +
(currentItem?.salePrice > 0
? currentItem?.salePrice
: currentItem?.price) *
currentItem?.quantity,
0
)
: 0;
function handleInitiatePaypalPayment() {
if (cartItems.length === 0) {
toast({
title: "Your cart is empty. Please add items to proceed",
variant: "destructive",
});
return;
}
if (currentSelectedAddress === null) {
toast({
title: "Please select one address to proceed.",
variant: "destructive",
});
return;
}
const orderData = {
userId: user?.id,
cartId: cartItems?._id,
cartItems: cartItems.items.map((singleCartItem) => ({
productId: singleCartItem?.productId,
title: singleCartItem?.title,
image: singleCartItem?.image,
3
price:
singleCartItem?.salePrice > 0
? singleCartItem?.salePrice
: singleCartItem?.price,
quantity: singleCartItem?.quantity,
})),
addressInfo: {
addressId: currentSelectedAddress?._id,
address: currentSelectedAddress?.address,
city: currentSelectedAddress?.city,
pincode: currentSelectedAddress?.pincode,
phone: currentSelectedAddress?.phone,
notes: currentSelectedAddress?.notes,
},
orderStatus: "pending",
paymentMethod: "paypal",
paymentStatus: "pending",
totalAmount: totalCartAmount,
orderDate: new Date(),
orderUpdateDate: new Date(),
paymentId: "",
payerId: "",
};
dispatch(createNewOrder(orderData)).then((data) => {
console.log(data, "sangam");
if (data?.payload?.success) {
setIsPaymemntStart(true);
} else {
setIsPaymemntStart(false);
}
});
}
if (approvalURL) {
window.location.href = approvalURL;
}
return (
<div className="flex flex-col">
<div className="relative h-[300px] w-full overflow-hidden">
<img src={img} className="h-full w-full object-cover object-center" />
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-5 mt-5 p-5">
<Address
selectedId={currentSelectedAddress}
setCurrentSelectedAddress={setCurrentSelectedAddress}
/>
<div className="flex flex-col gap-4">
{cartItems && cartItems.items && cartItems.items.length > 0
? cartItems.items.map((item) => (
<UserCartItemsContent cartItem={item} />
))
: null}
<div className="mt-8 space-y-4">
<div className="flex justify-between">
<span className="font-bold">Total</span>
<span className="font-bold">${totalCartAmount}</span>
</div>
</div>
<div className="mt-4 w-full">
3
<Button onClick={handleInitiatePaypalPayment} className="w-full">
{isPaymentStart
? "Processing Paypal Payment..."
: "Checkout with Paypal"}
</Button>
</div>
</div>
</div>
</div>
);
}
Pages/Home.jsx
import { Button } from "@/components/ui/button";
import bannerOne from "../../assets/banner-1.webp";
import bannerTwo from "../../assets/banner-2.webp";
import bannerThree from "../../assets/banner-3.webp";
import {
Airplay,
BabyIcon,
ChevronLeftIcon,
ChevronRightIcon,
CloudLightning,
Heater,
Images,
Shirt,
ShirtIcon,
ShoppingBasket,
UmbrellaIcon,
WashingMachine,
WatchIcon,
} from "lucide-react";
import { Card, CardContent } from "@/components/ui/card";
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
fetchAllFilteredProducts,
fetchProductDetails,
} from "@/store/shop/products-slice";
import ShoppingProductTile from "@/components/shopping-view/product-tile";
import { useNavigate } from "react-router-dom";
import { addToCart, fetchCartItems } from "@/store/shop/cart-slice";
import { useToast } from "@/components/ui/use-toast";
import ProductDetailsDialog from "@/components/shopping-view/product-details";
import { getFeatureImages } from "@/store/common-slice";
const categoriesWithIcon = [
{ id: "men", label: "Men", icon: ShirtIcon },
{ id: "women", label: "Women", icon: CloudLightning },
{ id: "kids", label: "Kids", icon: BabyIcon },
{ id: "accessories", label: "Accessories", icon: WatchIcon },
{ id: "footwear", label: "Footwear", icon: UmbrellaIcon },
];
const brandsWithIcon = [
{ id: "nike", label: "Nike", icon: Shirt },
3
{ id: "adidas", label: "Adidas", icon: WashingMachine },
{ id: "puma", label: "Puma", icon: ShoppingBasket },
{ id: "levi", label: "Levi's", icon: Airplay },
{ id: "zara", label: "Zara", icon: Images },
{ id: "h&m", label: "H&M", icon: Heater },
];
function ShoppingHome() {
const [currentSlide, setCurrentSlide] = useState(0);
const { productList, productDetails } = useSelector(
(state) => state.shopProducts
);
const { featureImageList } = useSelector((state) => state.commonFeature);
sessionStorage.setItem("filters", JSON.stringify(currentFilter));
navigate(`/shop/listing`);
}
function handleGetProductDetails(getCurrentProductId) {
dispatch(fetchProductDetails(getCurrentProductId));
}
function handleAddtoCart(getCurrentProductId) {
dispatch(
addToCart({
userId: user?.id,
productId: getCurrentProductId,
quantity: 1,
})
).then((data) => {
if (data?.payload?.success) {
dispatch(fetchCartItems(user?.id));
toast({
title: "Product is added to cart",
});
}
});
}
useEffect(() => {
if (productDetails !== null) setOpenDetailsDialog(true);
}, [productDetails]);
useEffect(() => {
const timer = setInterval(() => {
setCurrentSlide((prevSlide) => (prevSlide + 1) % featureImageList.length);
}, 15000);
3
return () => clearInterval(timer);
}, [featureImageList]);
useEffect(() => {
dispatch(
fetchAllFilteredProducts({
filterParams: {},
sortParams: "price-lowtohigh",
})
);
}, [dispatch]);
console.log(productList, "productList");
useEffect(() => {
dispatch(getFeatureImages());
}, [dispatch]);
return (
<div className="flex flex-col min-h-screen">
<div className="relative w-full h-[600px] overflow-hidden">
{featureImageList && featureImageList.length > 0
? featureImageList.map((slide, index) => (
<img
src={slide?.image}
key={index}
className={`${
index === currentSlide ? "opacity-100" : "opacity-0"
} absolute top-0 left-0 w-full h-full object-cover transition-opacity duration-1000`}
/>
))
: null}
<Button
variant="outline"
size="icon"
onClick={() =>
setCurrentSlide(
(prevSlide) =>
(prevSlide - 1 + featureImageList.length) %
featureImageList.length
)
}
className="absolute top-1/2 left-4 transform -translate-y-1/2 bg-white/80"
>
<ChevronLeftIcon className="w-4 h-4" />
</Button>
<Button
variant="outline"
size="icon"
onClick={() =>
setCurrentSlide(
(prevSlide) => (prevSlide + 1) % featureImageList.length
)
}
className="absolute top-1/2 right-4 transform -translate-y-1/2 bg-white/80"
>
<ChevronRightIcon className="w-4 h-4" />
</Button>
</div>
<section className="py-12 bg-gray-50">
<div className="container mx-auto px-4">
3
<h2 className="text-3xl font-bold text-center mb-8">
Shop by category
</h2>
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4">
{categoriesWithIcon.map((categoryItem) => (
<Card
onClick={() =>
handleNavigateToListingPage(categoryItem, "category")
}
className="cursor-pointer hover:shadow-lg transition-shadow"
>
<CardContent className="flex flex-col items-center justify-center p-6">
<categoryItem.icon className="w-12 h-12 mb-4 text-primary" />
<span className="font-bold">{categoryItem.label}</span>
</CardContent>
</Card>
))}
</div>
</div>
</section>
<section className="py-12">
<div className="container mx-auto px-4">
<h2 className="text-3xl font-bold text-center mb-8">
Feature Products
</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
{productList && productList.length > 0
? productList.map((productItem) => (
<ShoppingProductTile
handleGetProductDetails={handleGetProductDetails}
product={productItem}
handleAddtoCart={handleAddtoCart}
/>
))
: null}
</div>
</div>
</section>
<ProductDetailsDialog
open={openDetailsDialog}
setOpen={setOpenDetailsDialog}
3
productDetails={productDetails}
/>
</div>
);
}
/lib/appwrite/index.ts
import ProductFilter from "@/components/shopping-view/filter";
import ProductDetailsDialog from "@/components/shopping-view/product-details";
import ShoppingProductTile from "@/components/shopping-view/product-tile";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useToast } from "@/components/ui/use-toast";
import { sortOptions } from "@/config";
import { addToCart, fetchCartItems } from "@/store/shop/cart-slice";
import {
fetchAllFilteredProducts,
fetchProductDetails,
} from "@/store/shop/products-slice";
import { ArrowUpDownIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useSearchParams } from "react-router-dom";
function createSearchParamsHelper(filterParams) {
const queryParams = [];
queryParams.push(`${key}=${encodeURIComponent(paramValue)}`);
}
}
console.log(queryParams, "queryParams");
return queryParams.join("&");
}
function ShoppingListing() {
const dispatch = useDispatch();
const { productList, productDetails } = useSelector(
(state) => state.shopProducts
);
const { cartItems } = useSelector((state) => state.shopCart);
const { user } = useSelector((state) => state.auth);
const [filters, setFilters] = useState({});
const [sort, setSort] = useState(null);
const [searchParams, setSearchParams] = useSearchParams();
const [openDetailsDialog, setOpenDetailsDialog] = useState(false);
const { toast } = useToast();
3
function handleSort(value) {
setSort(value);
}
setFilters(cpyFilters);
sessionStorage.setItem("filters", JSON.stringify(cpyFilters));
}
function handleGetProductDetails(getCurrentProductId) {
console.log(getCurrentProductId);
dispatch(fetchProductDetails(getCurrentProductId));
}
if (getCartItems.length) {
const indexOfCurrentItem = getCartItems.findIndex(
(item) => item.productId === getCurrentProductId
);
if (indexOfCurrentItem > -1) {
const getQuantity = getCartItems[indexOfCurrentItem].quantity;
if (getQuantity + 1 > getTotalStock) {
toast({
title: `Only ${getQuantity} quantity can be added for this item`,
variant: "destructive",
});
return;
}
}
}
dispatch(
addToCart({
userId: user?.id,
productId: getCurrentProductId,
quantity: 1,
})
3
).then((data) => {
if (data?.payload?.success) {
dispatch(fetchCartItems(user?.id));
toast({
title: "Product is added to cart",
});
}
});
}
useEffect(() => {
setSort("price-lowtohigh");
setFilters(JSON.parse(sessionStorage.getItem("filters")) || {});
}, [categorySearchParam]);
useEffect(() => {
if (filters && Object.keys(filters).length > 0) {
const createQueryString = createSearchParamsHelper(filters);
setSearchParams(new URLSearchParams(createQueryString));
}
}, [filters]);
useEffect(() => {
if (filters !== null && sort !== null)
dispatch(
fetchAllFilteredProducts({ filterParams: filters, sortParams: sort })
);
}, [dispatch, sort, filters]);
useEffect(() => {
if (productDetails !== null) setOpenDetailsDialog(true);
}, [productDetails]);
console.log(productList, "productListproductListproductList");
return (
<div className="grid grid-cols-1 md:grid-cols-[200px_1fr] gap-6 p-4 md:p-6">
<ProductFilter filters={filters} handleFilter={handleFilter} />
<div className="bg-background w-full rounded-lg shadow-sm">
<div className="p-4 border-b flex items-center justify-between">
<h2 className="text-lg font-extrabold">All Products</h2>
<div className="flex items-center gap-3">
<span className="text-muted-foreground">
{productList?.length} Products
</span>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
size="sm"
className="flex items-center gap-1"
>
<ArrowUpDownIcon className="h-4 w-4" />
<span>Sort by</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-[200px]">
<DropdownMenuRadioGroup value={sort} onValueChange={handleSort}>
{sortOptions.map((sortItem) => (
<DropdownMenuRadioItem
value={sortItem.id}
3
key={sortItem.id}
>
{sortItem.label}
</DropdownMenuRadioItem>
))}
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 p-4">
{productList && productList.length > 0
? productList.map((productItem) => (
<ShoppingProductTile
handleGetProductDetails={handleGetProductDetails}
product={productItem}
handleAddtoCart={handleAddtoCart}
/>
))
: null}
</div>
</div>
<ProductDetailsDialog
open={openDetailsDialog}
setOpen={setOpenDetailsDialog}
productDetails={productDetails}
/>
</div>
);
}
/hooks/use-toast.ts
"use client"
import type
{ ToastActionElement,
ToastProps,
} from
"@/components/ui/toast"
const TOAST_LIMIT = 1
const TOAST_REMOVE_DELAY =
&{
id: string
title?: React.ReactNode
description?: React.ReactNode
action?: ToastActionElement
const actionTypes =
{ ADD_TOAST:
"ADD_TOAST",
UPDATE_TOAST: "UPDATE_TOAST",
DISMISS_TOAST: "DISMISS_TOAST",
REMOVE_TOAST:
"REMOVE_TOAST", } as const
let count = 0
function genId() {
return count.toString()
type Action =
|{
type: ActionType["ADD_TOAST"]
toast: ToasterToast
|{
type: ActionType["UPDATE_TOAST"]
toast: Partial<ToasterToast>
|{
type: ActionType["DISMISS_TOAST"]
toastId?: ToasterToast["id"]
|{
type: ActionType["REMOVE_TOAST"]
toastId?: ToasterToast["id"]
interface State
{ toasts:
ToasterToast[]
}
33
if (toastTimeouts.has(toastId))
{ return
{ toastTimeouts.delete(toastId)
dispatch({
type: "REMOVE_TOAST",
toastId: toastId,
})
}, TOAST_REMOVE_DELAY)
toastTimeouts.set(toastId, timeout)
{ case
"ADD_TOAST":
return {
...state,
case "UPDATE_TOAST":
return {
34
...state,
),
case "DISMISS_TOAST": {
if (toastId) {
addToRemoveQueue(toastId)
} else
{ state.toasts.forEach((toast) =>
{ addToRemoveQueue(toast.id)
})}
return {
...state,
?{
...t,
open: false,
}: t),}
case "REMOVE_TOAST":
return {
...state,
toasts: [],
}}
return {
...state,
} }}
listeners.forEach((listener) => {
listener(memoryState)
}) }
{ const id = genId()
dispatch({
type: "UPDATE_TOAST",
toast: { ...props, id },
})
dispatch({
type: "ADD_TOAST",
toast: {
...props,
id,
open: true,
{ if (!open) dismiss()
},
}, })
return {
id: id,
dismiss,
update, }}
function useToast() {
listeners.push(setState)
return () => {
listeners.splice(index, 1)
}
37
}, [state])
return {
...state,
toast,
}}
11. Testing
- Define Testing Objectives: Identify key functionalities (like file upload,
sharing, authentication) that need validation for correctness,
performance, and security.
- Testing Environment Setup: Prepare development, staging, and production
environments using the same Appwrite configurations to ensure
consistency.
- Testing Tools & Schedule: Choose tools for unit testing (Jest),
end-to-end testing (Cypress/Playwright), and API testing (Postman),
and define the testing schedule and milestones.
Functional Testing:
- File Upload and Download Speed: Test how the system performs
when uploading/downloading files of various sizes and types.
- Concurrent Usage: Simulate multiple users uploading files
simultaneously to evaluate backend handling and app stability.
38
Create a new file named .env.local in the root of your project and
add the following content:
NEXT_PUBLIC_APPWRITE_ENDPOINT="https://cloud.appwrite.io/v1"
NEXT_PUBLIC_APPWRITE_PROJECT=""
NEXT_PUBLIC_APPWRITE_DATABASE=""
NEXT_PUBLIC_APPWRITE_USERS_COLLECTION=""
NEXT_PUBLIC_APPWRITE_FILES_COLLECTION=""
NEXT_PUBLIC_APPWRITE_BUCKET=""
NEXT_APPWRITE_KEY=""
39
13. Bibliography
- Next.js 15 Documentation
Vercel. (2024). Next.js Documentation (App
Router & Features). Retrieved from:
https://nextjs.org/docs
- React 19 Documentation
Meta. (2024). React – A JavaScript library for
building user interfaces. Retrieved from:
https://reactjs.org
- Appwrite Documentation
Appwrite. (2024). Secure Backend Server for Web, Mobile &
Flutter Developers. Retrieved from: https://appwrite.io/docs
- TypeScript Handbook
Microsoft. (2024). TypeScript
Documentation. Retrieved from:
https://www.typescriptlang.org/docs/
- Tailwind CSS Documentation
Tailwind Labs. (2024). Tailwind CSS – Rapidly build
modern websites. Retrieved from:
https://tailwindcss.com/docs
- ShadCN UI Components
ShadCN. (2024). Beautifully designed components built with Radix
UI and Tailwind CSS. Retrieved from: https://ui.shadcn.dev
- MDN Web Docs
Mozilla Foundation. (2024). JavaScript, HTML, and
CSS Documentation. Retrieved from:
https://developer.mozilla.org
- Excalidraw
https://excalidraw.com
- Vercel Retrieved from: https://vercel.com
- Stack Overflow
Retrieved from: https://stackoverflow.com
- Google.com