Skip to content
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

[feat] 로그인 온보딩 디자인 구현 #39

Open
wants to merge 18 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"next": "14.2.23",
"react": "^18",
"react-dom": "^18",
"swiper": "^11.2.4",
"tailwind-merge": "^3.0.1",
"tailwindcss-animate": "^1.0.7"
},
Expand Down
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added public/images/login/onboarding/onboarding-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/login/onboarding/onboarding-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 25 additions & 6 deletions src/app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
import KakaoLoginButton from '@/features/auth/ui/KakaoLoginButton';
'use client';

import { useState } from 'react';

import { LoginFeature } from '@/features/auth/login/ui/LoginFeature';
import { OnboardingFeature } from '@/features/onboarding/ui/OnboardingFeature';

export default function LoginPage() {
const [showOnboarding, setShowOnboarding] = useState(true);

const handleOnboardingComplete = () => {
setShowOnboarding(false);
};

function page() {
return (
<div className="space-y-4">
<KakaoLoginButton />
<div className="flex h-dvh w-full justify-center">
<div
className="h-full w-full max-w-[600px] flex-col items-center justify-center bg-black"
style={{
backgroundImage: `url(${'/images/login/onboarding/onboarding-bg.png'})`,
backgroundSize: 'contain',
backgroundPosition: 'bottom',
backgroundRepeat: 'no-repeat',
}}
>
{showOnboarding ? <OnboardingFeature onComplete={handleOnboardingComplete} /> : <LoginFeature />}
</div>
</div>
);
}

export default page;
23 changes: 23 additions & 0 deletions src/features/auth/login/ui/LoginFeature.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Image from 'next/image';

import KakaoLoginButton from '../../ui/KakaoLoginButton';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

../ 이상의 폴더 접근은 @shared/ui로 절대경로로 접근해주시면 더 좋을거 같습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

엇 수정하겠습니다. 🥲


export function LoginFeature() {
const imgUrl = '/images/login/onboarding/onboarding-login.png';
return (
<div className="flex h-full w-full flex-col">
<div className="relative flex h-full w-full flex-col items-center justify-between">
{/* 이미지 배치가 안되서 임시로 top-[45%]사용했습니다. */}
<div className="absolute left-1/2 top-[45%] h-[23%] w-full -translate-x-1/2 -translate-y-1/2 transform">
{/* 이미지 가운데 배치가 안되서 임시로 pl-[1rem]사용했습니다. */}
<Image src={imgUrl} alt="온보딩 이미지" fill className="object-contain pl-[1rem]" priority />
Comment on lines +10 to +13
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵넵 확인했습니다. 추후 온보딩 디자인이 변경될 가능성이 높아서, 우선은 이와같이 진행해도 될 것 같습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 일단 진행하겠습니다!

</div>

<div className="absolute bottom-0 w-full space-y-4 px-6 pb-10">
<KakaoLoginButton />
<KakaoLoginButton /> {/* 구글 , 애플 로그인 버튼으로 바꿀 예정 */}
</div>
</div>
</div>
);
}
16 changes: 11 additions & 5 deletions src/features/auth/ui/KakaoLoginButton.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
'use client';

// 추가적으로 디자인이 수정될 페이지 같아서 임시로 작성했습니다

import { Typography } from '@/shared/ui/typography';

import { kakaoLogin } from '../model/providers/kakao';

function KakaoLoginButton() {
Expand All @@ -13,11 +17,13 @@ function KakaoLoginButton() {
);

return (
<button onClick={kakaoLogin} className="flex items-center rounded bg-yellow-300 px-4 py-2">
<span className="mr-2">
<KakaoIcon />
</span>
카카오 로그인
<button
onClick={kakaoLogin}
className="flex w-full items-center justify-between rounded-md bg-yellow-300 px-4 py-[15px] text-gray-800"
>
<KakaoIcon />
<Typography variant="body-2">카카오톡으로 시작하기</Typography>
<div></div>
</button>
);
}
Expand Down
17 changes: 17 additions & 0 deletions src/features/onboarding/config/slides.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export const slides = [
{
id: 1,
description: ['과한 시작정보는 빼고', '내가 원하는 정보만'],
imageUrl: '/images/login/onboarding/onboarding-1.png',
},
{
id: 2,
description: ['QR로 쉽게 공유하고', '블루투스로 쉽게 아카이빙하는'],
imageUrl: '/images/login/onboarding/onboarding-2.png',
},
{
id: 3,
description: ['손쉽게 툭 공유하는', '나만의 명함'],
imageUrl: '/images/login/onboarding/onboarding-3.png',
},
];
85 changes: 85 additions & 0 deletions src/features/onboarding/ui/OnboardingCarousel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
'use client';

import React, { useState, useRef } from 'react';
import { Swiper as SwiperType } from 'swiper';
import { Navigation, Autoplay, Pagination } from 'swiper/modules';
import { Swiper, SwiperSlide } from 'swiper/react';

import 'swiper/css';
import 'swiper/css/pagination';
import 'swiper/css/navigation';

import { slides } from '@/features/onboarding/config/slides';
import { Button } from '@/shared/ui/button';
import { Typography } from '@/shared/ui/typography';

import OnboardingSlide from './OnboardingSlide';

interface OnboardingCarouselProps {
onComplete: () => void;
}

function OnboardingCarousel({ onComplete }: OnboardingCarouselProps) {
const [activeIndex, setActiveIndex] = useState(0);
const swiperRef = useRef<SwiperType | null>(null);

const handleNext = () => {
if (activeIndex === slides.length - 1) {
onComplete();
} else if (swiperRef.current) {
swiperRef.current.slideNext();
}
};

const handleSlideChange = (swiper: SwiperType) => {
setActiveIndex(swiper.realIndex);
};

return (
<div className="flex h-full w-full flex-col items-center">
<div className="relative w-full flex-1">
{/*module Pagination사용으로 수정 */}
<Swiper
modules={[Pagination, Navigation, Autoplay]}
Copy link

@coderabbitai coderabbitai bot Mar 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

modules 배열에 잘못된 컴포넌트가 포함되어 있습니다.

modules 배열에 로컬에서 정의한 Pagination 컴포넌트가 포함되어 있습니다. 이는 swiper의 Pagination 모듈과 다른 것입니다. swiper에서 가져온 모듈만 사용해야 합니다.

다음과 같이 수정해주세요:

- modules={[Pagination, Navigation, Autoplay]}
+ modules={[Navigation, Autoplay]}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
modules={[Pagination, Navigation, Autoplay]}
modules={[Navigation, Autoplay]}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이건 코드래빗 리뷰가 잘못됐습니다. 반영하지 마세요.

-레퍼런스-

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

@semnil5202 감사합니다! 제가 제안한 이전 리뷰가 잘못되었네요. 스와이퍼의 modules 속성은 로컬 컴포넌트를 참조하는 것이 아니라 import 해온 모듈을 참조하는 것이 맞습니다.

현재 코드를 보니 import { Navigation, Autoplay } from 'swiper/modules';는 있지만, Swiper의 Pagination 모듈은 import하지 않았습니다. 그러나 modules 배열에 로컬 Pagination 컴포넌트를 포함시키는 것은 Swiper에서 지원하는 방식이 아닙니다.

제 리뷰 제안을 취소합니다. 올바른 수정은 아래와 같을 것입니다:

// Pagination 모듈 추가로 import
import { Navigation, Autoplay, Pagination } from 'swiper/modules';

// 그리고 modules 배열에 Pagination 추가
modules={[Pagination, Navigation, Autoplay]}

또는 현재 커스텀 Pagination을 사용하고 있다면, modules 배열에서 제거하는 것이 맞습니다:

modules={[Navigation, Autoplay]}

제공해주신 레퍼런스 링크를 확인하겠습니다. 혼란을 드려 죄송합니다.


🏁 Script executed:

#!/bin/bash
# Explore the swiper imports in the codebase to understand the correct usage
head -n 10 src/features/onboarding/ui/OnboardingCarousel.tsx

# Also search for how Pagination is imported and used in the file
grep -A 5 "import.*Pagination" src/features/onboarding/ui/OnboardingCarousel.tsx || echo "No direct Pagination import found"

# Search for any other file that might use Swiper Pagination correctly
rg "import.*Pagination.*from 'swiper" || echo "No Swiper Pagination imports found in the codebase"

# Check available modules in package
npm list swiper || echo "Swiper package details not available"

Length of output: 827


🌐 Web query:

How to use Pagination module in Swiper React correctly

💡 Result:

To implement pagination in Swiper React correctly, follow these key steps:

Core Implementation

  1. Install Required Packages:
npm install swiper
  1. Basic Component Setup:
import { Pagination } from 'swiper/modules';
import { Swiper, SwiperSlide } from 'swiper/react';
import 'swiper/css';
import 'swiper/css/pagination';

export default () => {
  return (
    <Swiper
      modules={[Pagination]}
      pagination={{ clickable: true }}
    >
      <SwiperSlide>Slide 1</SwiperSlide>
      <SwiperSlide>Slide 2</SwiperSlide>
      <SwiperSlide>Slide 3</SwiperSlide>
    </Swiper>
  );
};

Key Configuration Options

Parameter Type Description Default
clickable boolean Enables bullet click navigation false
dynamicBullets boolean Animates bullet size for active slide false
type string Pagination style (bullets, fraction, progressbar) bullets

Advanced Usage Example:

<Swiper
  modules={[Pagination]}
  pagination={{
    type: 'progressbar',
    clickable: true,
    dynamicBullets: true,
    renderBullet: (index, className) => {
      return `<span class="${className}">${index + 1}</span>`;
    }
  }}
>

Common Issues & Solutions

  1. Missing Pagination Elements:

    • Ensure CSS imports are included:
    import 'swiper/css/pagination';
    • Verify module registration in modules array
  2. Custom Styling:

.swiper-pagination-bullet {
  background: #000;
  opacity: 0.5;
  width: 12px;
  height: 12px;
}

.swiper-pagination-bullet-active {
  background: #ff0000;
  opacity: 1;
}
  1. Position Adjustment:
<Swiper
  pagination={{
    el: '.custom-pagination',
    ...
  }}
  className="my-swiper"
>
<div className="custom-pagination"></div>

Pro Tip: For TypeScript users, utilize the SwiperOptions type from swiper/types for proper type checking[4].

The pagination module works with Swiper's responsive breakpoints system and supports touch/swipe navigation out of the box. For complex implementations, combine with other modules like Navigation and Scrollbar using the same pattern[7][9].

Citations:


@semnil5202 죄송합니다! 제 리뷰가 부정확했습니다. 코드를 다시 검토해보니 현재 코드에서는 커스텀 Pagination 컴포넌트를 정의하고 있는데, 이것을 Swiper의 modules 배열에 포함시키고 있습니다.

제가 살펴본 결과:

  1. 현재 파일에서는 import { Navigation, Autoplay } from 'swiper/modules';로 Swiper 모듈을 가져오고 있지만, Pagination 모듈은 import하지 않았습니다.
  2. 대신 컴포넌트 내부에 로컬 Pagination 함수형 컴포넌트를 정의했습니다.
  3. 이 로컬 컴포넌트는 Swiper의 modules 배열에 포함되면 안 됩니다.

올바른 접근 방식은 다음 두 가지 중 하나입니다:

  1. Swiper의 내장 Pagination을 사용하려면:
import { Navigation, Autoplay, Pagination } from 'swiper/modules';
// 그리고 modules={[Pagination, Navigation, Autoplay]}
  1. 현재처럼 커스텀 pagination UI를 사용하려면:
modules={[Navigation, Autoplay]} // Pagination 제외
// 커스텀 Pagination 컴포넌트는 JSX에서 별도로 렌더링

제공해주신 레퍼런스를 통해 올바른 사용법을 확인할 수 있었습니다. 잘못된 리뷰로 혼란을 드려 죄송합니다.


✏️ Learnings added
Learnt from: semnil5202
PR: depromeet/Took-FE#39
File: src/features/onboarding/ui/OnboardingCarousel.tsx:56-56
Timestamp: 2025-03-01T15:22:18.564Z
Learning: Swiper 라이브러리 사용 시, `modules` 속성에는 'swiper/modules'에서 import한 모듈만 포함되어야 하며, 로컬에서 정의한 React 컴포넌트는 포함하지 않아야 합니다. 커스텀 pagination UI는 별도의 JSX로 구현하는 것이 적절합니다.

Learnt from: semnil5202
PR: depromeet/Took-FE#39
File: src/features/onboarding/ui/OnboardingCarousel.tsx:56-56
Timestamp: 2025-03-01T15:22:18.564Z
Learning: Swiper 라이브러리 사용 시, `modules` 속성에는 'swiper/modules'에서 import한 모듈만 포함되어야 하며, 로컬에서 정의한 React 컴포넌트는 포함하지 않아야 합니다. 커스텀 pagination UI는 별도의 JSX로 구현하는 것이 적절합니다.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

onSwiper={(swiper) => {
swiperRef.current = swiper;
}}
onSlideChange={handleSlideChange}
pagination={{
clickable: true,
el: '.custom-pagination',
bulletClass: 'inline-block h-2 w-2 rounded-full mx-1 bg-white opacity-50',
bulletActiveClass: 'opacity-100',
}}
navigation={false}
slidesPerView={1}
autoplay={{ delay: 5000, disableOnInteraction: false }}
loop={true}
className="h-full w-full"
>
{slides.map((slide) => (
<SwiperSlide key={slide.id}>
<OnboardingSlide description={slide.description} imageUrl={slide.imageUrl} />
</SwiperSlide>
))}
</Swiper>

{/**
* 커스텀 페이지네이션 위치
* 반응형으로 위치 조정
*/}
<div className="absolute bottom-28 left-0 right-0 z-10 sm:bottom-36 md:bottom-40 lg:bottom-48">
<div className="custom-pagination flex justify-center"></div>
</div>
</div>

<div className="mt-auto w-full bg-black p-6">
<Button onClick={handleNext} className="w-full">
<Typography variant="body-2">다음</Typography>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

만든 타이포컴포넌트 사용하신부분 좋습니다!!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

너무 편리합니다 ㅎㅎ 체고

</Button>
</div>
</div>
);
}

export default OnboardingCarousel;
24 changes: 24 additions & 0 deletions src/features/onboarding/ui/OnboardingFeature.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use client';

import OnboardingCarousel from './OnboardingCarousel';
import { OnboardingHeader } from './OnboardingHeader';

/**
* OnboardingFeatureProps 설명
* onComplete , showOnboarding 상태를 false로 바구는 함수입니다.
* 헤더의 건너뛰기 , 모든 온보딩 페이지를 다 확인했을 때 사용합니다.
* onComplete = 온보딩 마지막 페이지에서 사용
*/

interface OnboardingFeatureProps {
onComplete: () => void;
}

export function OnboardingFeature({ onComplete }: OnboardingFeatureProps) {
return (
<div className="flex h-full w-full flex-col">
<OnboardingHeader onComplete={onComplete} />
<OnboardingCarousel onComplete={onComplete} />
</div>
);
}
16 changes: 16 additions & 0 deletions src/features/onboarding/ui/OnboardingHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Typography } from '@/shared/ui/typography';

interface OnboardingHeaderProps {
onComplete: () => void;
}

export function OnboardingHeader({ onComplete }: OnboardingHeaderProps) {
return (
<header className="flex items-center justify-between p-4">
<div></div>
<button onClick={onComplete} className="font-medium text-gray-500">
<Typography variant="body-2">건너뛰기</Typography>
</button>
</header>
);
}
32 changes: 32 additions & 0 deletions src/features/onboarding/ui/OnboardingSlide.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Image from 'next/image';

import { Typography } from '@/shared/ui/typography';

interface OnboardingSlideProps {
description: string[];
imageUrl: string;
}

function OnboardingSlide({ description, imageUrl }: OnboardingSlideProps) {
return (
<div className="relative flex h-full w-full flex-col items-center">
<div className="absolute inset-0 flex w-full items-center justify-center">
<div className="relative h-[75%] w-[65%]">
<Image src={imageUrl} alt="온보딩 이미지" fill className="object-contain" priority />
</div>
</div>

<div className="absolute bottom-0 left-0 right-0 h-2/5 bg-gradient-to-t from-black via-black to-transparent pt-32">
<div className="flex flex-col items-center px-6 pt-16">
{description.map((line, index) => (
<Typography key={index} variant="title-2">
{line}
</Typography>
))}
</div>
</div>
</div>
);
}

export default OnboardingSlide;