대략 일주일간 많은 부분에 대한 개발을 했고,
또 그와 비슷한 양의 문제점과 아쉬운점을 느끼게 되었습니다.
진행한 부분과 느낀점에 대해 정리하겠습니다.
기존 배너의 가로세로 비율을 정할 수 있고 자동으로 돌아가는 시간을
props로 입력받을 수 있게 이미지 캐러셀을 리팩토링 했습니다.
// components/NextCarousel.tsx
"use client";
import React, { useState, useEffect, useCallback, ReactNode } from "react";
import Image from "next/image";
type ImageType = {
src: string;
alt: string;
};
interface NextCarouselProps {
items: ImageType[] | ReactNode[];
images: boolean;
aspectRatio?: string;
autoPlayInterval?: number;
}
const NextCarousel: React.FC<NextCarouselProps> = ({
items,
images,
aspectRatio = "16/9",
autoPlayInterval = 3000,
}) => {
const [currentIndex, setCurrentIndex] = useState(0);
const goToPrevious = () => {
const isFirstItem = currentIndex === 0;
const newIndex = isFirstItem ? items.length - 1 : currentIndex - 1;
setCurrentIndex(newIndex);
};
const goToNext = useCallback(() => {
const isLastItem = currentIndex === items.length - 1;
const newIndex = isLastItem ? 0 : currentIndex + 1;
setCurrentIndex(newIndex);
}, [currentIndex, items.length]);
useEffect(() => {
const interval = setInterval(goToNext, autoPlayInterval);
return () => clearInterval(interval);
}, [currentIndex, autoPlayInterval, goToNext]);
return (
<div className="relative">
<div className="overflow-hidden" style={{ aspectRatio }}>
<div
className="flex transition-transform duration-300"
style={{ transform: `translateX(-${currentIndex * 100}%)` }}
>
{images
? (items as ImageType[]).map((item, index) => (
<div key={index} className="flex-shrink-0 w-full h-full">
<Image
src={item.src}
alt={item.alt}
width={1000}
height={475}
className="w-full h-full object-cover"
/>
</div>
))
: (items as ReactNode[]).map((item, index) => (
<div key={index} className="flex-shrink-0 w-full h-full">
{item}
</div>
))}
</div>
</div>
<button
className="absolute top-1/2 left-0 transform -translate-y-1/2 bg-gray-800 text-white p-2"
onClick={goToPrevious}
>
<
</button>
<button
className="absolute top-1/2 right-0 transform -translate-y-1/2 bg-gray-800 text-white p-2"
onClick={goToNext}
>
>
</button>
</div>
);
};
export default NextCarousel;
이를 통해 기존에 비해 배너 이미지 사이즈를 줄이고 요청받은
클랜 BJ들의 소개 페이지 및 방송 다시보기 영역을 구현했습니다.
"use client";
import {
Avatar,
Card,
CardBody,
CardHeader,
Image,
Link,
} from "@nextui-org/react";
import NextCarousel from "./NextCarousel";
import styles from "../styles/style.module.css";
import { useEffect, useState } from "react";
export interface CarouselItemProps {
src: string;
alt: string;
}
const bannerItems = [
{
src: "https://upload3.inven.co.kr/upload/2024/04/10/bbs/i015769532273.gif",
alt: "Copy link",
},
{
src: "/후원.png",
alt: "Edit file",
},
];
const Banner: React.FC = () => {
const [isMobile, setIsMobile] = useState(false);
useEffect(() => {
const handleResize = () => {
setIsMobile(window.innerWidth < 768);
};
window.addEventListener("resize", handleResize);
handleResize();
return () => window.removeEventListener("resize", handleResize);
}, []);
return (
<div className="flex flex-col items-center justify-center gap-2">
<NextCarousel
images={true}
items={bannerItems}
aspectRatio="26/9"
autoPlayInterval={10000}
/>
<div className="flex flex-wrap items-center justify-center gap-2 w-full overflow-auto">
<div
className={`w-full md:w-1/2 h-[350px] overflow-y-auto ${styles.customCard} flex flex-col gap-2`}
>
<Card>
<div className="flex flex-wrap items-center justify-between w-full p-2">
<div className="flex items-center gap-2 md:w-2/5 w-3/5">
<div className="w-24 h-24 rounded-full flex justify-center items-center">
<Image
alt="nextui logo"
src="https://profile.img.afreecatv.com/LOGO/ks/ksmo54/ksmo54.jpg"
width={80}
height={80}
/>
</div>
<div className="ml-2">
<p className="font-bold text-xl">구라미스</p>
<p className="font-bold text-md">스트리머</p>
</div>
</div>
<div className="flex flex-col justify-center items-center md:w-3/5 w-2/5">
<p className="font-bold text-xl">방송국 주소</p>
<Link
className="font-bold text-sm cursor-pointer"
href="https://bj.afreecatv.com/ksmo54"
>
{isMobile ? "바로가기" : "https://bj.afreecatv.com/ksmo54"}
</Link>
</div>
</div>
</Card>
<Card>
<div className="flex flex-wrap items-center justify-between w-full p-2">
<div className="flex items-center gap-2 md:w-2/5 w-3/5">
<div className="w-24 h-24 rounded-full flex justify-center items-center">
<Image
alt="nextui logo"
src="https://profile.img.afreecatv.com/LOGO/eh/ehdnjs1111/ehdnjs1111.jpg?dummy=1716366745576"
width={80}
height={80}
/>
</div>
<div className="ml-2">
<p className="font-bold text-xl">라으페</p>
<p className="font-bold text-md">해설BJ</p>
</div>
</div>
<div className="flex flex-col justify-center items-center md:w-3/5 w-2/5">
<p className="font-bold text-xl">방송국 주소</p>
<Link
className="font-bold text-sm cursor-pointer"
href="https://bj.afreecatv.com/ehdnjs1111"
>
{isMobile
? "바로가기"
: "https://bj.afreecatv.com/ehdnjs1111"}
</Link>
</div>
</div>
</Card>
<Card>
<div className="flex flex-wrap items-center justify-between w-full p-2">
<div className="flex items-center gap-2 md:w-2/5 w-3/5">
<div className="w-24 h-24 rounded-full flex justify-center items-center">
<Image
alt="nextui logo"
src="https://i.pinimg.com/736x/69/6c/28/696c28e1ac1e53b7da2747a504c0e144.jpg"
width={80}
height={80}
/>
</div>
<div className="ml-2">
<p className="font-bold text-xl">잡 지</p>
<p className="font-bold text-md">해설BJ</p>
</div>
</div>
<div className="flex flex-col justify-center items-center md:w-3/5 w-2/5">
<p className="font-bold text-xl">방송국 주소</p>
<Link
className="font-bold text-sm cursor-pointer"
href="https://bj.afreecatv.com/lycosc"
>
{isMobile ? "바로가기" : "https://bj.afreecatv.com/lycosc"}
</Link>
</div>
</div>
</Card>
</div>
<div
className={`w-full md:w-1/2 h-[350px] ${styles.customCard} flex flex-col gap-2 items-center justify-center`}
>
<Card className="w-full h-[350px] flex items-center justify-center">
<CardHeader>VOD 다시보기</CardHeader>
<div className="flex justify-center items-center w-full">
<div>
<iframe
id="afreecatv_player_video"
width="350"
height="200"
src="https://vod.afreecatv.com/player/123390443/embed?autoPlay=false&mutePlay=true"
allowFullScreen={true}
allow="clipboard-write"
></iframe>
</div>
</div>
</Card>
</div>
</div>
</div>
);
};
export default Banner;
해당 방식으로 자주 변경되지 않는 bj파트에 대해 하드코딩으로 임시적인 방송기능을 구현했고, 이를 추후 admin페이지에서 관리할 수 있는 방향으로 리팩토링 할 생각입니다.
기존 회원가입 방식에서 신분증이미지와 전화번호를 추가적으로 입력받고, 가입 승인을 통해 Member 등급으로 업데이트 하도록 사이트 이용방식을 변경하게 되었습니다.
그에 따라 어드민페이지가 필요하게 되어 당장 필요한 수준으로 간단하게 구현하게 되었습니다.
로그인시 계정의 등급이 admin 레벨일 경우에만 어드민 페이지 버튼을 활성화 시키며 ,
유저 / 포인트내역 / 게시물정보 에 대한 관리 페이지를 지원하게 구현하였습니다.
당장 마감 기한이 많이 남지 않아 서비스를 오픈하는데에 필요한 필수조건에 대한 구현만 이루어졌지만 편의성을 보장 할 수 있는 방향으로 시간을 투자하여 완성하고 싶은 파트입니다.
프로리그 순위/일정 페이지와
프로리그 정보/관리 페이지를 개발했습니다.
진행중인 시즌의 프로리그의 정보를 렌더하고
Fullcalendar 컴포넌트를 통해 db에서 일정을 받아 렌더링하고 클릭시 해당일자의 정보를 확인할 수 있습니다.
로그인중인 계정이 관리자등급일 경우 정보에 대한 CRUD 권한을 갖고 기능을 위한 버튼이 나타납니다.
해당방식으로 경기에 대한 정보를 입력/수정 할 수 있으며,
react-beautiful-dnd 라이브러리를 이용하여 세트별 정보를 간편하게
드래그앤 드랍을 통해 이동시킬 수 있습니다.
시즌진행중인 팀들의 정보를 버튼을 통해 선택하여 확인가능하며,
관리자일 경우 관리자 페이지 진입 및 팀정보 페이지에서 팀원들에 대한 정보확인 및 관리를 할 수 있습니다.
관리자 페이지 진입시 팀 정보 확인 버튼과 팀생성 카드가 렌더됩니다.
해당 파트에서 새로운 팀을 생성할 수 있습니다.
팀 버튼을 클릭시 해당 팀의 정보를 수정하거나 삭제하는 카드가 렌더됩니다.
해당 파트에서 기존 팀의 정보를 수정하거나 삭제할 수 있습니다.
페이지의 하단에는 팀에 소속된 유저와 소속되지 않은 유저를 구분하여 렌더하고 있으며, 필터링 및 페이지네이션을 통해 편하게 확인할 수 있으며 , 수정버튼 클릭시
시즌에서 현존하는 팀의 리스트가 뜨며 버튼을 누르면 해당 팀으로 이적 시킬 수 있습니다.
보다 공용컴포넌트화에 대한 중요성을 느껴야 했고,
보다 타입정의와 폴더구조에 대한 고민을 해야 했다고 생각합니다.
사실 정말 열심히 했던만큼 많은 기능을 개발했습니다.
그러나 최근 스스로 만든 작업물에 대한 아쉬움과 두려움이 생깁니다.
이게 만약 회사의 서비스였다면 얼마나 무서웠을까 라는 생각이
한번씩 마음을 스칠때마다 리팩토링 욕심이 솟구칩니다.
하지만 마감기한이 많이 남지 않았고 ,
시작할때 더 튼튼한 내실을 다져야 했다는 사실을
마주하면서 스스로의 부족함을 알게 됩니다.
많은 회사들이 가지고있는 기술부채라는 것이 이런식으로 생기는 거구나
라는 감상을 느끼게 하고 내가 필요하다고 느끼지 않았던 많은 업무 프로세스 들이 사실은 가장 중요했다는 것을 배우게 해준
이번 프로젝트에 진심으로 감사하는 마음을 갖습니다.