Skip to content

Commit

Permalink
Merge branch '23-티켓팅-기능-개발' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
ho991217 committed Apr 3, 2024
2 parents 386a432 + 7fa1d8b commit bd19f1b
Show file tree
Hide file tree
Showing 16 changed files with 511 additions and 74 deletions.
2 changes: 1 addition & 1 deletion app/[locale]/(back-nav)/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default function LoginPage() {
};

return (
<section className='w-full flex flex-col justify-end items-center'>
<section className='w-full flex flex-col justify-end items-center px-5'>
<Form
className='flex flex-col gap-2'
onSubmit={onSubmit}
Expand Down
8 changes: 7 additions & 1 deletion app/[locale]/(back-nav)/my-tickets/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
import Ticket from '@/components/my-tickets/ticket';

export default function Detail({ params: { id } }: { params: { id: number } }) {
return <div>Detail {id}</div>;
return (
<section className='flex flex-col px-5 pt-5 w-full h-[calc(100dvh-100px)] bg-primary items-center'>
<Ticket ticketId={id} />
</section>
);
}
2 changes: 2 additions & 0 deletions components/common/button/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
'use client';

import { MotionProps, motion } from 'framer-motion';
import { variants } from './motion';
import PulseLoader from 'react-spinners/PulseLoader';
Expand Down
55 changes: 2 additions & 53 deletions components/common/carousel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,15 @@

import React from 'react';
import useEmblaCarousel from 'embla-carousel-react';
import Image from 'next/image';
import { FiCalendar, FiClock, FiHeart } from 'react-icons/fi';
import {
NextIntlClientProvider,
useFormatter,
useTranslations,
useLocale,
} from 'next-intl';
import { NextIntlClientProvider, useLocale } from 'next-intl';
import Tile, { TileProps } from './tile';
import en from '../../../messages/en.json';
import ko from '../../../messages/ko.json';

type CarouselProps = {
images: TileProps[];
};

export type TileProps = {
src: string;
alt: string;
artistName: string;
};

export default function Carousel({ images }: CarouselProps) {
const [emblaRef] = useEmblaCarousel();
const locale = useLocale();
Expand Down Expand Up @@ -52,42 +40,3 @@ export default function Carousel({ images }: CarouselProps) {
</NextIntlClientProvider>
);
}

function Tile({ src, alt, artistName }: TileProps) {
const format = useFormatter();
const t = useTranslations('Carousel');

return (
<div className='flex-[0_0_100%] aspect-[3/4] bg-neutral-500 rounded-2xl overflow-hidden relative'>
<Image
src={src}
alt={alt}
className='absolute left-0 right-0 top-0 bottom-0 object-cover'
placeholder='blur'
quality={100}
blurDataURL='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAFklEQVR42mN8//HLfwYiAOOoQvoqBABbWyZJf74GZgAAAABJRU5ErkJggg=='
fill
/>
<div className='absolute p-4 flex flex-col justify-between h-1/3 left-4 right-4 bottom-4 bg-neutral-700 rounded-2xl bg-clip-padding backdrop-filter backdrop-blur-xl bg-opacity-0 text-white shadow-lg'>
<span className='text-xs flex gap-2 items-center'>
<FiHeart color='white' />
{t('nextup')}
</span>
<div>
<h4 className='font-bold text-2xl'>{artistName}</h4>
<span className='text-sm'>Coldplay</span>
</div>
<div className='text-sm flex gap-4 font-normal'>
<span className='flex gap-2 items-center justify-center'>
<FiCalendar color='white' />
{format.dateTime(new Date('2024-03-09T10:36:01.516Z'), 'short')}
</span>
<span className='flex gap-2 items-center justify-center'>
<FiClock color='white' />
19:00
</span>
</div>
</div>
</div>
);
}
48 changes: 48 additions & 0 deletions components/common/carousel/tile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useFormatter, useTranslations } from 'next-intl';
import Image from 'next/image';
import { FiCalendar, FiClock, FiHeart } from 'react-icons/fi';

export type TileProps = {
src: string;
alt: string;
artistName: string;
};

export default function Tile({ src, alt, artistName }: TileProps) {
const format = useFormatter();
const t = useTranslations('Carousel');

return (
<div className='flex-[0_0_100%] aspect-[3/4] bg-neutral-500 rounded-2xl overflow-hidden relative'>
<Image
src={src}
alt={alt}
className='absolute left-0 right-0 top-0 bottom-0 object-cover'
placeholder='blur'
quality={100}
blurDataURL='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAFklEQVR42mN8//HLfwYiAOOoQvoqBABbWyZJf74GZgAAAABJRU5ErkJggg=='
fill
/>
<div className='absolute p-4 flex flex-col justify-between h-1/3 left-4 right-4 bottom-4 bg-neutral-700 rounded-2xl bg-clip-padding backdrop-filter backdrop-blur-xl bg-opacity-0 text-white shadow-lg'>
<span className='text-xs flex gap-2 items-center'>
<FiHeart color='white' />
{t('nextup')}
</span>
<div>
<h4 className='font-bold text-2xl'>{artistName}</h4>
<span className='text-sm'>Coldplay</span>
</div>
<div className='text-sm flex gap-4 font-normal'>
<span className='flex gap-2 items-center justify-center'>
<FiCalendar color='white' />
{format.dateTime(new Date('2024-03-09T10:36:01.516Z'), 'short')}
</span>
<span className='flex gap-2 items-center justify-center'>
<FiClock color='white' />
19:00
</span>
</div>
</div>
</div>
);
}
1 change: 1 addition & 0 deletions components/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export { default as Navigation } from './navigation';
export { default as Form } from './form';
export { default as SideNav } from './side-nav';
export { default as Button } from './button';
export { default as Link } from './link';
62 changes: 62 additions & 0 deletions components/common/link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
'use client';

import { default as NextLink } from 'next/link';
import { useRouter } from 'next/navigation';
import If from '../util/if';
import { cn } from '@/lib/utils';
import { useAuth } from '@/hooks';
import { toast } from 'sonner';
import { motion } from 'framer-motion';

type Props = {
children: React.ReactNode;
href?: string;
className?: string;
back?: boolean;
auth?: boolean;
};

export default function Link({
children,
href,
className,
back = false,
auth = false,
}: Props) {
const router = useRouter();
const { isLoggedIn } = useAuth();

const checkAuth = () => {
if (!isLoggedIn) {
requestAnimationFrame(() => {
toast.error('로그인이 필요합니다.');
});
router.push('/login');
}
};

return (
<motion.div whileTap={{ scale: 0.98 }} className={className}>
<If condition={back}>
<If.Then>
<button onClick={() => router.back()} className='w-full h-full'>
{children}
</button>
</If.Then>
<If.Else>
<NextLink
href={href ?? '/'}
className='w-full h-full'
onClick={() => {
if (auth) {
checkAuth();
}
}}
>
{children}
</NextLink>
</If.Else>
</If>
</motion.div>
);
}
4 changes: 2 additions & 2 deletions components/common/navigation.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Link from 'next/link';
import Link from '@/components/common/link';
import SideNav from './side-nav';
import { useTranslations } from 'next-intl';
import { IoArrowBackOutline } from 'react-icons/io5';
Expand All @@ -14,7 +14,7 @@ export default function Navigation({ hasBackButton = false }: Props) {
<nav className='flex w-full justify-between items-start h-[100px] px-5 pt-5'>
<If condition={hasBackButton}>
<If.Then>
<Link href={'/'}>
<Link back>
<IoArrowBackOutline size={25} />
</Link>
</If.Then>
Expand Down
13 changes: 10 additions & 3 deletions components/common/side-nav/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import LocaleSwitcher from '../locale-switcher';
import Link from 'next/link';
// import Link from 'next/link';
import {
Sheet,
SheetClose,
Expand All @@ -14,11 +14,13 @@ import { NextIntlClientProvider } from 'next-intl';
import ko from '@/messages/ko.json';
import en from '@/messages/en.json';
import { Separator } from '@/components/ui/separator';
import Link from '@/components/common/link';

type LinkInfo = {
id: number;
link: string;
nameKey: string;
privateRoute?: boolean;
};

const links: LinkInfo[] = [
Expand All @@ -31,6 +33,7 @@ const links: LinkInfo[] = [
id: 2,
link: '/ticketing',
nameKey: 'ticketing',
privateRoute: true,
},
{
id: 3,
Expand Down Expand Up @@ -59,9 +62,13 @@ export default async function SideNav() {
<SheetContent className='bg-white dark:bg-[#0C0C0C] dark:border-[#181818] flex flex-col justify-between'>
<SheetDescription className='flex flex-col items-end gap-4'>
<ul className='flex flex-col items-end gap-1 w-full'>
{links.map(({ id, link, nameKey }) => (
{links.map(({ id, link, nameKey, privateRoute }) => (
<li key={id} className='w-full flex justify-end'>
<Link href={`/${locale}${link}`} className={className}>
<Link
href={`/${locale}${link}`}
className={className}
auth={privateRoute}
>
<SheetClose>{t(nameKey)}</SheetClose>
</Link>
</li>
Expand Down
3 changes: 2 additions & 1 deletion components/home/lineup-tile.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useTranslations } from 'next-intl';
import Carousel, { type TileProps } from '../common/carousel';
import Carousel from '../common/carousel';
import TileHeader from './tile-header';
import { TileProps } from '../common/carousel/tile';

const data: TileProps[] = [
{
Expand Down
32 changes: 25 additions & 7 deletions components/my-tickets/qr-code.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@

import { useQRCode } from 'next-qrcode';
import sign from 'jwt-encode';
import { useEffect, useRef, useState } from 'react';
import { parseMStoMinSec } from '@/lib/utils';
import { Suspense, useEffect, useRef, useState } from 'react';
import { cn, parseMStoMinSec } from '@/lib/utils';
import { Button } from '@/components/common';

const SECRET = process.env.NEXT_PUBLIC_JWT_SECRET;
const SECOND = 1000;

type QrCodeProps = {
ticketId: number;
validTime?: number;
className?: string;
};

type Payload = {
Expand All @@ -22,10 +24,12 @@ type Payload = {
export default function QrCode({
ticketId,
validTime = 1000 * 60 * 3,
className,
}: QrCodeProps) {
if (!SECRET) throw new Error('JWT 시크릿이 설정되지 않았습니다.');

const { Canvas } = useQRCode();
const [isLoading, setIsLoading] = useState(true);
const [token, setToken] = useState<string>('no token');
const [leftTime, setLeftTime] = useState<number>(validTime);
const interval = useRef<ReturnType<typeof setInterval> | null>(null);
Expand All @@ -43,8 +47,9 @@ export default function QrCode({
if (interval.current) clearInterval(interval.current);
if (timer.current) clearInterval(timer.current);

generateToken();
setLeftTime(validTime);
generateToken();
setIsLoading(false);
interval.current = setInterval(() => {
setLeftTime(validTime);
generateToken();
Expand All @@ -65,10 +70,23 @@ export default function QrCode({
}, []);

return (
<div>
<Canvas text={token} options={{ errorCorrectionLevel: 'L' }} />
<div>유효 시간: {parseMStoMinSec(leftTime)}</div>
<button onClick={startTimer}>재생성</button>
<div className={cn('flex flex-col w-full items-center', className)}>
<Suspense fallback={<div>로딩중</div>}>
{!isLoading && (
<Canvas
text={token}
options={{ errorCorrectionLevel: 'L', width: 300 }}
/>
)}
</Suspense>
<div className='flex flex-col p-4 w-full gap-4'>
<span className='text-sm text-neutral-400 w-full text-center'>
유효 시간: {parseMStoMinSec(leftTime)}
</span>
<Button variant='filled' onClick={startTimer} animateOnClick>
재생성
</Button>
</div>
</div>
);
}
12 changes: 8 additions & 4 deletions components/my-tickets/ticket-tile.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { FiCalendar } from 'react-icons/fi';
import Link from 'next/link';
import TicketIcon from '@/public/images/ticket.svg';
import { getLocale } from 'next-intl/server';
import Link from '@/components/common/link';

export default async function TicketTile({ id }: { id: number }) {
const locale = await getLocale();
return (
<div className='relative py-4 px-6 w-[327px] h-[161px] flex flex-col justify-between items-start'>
<div className='w-full flex flex-col gap-2'>
<div className='relative w-[327px] h-[161px] flex flex-col justify-between items-start'>
<div className='w-full flex flex-col gap-2 mx-5 mt-5'>
<h4 className='font-bold text-base'>
단국대학교 2024 단페스타 1일차 티켓
</h4>
Expand All @@ -23,7 +23,11 @@ export default async function TicketTile({ id }: { id: number }) {
</div>
</div>
<div className='w-full'>
<Link href={`/${locale}/my-tickets/${id}`} className='text-primary'>
<Link
href={`/${locale}/my-tickets/${id}`}
className='text-primary py-4 px-5 w-full text-center rounded-b-xl'
auth
>
티켓 보기
</Link>
</div>
Expand Down
Loading

0 comments on commit bd19f1b

Please sign in to comment.