Skip to content

Conversation

@minwoo1999
Copy link
Member

@minwoo1999 minwoo1999 commented Feb 2, 2025

PR 제목은 핵심 변경 사항을 요약해주세요.


🙋‍ Summary (요약)

  • 구독시스템을 개발합니다.
  • 기존에 사용되고 있는 모달 컴포넌트의 재사용

🤔 Describe your Change (변경사항)

  • 이메일인증

🔗 Issue number and link (참고)

  • 추후 디자인 변경 가능성 있습니다.

Summary by CodeRabbit

  • New Features

    • Introduced a subscription button on the Home page that opens an interactive modal.
    • The modal guides users through email entry, validation, and verification, along with subscription option selection.
    • Enhanced email verification flows during signup to provide clearer context.
    • Added support for different email purposes (SIGNUP and SUBSCRIBE) in verification processes.
  • Style

    • Improved modal responsiveness with dynamic sizing and refined spacing for a cleaner, more adaptable layout.

@minwoo1999 minwoo1999 added the enhancement New feature or request label Feb 2, 2025
@minwoo1999 minwoo1999 self-assigned this Feb 2, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 2, 2025

Walkthrough

This pull request implements a new subscription feature on the HomePage, integrating a modal interface for user subscriptions. It introduces a dedicated SubscriptionModalContent component, updates API hooks for sending and verifying emails with an additional emailPurpose parameter, and adjusts modal styling and state handling (including conditional button display). The signup flow now leverages the new EMAIL_PURPOSE constant for better context in verification, and a new API call for subscribing has been added.

Changes

File(s) Summary
src/app/home/index.tsx
src/components/common/modal/SubscriptionModalContent.tsx
Added subscription button and modal functionality with a new SubscriptionModalContent component for handling subscriptions.
src/app/signup/index.tsx
src/hooks/api/member/useApiSendVerifyEmail.ts
src/hooks/api/member/useApiVerifyEmail.ts
Updated email verification functions to include an emailPurpose parameter (using EMAIL_PURPOSE.SIGNUP) for enhanced context.
src/components/common/modal/Modal.tsx
src/hooks/useModal.ts
src/store/modal/modalAtom.ts
src/types/modal.ts
Modified modal styling and behavior: dynamic height, added row-gap, and conditional button rendering via the isVisibleBtn property.
src/hooks/api/subscribe/useApiSendSubscribe.ts Introduced a new API function sendSubscribeEmail for handling subscription requests.
src/utils/common.ts Added a new constant EMAIL_PURPOSE with properties SIGNUP and SUBSCRIBE, and updated the export statement.

Sequence Diagram(s)

sequenceDiagram
  participant U as User
  participant H as HomePage
  participant M as Modal Hook
  participant SM as SubscriptionModalContent
  participant API as Subscription API

  U->>H: Click "Subscribe" button
  H->>M: openSubscriptionModal()
  M->>SM: Open Subscription Modal with content
  SM->>API: sendSubscribeEmail({ email })
  API-->>SM: API Response
  SM-->>U: Show subscription result
Loading
sequenceDiagram
  participant U as User
  participant S as Signup Page
  participant API1 as SendVerifyEmail API
  participant API2 as VerifyEmail API

  U->>S: Enter email & request verification
  S->>API1: sendVerifyEmail({ email, EMAIL_PURPOSE: SIGNUP })
  API1-->>S: Email sent confirmation
  U->>S: Input verification code
  S->>API2: verifyEmail({ email, randomNumber, EMAIL_PURPOSE: SIGNUP })
  API2-->>S: Verification result
Loading

Poem

I'm a little rabbit in a code garden,
Hopping through changes with a joyful pardon.
A new button here, a modal so bright,
Emails verified, subscriptions take flight.
With lines of code, my heart does sing –
🐰🎉 Let the updates bring spring!


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (9)
src/types/modal.ts (1)

3-3: LGTM! Consider a more specific property name.

The optional boolean property is well-typed and follows TypeScript conventions. However, consider renaming it to be more specific about which button's visibility it controls (e.g., isActionButtonVisible or isPrimaryButtonVisible).

src/utils/common.ts (1)

6-9: Add JSDoc documentation for EMAIL_PURPOSE constant.

The constant is well-defined with appropriate const assertion. Consider adding JSDoc documentation to explain its purpose and usage, similar to the existing comment above.

+/**
+ * Email purpose constants used for different email verification flows
+ */
 const EMAIL_PURPOSE = {
     SIGNUP: 'SIGNUP',
     SUBSCRIBE: 'SUBSCRIBE',
 } as const
src/hooks/api/member/useApiSendVerifyEmail.ts (1)

3-6: Consider using an enum or union type for emailPurpose.

Using a string type for emailPurpose could lead to typos or invalid values. Consider using an enum or union type to restrict the possible values.

+export type EmailPurpose = 'SIGNUP' | 'SUBSCRIPTION';
+
 interface sendVerifyEmailProps {
   email: string;
-  emailPurpose: string;
+  emailPurpose: EmailPurpose;
 }
src/hooks/api/member/useApiVerifyEmail.ts (1)

3-7: Maintain type consistency with useApiSendVerifyEmail.

For consistency, use the same type for emailPurpose as in useApiSendVerifyEmail.

+import { EmailPurpose } from './useApiSendVerifyEmail';
+
 interface verifyEmailProps {
   email: string;
   randomNumber: string;
-  emailPurpose: string;
+  emailPurpose: EmailPurpose;
 }
src/components/common/modal/Modal.tsx (2)

12-12: Consider internationalizing UI strings.

The UI strings ("취소", "확인") and comments are in Korean. Consider using an internationalization solution for better maintainability and future localization support.

-              취소
+              {t('common.cancel')}
-            <Button onClick={modalDataState.callBack}>확인</Button>
+            <Button onClick={modalDataState.callBack}>{t('common.confirm')}</Button>

Also applies to: 113-113, 115-115


93-105: Consider using CSS-in-JS theme variables.

The margin and gap values could be moved to theme variables for better consistency across the application.

 <div
   css={css`
     display:${modalDataState.isVisibleBtn ? 'flex' : 'none'};
     align-items: center;
-    column-gap: 1.2rem;
+    column-gap: ${theme.spacing.md};
     width: 100%;
     position: relative;
     margin-top: auto;
     div {
       width: 100%;
     }
   `}
 >
src/components/common/modal/SubscriptionModalContent.tsx (2)

18-21: Consider using an enum or constant for subscription categories.

The categories object could be defined as a constant or enum to maintain consistency and reusability across the application.

-  const [categories, setCategories] = useState({
-    bitcoinPrediction: false,
-    latestNews: false,
-  });
+const SUBSCRIPTION_CATEGORIES = {
+  BITCOIN_PREDICTION: 'bitcoinPrediction',
+  LATEST_NEWS: 'latestNews',
+} as const;
+
+  const [categories, setCategories] = useState({
+    [SUBSCRIPTION_CATEGORIES.BITCOIN_PREDICTION]: false,
+    [SUBSCRIPTION_CATEGORIES.LATEST_NEWS]: false,
+  });

263-290: Improve button accessibility and styling consistency.

The buttons should have consistent styling and proper accessibility attributes.

   <Button
     css={css`
       width: 8.4rem !important;
       height: 4.5rem;
       ${DESIGN_SYSTEM_COLOR.GRAY_50}
       font-size: 1.6rem;
       font-weight: normal;
       left: 2rem;
       bottom: 2rem;
     `}
+    aria-label="취소"
     onClick={close}
   >
     취소
   </Button>
   <Button
     css={css`
       width: 8.4rem !important;
       height: 4.5rem;
       ${DESIGN_SYSTEM_COLOR.GRAY_50}
       font-size: 1.6rem;
       font-weight: normal;
     `}
+    aria-label="구독"
+    disabled={!verifyEmailNumCheck}
     state={Boolean(verifyEmailNumCheck)}
     onClick={handleCompleteSubscription}
   >
     구독
   </Button>
src/app/home/index.tsx (1)

226-239: Consider adding loading state to the subscription button.

The button should indicate when a subscription request is in progress.

+  const [isSubscribing, setIsSubscribing] = useState(false);
+
+  const handleSubscriptionClick = async () => {
+    setIsSubscribing(true);
+    try {
+      await openSubscriptionModal();
+    } finally {
+      setIsSubscribing(false);
+    }
+  };

   <Button
     css={css`
       width: 11.4rem !important;
       height: 4.5rem;
       ${DESIGN_SYSTEM_COLOR.GRAY_50}
       font-size: 1.6rem;
       font-weight: normal;
     `}
-    onClick={openSubscriptionModal}
+    onClick={handleSubscriptionClick}
+    disabled={isSubscribing}
   >
-    구독하기
+    {isSubscribing ? '처리 중...' : '구독하기'}
   </Button>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3482307 and 80d2c96.

📒 Files selected for processing (11)
  • src/app/home/index.tsx (3 hunks)
  • src/app/signup/index.tsx (3 hunks)
  • src/components/common/modal/Modal.tsx (2 hunks)
  • src/components/common/modal/SubscriptionModalContent.tsx (1 hunks)
  • src/hooks/api/member/useApiSendVerifyEmail.ts (1 hunks)
  • src/hooks/api/member/useApiVerifyEmail.ts (1 hunks)
  • src/hooks/api/subscribe/useApiSendSubscribe.ts (1 hunks)
  • src/hooks/useModal.ts (1 hunks)
  • src/store/modal/modalAtom.ts (1 hunks)
  • src/types/modal.ts (1 hunks)
  • src/utils/common.ts (1 hunks)
🔇 Additional comments (7)
src/store/modal/modalAtom.ts (1)

7-7: LGTM!

The default value of true maintains backward compatibility for existing modals. The implementation follows Jotai atom patterns correctly.

src/hooks/api/member/useApiSendVerifyEmail.ts (1)

16-16: Empty hook implementation.

The useApiSendVerifyEmail hook is currently empty. Consider implementing proper hook functionality or removing it if not needed.

src/hooks/api/member/useApiVerifyEmail.ts (1)

18-18: Empty hook implementation.

The useApiVerifyEmail hook is currently empty. Consider implementing proper hook functionality or removing it if not needed.

src/hooks/useModal.ts (1)

14-25: LGTM! Clean implementation of modal visibility control.

The implementation is well-structured with:

  • Good use of TypeScript's Omit utility type
  • Proper default value handling using nullish coalescing
  • Clean callback implementation with useCallback
src/components/common/modal/Modal.tsx (1)

56-57: LGTM! Improved modal layout flexibility.

The changes to max-height and row-gap improve the modal's adaptability to different content sizes.

Also applies to: 64-64

src/app/signup/index.tsx (1)

17-17: LGTM!

The changes correctly integrate the EMAIL_PURPOSE constant for email verification in the signup flow.

Also applies to: 86-87, 109-110

src/app/home/index.tsx (1)

115-124: LGTM!

The subscription modal opening logic is well-documented and correctly implemented.

Comment on lines +3 to +12
interface sendSubscribeEmail {
email: string;
}
export const sendSubscribeEmail = ({ email }: sendSubscribeEmail) => {
const res = api.post(`/user-service/api/v1/subscribers/subscribe`, {
email: email,
});

return res;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Several improvements needed for the API hook implementation.

  1. Interface name should be PascalCase
  2. Add error handling
  3. Define response type
  4. Remove redundant property shorthand
  5. Add return type annotation
-interface sendSubscribeEmail {
+interface SendSubscribeEmailParams {
   email: string;
 }
-export const sendSubscribeEmail = ({ email }: sendSubscribeEmail) => {
+interface SubscribeResponse {
+  success: boolean;
+  message: string;
+}
+
+export const sendSubscribeEmail = async ({ 
+  email 
+}: SendSubscribeEmailParams): Promise<SubscribeResponse> => {
+  try {
     const res = api.post(`/user-service/api/v1/subscribers/subscribe`, {
-      email: email,
+      email,
     });
 
     return res;
+  } catch (error) {
+    throw new Error('Failed to send subscription email');
+  }
 };

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +54 to +76
const handleCompleteSubscription = async () => {
if (!verifyEmailNumCheck) {
alert("이메일 인증을 완료해주세요.");
return;
}

const selectedCategories = Object.keys(categories).filter(
(key) => categories[key as keyof typeof categories]
);

if (selectedCategories.length === 0) {
alert("최소 하나 이상의 구독 카테고리를 선택해주세요.");
return;
}

try {
await sendSubscribeEmail({ email });
alert("구독이 완료되었습니다. 매일 자정에 구독 정보를 보내드립니다.");
close(); // 모달 창 닫기
} catch (error) {
alert("구독 요청에 실패했습니다. 다시 시도해주세요.");
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for the subscription request.

The subscription request should handle specific error cases and provide appropriate feedback to users.

   const handleCompleteSubscription = async () => {
     if (!verifyEmailNumCheck) {
       alert("이메일 인증을 완료해주세요.");
       return;
     }

     const selectedCategories = Object.keys(categories).filter(
       (key) => categories[key as keyof typeof categories]
     );

     if (selectedCategories.length === 0) {
       alert("최소 하나 이상의 구독 카테고리를 선택해주세요.");
       return;
     }

     try {
       await sendSubscribeEmail({ email });
       alert("구독이 완료되었습니다. 매일 자정에 구독 정보를 보내드립니다.");
       close();
     } catch (error) {
-      alert("구독 요청에 실패했습니다. 다시 시도해주세요.");
+      if (error instanceof Error) {
+        if (error.message.includes('already subscribed')) {
+          alert("이미 구독 중인 이메일입니다.");
+        } else if (error.message.includes('invalid email')) {
+          alert("유효하지 않은 이메일입니다.");
+        } else {
+          alert("구독 요청에 실패했습니다. 다시 시도해주세요.");
+        }
+      }
     }
   };
📝 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
const handleCompleteSubscription = async () => {
if (!verifyEmailNumCheck) {
alert("이메일 인증을 완료해주세요.");
return;
}
const selectedCategories = Object.keys(categories).filter(
(key) => categories[key as keyof typeof categories]
);
if (selectedCategories.length === 0) {
alert("최소 하나 이상의 구독 카테고리를 선택해주세요.");
return;
}
try {
await sendSubscribeEmail({ email });
alert("구독이 완료되었습니다. 매일 자정에 구독 정보를 보내드립니다.");
close(); // 모달 창 닫기
} catch (error) {
alert("구독 요청에 실패했습니다. 다시 시도해주세요.");
}
};
const handleCompleteSubscription = async () => {
if (!verifyEmailNumCheck) {
alert("이메일 인증을 완료해주세요.");
return;
}
const selectedCategories = Object.keys(categories).filter(
(key) => categories[key as keyof typeof categories]
);
if (selectedCategories.length === 0) {
alert("최소 하나 이상의 구독 카테고리를 선택해주세요.");
return;
}
try {
await sendSubscribeEmail({ email });
alert("구독이 완료되었습니다. 매일 자정에 구독 정보를 보내드립니다.");
close(); // 모달 창 닫기
} catch (error) {
if (error instanceof Error) {
if (error.message.includes('already subscribed')) {
alert("이미 구독 중인 이메일입니다.");
} else if (error.message.includes('invalid email')) {
alert("유효하지 않은 이메일입니다.");
} else {
alert("구독 요청에 실패했습니다. 다시 시도해주세요.");
}
}
}
};

Copy link
Collaborator

@klmhyeonwoo klmhyeonwoo left a comment

Choose a reason for hiding this comment

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

고생하셨습니다~

@klmhyeonwoo klmhyeonwoo added feature Add Service Feature and removed enhancement New feature or request labels Feb 2, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/app/signup/index.tsx (1)

104-117: Improve error handling.

The error handling could be enhanced:

  1. Remove console.log in production
  2. Consider providing more specific error messages based on error types
 .catch((err) => {
-  console.log(err);
   alert(
-    "인증번호 입력 중 오류가 발생했습니다, 관리자에게 문의해주세요!",
+    err.response?.data?.message || 
+    "인증번호 입력 중 오류가 발생했습니다, 관리자에게 문의해주세요!"
   );
 });
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 80d2c96 and 4ebd94c.

📒 Files selected for processing (5)
  • src/app/signup/index.tsx (3 hunks)
  • src/components/common/modal/SubscriptionModalContent.tsx (1 hunks)
  • src/hooks/api/member/useApiSendVerifyEmail.ts (1 hunks)
  • src/hooks/api/member/useApiVerifyEmail.ts (1 hunks)
  • src/utils/regex.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/utils/regex.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/hooks/api/member/useApiVerifyEmail.ts
  • src/hooks/api/member/useApiSendVerifyEmail.ts
  • src/components/common/modal/SubscriptionModalContent.tsx
🔇 Additional comments (2)
src/app/signup/index.tsx (2)

17-18: LGTM! Good refactoring of constants.

Moving EMAIL_REGEX to a shared location and introducing EMAIL_PURPOSE improves code reusability and standardization.


84-84: LGTM! Good addition of email purpose context.

The addition of emailPurpose parameter helps distinguish between signup and subscription verification flows.

@minwoo1999 minwoo1999 merged commit 731816b into main Feb 2, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Add Service Feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants