diff --git a/app/(api)/_datalib/_resolvers/Order.ts b/app/(api)/_datalib/_resolvers/Order.ts index 858b56c..0cd3593 100644 --- a/app/(api)/_datalib/_resolvers/Order.ts +++ b/app/(api)/_datalib/_resolvers/Order.ts @@ -1,5 +1,11 @@ import Orders from '../_services/Orders'; -import { OrderInput, Order, OrderProductInput } from '@datatypes/Order'; +import { + Order, + OrderInput, + OrderProductInput, + OrderStatus, + CancellationStatus, +} from '@datatypes/Order'; import { ApolloContext } from '../apolloServer'; const resolvers = { @@ -13,14 +19,32 @@ const resolvers = { orders: ( _: never, args: { - statuses: string[]; + statuses: OrderStatus[]; + cancellation_statuses: CancellationStatus[]; search: string; offset: number; limit: number; }, ctx: ApolloContext ) => - Orders.findMany(args.statuses, args.search, args.offset, args.limit, ctx), + Orders.findMany( + args.statuses, + args.cancellation_statuses, + args.search, + args.offset, + args.limit, + ctx + ), + ordersCount: ( + _: never, + args: { + statuses: OrderStatus[]; + cancellation_statuses: CancellationStatus[]; + search: string; + }, + ctx: ApolloContext + ) => + Orders.count(args.statuses, args.cancellation_statuses, args.search, ctx), }, Mutation: { updateOrder: ( diff --git a/app/(api)/_datalib/_services/Orders.ts b/app/(api)/_datalib/_services/Orders.ts index c38a7ee..616628c 100644 --- a/app/(api)/_datalib/_services/Orders.ts +++ b/app/(api)/_datalib/_services/Orders.ts @@ -1,6 +1,12 @@ import revalidateCache from '@actions/revalidateCache'; import prisma from '../_prisma/client'; -import { OrderInput, OrderProductInput } from '@datatypes/Order'; +import { + CancellationStatus, + OrderInput, + OrderProductInput, + OrderStatus, + OrderUpdateInput, +} from '@datatypes/Order'; import { ApolloContext } from '../apolloServer'; import { Prisma } from '@prisma/client'; import Stripe from 'stripe'; @@ -14,7 +20,7 @@ export default class Orders { data: { ...input, // Spread the input fields total: 0, - status: 'pending', // Default status + status: OrderStatus.PENDING, // Default status created_at: new Date(), // Current timestamp }, }); @@ -34,20 +40,41 @@ export default class Orders { } static async findMany( - statuses: string[], + statuses: OrderStatus[], + cancellation_statuses: CancellationStatus[], search: string, offset: number, limit: number, ctx: ApolloContext ) { if (!ctx.isOwner && !ctx.hasValidApiKey) return null; - if (offset < 0 || limit <= 0) return null; - const whereClause: Prisma.OrderWhereInput = {}; + const queryConditions: Prisma.OrderWhereInput[] = []; + + const statusFilters: Prisma.OrderWhereInput[] = []; + if (statuses?.length) { + statusFilters.push({ status: { in: statuses } }); + } + if (cancellation_statuses?.length) { + statusFilters.push({ + cancellation_status: { in: cancellation_statuses }, + }); + } - if (statuses && statuses.length > 0) { - whereClause.status = { in: statuses }; + if (statusFilters.length > 1) { + queryConditions.push({ OR: statusFilters }); + } else if (statusFilters.length === 1) { + queryConditions.push(statusFilters[0]); + } + + // For in progress requests, ensure there's no cancellation status. + const isInProgressRequest = + statuses?.length && + !cancellation_statuses?.length && + !statuses.includes(OrderStatus.DELIVERED); + if (isInProgressRequest) { + queryConditions.push({ cancellation_status: null }); } if (search) { @@ -75,19 +102,86 @@ export default class Orders { }); } - whereClause.OR = searchConditions; + queryConditions.push({ OR: searchConditions }); } return prisma.order.findMany({ - where: whereClause, + where: { AND: queryConditions }, orderBy: { created_at: 'desc', }, - skip: offset * limit, + skip: offset, take: limit, }); } + static async count( + statuses: OrderStatus[], + cancellation_statuses: CancellationStatus[], + search: string, + ctx: ApolloContext + ) { + if (!ctx.isOwner && !ctx.hasValidApiKey) return null; + + const queryConditions: Prisma.OrderWhereInput[] = []; + + const statusFilters: Prisma.OrderWhereInput[] = []; + if (statuses?.length) { + statusFilters.push({ status: { in: statuses } }); + } + if (cancellation_statuses?.length) { + statusFilters.push({ + cancellation_status: { in: cancellation_statuses }, + }); + } + + if (statusFilters.length > 1) { + queryConditions.push({ OR: statusFilters }); + } else if (statusFilters.length === 1) { + queryConditions.push(statusFilters[0]); + } + + const isInProgressRequest = + statuses?.length && + !cancellation_statuses?.length && + !statuses.includes(OrderStatus.DELIVERED); + if (isInProgressRequest) { + queryConditions.push({ cancellation_status: null }); + } + + if (search) { + const searchConditions: Prisma.OrderWhereInput[] = [ + { customer_name: { contains: search, mode: 'insensitive' } }, + { customer_email: { contains: search, mode: 'insensitive' } }, + { customer_phone_num: { contains: search, mode: 'insensitive' } }, + ]; + + const searchAsNumber = parseInt(search, 10); + if (!isNaN(searchAsNumber)) { + searchConditions.push({ id: searchAsNumber }); + searchConditions.push({ + id: { + in: await prisma.order + .findMany({ + select: { id: true }, + }) + .then((orders) => + orders + .filter((order) => order.id.toString().includes(search)) + .map((order) => order.id) + ), + }, + }); + } + + queryConditions.push({ OR: searchConditions }); + } + + return prisma.order.count({ + where: { AND: queryConditions }, + }); + } + static async getProducts(order_id: number, ctx: ApolloContext) { if (!ctx.isOwner && !ctx.hasValidApiKey) return null; @@ -109,7 +203,7 @@ export default class Orders { } //UPDATE - static async update(id: number, input: OrderInput, ctx: ApolloContext) { + static async update(id: number, input: OrderUpdateInput, ctx: ApolloContext) { if (!ctx.isOwner && !ctx.hasValidApiKey) return null; try { @@ -283,12 +377,12 @@ export default class Orders { id, }, select: { - status: true, + cancellation_status: true, }, }); if (order) { - if (order.status == 'refunded') { + if (order.cancellation_status == 'REFUNDED') { await prisma.order.delete({ where: { id, @@ -302,7 +396,7 @@ export default class Orders { id, }, data: { - status: 'needs refund', + cancellation_status: CancellationStatus.CANCELLED, }, }); // could possibly add a message? "this order need to be refunded" or smth @@ -321,70 +415,62 @@ export default class Orders { products: OrderProductInput[], ctx: ApolloContext ) { - if (!ctx.isOwner && !ctx.hasValidApiKey) return null; - - try { - const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { - apiVersion: '2025-05-28.basil', // explicitly set the API version - }); - - // Lookup product prices from DB - const productIds = products.map((p) => p.product_id); - const dbProducts = await prisma.product.findMany({ - where: { id: { in: productIds } }, - }); - - const productMap = Object.fromEntries(dbProducts.map((p) => [p.id, p])); - - const total = products.reduce((sum, item) => { - const product = productMap[item.product_id]; - return sum + (product?.price ?? 0) * item.quantity; - }, 0); + if (!ctx.isOwner && !ctx.hasValidApiKey) { + throw new Error('Unauthorized'); + } - // Stripe counts payment amounts in cents - const amountInCents = Math.round(total * 100); + const stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string, { + apiVersion: '2025-05-28.basil', + }); - // Create the order - const createdOrder = await prisma.order.create({ - data: { - ...input, - total: total, - status: 'pending', - created_at: new Date(), - products: { - create: products.map((p) => ({ - quantity: p.quantity, - product: { connect: { id: p.product_id } }, - })), - }, - }, - include: { products: { include: { product: true } } }, - }); + let total = 0; + await Promise.all( + products.map(async (p) => { + const product = await prisma.product.findUnique({ + where: { id: p.product_id }, + }); + if (!product) { + throw new Error(`Product with ID ${p.product_id} not found`); + } + total += (product.price - (product.discount ?? 0)) * p.quantity; + }) + ); - // Create Stripe PaymentIntent - const paymentIntent = await stripe.paymentIntents.create({ - amount: amountInCents, - currency: 'usd', - metadata: { - orderId: createdOrder.id, + const order = await prisma.order.create({ + data: { + ...input, + total, + status: OrderStatus.ORDERED, + created_at: new Date(), + products: { + create: products.map((p) => ({ + product_id: p.product_id, + quantity: p.quantity, + })), }, - }); + }, + include: { + products: true, + }, + }); - // Save paymentIntentId to order - const updatedOrder = await prisma.order.update({ - where: { id: createdOrder.id }, - data: { paymentIntentId: paymentIntent.id }, - include: { products: { include: { product: true } } }, - }); + const paymentIntent = await stripe.paymentIntents.create({ + amount: Math.round(total * 100), + currency: 'usd', + metadata: { + orderId: order.id, + }, + }); - revalidateCache(['orders', 'products']); + await prisma.order.update({ + where: { id: order.id }, + data: { paymentIntentId: paymentIntent.id }, + }); - return { - order: updatedOrder, - clientSecret: paymentIntent.client_secret, - }; - } catch (e) { - return e; - } + revalidateCache(['orders']); + return { + order, + clientSecret: paymentIntent.client_secret, + }; } } diff --git a/app/(api)/_datalib/_typeDefs/Order.ts b/app/(api)/_datalib/_typeDefs/Order.ts index f2a1755..9ce45f8 100644 --- a/app/(api)/_datalib/_typeDefs/Order.ts +++ b/app/(api)/_datalib/_typeDefs/Order.ts @@ -1,6 +1,19 @@ import gql from 'graphql-tag'; const typeDefs = gql` + enum OrderStatus { + PENDING + ORDERED + SHIPPED + IN_TRANSIT + DELIVERED + } + + enum CancellationStatus { + CANCELLED + REFUNDED + } + type Order { id: ID! paymentIntentId: String @@ -19,7 +32,8 @@ const typeDefs = gql` shipping_city: String! shipping_zip: String! shipping_country: String! - status: String! + status: OrderStatus! + cancellation_status: CancellationStatus created_at: String! } @@ -37,7 +51,8 @@ const typeDefs = gql` shipping_city: String! shipping_zip: String! shipping_country: String! - status: String! + status: OrderStatus + cancellation_status: CancellationStatus } input OrderUpdateInput { @@ -55,7 +70,8 @@ const typeDefs = gql` shipping_city: String shipping_zip: String shipping_country: String - status: String + status: OrderStatus + cancellation_status: CancellationStatus } input OrderProductInput { @@ -76,11 +92,17 @@ const typeDefs = gql` type Query { order(id: ID!): Order orders( - statuses: [String] + statuses: [OrderStatus] + cancellation_statuses: [CancellationStatus] search: String offset: Int! limit: Int! ): [Order] + ordersCount( + statuses: [OrderStatus] + cancellation_statuses: [CancellationStatus] + search: String + ): Int } type Mutation { diff --git a/app/(api)/_types/Order.ts b/app/(api)/_types/Order.ts index e71ca62..b0bcb04 100644 --- a/app/(api)/_types/Order.ts +++ b/app/(api)/_types/Order.ts @@ -1,5 +1,18 @@ import { Product } from './Product'; +export enum OrderStatus { + PENDING = 'PENDING', + ORDERED = 'ORDERED', + SHIPPED = 'SHIPPED', + IN_TRANSIT = 'IN_TRANSIT', + DELIVERED = 'DELIVERED', +} + +export enum CancellationStatus { + CANCELLED = 'CANCELLED', + REFUNDED = 'REFUNDED', +} + export type Order = { id: number; total: number; @@ -16,8 +29,9 @@ export type Order = { shipping_city: string; shipping_zip: string; shipping_country: string; - status: string; - created_at: Date; + status: OrderStatus; + cancellation_status: CancellationStatus | null; + created_at: string; products: ProductToOrder[]; }; @@ -53,6 +67,8 @@ export type OrderInput = { shipping_city: string; shipping_zip: string; shipping_country: string; + status?: OrderStatus; + cancellation_status?: CancellationStatus; }; //make all inputs optional diff --git a/app/(pages)/(app)/(index-page)/_components/Hero/Hero.module.scss b/app/(pages)/(app)/(index-page)/_components/Hero/Hero.module.scss deleted file mode 100644 index b31ed3b..0000000 --- a/app/(pages)/(app)/(index-page)/_components/Hero/Hero.module.scss +++ /dev/null @@ -1,41 +0,0 @@ -@use 'media'; - -.container { - display: flex; - flex-direction: row; - justify-content: center; - height: calc(100vh - var(--navbar-height)); - width: 100%; - position: relative; - padding: var(--large-spacer); - @include media.phone { - padding: var(--medium-spacer); - } - - .img_container { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: black; - z-index: -1; - } - - .welcome { - width: 100%; - max-width: 1620px; - height: 100%; - border-radius: var(--b-radius); - border: solid 2px var(--light-background); - display: flex; - flex-direction: column; - justify-content: center; - text-align: center; - padding: var(--spacer); - - > h1 { - color: var(--text-light); - } - } -} \ No newline at end of file diff --git a/app/(pages)/(app)/(index-page)/_components/Hero/Hero.tsx b/app/(pages)/(app)/(index-page)/_components/Hero/Hero.tsx deleted file mode 100644 index 772990b..0000000 --- a/app/(pages)/(app)/(index-page)/_components/Hero/Hero.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import Image from 'next/image'; - -import styles from './Hero.module.scss'; - -export default function Hero() { - return ( -
-
- serene forest scene in the night -
-
-

ESTORE MANAGEMENT TOOL

-
-
- ); -} diff --git a/app/(pages)/(app)/(index-page)/page.tsx b/app/(pages)/(app)/(index-page)/page.tsx deleted file mode 100644 index 998843f..0000000 --- a/app/(pages)/(app)/(index-page)/page.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import Hero from './_components/Hero/Hero'; - -export default function Home() { - return ( -
- -
- ); -} diff --git a/app/(pages)/(app)/(orders)/_components/OrderCard.module.scss b/app/(pages)/(app)/(orders)/_components/OrderCard.module.scss new file mode 100644 index 0000000..5d38b74 --- /dev/null +++ b/app/(pages)/(app)/(orders)/_components/OrderCard.module.scss @@ -0,0 +1,192 @@ +.container { + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + padding: 24px; + background-color: var(--background-secondary); + border-radius: 12px; + box-shadow: 0px 4px 20px rgba(51, 51, 51, 0.06); + margin-bottom: 16px; +} + +.content { + display: flex; + align-items: center; + flex: 1; + gap: 24px; +} + +.product_image { + width: 240px; + height: 160px; + position: relative; + border-radius: 8px; + overflow: hidden; + border: 1px solid var(--gray-300); + flex-shrink: 0; + + .image { + object-fit: cover; + } +} + +.order_info { + flex: 1; + display: flex; + flex-direction: column; + gap: 16px; +} + +.order_details { + display: flex; + flex-direction: column; + gap: 4px; +} + +.order_id { + font-size: 18px; + font-weight: 600; + color: var(--text-dark); + margin: 0; +} + +.shipping_info { + font-size: 14px; + font-weight: 400; + color: var(--purple); + margin: 0; +} + +.order_date { + font-size: 14px; + font-weight: 400; + color: var(--gray-600); + margin: 0; +} + +.progress_section { + margin-top: 8px; +} + +.progress_bar { + display: flex; + align-items: center; + justify-content: space-between; + position: relative; + width: 100%; + max-width: 500px; + + &::before { + content: ''; + position: absolute; + top: 20%; + left: 0; + right: 0; + height: 2px; + background-color: var(--gray-300); + z-index: 1; + } +} + +.status_item { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + position: relative; + z-index: 2; +} + +.status_dot { + width: 20px; + height: 20px; + border-radius: 50%; + border: 2px solid var(--gray-300); + background-color: var(--background-secondary); + position: relative; + + &.completed { + background-color: var(--purple); + border-color: var(--purple); + + &::after { + content: '✓'; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: var(--text-light); + font-size: 12px; + font-weight: bold; + } + } + + &.current { + border-color: var(--purple); + background-color: var(--purple); + + &::after { + content: '✓'; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: var(--text-light); + font-size: 12px; + font-weight: bold; + } + } + + &.pending { + background-color: var(--background-secondary); + border-color: var(--gray-300); + } +} + +.status_label { + font-size: 12px; + font-weight: 400; + color: var(--gray-600); + text-align: center; + white-space: nowrap; +} + +.order_id_container { + display: flex; + align-items: center; + gap: 1rem; +} + +.status_badge { + padding: 0.25rem 0.75rem; + border-radius: 1rem; + font-size: 0.875rem; + font-weight: 500; + + &.refunded { + background-color: var(--error-light); + color: var(--error-dark); + } + + &.cancelled { + background-color: var(--gray-200); + color: var(--gray-800); + } +} + +.view_order_btn { + padding: 12px 24px; + background-color: var(--purple); + color: var(--text-light); + border: none; + border-radius: 8px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: background-color 0.2s ease; + + &:hover { + background-color: #2A1B5E; + } +} \ No newline at end of file diff --git a/app/(pages)/(app)/(orders)/_components/OrderCard.tsx b/app/(pages)/(app)/(orders)/_components/OrderCard.tsx new file mode 100644 index 0000000..378d97e --- /dev/null +++ b/app/(pages)/(app)/(orders)/_components/OrderCard.tsx @@ -0,0 +1,100 @@ +'use client'; + +import Image from 'next/image'; +import styles from './OrderCard.module.scss'; +import { CancellationStatus, OrderStatus } from '@datatypes/Order'; + +interface OrderCardProps { + id: number; + created_at: string; + status: OrderStatus; + cancellation_status: CancellationStatus | null; + image: string; +} + +export default function OrderCard({ + id, + created_at, + status, + cancellation_status, + image, +}: OrderCardProps) { + const progressBarStatuses = [ + OrderStatus.PENDING, + OrderStatus.ORDERED, + OrderStatus.SHIPPED, + OrderStatus.IN_TRANSIT, + OrderStatus.DELIVERED, + ]; + + const progressBarLabels: { [key: string]: string } = { + [OrderStatus.PENDING]: 'Pending', + [OrderStatus.ORDERED]: 'Ordered', + [OrderStatus.SHIPPED]: 'Shipped', + [OrderStatus.IN_TRANSIT]: 'In Transit', + [OrderStatus.DELIVERED]: 'Delivered', + }; + + const badgeStatusLabels: { [key: string]: string } = { + [CancellationStatus.CANCELLED]: 'Cancelled', + [CancellationStatus.REFUNDED]: 'Refunded', + }; + + const currentStatusIndex = progressBarStatuses.indexOf(status); + + return ( +
+
+
+ Product +
+ +
+
+
+

Order #{id}

+ {cancellation_status && ( + + {badgeStatusLabels[cancellation_status]} + + )} +
+

+ Order Placed:{' '} + {new Date(Number(created_at)).toLocaleDateString(undefined, { + year: 'numeric', + month: 'long', + day: 'numeric', + })} +

+
+ +
+
+ {progressBarStatuses.map((value, index) => ( +
+
+ + {progressBarLabels[value]} + +
+ ))} +
+
+
+
+ + +
+ ); +} diff --git a/app/(pages)/(app)/(orders)/_components/OrderSection/OrderSection.module.scss b/app/(pages)/(app)/(orders)/_components/OrderSection/OrderSection.module.scss new file mode 100644 index 0000000..d4cdd22 --- /dev/null +++ b/app/(pages)/(app)/(orders)/_components/OrderSection/OrderSection.module.scss @@ -0,0 +1,99 @@ +.container { + display: flex; + flex-direction: column; + gap: 24px; + width: 100%; +} + +.section_header { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + + h2 { + font-size: 24px; + font-weight: 600; + color: var(--text-dark); + margin: 0; + } +} + +.pagination { + display: flex; + align-items: center; + gap: 1rem; + background-color: var(--background-secondary); + padding: 0.5rem; + border-radius: 2rem; +} + +.pagination_btn { + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + border: none; + border-radius: 50%; + background-color: var(--background-primary); + color: var(--purple); + cursor: pointer; + transition: all 0.2s ease; + + &:hover { + background-color: var(--light-purple); + color: var(--text-light) + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + background-color: var(--gray-100); + color: var(--gray-400); + } +} + +.pagination_text { + font-size: 14px; + font-weight: 500; + color: var(--text-dark); +} + +.status_filters { + display: flex; + gap: 8px; + flex-wrap: wrap; + width: 100%; +} + +.filter_btn { + padding: 12px 20px; + font-size: 14px; + font-weight: 500; + border: 1px solid var(--gray-300); + border-radius: 10px; + background-color: var(--background-secondary); + color: var(--gray-700); + cursor: pointer; + transition: all 0.2s ease; + box-shadow: 0px 4px 20px rgba(51, 51, 51, 0.06); + + &:hover { + background-color: var(--background-primary); + border-color: var(--gray-400); + } + + &.selected { + background-color: var(--purple); + color: var(--text-light); + border-color: var(--purple); + } +} + +.order_cards { + display: flex; + flex-direction: column; + gap: 16px; + width: 100%; +} \ No newline at end of file diff --git a/app/(pages)/(app)/(orders)/_components/OrderSection/OrderSection.tsx b/app/(pages)/(app)/(orders)/_components/OrderSection/OrderSection.tsx new file mode 100644 index 0000000..011bb10 --- /dev/null +++ b/app/(pages)/(app)/(orders)/_components/OrderSection/OrderSection.tsx @@ -0,0 +1,88 @@ +import { FaChevronLeft, FaChevronRight } from 'react-icons/fa'; +import { Order } from '@datatypes/Order'; +import OrderCard from '../OrderCard'; +import styles from './OrderSection.module.scss'; + +interface OrderSectionProps { + title: string; + orders: Order[]; + statusOptions: (T | 'All')[]; + statusClassMap: { [key: string]: string }; + selectedStatus: T | 'All'; + onStatusChange: (status: T | 'All') => void; + page: number; + onPageChange: (page: number) => void; + highestPage: number; +} + +export default function OrderSection({ + title, + orders, + statusOptions, + statusClassMap, + selectedStatus, + onStatusChange, + page, + onPageChange, + highestPage, +}: OrderSectionProps) { + return ( +
+
+

{title}

+
+ + + {page + 1} of {highestPage + 1} + + +
+
+ +
+ {statusOptions.map((status) => ( + + ))} +
+ +
+ {orders.map((order) => ( + + ))} +
+
+ ); +} diff --git a/app/(pages)/(app)/(orders)/order-details/_components/OrderPart.module.scss b/app/(pages)/(app)/(orders)/order-details/_components/OrderPart.module.scss new file mode 100644 index 0000000..abc6721 --- /dev/null +++ b/app/(pages)/(app)/(orders)/order-details/_components/OrderPart.module.scss @@ -0,0 +1,68 @@ +@use 'media'; +@import url('https://fonts.googleapis.com/css2?family=Manrope:wght@400;600;700&display=swap'); + +.container { + width: 882px; + height: 120px; + background-color: white; + padding: 20px; + box-shadow: 0px 4px 20px 0px #3333330F; + border-radius: 10px; + display:flex; + align-items: center; +} +.text { + font-size: 18px; + font-family: 'Manrope', sans-serif; +} +.material { + width: 300px; + height: 80px; + margin-right: 121px; + display: flex; + align-items: center; + +} +.image { + width: 80px; + height: 80px; + margin-right: 16px; + border-radius: 10px; +} +.materialtext { + flex-direction: column; + gap: 8px; + width: 124px; + height: 52px; + font-size: 18px; + line-height: 120%; + font-family: 'Manrope', sans-serif; +} +.priceBreakdown { + margin-right: 121px; + width: 200px; + height: 22px; + font-family: 'Manrope', sans-serif; + font-size: 18px; + line-height: 120%; + display: flex; +} +.number +{ + width: 8px; + margin-right: 24px; +} +.times +{ + width: 11px; + margin-right: 24px; +} +.pricetimes { + width: 57px; +} +.price { + width: 100px; + height: 22px; + font-family: 'Manrope', sans-serif; + font-size: 18px; +} \ No newline at end of file diff --git a/app/(pages)/(app)/(orders)/order-details/_components/OrderPart.tsx b/app/(pages)/(app)/(orders)/order-details/_components/OrderPart.tsx new file mode 100644 index 0000000..e0f3f84 --- /dev/null +++ b/app/(pages)/(app)/(orders)/order-details/_components/OrderPart.tsx @@ -0,0 +1,31 @@ +'use client'; +import Image from 'next/image'; +import styles from './OrderPart.module.scss'; +import material from './material.jpg'; +export default function OrderPart() { + return ( +
+
+ material image +
+

Material Name

+

Category

+
+
+
+
+

1

+
+
+

X

+
+
+

$15.00

+
+
+
+

$15.00

+
+
+ ); +} diff --git a/app/(pages)/(app)/(orders)/order-details/_components/material.jpg b/app/(pages)/(app)/(orders)/order-details/_components/material.jpg new file mode 100644 index 0000000..9f9e69d Binary files /dev/null and b/app/(pages)/(app)/(orders)/order-details/_components/material.jpg differ diff --git a/app/(pages)/(app)/(orders)/order-details/page.module.scss b/app/(pages)/(app)/(orders)/order-details/page.module.scss new file mode 100644 index 0000000..765aa44 --- /dev/null +++ b/app/(pages)/(app)/(orders)/order-details/page.module.scss @@ -0,0 +1,151 @@ +@use 'media'; +@import url('https://fonts.googleapis.com/css2?family=Manrope:wght@400;600;700&display=swap'); + +.container { + display:flex; + width: 1428px; + height: 1117px; + background-color: #F6F6F6; + margin-left: 300px; +} +.seccontainer { + width: 1268px; + height: 100%; + margin-right: 80px; + margin-left: 80px; +} +.back { + width: 1268px; + height: 35px; + margin-top: 48px; + margin-top: 48px; + display:flex; +} +.maincontainer { + width: 1268px; +} +.orderdelete { + display: flex; + width: 1268px; + height: 64px; + margin-bottom: 16px; +} +.info{ + margin-top: 16px; + height: 580px; + display: flex; +} +.materialcost { + width: 882px; + height: 580px; +} +.materials { + flex-direction: column; + width: 882px; + height: 360px; + margin-bottom: 16px; + background-color: white; + border-radius: 10px; +} +.cost { + width: 882px; + height: 204px; + background-color: white; + border-radius: 10px; +} +.shipping { + width: 366px; + height: 532px; + background-color: white; + margin-left: 20px; + border-radius: 10px; + padding: 40px; + box-shadow: 0px 4px 20px 0px #3333330F; + +} +.arrowcontainer { + width: 18px; + height: 35px; +} +.arrow { + + border: solid black; + border-width: 0 3px 3px 0; + display: inline-block; + + cursor: pointer; + transform: rotate(135deg); + -webkit-transform: rotate(135deg); + +} +.left-arrow:hover { + background-color: #0056b3; +} +.text { + font-size: 18px; + font-family: 'Manrope', sans-serif; +} +.h1text{ + font-size: 48px; + font-family: 'Manrope', sans-serif; + width: 634px; + height: 64px; + margin-right: 462px; +} +.deletebutton{ + cursor: pointer; + width:172px; + height:64px; + border-radius: 10px; + padding:10px 40px 10px 40px; + background-color: white; + box-shadow: 0px 4px 20px 0px #3333330F; + border: none; +} +.dropdown { + position: relative; + display: inline-block; + } + + .dropdownToggle { + box-shadow: 0px 4px 20px 0px #3333330F; + width: 200px; + height: 48px; + padding: 10px 20px; + background-color: white; + color: black; + border: none; + cursor: pointer; + border-radius: 4px; + font-size: 16px; + } + + .dropdownToggle:hover { + background-color: #aeb6c0; + } + + .dropdownMenu { + position: absolute; + top: 110%; + left: 0; + background-color: white; + min-width: 160px; + box-shadow: 0px 8px 16px rgba(0,0,0,0.2); + border-radius: 4px; + padding: 8px 0; + z-index: 1; + } + .dropdownMenu li { + list-style: none; + } + + .dropdownMenu li a { + padding: 10px 16px; + display: block; + color: #333; + text-decoration: none; + } + + .dropdownMenu li a:hover { + background-color: #f1f1f1; + } \ No newline at end of file diff --git a/app/(pages)/(app)/(orders)/order-details/page.tsx b/app/(pages)/(app)/(orders)/order-details/page.tsx new file mode 100644 index 0000000..57fdbd3 --- /dev/null +++ b/app/(pages)/(app)/(orders)/order-details/page.tsx @@ -0,0 +1,57 @@ +'use client'; +import OrderPart from './_components/OrderPart'; +import React, { useState } from 'react'; +import styles from './page.module.scss'; + +export default function OrderDetails() { + const [isOpen, setIsOpen] = useState(false); + const toggleDropdown = () => { + setIsOpen((prevState) => !prevState); + }; + return ( +
+
+
+
+ +
+
+
+
+

Order #48219

+ +
+
+ + {isOpen && ( + + )} +
+
+
+
+ + + +
+
+
+
+
+
+
+
+ ); +} diff --git a/app/(pages)/(app)/(orders)/page.module.scss b/app/(pages)/(app)/(orders)/page.module.scss new file mode 100644 index 0000000..a57b6dc --- /dev/null +++ b/app/(pages)/(app)/(orders)/page.module.scss @@ -0,0 +1,160 @@ +.page_container { + display: flex; + flex-direction: column; + background-color: whitesmoke; + padding: 32px; + gap: 32px; + min-height: 100vh; + width: 100%; +} + +.header { + display: flex; + flex-direction: column; + gap: 24px; + width: 100%; +} + +.title { + font-size: 48px; + font-weight: 700; + color: var(--text-dark); + margin: 0; +} + +.searchbar { + display: flex; + align-items: center; + gap: 16px; + max-width: 600px; + width: 100%; + + .search { + flex: 1; + padding: 12px 20px; + font-size: 16px; + border: 1px solid var(--gray-300); + border-radius: 50px; + outline: none; + transition: border-color 0.2s ease; + + &:focus { + border-color: var(--purple); + } + + &::placeholder { + color: var(--gray-500); + } + } + + .search_submit { + padding: 12px 24px; + font-size: 16px; + font-weight: 600; + border: none; + border-radius: 10px; + background-color: var(--purple); + color: var(--text-light); + cursor: pointer; + transition: background-color 0.2s ease; + + &:hover { + background-color: #2A1B5E; + } + } +} + +.pagination { + display: flex; + align-items: center; + gap: 16px; +} + +.pagination_btn { + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + border: 1px solid var(--gray-300); + border-radius: 6px; + background-color: var(--background-secondary); + color: var(--gray-600); + cursor: pointer; + transition: all 0.2s ease; + + &:hover { + background-color: var(--background-primary); + border-color: var(--gray-400); + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } +} + +.pagination_text { + font-size: 14px; + font-weight: 500; + color: var(--gray-600); +} + +.status_filters { + display: flex; + gap: 8px; + flex-wrap: wrap; + width: 100%; +} + +.filter_btn { + padding: 12px 20px; + font-size: 14px; + font-weight: 500; + border: 1px solid var(--gray-300); + border-radius: 10px; + background-color: var(--background-secondary); + color: var(--gray-700); + cursor: pointer; + transition: all 0.2s ease; + box-shadow: 0px 4px 20px rgba(51, 51, 51, 0.06); + + &:hover { + background-color: var(--background-primary); + border-color: var(--gray-400); + } + + &.selected { + background-color: var(--purple); + color: var(--text-light); + border-color: var(--purple); + } + + // Individual button widths to match design + &.all-btn { + min-width: 81px; + } + + &.ordered { + min-width: 130px; + } + + &.packed { + min-width: 122px; + } + + &.shipped { + min-width: 130px; + } + + &.transit { + min-width: 138px; + } +} + +.order_cards { + display: flex; + flex-direction: column; + gap: 16px; + width: 100%; +} \ No newline at end of file diff --git a/app/(pages)/(app)/(orders)/page.tsx b/app/(pages)/(app)/(orders)/page.tsx new file mode 100644 index 0000000..865d325 --- /dev/null +++ b/app/(pages)/(app)/(orders)/page.tsx @@ -0,0 +1,271 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { gql } from 'graphql-tag'; +import sendApolloRequest from '../../_utils/sendApolloRequest'; +import { Order, OrderStatus, CancellationStatus } from '@datatypes/Order'; +import OrderSection from './_components/OrderSection/OrderSection'; +import styles from './page.module.scss'; + +const QUERY_LIMIT = 3; + +const ordersQuery = gql` + query OrderQuery( + $offset: Int! + $limit: Int! + $statuses: [OrderStatus!] + $cancellation_statuses: [CancellationStatus!] + $search: String + ) { + orders( + offset: $offset + limit: $limit + statuses: $statuses + cancellation_statuses: $cancellation_statuses + search: $search + ) { + id + customer_name + customer_email + customer_phone_num + created_at + status + cancellation_status + total + shipping_address_line_1 + shipping_address_line_2 + shipping_city + shipping_country + shipping_zip + billing_address_line_1 + billing_address_line_2 + billing_city + billing_country + billing_zip + products { + product { + id + name + price + discount + } + quantity + } + } + } +`; + +const countQuery = gql` + query OrdersCount( + $statuses: [OrderStatus!] + $cancellation_statuses: [CancellationStatus!] + $search: String + ) { + ordersCount( + statuses: $statuses + cancellation_statuses: $cancellation_statuses + search: $search + ) + } +`; + +export default function ViewOrderCards() { + const [inProgressOrders, setInProgressOrders] = useState([]); + const [inProgressOrdersCount, setInProgressOrdersCount] = useState(0); + const [completedOrders, setCompletedOrders] = useState([]); + const [completedOrdersCount, setCompletedOrdersCount] = useState(0); + const [selectedStatus, setSelectedStatus] = useState( + 'All' + ); + const [selectedCompletedStatus, setSelectedCompletedStatus] = useState< + CancellationStatus | 'All' | 'DELIVERED' + >('All'); + const [inProgressPage, setInProgressPage] = useState(0); + const [completedPage, setCompletedPage] = useState(0); + const [searchInput, setSearchInput] = useState(''); + const [searchTerm, setSearchTerm] = useState(''); + + const statusClassMap: { [key: string]: string } = { + All: 'all-btn', + [OrderStatus.PENDING]: 'pending', + [OrderStatus.ORDERED]: 'ordered', + [OrderStatus.SHIPPED]: 'shipped', + [OrderStatus.IN_TRANSIT]: 'transit', + }; + + const completedStatusClassMap: { [key: string]: string } = { + All: 'all-btn', + DELIVERED: 'delivered', + [CancellationStatus.CANCELLED]: 'cancelled', + [CancellationStatus.REFUNDED]: 'refunded', + }; + + const statusOptions: (OrderStatus | 'All')[] = [ + 'All', + OrderStatus.PENDING, + OrderStatus.ORDERED, + OrderStatus.SHIPPED, + OrderStatus.IN_TRANSIT, + ]; + + const completedStatusOptions: (CancellationStatus | 'All' | 'DELIVERED')[] = [ + 'All', + 'DELIVERED', + ...Object.values(CancellationStatus), + ]; + + const handleSearch = (e: React.FormEvent) => { + e.preventDefault(); + setSearchTerm(searchInput); + setInProgressPage(0); + setCompletedPage(0); + }; + + useEffect(() => { + const fetchInProgressOrders = async () => { + const inProgressStatuses = + selectedStatus === 'All' + ? [ + OrderStatus.PENDING, + OrderStatus.ORDERED, + OrderStatus.SHIPPED, + OrderStatus.IN_TRANSIT, + ] + : [selectedStatus]; + const ordersPromise = sendApolloRequest({ + request: ordersQuery, + variables: { + offset: inProgressPage * QUERY_LIMIT, + limit: QUERY_LIMIT, + statuses: inProgressStatuses, + search: searchTerm, + }, + }); + const countPromise = sendApolloRequest({ + request: countQuery, + variables: { + statuses: inProgressStatuses, + search: searchTerm, + }, + }); + const [ordersData, countData] = await Promise.all([ + ordersPromise, + countPromise, + ]); + setInProgressOrders(ordersData.data.orders); + setInProgressOrdersCount(countData.data.ordersCount); + }; + fetchInProgressOrders(); + }, [selectedStatus, inProgressPage, searchTerm]); + + useEffect(() => { + const fetchCompletedOrders = async () => { + let completed_statuses: OrderStatus[] = []; + let cancellation_statuses: CancellationStatus[] = []; + + if (selectedCompletedStatus === 'All') { + completed_statuses = [OrderStatus.DELIVERED]; + cancellation_statuses = Object.values(CancellationStatus); + } else if (selectedCompletedStatus === 'DELIVERED') { + completed_statuses = [OrderStatus.DELIVERED]; + } else { + cancellation_statuses = [selectedCompletedStatus as CancellationStatus]; + } + + const ordersPromise = sendApolloRequest({ + request: ordersQuery, + variables: { + offset: completedPage * QUERY_LIMIT, + limit: QUERY_LIMIT, + statuses: + completed_statuses.length > 0 ? completed_statuses : undefined, + cancellation_statuses: + cancellation_statuses.length > 0 + ? cancellation_statuses + : undefined, + search: searchTerm, + }, + }); + const countPromise = sendApolloRequest({ + request: countQuery, + variables: { + statuses: + completed_statuses.length > 0 ? completed_statuses : undefined, + cancellation_statuses: + cancellation_statuses.length > 0 + ? cancellation_statuses + : undefined, + search: searchTerm, + }, + }); + const [ordersData, countData] = await Promise.all([ + ordersPromise, + countPromise, + ]); + setCompletedOrders(ordersData.data.orders); + setCompletedOrdersCount(countData.data.ordersCount); + }; + fetchCompletedOrders(); + }, [selectedCompletedStatus, completedPage, searchTerm]); + + const highestInProgressPage = Math.max( + 0, + Math.ceil(inProgressOrdersCount / QUERY_LIMIT) - 1 + ); + const highestCompletedPage = Math.max( + 0, + Math.ceil(completedOrdersCount / QUERY_LIMIT) - 1 + ); + + return ( +
+
+

Orders

+ +
+ setSearchInput(e.target.value)} + /> + +
+
+ + { + setSelectedStatus(status); + setInProgressPage(0); + }} + page={inProgressPage} + onPageChange={setInProgressPage} + highestPage={highestInProgressPage} + /> + + { + setSelectedCompletedStatus(status); + setCompletedPage(0); + }} + page={completedPage} + onPageChange={setCompletedPage} + highestPage={highestCompletedPage} + /> +
+ ); +} diff --git a/app/(pages)/(app)/orders/view-order/_components/ContactInfo/ContactInfo.module.scss b/app/(pages)/(app)/(orders)/view-order/_components/ContactInfo/ContactInfo.module.scss similarity index 100% rename from app/(pages)/(app)/orders/view-order/_components/ContactInfo/ContactInfo.module.scss rename to app/(pages)/(app)/(orders)/view-order/_components/ContactInfo/ContactInfo.module.scss diff --git a/app/(pages)/(app)/orders/view-order/_components/ContactInfo/ContactInfo.tsx b/app/(pages)/(app)/(orders)/view-order/_components/ContactInfo/ContactInfo.tsx similarity index 100% rename from app/(pages)/(app)/orders/view-order/_components/ContactInfo/ContactInfo.tsx rename to app/(pages)/(app)/(orders)/view-order/_components/ContactInfo/ContactInfo.tsx diff --git a/app/(pages)/(app)/orders/view-order/_components/CreditCardPayment/CreditCardPayment.module.scss b/app/(pages)/(app)/(orders)/view-order/_components/CreditCardPayment/CreditCardPayment.module.scss similarity index 100% rename from app/(pages)/(app)/orders/view-order/_components/CreditCardPayment/CreditCardPayment.module.scss rename to app/(pages)/(app)/(orders)/view-order/_components/CreditCardPayment/CreditCardPayment.module.scss diff --git a/app/(pages)/(app)/orders/view-order/_components/CreditCardPayment/CreditCardPayment.tsx b/app/(pages)/(app)/(orders)/view-order/_components/CreditCardPayment/CreditCardPayment.tsx similarity index 100% rename from app/(pages)/(app)/orders/view-order/_components/CreditCardPayment/CreditCardPayment.tsx rename to app/(pages)/(app)/(orders)/view-order/_components/CreditCardPayment/CreditCardPayment.tsx diff --git a/app/(pages)/(app)/orders/view-order/_components/Navigator/Navigator.module.scss b/app/(pages)/(app)/(orders)/view-order/_components/Navigator/Navigator.module.scss similarity index 100% rename from app/(pages)/(app)/orders/view-order/_components/Navigator/Navigator.module.scss rename to app/(pages)/(app)/(orders)/view-order/_components/Navigator/Navigator.module.scss diff --git a/app/(pages)/(app)/orders/view-order/_components/Navigator/Navigator.tsx b/app/(pages)/(app)/(orders)/view-order/_components/Navigator/Navigator.tsx similarity index 100% rename from app/(pages)/(app)/orders/view-order/_components/Navigator/Navigator.tsx rename to app/(pages)/(app)/(orders)/view-order/_components/Navigator/Navigator.tsx diff --git a/app/(pages)/(app)/orders/view-order/_components/OrderDescription/OrderDescription.module.scss b/app/(pages)/(app)/(orders)/view-order/_components/OrderDescription/OrderDescription.module.scss similarity index 100% rename from app/(pages)/(app)/orders/view-order/_components/OrderDescription/OrderDescription.module.scss rename to app/(pages)/(app)/(orders)/view-order/_components/OrderDescription/OrderDescription.module.scss diff --git a/app/(pages)/(app)/orders/view-order/_components/OrderDescription/OrderDescription.tsx b/app/(pages)/(app)/(orders)/view-order/_components/OrderDescription/OrderDescription.tsx similarity index 100% rename from app/(pages)/(app)/orders/view-order/_components/OrderDescription/OrderDescription.tsx rename to app/(pages)/(app)/(orders)/view-order/_components/OrderDescription/OrderDescription.tsx diff --git a/app/(pages)/(app)/orders/view-order/_components/ProgessBar/ProgressBar.module.scss b/app/(pages)/(app)/(orders)/view-order/_components/ProgessBar/ProgressBar.module.scss similarity index 100% rename from app/(pages)/(app)/orders/view-order/_components/ProgessBar/ProgressBar.module.scss rename to app/(pages)/(app)/(orders)/view-order/_components/ProgessBar/ProgressBar.module.scss diff --git a/app/(pages)/(app)/orders/view-order/_components/ProgessBar/ProgressBar.tsx b/app/(pages)/(app)/(orders)/view-order/_components/ProgessBar/ProgressBar.tsx similarity index 100% rename from app/(pages)/(app)/orders/view-order/_components/ProgessBar/ProgressBar.tsx rename to app/(pages)/(app)/(orders)/view-order/_components/ProgessBar/ProgressBar.tsx diff --git a/app/(pages)/(app)/orders/view-order/_components/ShippingLabel/ShippingLabel.module.scss b/app/(pages)/(app)/(orders)/view-order/_components/ShippingLabel/ShippingLabel.module.scss similarity index 100% rename from app/(pages)/(app)/orders/view-order/_components/ShippingLabel/ShippingLabel.module.scss rename to app/(pages)/(app)/(orders)/view-order/_components/ShippingLabel/ShippingLabel.module.scss diff --git a/app/(pages)/(app)/orders/view-order/_components/ShippingLabel/ShippingLabel.tsx b/app/(pages)/(app)/(orders)/view-order/_components/ShippingLabel/ShippingLabel.tsx similarity index 100% rename from app/(pages)/(app)/orders/view-order/_components/ShippingLabel/ShippingLabel.tsx rename to app/(pages)/(app)/(orders)/view-order/_components/ShippingLabel/ShippingLabel.tsx diff --git a/app/(pages)/(app)/orders/view-order/page.module.scss b/app/(pages)/(app)/(orders)/view-order/page.module.scss similarity index 100% rename from app/(pages)/(app)/orders/view-order/page.module.scss rename to app/(pages)/(app)/(orders)/view-order/page.module.scss diff --git a/app/(pages)/(app)/orders/view-order/page.tsx b/app/(pages)/(app)/(orders)/view-order/page.tsx similarity index 100% rename from app/(pages)/(app)/orders/view-order/page.tsx rename to app/(pages)/(app)/(orders)/view-order/page.tsx diff --git a/app/(pages)/(app)/orders/order-cards/_components/OrderCard.module.scss b/app/(pages)/(app)/orders/order-cards/_components/OrderCard.module.scss deleted file mode 100644 index 2423aaa..0000000 --- a/app/(pages)/(app)/orders/order-cards/_components/OrderCard.module.scss +++ /dev/null @@ -1,90 +0,0 @@ -@use 'media'; - -.container { - width: 60%; - display: flex; - flex-direction: row; - align-items: center; - justify-content: space-between; - padding: var(--small-spacer) var(--medium-spacer); - background-color: var(--secondary); - background-color: var(--background-secondary); - color: var(--text-light); - border-style: solid; - border-width: 2px; // change from hard code - border-color: var(--gray-100); - - @include media.tablet { - flex-direction: column; - gap: var(--small-spacer); - } -} - -.item_image { - width: 185px; - height: 140px; - position: relative; - border-style: solid; - border-width: 1px; - border-color: var(--gray-300); -} - -.item_info { - display: flex; - flex-direction: column; - gap: var(--tiny-spacer); - - h4 { - font-size: 1.2rem; - font-weight: 600; - } -} - -.item_status { - width: 90%; - padding: var(--tiny-spacer); - display: flex; - justify-content: space-between; - position: relative; - - .status_bar { - height: 2px; - background-color: var(--gray-500); - position: absolute; - width: 80%; - bottom: 65%; - left: 10%; - right: 10%; - } - - .status_options { - display: flex; - flex-direction: column; - align-items: center; - gap: var(--tiny-spacer); - position: relative; - - .status_circle { - width: 32px; - height: 32px; - background-color: var(--background-secondary); - border-style: solid; - border-width: 1px; - border-color: var(--gray-500); - border-radius: 50%; - } - - p { - color: var(--gray-700); - } - } -} - -.info_button { - height: 47px; - width: 47px; - cursor: pointer; - background-color: var(--purple); - border-style: none; - border-radius: 10%; -} \ No newline at end of file diff --git a/app/(pages)/(app)/orders/order-cards/_components/OrderCard.tsx b/app/(pages)/(app)/orders/order-cards/_components/OrderCard.tsx deleted file mode 100644 index 8820c35..0000000 --- a/app/(pages)/(app)/orders/order-cards/_components/OrderCard.tsx +++ /dev/null @@ -1,77 +0,0 @@ -'use client'; -import styles from './OrderCard.module.scss'; -import Image from 'next/image'; -import arrowRight from '/public/icons/arrow_right_line.svg'; -import purpleCheck from '/public/icons/purple_check.svg'; -import issue from '/public/icons/yellow_error.svg'; -import error from '/public/icons/red_error.svg'; -import greenCheck from '/public/icons/green_check.svg'; - -interface TextboxProps { - title: string; - date: Date; - status: number; - image: string; - icon: number; -} - -const OrderCard: React.FC = (props) => { - const itemStatuses = [ - 'Customer', - 'Payment', - 'Shipping', - 'Confirm', - 'Delivered', - ]; - - function displayIcon(status: number, icon: number, index: number) { - if (index < status) { - return purpleCheck; - } else if (index === status) { - if (icon === 0) { - return purpleCheck; - } else if (icon === 1) { - return issue; - } else if (icon === 2) { - return error; - } else { - return greenCheck; - } - } else { - return ''; - } - } - return ( -
-
- product image -
-
-

{props.title}

-

Order Placed: {props.date.toDateString()}

-
-
- {itemStatuses.map((status, index) => ( -
- {props.status <= 4 && - displayIcon(props.status, props.icon, index) !== '' ? ( - check icon - ) : ( -
- )} -

{status}

-
- ))} -
-
- -
- ); -}; - -export default OrderCard; diff --git a/app/(pages)/(app)/orders/order-cards/page.module.scss b/app/(pages)/(app)/orders/order-cards/page.module.scss deleted file mode 100644 index cfa24ed..0000000 --- a/app/(pages)/(app)/orders/order-cards/page.module.scss +++ /dev/null @@ -1,70 +0,0 @@ -@use 'media'; - -.page_container { - display: flex; - flex-direction: column; - background-color: var(--background-primary); - padding: var(--small-spacer) var(--medium-spacer); - gap: var(--small-spacer); - - h4 { - font-weight: 600; - padding: var(--small-spacer) 0; - } -} - -.order_form { - width: 60%; - display: grid; - grid-template-columns: 70% 30%; -} - -.order_cards { - display: flex; - flex-direction: column; - gap: var(--small-spacer); -} - -.searchbar { - display: flex; - justify-self: start; - width: 100%; - align-self: center; - - .search { - display: inline-block; - padding: var(--tiny-spacer); - font-size: 1rem; - border: 1px solid var(--gray-500); - border-radius: 5px 0 0 5px; - width: 100%; - outline: none; - } - - .search_submit { - display: inline-block; - padding: var(--tiny-spacer); - font-size: 1rem; - border: 1px solid var(--gray-500); - border-left: none; - border-radius: 0 5px 5px 0; - background-color: var(--purple); - color: var(--text-light); - cursor: pointer; - } -} - -.filter { - justify-self: end; - align-self: center; - width: 80%; - - select { - padding: var(--tiny-spacer); - font-size: 1rem; - width: 100%; - border-radius: 5px; - border-color: var(--gray-500); - outline: none; - } -} \ No newline at end of file diff --git a/app/(pages)/(app)/orders/order-cards/page.tsx b/app/(pages)/(app)/orders/order-cards/page.tsx deleted file mode 100644 index 6cb6d55..0000000 --- a/app/(pages)/(app)/orders/order-cards/page.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import OrderCard from './_components/OrderCard'; -import styles from './page.module.scss'; - -export default function ViewOrderCards() { - const dateTime = new Date('2024-03-01T10:36:01.516Z'); - const progressList = [ - { - name: 'Lightweight Water-Resistant Hooded Puffer Coat', - date: dateTime, - status: 3, - image: '/sample-product/puffer.png', - icon: 0, - }, - { - name: 'Lightweight Water-Resistant Hooded Puffer Coat', - date: dateTime, - status: 1, - image: '/sample-product/watch.png', - icon: 2, - }, - { - name: 'Lightweight Water-Resistant Hooded Puffer Coat', - date: dateTime, - status: 1, - image: '/sample-product/sneaker.png', - icon: 1, - }, - { - name: 'Lightweight Water-Resistant Hooded Puffer Coat', - date: dateTime, - status: 1, - image: '/sample-product/plant.png', - icon: 0, - }, - ]; - const deliveredList = [ - { - name: 'Lightweight Water-Resistant Hooded Puffer Coat', - date: dateTime, - status: 4, - image: '/sample-product/cap.png', - icon: 3, - }, - { - name: 'Lightweight Water-Resistant Hooded Puffer Coat', - date: dateTime, - status: 4, - image: '/sample-product/bear.png', - icon: 3, - }, - { - name: 'Lightweight Water-Resistant Hooded Puffer Coat', - date: dateTime, - status: 4, - image: '/sample-product/hydro.png', - icon: 3, - }, - ]; - return ( -
-
-

Orders

-
-
- - -
-
- -
-
-
-
-

Progress

-
- {progressList.map((item, index) => ( -
- -
- ))} -
-
-
-

Past Orders

-
- {deliveredList.map((item, index) => ( -
- -
- ))} -
-
-
- ); -} diff --git a/app/(pages)/_components/Sidebar/Sidebar.module.scss b/app/(pages)/_components/Sidebar/Sidebar.module.scss index de85dc4..c92d9dd 100644 --- a/app/(pages)/_components/Sidebar/Sidebar.module.scss +++ b/app/(pages)/_components/Sidebar/Sidebar.module.scss @@ -6,7 +6,7 @@ left: 0; height: 100vh; width: fit-content; - background-color: whitesmoke; + background-color: white; .header { display: flex; diff --git a/app/(pages)/_components/Sidebar/Sidebar.tsx b/app/(pages)/_components/Sidebar/Sidebar.tsx index 1fb9596..c99ead7 100644 --- a/app/(pages)/_components/Sidebar/Sidebar.tsx +++ b/app/(pages)/_components/Sidebar/Sidebar.tsx @@ -1,12 +1,13 @@ 'use client'; import { useState } from 'react'; +import { usePathname } from 'next/navigation'; import Link from 'next/link'; -import styles from './Sidebar.module.scss'; -import { CgProfile } from 'react-icons/cg'; -import { IoPricetagOutline } from 'react-icons/io5'; -import { FiShoppingCart } from 'react-icons/fi'; +import { FiEdit, FiShoppingCart } from 'react-icons/fi'; +import { RiFileList3Line } from 'react-icons/ri'; + +import styles from './Sidebar.module.scss'; interface NavLink { name: string; @@ -16,24 +17,25 @@ interface NavLink { const navLinks: NavLink[] = [ { - name: 'Profile', - slug: '/profile/account-settings', - icon: , + name: 'Orders', + slug: '/', + icon: , }, { - name: 'Product', + name: 'Product Listings', slug: '/products', - icon: , + icon: , }, { - name: 'Order', - slug: '/orders', - icon: , + name: 'Edit Store', + slug: '/profile/account-settings', + icon: , }, ]; export default function Sidebar() { - const [selectedSlug, setSelectedSlug] = useState(null); + const pathname = usePathname(); + const [selectedSlug, setSelectedSlug] = useState(pathname); const handleSelect = (slug: string) => { setSelectedSlug(slug); diff --git a/app/(pages)/_globals/styles/colors.scss b/app/(pages)/_globals/styles/colors.scss index 3f5c1e3..a176e35 100644 --- a/app/(pages)/_globals/styles/colors.scss +++ b/app/(pages)/_globals/styles/colors.scss @@ -5,18 +5,23 @@ --blue: #0D6EFD; --light-purple: #DBADED; - --background-primary: #F4F2FA; + --error-light: #FFEDED; + --error-dark: #DE5D4B; + + --background-primary: #ede8fc; --background-secondary: #FFFFFF; --background-tertiary: #DEE2E7; --background-upload: #F9F9F9; --purple-highlight: #7B61FF; --text-dark: #1C1C1C; + --gray-800: #333333; --gray-700: #4E5566; --gray-600: #6E7485; --gray-500: #8B96A5; --gray-400: #CFCFCF; --gray-300: #DEE2E7; + --gray-200: #DEE2E7; --gray-100: #E9EAF0; --text-light: white; diff --git a/prisma/migrations/20250710064028_order_status/migration.sql b/prisma/migrations/20250710064028_order_status/migration.sql new file mode 100644 index 0000000..d209377 --- /dev/null +++ b/prisma/migrations/20250710064028_order_status/migration.sql @@ -0,0 +1,12 @@ +/* + Warnings: + + - Changed the type of `status` on the `Order` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required. + +*/ +-- CreateEnum +CREATE TYPE "OrderStatus" AS ENUM ('PENDING', 'ORDERED', 'SHIPPED', 'IN_TRANSIT', 'DELIVERED', 'CANCELLED', 'REFUNDED'); + +-- AlterTable +ALTER TABLE "Order" DROP COLUMN "status", +ADD COLUMN "status" "OrderStatus" NOT NULL; diff --git a/prisma/migrations/20250710200308_order_cancellation_status/migration.sql b/prisma/migrations/20250710200308_order_cancellation_status/migration.sql new file mode 100644 index 0000000..a672f1c --- /dev/null +++ b/prisma/migrations/20250710200308_order_cancellation_status/migration.sql @@ -0,0 +1,20 @@ +/* + Warnings: + + - The values [CANCELLED,REFUNDED] on the enum `OrderStatus` will be removed. If these variants are still used in the database, this will fail. + +*/ +-- CreateEnum +CREATE TYPE "CancellationStatus" AS ENUM ('CANCELLED', 'REFUNDED'); + +-- AlterEnum +BEGIN; +CREATE TYPE "OrderStatus_new" AS ENUM ('PENDING', 'ORDERED', 'SHIPPED', 'IN_TRANSIT', 'DELIVERED'); +ALTER TABLE "Order" ALTER COLUMN "status" TYPE "OrderStatus_new" USING ("status"::text::"OrderStatus_new"); +ALTER TYPE "OrderStatus" RENAME TO "OrderStatus_old"; +ALTER TYPE "OrderStatus_new" RENAME TO "OrderStatus"; +DROP TYPE "OrderStatus_old"; +COMMIT; + +-- AlterTable +ALTER TABLE "Order" ADD COLUMN "cancellation_status" "CancellationStatus"; diff --git a/prisma/schema/Order.prisma b/prisma/schema/Order.prisma index d599fd2..5e8b919 100644 --- a/prisma/schema/Order.prisma +++ b/prisma/schema/Order.prisma @@ -1,5 +1,5 @@ model Order { - id Int @id @default(autoincrement()) + id Int @id @default(autoincrement()) paymentIntentId String? total Float customer_name String @@ -15,7 +15,21 @@ model Order { shipping_city String shipping_zip String shipping_country String - status String + status OrderStatus + cancellation_status CancellationStatus? created_at DateTime products ProductToOrder[] } + +enum OrderStatus { + PENDING + ORDERED + SHIPPED + IN_TRANSIT + DELIVERED +} + +enum CancellationStatus { + CANCELLED + REFUNDED +}