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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 201 additions & 0 deletions components/common/charge-confirm/ConfirmChargeDrawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
"use client";

import React from "react";
import { cn } from "@/lib/utils";
import { X, PencilLine, Check } from "lucide-react";
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@/components/ui/drawer";

import Button from "@/components/ui/Button";
import { BANK_INFO } from "@/lib/constants/charge";

/* ── Props ── */
interface ConfirmChargeDrawerProps {
trigger: React.ReactNode;
/** 입금액 (원 단위 숫자) */
amount: number;
/** 입금자명 (사전 설정된 값) */
depositorName?: string;
}

/* ────────────────────────────────────── */

export default function ConfirmChargeDrawer({
trigger,
amount,
depositorName = "천승환",
}: ConfirmChargeDrawerProps) {
const [agreed, setAgreed] = React.useState(false);
const [name, setName] = React.useState(depositorName);
const [isEditingName, setIsEditingName] = React.useState(false);
const inputRef = React.useRef<HTMLInputElement>(null);

/* 입금자명 수정 시 input focus */
React.useEffect(() => {
if (isEditingName) inputRef.current?.focus();
}, [isEditingName]);

/* 계좌번호 복사 */
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(
`${BANK_INFO.bank} ${BANK_INFO.account}`,
);
alert("계좌 정보가 복사되었습니다.");
} catch (err) {
console.error("Failed to copy: ", err);
}
};

/* 충전 완료 알림 */
const handleConfirm = () => {
alert("충전 요청이 완료되었습니다!");
};

return (
<Drawer onOpenChange={() => setAgreed(false)}>
<DrawerTrigger asChild>{trigger}</DrawerTrigger>
<DrawerContent
className="rounded-t-[24px] bg-white outline-none"
showHandle={false}
>
<div className="flex flex-col px-[15px] pt-6 pb-10">
<div className="flex flex-col items-center gap-8">
{/* ── Header ── */}
<DrawerHeader className="w-full shrink-0 gap-0 p-0">
<div className="flex items-center justify-between">
<DrawerTitle className="typo-20-700 text-black">
계좌이체
</DrawerTitle>
<DrawerClose aria-label="닫기">
<X size={20} className="text-color-gray-400" />
</DrawerClose>
</div>
</DrawerHeader>

{/* ── Content ── */}
<div className="flex w-full flex-col gap-6">
{/* ── 입금 정보 카드 ── */}
<div className="bg-color-gray-50 flex flex-col gap-4 rounded-[16px] p-4">
{/* 입금계좌 라벨 + 복사 */}
<div className="flex flex-col gap-4">
<div className="flex items-center gap-[10px]">
<span className="typo-16-600 text-[#777777]">입금계좌</span>
<button
type="button"
onClick={handleCopy}
className="bg-color-gray-64 border-color-gray-100 typo-10-500 text-color-gray-300 flex items-center justify-center rounded-full border px-2 py-1 backdrop-blur-[50px]"
>
복사
</button>
</div>

{/* 계좌 정보 박스 */}
<div className="flex flex-col items-center gap-2 rounded-[8px] bg-white py-4">
<span className="typo-16-500 text-color-gray-900 text-center">
{BANK_INFO.bank}
</span>
<span className="typo-16-500 text-color-gray-900 text-center">
{BANK_INFO.account}
</span>
<span className="typo-16-500 text-color-gray-900 text-center">
예금주 : {BANK_INFO.holder}
</span>
</div>
</div>

{/* 입금자명 */}
<div className="flex items-center gap-2">
<span className="typo-16-600 pb-[2px] text-[#777777]">
입금자명
</span>
{isEditingName ? (
<div className="flex items-center border-b border-[#6A6A6A] pb-[2px]">
<input
ref={inputRef}
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
onBlur={() => setIsEditingName(false)}
onKeyDown={(e) => {
if (e.key === "Enter") setIsEditingName(false);
}}
className="typo-16-700 text-color-gray-900 h-[24px] w-[90px] bg-transparent text-center outline-none"
maxLength={6}
/>
</div>
) : (
<button
type="button"
onClick={() => setIsEditingName(true)}
className="flex items-center gap-1 border-b border-[#6A6A6A] pb-[2px]"
>
<span className="typo-16-700 text-color-gray-900 leading-none">
{name}
</span>
<PencilLine size={12} className="text-[#6A6A6A]" />
</button>
)}
</div>

{/* 입금액 */}
<div className="flex items-center gap-2">
<span className="typo-16-600 pb-[2px] text-[#777777]">
입금액
</span>
<div className="flex items-center gap-1 pb-[2px]">
<span className="typo-16-700 text-color-gray-900">
{amount.toLocaleString()}
</span>
<span className="typo-16-700 text-color-gray-900">원</span>
</div>
</div>
</div>

{/* ── 동의 체크 ── */}
<div className="flex items-center gap-3">
<button
type="button"
onClick={() => setAgreed((prev) => !prev)}
className={cn(
"flex h-6 w-6 shrink-0 items-center justify-center rounded-full transition-colors",
agreed
? "bg-gradient-to-br from-[#FF775E] via-[#FF4D61] to-[#E83ABC]"
: "border-2 border-[#CCCCCC] bg-white",
)}
aria-label="동의 체크"
>
{agreed && (
<Check size={14} className="text-white" strokeWidth={3} />
)}
</button>
<span className="typo-16-700 text-black">
상기 계좌로 입금을 완료했습니다.
</span>
</div>
</div>

{/* ── 하단 버튼 영역 ── */}
<div className="flex w-full flex-col items-center gap-4">
{/* CTA 버튼 */}
<Button disabled={!agreed} onClick={handleConfirm}>
충전 요청 보내기
</Button>

{/* Toss 링크 */}
<span className="typo-12-500 text-center leading-[140%] text-[#999999]">
혹은 Toss로 계좌이체하기
</span>
</div>
</div>
</div>
</DrawerContent>
</Drawer>
);
}
9 changes: 7 additions & 2 deletions components/common/charge/DepositorNameContent.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"use client";

import React from "react";
import { cn } from "@/lib/utils";

Check warning on line 4 in components/common/charge/DepositorNameContent.tsx

View workflow job for this annotation

GitHub Actions / lint

'cn' is defined but never used
import { isValidDepositorName } from "@/lib/validators";
import Button from "@/components/ui/Button";

export default function DepositorNameContent() {
const [name, setName] = React.useState("");
Expand All @@ -15,7 +16,7 @@
};

return (
<div className="flex flex-col gap-[23px] pt-8">
<div className="flex h-full flex-col gap-[23px] pt-8 pb-[100px]">
{/* ── 입금자명 입력 ── */}
<div className="flex flex-col gap-2">
<span className="typo-14-500 text-[#666666]">입금자명</span>
Expand All @@ -31,7 +32,7 @@
value={name}
onChange={handleNameChange}
placeholder="이름을 입력해주세요"
className="typo-16-500 w-full bg-transparent text-center text-[#1A1A1A] outline-none placeholder:text-[#B3B3B3]"
className="typo-16-500 w-full bg-transparent text-left text-[#1A1A1A] outline-none placeholder:text-[#B3B3B3]"
maxLength={6}
/>
</div>
Expand Down Expand Up @@ -72,6 +73,10 @@
</p>
</div>
</div>

<Button fixed bottom={32} disabled={!name || name.length < 2}>
입금자명 설정하기
</Button>
</div>
);
}
20 changes: 14 additions & 6 deletions components/common/charge/QuickBundleCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
import React from "react";
import Image from "next/image";
import { ICON_SIZE } from "@/lib/constants/charge";
import ConfirmChargeDrawer from "@/components/common/charge-confirm/ConfirmChargeDrawer";

interface QuickBundleCardProps {
title: string;
icon: string;
description: string;
bonus: string;
price: string;
priceValue: number;
}

export default function QuickBundleCard({
Expand All @@ -18,6 +20,7 @@ export default function QuickBundleCard({
description,
bonus,
price,
priceValue,
}: QuickBundleCardProps) {
return (
<div className="border-color-gray-64 bg-color-gray-50 flex flex-1 flex-col items-center justify-between rounded-[16px] border p-2 pt-4">
Expand All @@ -40,12 +43,17 @@ export default function QuickBundleCard({
</div>
</div>
</div>
<button
type="button"
className="typo-16-600 bg-color-flame-700 mt-4 flex h-10 w-full items-center justify-center rounded-[8px] text-white"
>
{price}
</button>
<ConfirmChargeDrawer
trigger={
<button
type="button"
className="typo-16-600 bg-color-flame-700 mt-4 flex h-10 w-full items-center justify-center rounded-[8px] text-white"
>
{price}
</button>
}
amount={priceValue}
/>
</div>
);
}
57 changes: 15 additions & 42 deletions components/common/charge/ShopContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import React from "react";
import { useItems } from "@/hooks/useItems";
import ChargeInventoryCard from "./ChargeInventoryCard";
import QuickBundleCard from "./QuickBundleCard";
import { ShopItemRow } from "./ShopRows";
import { ShopBundleRow } from "./ShopRows";
import {
QUICK_BUNDLES,
INDIVIDUAL_ITEMS,
BUNDLE_ITEMS,
SHOP_ITEMS_API,
USAGE_INFO,
BUSINESS_INFO,
} from "@/lib/constants/charge";
Expand All @@ -20,6 +21,14 @@ export default function ShopContent() {
option: data?.data.optionTicketCount ?? 0,
};

// 개별 아이템: 리워드가 1개인 경우
const individualItems = SHOP_ITEMS_API.filter(
(item) => item.rewards.length === 1,
);

// 번들 아이템: 리워드가 2개 이상인 경우
const bundleItems = SHOP_ITEMS_API.filter((item) => item.rewards.length >= 2);

return (
<div className="flex flex-col gap-8 pt-8">
{/* ── 보유현황 카드 ── */}
Expand All @@ -39,21 +48,8 @@ export default function ShopContent() {
<div className="flex flex-col gap-1">
<span className="typo-16-700 text-[#373737]">전체</span>
<div className="flex flex-col">
{INDIVIDUAL_ITEMS.map((item) => (
<div
key={item.label}
className="border-color-gray-100 flex items-center justify-between border-b py-4"
>
<span className="typo-16-600 text-color-text-black">
{item.label}
</span>
<button
type="button"
className="typo-16-600 bg-color-flame-700 flex h-10 w-24 items-center justify-center rounded-[8px] text-white"
>
{item.price}
</button>
</div>
{individualItems.map((item) => (
<ShopItemRow key={item.id} item={item} />
))}
</div>
</div>
Expand All @@ -62,31 +58,8 @@ export default function ShopContent() {
<div className="flex flex-col gap-1">
<span className="typo-16-700 text-[#373737]">번들</span>
<div className="flex flex-col">
{BUNDLE_ITEMS.map((item) => (
<div
key={item.title}
className="border-color-gray-100 flex items-center justify-between border-b py-4"
>
<div className="flex flex-col gap-1">
<div className="flex items-center gap-1">
<span className="typo-16-600 text-color-text-black">
{item.title}
</span>
<span className="typo-10-600 text-color-text-caption2">
{item.description}
</span>
</div>
<span className="typo-10-600 text-color-flame-700">
{item.bonus}
</span>
</div>
<button
type="button"
className="typo-16-600 bg-color-flame-700 flex h-10 w-24 shrink-0 items-center justify-center rounded-[8px] text-white"
>
{item.price}
</button>
</div>
{bundleItems.map((item) => (
<ShopBundleRow key={item.id} item={item} />
))}
</div>
</div>
Expand Down
Loading
Loading