page.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. 'use client';
  2. import { useCallback, useEffect, useState } from 'react';
  3. import Link from 'next/link';
  4. import { Gamepad2, Ticket } from 'lucide-react';
  5. import { fetchApi } from '@/lib/utils/client';
  6. import Loading from '@/app/component/Loading';
  7. import Pagination from '@/app/component/Pagination';
  8. import NavTabs from '../navTabs';
  9. import type { InventoryGroupRow, InventoryGroupsResponse } from '@/types/store';
  10. const PER_PAGE = 24;
  11. function TypeBadgeCoupon()
  12. {
  13. return (
  14. <span
  15. title='쿠폰'
  16. aria-label='쿠폰'
  17. className='inline-flex items-center justify-center p-1 rounded bg-amber-100 text-amber-800 dark:bg-amber-900/40 dark:text-amber-300'
  18. >
  19. <Ticket className='size-3' />
  20. </span>
  21. );
  22. }
  23. export default function InventoryGroupsPage()
  24. {
  25. const [groups, setGroups] = useState<InventoryGroupRow[]>([]);
  26. const [total, setTotal] = useState(0);
  27. const [page, setPage] = useState(1);
  28. const [loading, setLoading] = useState(true);
  29. const load = useCallback(async () => {
  30. setLoading(true);
  31. const res = await fetchApi<InventoryGroupsResponse>(`/api/store/inventory/groups?page=${page}&perPage=${PER_PAGE}`, { silent: true });
  32. if (res.success && res.data) {
  33. setGroups(res.data.list);
  34. setTotal(res.data.total);
  35. }
  36. else {
  37. setGroups([]);
  38. setTotal(0);
  39. }
  40. setLoading(false);
  41. }, [page]);
  42. useEffect(() => {
  43. load();
  44. }, [load]);
  45. return (
  46. <>
  47. <NavTabs />
  48. <div className='mx-auto px-4 sm:px-8 pb-8'>
  49. <h1 className='text-2xl font-bold mb-1'>보관함</h1>
  50. <p className='text-sm text-neutral-500 mb-4'>구매한 쿠폰을 상품별로 확인하실 수 있습니다. 상품을 클릭하면 쿠폰 코드를 확인 / 관리할 수 있어요.</p>
  51. <div className='text-sm text-neutral-500 mb-3'>총 {total.toLocaleString()}종</div>
  52. {loading ? (
  53. <Loading />
  54. ) : groups.length === 0 ? (
  55. <div className='text-center py-20 text-neutral-500'>보관함이 비어있습니다.</div>
  56. ) : (
  57. <>
  58. <div className='grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4'>
  59. {groups.map((g) => {
  60. const allUsed = g.unusedCount === 0;
  61. return (
  62. <Link
  63. key={g.productID}
  64. href={`/inventory/${g.productID}`}
  65. className={`group block rounded-lg border border-neutral-200 dark:border-neutral-800 overflow-hidden hover:shadow-lg transition-shadow bg-white dark:bg-neutral-900 ${allUsed ? 'opacity-70' : ''}`}
  66. >
  67. <div className='aspect-square bg-neutral-100 dark:bg-neutral-800 overflow-hidden flex items-center justify-center relative'>
  68. {g.productThumbnail ? (
  69. // eslint-disable-next-line @next/next/no-img-element
  70. <img
  71. src={g.productThumbnail}
  72. alt={g.productName}
  73. className='w-full h-full max-w-[calc(100%/1.6)] object-scale-down group-hover:scale-105 transition-transform duration-300'
  74. />
  75. ) : (
  76. <span className='text-neutral-400 text-xs'>이미지 없음</span>
  77. )}
  78. {allUsed && (
  79. <div className='absolute inset-0 bg-black/50 flex items-center justify-center'>
  80. <span className='text-white text-sm font-bold tracking-widest'>사용 완료</span>
  81. </div>
  82. )}
  83. </div>
  84. <div className='p-3'>
  85. <div className='flex items-center gap-1.5 mb-1.5'>
  86. <TypeBadgeCoupon />
  87. <Gamepad2 className='size-3.5 shrink-0 text-purple-600 dark:text-purple-400' />
  88. <span className='text-xs font-semibold truncate text-purple-600 dark:text-purple-400' title={g.gameName}>
  89. {g.gameName}
  90. </span>
  91. </div>
  92. <div className='text-sm font-semibold mb-1.5 line-clamp-2 min-h-[1.6rem]' title={g.productName}>
  93. {g.productName}
  94. </div>
  95. <div className='text-lg font-extrabold text-red-600 dark:text-red-400'>
  96. {allUsed ? '사용 완료' : `잔여 ${g.unusedCount}장`}
  97. <span className='text-xs text-neutral-500 dark:text-neutral-400 font-medium ml-1'>
  98. / 총 {g.totalCount}장
  99. </span>
  100. </div>
  101. </div>
  102. </Link>
  103. );
  104. })}
  105. </div>
  106. <hr className='my-4 border-neutral-200 dark:border-neutral-800' />
  107. <Pagination total={total} page={page} perPage={PER_PAGE} onChange={setPage} />
  108. </>
  109. )}
  110. </div>
  111. </>
  112. );
  113. }