-
Notifications
You must be signed in to change notification settings - Fork 3
[feature] 소셜 공유하기 기능 #541
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
[feature] 소셜 공유하기 기능 #541
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,9 +7,31 @@ export const ClubDetailHeaderContainer = styled.div` | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| margin-top: 150px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @media (max-width: 500px) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| & > button { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| display: none; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| margin-top: 40px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const ButtonContainer = styled.div` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| display: flex; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| flex-direction: column; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| align-items: flex-end; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gap: 12px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .share-button { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| padding: 8px 12px !important; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| font-size: 14px !important; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| min-width: auto !important; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| white-space: nowrap; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @media (max-width: 500px) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gap: 6px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .share-button { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| padding: 4px 8px !important; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| font-size: 11px !important; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| min-width: auto !important; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| border-radius: 4px !important; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+14
to
+37
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 스타일 구현을 개선해주세요. 다음 사항들을 개선하는 것을 권장합니다:
다음과 같이 개선할 수 있습니다: +const BREAKPOINT_MOBILE = '500px';
+const BUTTON_GAP_DESKTOP = '12px';
+const BUTTON_GAP_MOBILE = '6px';
export const ButtonContainer = styled.div`
display: flex;
flex-direction: column;
align-items: flex-end;
- gap: 12px;
+ gap: ${BUTTON_GAP_DESKTOP};
.share-button {
- padding: 8px 12px !important;
- font-size: 14px !important;
- min-width: auto !important;
+ padding: 8px 12px;
+ font-size: 14px;
+ min-width: auto;
white-space: nowrap;
}
- @media (max-width: 500px) {
+ @media (max-width: ${BREAKPOINT_MOBILE}) {
- gap: 6px;
+ gap: ${BUTTON_GAP_MOBILE};
.share-button {
- padding: 4px 8px !important;
- font-size: 11px !important;
- min-width: auto !important;
- border-radius: 4px !important;
+ padding: 4px 8px;
+ font-size: 11px;
+ min-width: auto;
+ border-radius: 4px;
}
}
`;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,6 +3,7 @@ import ClubProfile from '@/pages/ClubDetailPage/components/ClubProfile/ClubProfi | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import ClubApplyButton from '@/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { parseRecruitmentPeriod } from '@/utils/stringToDate'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import getDeadlineText from '@/utils/getDeadLineText'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import ShareButton from '../Share/ShareButton/ShareButton'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interface ClubDetailHeaderProps { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| category: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -12,6 +13,8 @@ interface ClubDetailHeaderProps { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| recruitmentPeriod: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| recruitmentForm?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| presidentPhoneNumber?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| clubId: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const ClubDetailHeader = ({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -23,6 +26,8 @@ const ClubDetailHeader = ({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| recruitmentPeriod, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| recruitmentForm, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| presidentPhoneNumber, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| clubId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }: ClubDetailHeaderProps) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { recruitmentStart, recruitmentEnd } = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parseRecruitmentPeriod(recruitmentPeriod); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -33,6 +38,10 @@ const ClubDetailHeader = ({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| new Date(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const shareUrl = `${window.location.origin}/club/${clubId}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const shareTitle = `${name} - 동아리 정보`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const shareText = `${description}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+41
to
+43
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion SSR 호환성을 위해 window 객체 사용을 개선해주세요.
다음과 같이 개선할 수 있습니다: +import { useMemo } from 'react';
const ClubDetailHeader = ({
// ... props
}) => {
// ... existing code
+ const shareData = useMemo(() => {
+ const baseUrl = typeof window !== 'undefined' ? window.location.origin : '';
+ return {
+ url: `${baseUrl}/club/${clubId}`,
+ title: `${name} - 동아리 정보`,
+ text: description
+ };
+ }, [clubId, name, description]);
- const shareUrl = `${window.location.origin}/club/${clubId}`;
- const shareTitle = `${name} - 동아리 정보`;
- const shareText = `${description}`;
return (
// ...
<ShareButton
- url={shareUrl}
- title={shareTitle}
- text={shareText}
+ url={shareData.url}
+ title={shareData.title}
+ text={shareData.text}
buttonText="공유"
className="share-button"
/>
// ...
);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain@seongwon030 좋은 질문입니다! SSR에서 SSR에서 window 객체 사용 시 발생할 수 있는 문제
하지만 현재 프로젝트에서 실제로 이런 문제가 발생하는지 확인해보겠습니다. 🏁 Script executed: Length of output: 5863 @seongwon030 죄송합니다! 제가 잘못된 가정을 했네요. 스크립트 실행 결과를 보니, 이 프로젝트는:
따라서 제가 Next.js 프로젝트로 잘못 가정하고 불필요한 SSR 호환성 제안을 드렸습니다. 현재 코드가 완전히 정상적이고 수정할 필요가 없습니다. 잘못된 리뷰 제안을 드려 죄송합니다! 🙏 🧬 Code Graph Analysis ResultsRelevant snippet from
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Styled.ClubDetailHeaderContainer> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <ClubProfile | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -42,12 +51,21 @@ const ClubDetailHeader = ({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tags={tags} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logo={logo} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <ClubApplyButton | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {...(deadlineText !== '모집 마감' && { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| recruitmentForm, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| presidentPhoneNumber, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| })} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Styled.ButtonContainer> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <ClubApplyButton | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {...(deadlineText !== '모집 마감' && { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| recruitmentForm, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| presidentPhoneNumber, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| })} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <ShareButton | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url={shareUrl} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title={shareTitle} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| text={shareText} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| buttonText="공유" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className="share-button" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+61
to
+67
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분또한 className을 prop으로 주지 않아도 되는 방법이 있어요. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Styled.ButtonContainer> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Styled.ClubDetailHeaderContainer> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,57 @@ | ||||||||||
| import React, { useState } from 'react'; | ||||||||||
| import ShareModal from '../ShareModal/ShareModal'; | ||||||||||
|
|
||||||||||
| interface ShareButtonProps { | ||||||||||
| url: string; | ||||||||||
| title: string; | ||||||||||
| text?: string; | ||||||||||
| buttonText?: string; | ||||||||||
| className?: string; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| const ShareButton: React.FC<ShareButtonProps> = ({ | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. React.FC에서 props는 children을 암시적으로 가지게 됩니다. 매개변수 옆에 타입을 정의하면 React.FC를 사용하지 않아도 됩니다. |
||||||||||
| url, | ||||||||||
| title, | ||||||||||
| text, | ||||||||||
| buttonText = '공유하기', | ||||||||||
| className = '' | ||||||||||
| }) => { | ||||||||||
| const [showModal, setShowModal] = useState<boolean>(false); | ||||||||||
|
|
||||||||||
| const isWebShareSupported = typeof navigator !== 'undefined' && 'share' in navigator; | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 브라우저 호환성 검사를 더 견고하게 만들어주세요. 현재 Navigator API 지원 검사가 기본적이며, 더 정확한 기능 감지가 필요합니다. - const isWebShareSupported = typeof navigator !== 'undefined' && 'share' in navigator;
+ const isWebShareSupported = typeof navigator !== 'undefined' &&
+ 'share' in navigator &&
+ typeof navigator.share === 'function';📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
|
|
||||||||||
| const handleShare = async (): Promise<void> => { | ||||||||||
| if (isWebShareSupported) { | ||||||||||
| try { | ||||||||||
| await navigator.share({ title, text, url }); | ||||||||||
| } catch (error) { | ||||||||||
| if (error instanceof Error && error.name !== 'AbortError') { | ||||||||||
| console.error('공유 실패:', error.message); | ||||||||||
| setShowModal(true); | ||||||||||
| } | ||||||||||
| } | ||||||||||
| } else { | ||||||||||
| setShowModal(true); | ||||||||||
| } | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| return ( | ||||||||||
| <> | ||||||||||
| <button | ||||||||||
| onClick={handleShare} | ||||||||||
| className={`px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors ${className}`} | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chain스타일링 일관성을 확인해주세요. Tailwind CSS 클래스와 styled-components를 혼합 사용하고 있습니다. 프로젝트의 스타일링 패턴과 일치하는지 확인해주세요. 프로젝트의 다른 컴포넌트들이 어떤 스타일링 방식을 사용하는지 확인하는 스크립트를 실행해보겠습니다: 🏁 Script executed: #!/bin/bash
# Description: Check styling patterns used in other components
# Check for Tailwind usage in other components
echo "=== Tailwind usage in components ==="
rg -A 2 -B 2 "className.*bg-|className.*text-|className.*px-" --type tsx
echo -e "\n=== Styled-components usage ==="
rg -A 2 -B 2 "styled\." --type tsx | head -20
echo -e "\n=== Mixed usage patterns ==="
rg -l "styled" --type tsx | xargs rg -l "className.*bg-|className.*text-" | head -5Length of output: 604 아래 스크립트로 🏁 Script executed: #!/bin/bash
# Tailwind CSS 클래스 사용 예시 검색
echo "=== Tailwind usage in .tsx files ==="
rg -g '*.tsx' -E "className=.*bg-|className=.*text-|className=.*px-" -n | head -20
echo -e "\n=== Styled-components usage in .tsx files ==="
rg -g '*.tsx' -E "styled\." -n | head -20
echo -e "\n=== Mixed usage (styled + className) ==="
rg -l -g '*.tsx' "styled\." | xargs rg -l -g '*.tsx' -E "className=.*bg-|className=.*text-|className=.*px-" | head -20Length of output: 1417 아래 수정된 스크립트로 .tsx 파일 전체를 다시 검사해보세요: 🏁 Script executed: #!/bin/bash
# Tailwind CSS 클래스 사용 예시 검색
echo "=== Tailwind usage in .tsx files ==="
rg --glob '*.tsx' 'className=.*\b(bg|text|px)-' -n | head -20
echo -e "\n=== styled-components usage in .tsx files ==="
rg --glob '*.tsx' 'styled\.' -n | head -20
echo -e "\n=== Mixed usage (styled + Tailwind) ==="
rg -l --glob '*.tsx' 'styled\.' | xargs rg -l --glob '*.tsx' 'className=.*\b(bg|text|px)-' | head -20Length of output: 1580 스타일링 일관성 유지: styled-components로 변환 필요 프로젝트 대부분의 컴포넌트가 styled-components를 사용하고 있으며, Tailwind 클래스 사용 예시는 PR 외에 확인되지 않습니다.
🤖 Prompt for AI Agents
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 일관성 유지 부탁드릴게욥~ |
||||||||||
| > | ||||||||||
| {buttonText} | ||||||||||
| </button> | ||||||||||
|
|
||||||||||
| <ShareModal | ||||||||||
| isOpen={showModal} | ||||||||||
| onClose={() => setShowModal(false)} | ||||||||||
| shareData={{ url, title, text }} | ||||||||||
| /> | ||||||||||
| </> | ||||||||||
| ); | ||||||||||
| }; | ||||||||||
|
|
||||||||||
|
|
||||||||||
| export default ShareButton; | ||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| export const overlayStyle: React.CSSProperties = { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이건 처음 보는 문법인데.. 혹시 어떤 경우에 쓰는건가요? |
||
| position: 'fixed', | ||
| top: 0, | ||
| left: 0, | ||
| right: 0, | ||
| bottom: 0, | ||
| backgroundColor: 'rgba(0, 0, 0, 0.5)', | ||
| display: 'flex', | ||
| alignItems: 'center', | ||
| justifyContent: 'center', | ||
| zIndex: 99999 | ||
| }; | ||
|
|
||
| export const modalContainerStyle: React.CSSProperties = { | ||
| backgroundColor: 'white', | ||
| padding: '24px', | ||
| borderRadius: '12px', | ||
| maxWidth: '400px', | ||
| width: '100%', | ||
| margin: '16px', | ||
| boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.25)', | ||
| position: 'relative' | ||
| }; | ||
|
|
||
| export const headerStyle: React.CSSProperties = { | ||
| display: 'flex', | ||
| justifyContent: 'space-between', | ||
| alignItems: 'center', | ||
| marginBottom: '16px' | ||
| }; | ||
|
|
||
| export const titleStyle: React.CSSProperties = { | ||
| fontSize: '18px', | ||
| fontWeight: '600', | ||
| margin: 0 | ||
| }; | ||
|
|
||
| export const closeButtonStyle: React.CSSProperties = { | ||
| background: 'none', | ||
| border: 'none', | ||
| fontSize: '20px', | ||
| cursor: 'pointer', | ||
| color: '#666' | ||
| }; | ||
|
|
||
| export const buttonContainerStyle: React.CSSProperties = { | ||
| marginBottom: '16px' | ||
| }; | ||
|
|
||
| export const shareButtonStyle: React.CSSProperties = { | ||
| width: '100%', | ||
| padding: '12px', | ||
| border: '1px solid #ddd', | ||
| borderRadius: '8px', | ||
| backgroundColor: 'white', | ||
| cursor: 'pointer', | ||
| marginBottom: '8px', | ||
| textAlign: 'left', | ||
| display: 'flex', | ||
| alignItems: 'center', | ||
| gap: '8px' | ||
| }; | ||
|
|
||
| export const linkCopyContainerStyle: React.CSSProperties = { | ||
| display: 'block', | ||
| fontSize: '14px', | ||
| fontWeight: '500', | ||
| marginBottom: '8px' | ||
| }; | ||
|
|
||
| export const inputContainerStyle: React.CSSProperties = { | ||
| display: 'flex' | ||
| }; | ||
|
|
||
| export const inputStyle: React.CSSProperties = { | ||
| flex: 1, | ||
| padding: '8px', | ||
| border: '1px solid #ddd', | ||
| borderRadius: '6px 0 0 6px', | ||
| backgroundColor: '#f9f9f9', | ||
| fontSize: '14px' | ||
| }; | ||
|
|
||
| export const copyButtonStyle: React.CSSProperties = { | ||
| padding: '8px 16px', | ||
| backgroundColor: '#3b82f6', | ||
| color: 'white', | ||
| border: 'none', | ||
| borderRadius: '0 6px 6px 0', | ||
| cursor: 'pointer' | ||
| }; | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,104 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import React from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { createPortal } from 'react-dom'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import * as styles from './ShareModal.styles'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interface ShareModalProps { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isOpen: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClose: () => void; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| shareData: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| text?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const ShareModal: React.FC<ShareModalProps> = ({ isOpen, onClose, shareData }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분도요 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const copyToClipboard = async (): Promise<void> => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await navigator.clipboard.writeText(shareData.url); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alert('링크가 복사되었습니다'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error('복사 실패:', error); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+16
to
+23
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 클립보드 API 사용에 적절한 피드백 UI를 제공해주세요.
+import { useState } from 'react';
const ShareModal: React.FC<ShareModalProps> = ({ isOpen, onClose, shareData }) => {
+ const [copySuccess, setCopySuccess] = useState<string | null>(null);
const copyToClipboard = async (): Promise<void> => {
try {
await navigator.clipboard.writeText(shareData.url);
- alert('링크가 복사되었습니다');
+ setCopySuccess('링크가 복사되었습니다');
+ setTimeout(() => setCopySuccess(null), 2000);
} catch (error) {
console.error('복사 실패:', error);
+ setCopySuccess('복사에 실패했습니다');
+ setTimeout(() => setCopySuccess(null), 2000);
}
};📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const shareToSocial = (platform: string): void => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const encodedUrl = encodeURIComponent(shareData.url); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const encodedTitle = encodeURIComponent(shareData.title); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const encodedText = encodeURIComponent(shareData.text || ''); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let shareUrl = ''; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| switch (platform) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case 'facebook': | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| navigator.clipboard.writeText(shareData.url); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alert('링크가 복사되었습니다. 페이스북 앱에서 붙여넣기 해주세요.'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| shareUrl = `https://www.facebook.com/sharer.php?u=${encodedUrl}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case 'x': | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| shareUrl = `https://twitter.com/intent/tweet?url=${encodedUrl}&text=${encodedTitle}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case 'instagram': | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| navigator.clipboard.writeText(shareData.url); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alert('링크가 복사되었습니다. 인스타그램 앱에서 붙여넣기 해주세요.'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+32
to
+45
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainFacebook과 Instagram 공유 방식을 재검토해주세요. 현재 Facebook과 Instagram의 경우 실제 공유가 아닌 링크 복사만 수행합니다. 사용자가 직접 붙여넣기를 해야 하는 방식은 사용자 경험이 좋지 않을 수 있습니다. Facebook Sharer API가 여전히 작동하는지 확인해보겠습니다: 🌐 Web query: 💡 Result: As of 2024, the Facebook Sharer API, accessible via To enhance the effectiveness of your content sharing on Facebook, consider implementing the following best practices:
By adhering to these best practices, you can enhance the visibility and engagement of your content on Facebook, ensuring a more effective sharing experience for your audience. Citations:
Facebook 및 Instagram 공유 UX 개선 필요 현재 Facebook과 Instagram 공유 로직은 단순히 링크 복사 후 사용자가 직접 붙여넣기를 해야 하는 흐름으로, 사용자 경험이 떨어집니다. 2024년 기준 Facebook Sharer API( 수정 위치
예시 코드 변경안: - case 'facebook':
- navigator.clipboard.writeText(shareData.url);
- alert('링크가 복사되었습니다. 페이스북 앱에서 붙여넣기 해주세요.');
- shareUrl = `https://www.facebook.com/sharer.php?u=${encodedUrl}`;
- break;
+ case 'facebook':
+ window.open(`https://www.facebook.com/sharer.php?u=${encodedUrl}`, '_blank');
+ break;
- case 'instagram':
- navigator.clipboard.writeText(shareData.url);
- alert('링크가 복사되었습니다. 인스타그램 앱에서 붙여넣기 해주세요.');
- return;
+ case 'instagram':
+ if (navigator.share) {
+ navigator.share({ title: shareData.title, url: shareData.url });
+ } else {
+ alert('인스타그램 웹 공유는 지원되지 않습니다. 링크를 복사합니다.');
+ navigator.clipboard.writeText(shareData.url);
+ }
+ return;추가 검토 사항
📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (shareUrl) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| window.open(shareUrl, '_blank', 'width=600,height=400'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+25
to
+50
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 소셜 공유 로직을 개선해주세요. 현재 구현에서 몇 가지 문제점이 있습니다:
플랫폼별 공유 로직을 별도 훅이나 유틸리티 함수로 분리하는 것을 고려해보세요: // utils/socialShare.ts
export const createShareUrl = (platform: string, data: ShareData) => {
const { url, title } = data;
const encodedUrl = encodeURIComponent(url);
const encodedTitle = encodeURIComponent(title);
switch (platform) {
case 'facebook':
return `https://www.facebook.com/sharer.php?u=${encodedUrl}`;
case 'x':
return `https://twitter.com/intent/tweet?url=${encodedUrl}&text=${encodedTitle}`;
default:
return '';
}
};🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!isOpen) return null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const modalContent = ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div style={styles.overlayStyle} onClick={onClose}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. div를 styled-components로 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div style={styles.modalContainerStyle} onClick={(e) => e.stopPropagation()}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {/* 헤더 */} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div style={styles.headerStyle}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <h3 style={styles.titleStyle}>공유하기</h3> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <button onClick={onClose} style={styles.closeButtonStyle}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ✕ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {/* 공유 버튼들 */} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div style={styles.buttonContainerStyle}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <button onClick={() => shareToSocial('facebook')} style={styles.shareButtonStyle}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 📘 Facebook에서 공유 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <button onClick={() => shareToSocial('x')} style={styles.shareButtonStyle}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ❌ X(Twitter)에서 공유 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <button onClick={() => shareToSocial('instagram')} style={styles.shareButtonStyle}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 📷 Instagram에서 공유 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {/* 링크 복사 */} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <label style={styles.linkCopyContainerStyle}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 링크 복사 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </label> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div style={styles.inputContainerStyle}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <input | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type="text" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value={shareData.url} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| readOnly | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| style={styles.inputStyle} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <button onClick={copyToClipboard} style={styles.copyButtonStyle}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 복사 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return createPortal(modalContent, document.body); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default ShareModal; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
코드래빗에서 말한 것처럼 !important 는 기존 스타일을 덮어쓰는 문제가 있어요.
그리고 css 형식을 따르셨는데 프론트에서는
styled-components라이브러리를 사용하고 있어요.만약 share-button이라는 className을 사용하고 싶으시다면
처럼 사용할 수 있어요.