-
Notifications
You must be signed in to change notification settings - Fork 2
Feat/payment-page #55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
74fe111
88a58b1
61690a7
106131e
0d601d1
30021f1
94d19ef
8c3c340
975bdfb
db2c61e
6aea8d4
0e78a7c
5c318cc
222ac29
b433bff
c95be73
94f02a7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,12 +2,19 @@ | |||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| import { api } from '@/lib/api-client'; | ||||||||||||||||||||||||||||||||
| import { revalidatePath } from 'next/cache'; | ||||||||||||||||||||||||||||||||
| import { redirect, notFound } from 'next/navigation'; | ||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||
| OrderFromCartRequest, | ||||||||||||||||||||||||||||||||
| OrderFromProductRequest, | ||||||||||||||||||||||||||||||||
| OrderDetail, | ||||||||||||||||||||||||||||||||
| OrderListResponse, | ||||||||||||||||||||||||||||||||
| OrderListParams, | ||||||||||||||||||||||||||||||||
| } from '@/types/domain/order'; | ||||||||||||||||||||||||||||||||
| import type { PaymentDisplayItem } from '@/app/payment/_components/order-items'; | ||||||||||||||||||||||||||||||||
| import { getCartItems } from '@/app/actions/cart'; | ||||||||||||||||||||||||||||||||
| import { getProductDetail } from '@/app/actions/product'; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| /** μ₯λ°κ΅¬λ μ£Όλ¬Έ μμ± */ | ||||||||||||||||||||||||||||||||
| export async function createOrderFromCart( | ||||||||||||||||||||||||||||||||
| data: OrderFromCartRequest, | ||||||||||||||||||||||||||||||||
| ): Promise<number> { | ||||||||||||||||||||||||||||||||
|
|
@@ -21,11 +28,14 @@ export async function createOrderFromCart( | |||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||
| console.error('μ₯λ°κ΅¬λ μν μ£Όλ¬Έ μ€ν¨:', error); | ||||||||||||||||||||||||||||||||
| throw new Error( | ||||||||||||||||||||||||||||||||
| error instanceof Error ? error.message : 'μ₯λ°κ΅¬λ μν μ£Όλ¬Έμ μ€ν¨νμ΅λλ€.', | ||||||||||||||||||||||||||||||||
| error instanceof Error | ||||||||||||||||||||||||||||||||
| ? error.message | ||||||||||||||||||||||||||||||||
| : 'μ₯λ°κ΅¬λ μν μ£Όλ¬Έμ μ€ν¨νμ΅λλ€.', | ||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| /** μν μ§μ μ£Όλ¬Έ μμ± */ | ||||||||||||||||||||||||||||||||
| export async function createOrderFromProduct( | ||||||||||||||||||||||||||||||||
| data: OrderFromProductRequest, | ||||||||||||||||||||||||||||||||
| ): Promise<number> { | ||||||||||||||||||||||||||||||||
|
|
@@ -43,6 +53,92 @@ export async function createOrderFromProduct( | |||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| /** μ₯λ°κ΅¬λ μ£Όλ¬Έ μμ΄ν μ‘°ν */ | ||||||||||||||||||||||||||||||||
| export async function fetchCartOrderItems( | ||||||||||||||||||||||||||||||||
| params: Record<string, string | string[] | undefined>, | ||||||||||||||||||||||||||||||||
| ): Promise<PaymentDisplayItem[]> { | ||||||||||||||||||||||||||||||||
| const rawIds = params.items; | ||||||||||||||||||||||||||||||||
| const cartItemIds = ( | ||||||||||||||||||||||||||||||||
| typeof rawIds === 'string' ? rawIds.split(',') : (rawIds ?? []) | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| .map(Number) | ||||||||||||||||||||||||||||||||
| .filter((id) => !isNaN(id)); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if (cartItemIds.length === 0) redirect('/cart'); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const cartItems = await getCartItems(); | ||||||||||||||||||||||||||||||||
| const selected = cartItems.filter((c) => cartItemIds.includes(c.cartId)); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if (selected.length === 0) redirect('/cart'); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| return selected.map((c) => ({ | ||||||||||||||||||||||||||||||||
| productId: c.productId, | ||||||||||||||||||||||||||||||||
| productName: c.productName, | ||||||||||||||||||||||||||||||||
| brandName: c.brandName, | ||||||||||||||||||||||||||||||||
| thumbnailImageUrl: c.thumbnailImageUrl, | ||||||||||||||||||||||||||||||||
| selectedSize: c.selectedSize, | ||||||||||||||||||||||||||||||||
| selectedColor: c.selectedColor, | ||||||||||||||||||||||||||||||||
| quantity: c.quantity, | ||||||||||||||||||||||||||||||||
| originalPrice: c.price, | ||||||||||||||||||||||||||||||||
| price: c.price, | ||||||||||||||||||||||||||||||||
| totalPrice: c.totalPrice, | ||||||||||||||||||||||||||||||||
| cartItemId: c.cartId, | ||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| /** λ°λ‘ ꡬ맀 μμ΄ν μ‘°ν */ | ||||||||||||||||||||||||||||||||
| export async function fetchDirectOrderItems( | ||||||||||||||||||||||||||||||||
| params: Record<string, string | string[] | undefined>, | ||||||||||||||||||||||||||||||||
| ): Promise<PaymentDisplayItem[]> { | ||||||||||||||||||||||||||||||||
| const productId = Number(params.productId); | ||||||||||||||||||||||||||||||||
| if (!productId || isNaN(productId)) notFound(); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| let selections: { color: string; size: string; quantity: number }[]; | ||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||
| const raw = Array.isArray(params.selections) | ||||||||||||||||||||||||||||||||
| ? params.selections[0] | ||||||||||||||||||||||||||||||||
| : params.selections; | ||||||||||||||||||||||||||||||||
| selections = JSON.parse(raw || '[]'); | ||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||
| notFound(); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
Comment on lines
+96
to
+104
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
- selections = JSON.parse((params.selections as string) || '[]');
+ const raw = Array.isArray(params.selections)
+ ? params.selections[0]
+ : params.selections;
+ selections = JSON.parse(raw || '[]');π Committable suggestion
Suggested change
π€ Prompt for AI Agents |
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if (selections.length === 0) notFound(); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const product = await getProductDetail(productId); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| return selections.map((sel) => ({ | ||||||||||||||||||||||||||||||||
| productId: product.id, | ||||||||||||||||||||||||||||||||
| productName: product.name, | ||||||||||||||||||||||||||||||||
| brandName: product.brandName, | ||||||||||||||||||||||||||||||||
| thumbnailImageUrl: product.thumbnailImageUrl, | ||||||||||||||||||||||||||||||||
| selectedSize: sel.size, | ||||||||||||||||||||||||||||||||
| selectedColor: sel.color, | ||||||||||||||||||||||||||||||||
| quantity: sel.quantity, | ||||||||||||||||||||||||||||||||
| originalPrice: product.price, | ||||||||||||||||||||||||||||||||
| price: product.finalPrice, | ||||||||||||||||||||||||||||||||
| totalPrice: product.finalPrice * sel.quantity, | ||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| /** μ£Όλ¬Έ λ΄μ μ‘°ν */ | ||||||||||||||||||||||||||||||||
| export async function getOrders( | ||||||||||||||||||||||||||||||||
| params?: OrderListParams, | ||||||||||||||||||||||||||||||||
| ): Promise<OrderListResponse> { | ||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||
| const response = await api.get<OrderListResponse>('/orders', { | ||||||||||||||||||||||||||||||||
| params: params as Record<string, string | number | boolean | undefined>, | ||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||
| return response; | ||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||
| console.error('μ£Όλ¬Έ λ΄μ μ‘°ν μ€ν¨:', error); | ||||||||||||||||||||||||||||||||
| throw new Error( | ||||||||||||||||||||||||||||||||
| error instanceof Error ? error.message : 'μ£Όλ¬Έ λ΄μ μ‘°νμ μ€ν¨νμ΅λλ€.', | ||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| /** μ£Όλ¬Έ μμΈ μ‘°ν */ | ||||||||||||||||||||||||||||||||
| export async function getOrderDetail(orderId: number): Promise<OrderDetail> { | ||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||
| const orderDetail = await api.get<OrderDetail>(`/orders/${orderId}`); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| 'use server'; | ||
|
|
||
| import { api } from '@/lib/api-client'; | ||
| import type { ProductDetail } from '@/types/domain/product'; | ||
|
|
||
| /** μν μμΈ μ‘°ν */ | ||
| export async function getProductDetail( | ||
| productId: number, | ||
| ): Promise<ProductDetail> { | ||
| try { | ||
| const product = await api.get<ProductDetail>(`/products/${productId}`); | ||
| return product; | ||
| } catch (error) { | ||
| console.error('μν μμΈ μ‘°ν μ€ν¨:', error); | ||
| throw new Error( | ||
| error instanceof Error ? error.message : 'μν μμΈ μ‘°νμ μ€ν¨νμ΅λλ€.', | ||
| ); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| 'use server'; | ||
|
|
||
| import { api } from '@/lib/api-client'; | ||
| import type { UserInfoResDto } from '@/types/domain/user'; | ||
|
|
||
| /** λ΄ μ 보 μ‘°ν */ | ||
| export async function getUserInfo(): Promise<UserInfoResDto> { | ||
| try { | ||
| const user = await api.get<UserInfoResDto>('/users/me'); | ||
| if (typeof user.points === 'string') { | ||
| user.points = Number((user.points as string).replace(/,/g, '')) || 0; | ||
| } | ||
| return user; | ||
| } catch (error) { | ||
| console.error('μ μ μ 보 μ‘°ν μ€ν¨:', error); | ||
| throw new Error( | ||
| error instanceof Error ? error.message : 'μ μ μ 보 μ‘°νμ μ€ν¨νμ΅λλ€.', | ||
| ); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| export const SECTIONS = { | ||
| ITEMS: 'section-items', | ||
| SHIPPING: 'section-shipping', | ||
| PAYMENT: 'section-payment', | ||
| } as const; | ||
|
|
||
| export const SECTION_IDS = Object.values(SECTIONS); |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,96 @@ | ||||||||||||||||||||||||||||||
| import Image from 'next/image'; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| export interface PaymentDisplayItem { | ||||||||||||||||||||||||||||||
| productId: number; | ||||||||||||||||||||||||||||||
| productName: string; | ||||||||||||||||||||||||||||||
| brandName: string; | ||||||||||||||||||||||||||||||
| thumbnailImageUrl: string; | ||||||||||||||||||||||||||||||
| selectedSize: string; | ||||||||||||||||||||||||||||||
| selectedColor: string; | ||||||||||||||||||||||||||||||
| quantity: number; | ||||||||||||||||||||||||||||||
| originalPrice: number; // μ κ° | ||||||||||||||||||||||||||||||
| price: number; // μ΅μ’ κ°(νλ§€κ°) | ||||||||||||||||||||||||||||||
| totalPrice: number; | ||||||||||||||||||||||||||||||
| cartItemId?: number; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| interface Props { | ||||||||||||||||||||||||||||||
| items: PaymentDisplayItem[]; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| export default function OrderItemsSection({ items }: Props) { | ||||||||||||||||||||||||||||||
| const originalTotal = items.reduce( | ||||||||||||||||||||||||||||||
| (acc, item) => acc + item.originalPrice * item.quantity, | ||||||||||||||||||||||||||||||
| 0, | ||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||
| const discountTotal = items.reduce( | ||||||||||||||||||||||||||||||
| (acc, item) => acc + (item.originalPrice - item.price) * item.quantity, | ||||||||||||||||||||||||||||||
| 0, | ||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||
| const finalTotal = items.reduce((acc, item) => acc + item.totalPrice, 0); | ||||||||||||||||||||||||||||||
|
Comment on lines
+22
to
+30
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
π€ Prompt for AI Agents |
||||||||||||||||||||||||||||||
| const hasDiscount = discountTotal > 0; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||
| <div className="mt-8 flex flex-col gap-6 px-5"> | ||||||||||||||||||||||||||||||
| <p className="text-center text-3xl leading-normal font-medium"> | ||||||||||||||||||||||||||||||
| μ£Όλ¬Έ μ 보 νμΈν΄μ£ΌμΈμ | ||||||||||||||||||||||||||||||
| </p> | ||||||||||||||||||||||||||||||
| <ul className="mt-10 space-y-4"> | ||||||||||||||||||||||||||||||
| {items.map((item, index) => ( | ||||||||||||||||||||||||||||||
| <li key={`${item.productId}-${index}`} className="flex gap-4"> | ||||||||||||||||||||||||||||||
| <div className="flex h-full w-full items-center justify-center gap-4 rounded-2xl border border-black p-5"> | ||||||||||||||||||||||||||||||
| {item.thumbnailImageUrl ? ( | ||||||||||||||||||||||||||||||
| <Image | ||||||||||||||||||||||||||||||
| src={item.thumbnailImageUrl} | ||||||||||||||||||||||||||||||
| alt={item.productName} | ||||||||||||||||||||||||||||||
| width={110} | ||||||||||||||||||||||||||||||
| height={110} | ||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||||||||||
| <div className="flex h-[110px] w-[110px] items-center justify-center text-xl"> | ||||||||||||||||||||||||||||||
| No Image | ||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||
| <div className="flex flex-col justify-center gap-7 text-xl leading-[18px] font-medium"> | ||||||||||||||||||||||||||||||
| <span>{item.brandName}</span> | ||||||||||||||||||||||||||||||
| <p>{item.productName}</p> | ||||||||||||||||||||||||||||||
| <span> | ||||||||||||||||||||||||||||||
| {item.selectedColor} / {item.selectedSize} | ||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||
| <span>{item.quantity}κ°</span> | ||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||
| </li> | ||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||
| </ul> | ||||||||||||||||||||||||||||||
| {hasDiscount && ( | ||||||||||||||||||||||||||||||
| <p className="text-center text-xl leading-normal font-medium text-red-500"> | ||||||||||||||||||||||||||||||
| μ΅λ ν μΈμ΄ μ μ© λμ΄μ | ||||||||||||||||||||||||||||||
| </p> | ||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| <div className="mt-16 flex flex-col justify-center gap-6 text-xl leading-normal font-medium"> | ||||||||||||||||||||||||||||||
| <div className="flex items-center justify-between"> | ||||||||||||||||||||||||||||||
| <span>μν κΈμ‘</span> | ||||||||||||||||||||||||||||||
| <span className="text-2xl leading-[18px]"> | ||||||||||||||||||||||||||||||
| {originalTotal.toLocaleString()}μ | ||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||
| {hasDiscount && ( | ||||||||||||||||||||||||||||||
| <div className="flex items-center justify-between"> | ||||||||||||||||||||||||||||||
| <span>ν μΈ κΈμ‘</span> | ||||||||||||||||||||||||||||||
| <span className="text-2xl leading-[18px]"> | ||||||||||||||||||||||||||||||
| {discountTotal.toLocaleString()}μ | ||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||
|
Comment on lines
+79
to
+85
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ν μΈ κΈμ‘μ λΆνΈ νμ λλ½ ν μΈ κΈμ‘μ΄ μμλ‘ νμλμ΄ μΆκ° λΉμ©μ²λΌ λ³΄μΌ μ μμ΅λλ€. μμ μ μ <span className="text-2xl leading-[18px]">
- {discountTotal.toLocaleString()}μ
+ -{discountTotal.toLocaleString()}μ
</span>π Committable suggestion
Suggested change
π€ Prompt for AI Agents |
||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||
| <div className="flex items-center justify-between"> | ||||||||||||||||||||||||||||||
| <span>μ΄ κΈμ‘</span> | ||||||||||||||||||||||||||||||
| <span className="text-3xl leading-[18px]"> | ||||||||||||||||||||||||||||||
| {finalTotal.toLocaleString()}μ | ||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π§© Analysis chain
π Script executed:
rg -n "interface CartResponse|type CartResponse" -A 20 --type=tsRepository: IT-Cotato/12th-OnGil-FE
Length of output: 2229
π Script executed:
Repository: IT-Cotato/12th-OnGil-FE
Length of output: 1128
π Script executed:
Repository: IT-Cotato/12th-OnGil-FE
Length of output: 248
π Script executed:
Repository: IT-Cotato/12th-OnGil-FE
Length of output: 1352
π Script executed:
Repository: IT-Cotato/12th-OnGil-FE
Length of output: 1627
π Script executed:
Repository: IT-Cotato/12th-OnGil-FE
Length of output: 1568
CartResponseμoriginalPriceνλκ° μμ΄ ν μΈ κΈ°λ₯ λμ λΆκ°CartResponseλoriginalPriceνλλ₯Ό μ 곡νμ§ μμΌλ―λ‘, 82-83λ² μ€μμoriginalPrice: c.price,price: c.priceλ‘ λμΌν κ°μ λ§€νν μλ°μ μμ΅λλ€. μ΄λ‘ μΈν΄PaymentDisplayItemμ΄ κΈ°λνλ μ κ°μ νλ§€κ°μ ꡬλΆμ΄ λΆκ°λ₯νκ³ ,OrderItemsSectionμ ν μΈ κ³μ°(originalTotal - finalTotal)μ΄ νμ 0μ΄ λμ΄ ν μΈ νμκ° μ μ μλνμ§ μμ΅λλ€.APIμμ
CartResponseμoriginalPriceνλλ₯Ό μΆκ°νκ±°λ, λ³λμ μ κ° λ°μ΄ν° μμ€λ₯Ό ν보ν΄μΌ ν©λλ€.π€ Prompt for AI Agents