Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
38 changes: 19 additions & 19 deletions app/create/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { useState } from 'react';
import Image from 'next/image';
import { useRouter } from 'next/navigation';
import { useCreateMeeting } from '@/hooks/api/useMeeting';
import { useCreateMeeting } from '@/hooks/api/mutation/useCreateMeeting';
import type { MeetingCreateRequest } from '@/types/api';
import { useToast } from '@/hooks/useToast';
import Toast from '@/components/ui/toast';
Expand Down Expand Up @@ -167,7 +167,7 @@ export default function Page() {
value={meetingName}
onChange={(e) => setMeetingName(e.target.value)}
placeholder="플레이스 홀더"
className="border-gray-2 placeholder:text-gray-3 w-full rounded-[4px] border px-3 py-2 text-[15px] leading-[1.6] tracking-[0.144px] focus:outline-none"
className="border-gray-2 placeholder:text-gray-3 w-full rounded border px-3 py-2 text-[15px] leading-[1.6] tracking-[0.144px] focus:outline-none"
/>
</div>

Expand All @@ -183,7 +183,7 @@ export default function Page() {
setMeetingType('회의');
setSelectedSocialPlace(null);
}}
className={`flex-1 rounded-[4px] py-2.5 text-[15px] leading-[1.6] tracking-[0.144px] transition-colors sm:py-2 ${
className={`flex-1 rounded py-2.5 text-[15px] leading-[1.6] tracking-[0.144px] transition-colors sm:py-2 ${
meetingType === '회의'
? 'bg-blue-5 text-white'
: 'bg-gray-1 text-gray-7 hover:bg-gray-2'
Expand All @@ -197,7 +197,7 @@ export default function Page() {
setMeetingType('친목');
setSelectedLocation(null);
}}
className={`flex-1 rounded-[4px] py-2.5 text-[15px] leading-[1.6] tracking-[0.144px] transition-colors sm:py-2 ${
className={`flex-1 rounded py-2.5 text-[15px] leading-[1.6] tracking-[0.144px] transition-colors sm:py-2 ${
meetingType === '친목'
? 'bg-blue-5 text-white'
: 'bg-gray-1 text-gray-7 hover:bg-gray-2'
Expand All @@ -220,7 +220,7 @@ export default function Page() {
key={location}
type="button"
onClick={() => setSelectedLocation(isSelected ? null : location)}
className={`flex h-[43px] w-full items-center gap-[14px] rounded-[4px] border px-3 py-1 transition-colors ${
className={`flex h-10.75 w-full items-center gap-3.5 rounded border px-3 py-1 transition-colors ${
isSelected
? 'border-gray-1 bg-white'
: 'border-gray-1 hover:bg-gray-1 bg-white'
Expand Down Expand Up @@ -260,7 +260,7 @@ export default function Page() {
key={place}
type="button"
onClick={() => setSelectedSocialPlace(isSelected ? null : place)}
className={`flex h-[43px] w-full items-center gap-[14px] rounded-[4px] border px-3 py-1 transition-colors ${
className={`flex h-10.75 w-full items-center gap-3.5 rounded border px-3 py-1 transition-colors ${
isSelected
? 'border-gray-1 bg-white'
: 'border-gray-1 hover:bg-gray-1 bg-white'
Expand Down Expand Up @@ -292,33 +292,33 @@ export default function Page() {
<label className="text-gray-9 text-[14px] leading-[1.571] font-semibold tracking-[0.203px] sm:text-[15px] md:text-[14px]">
참여 인원을 알려주세요.
</label>
<div className="border-gray-2 relative flex h-[44px] items-center rounded-[4px] border bg-white">
<div className="border-gray-2 relative flex h-11 items-center rounded border bg-white">
<button
type="button"
onClick={handleDecreaseParticipants}
disabled={isParticipantUndecided || participantCount === 2}
className="bg-gray-1 absolute -top-px -left-px flex h-[44px] w-[44px] items-center justify-center rounded-tl-[4px] rounded-bl-[4px] disabled:opacity-50 sm:h-[44px] sm:w-[44px]"
className="bg-gray-1 absolute -top-px -left-px flex h-11 w-11 items-center justify-center rounded-tl-lg rounded-bl-lg disabled:opacity-50 sm:h-11 sm:w-11"
>
<Image src="/icon/minus.svg" alt="minus" width={20} height={20} />
</button>
<div className="text-gray-8 absolute top-1/2 left-1/2 flex -translate-x-1/2 -translate-y-1/2 items-center gap-[2px] text-[15px] leading-[1.6] tracking-[0.144px]">
<div className="text-gray-8 absolute top-1/2 left-1/2 flex -translate-x-1/2 -translate-y-1/2 items-center gap-0.5 text-[15px] leading-[1.6] tracking-[0.144px]">
<span>{participantCount}</span>
<span>명</span>
</div>
<button
type="button"
onClick={handleIncreaseParticipants}
disabled={isParticipantUndecided}
className="bg-gray-1 absolute -top-px -right-px flex h-[44px] w-[44px] items-center justify-center rounded-tr-[4px] rounded-br-[4px] disabled:opacity-50 sm:h-[44px] sm:w-[44px]"
className="bg-gray-1 absolute -top-px -right-px flex h-11 w-11 items-center justify-center rounded-tr-lg rounded-br-lg disabled:opacity-50 sm:h-11 sm:w-11"
>
<Image src="/icon/plus.svg" alt="plus" width={20} height={20} />
</button>
</div>
<div className="flex items-center gap-[10px]">
<div className="flex items-center gap-2.5">
<button
type="button"
onClick={handleParticipantCheckbox}
className={`flex h-5 w-5 items-center justify-center rounded-[4px] transition-colors ${
className={`flex h-5 w-5 items-center justify-center rounded transition-colors ${
isParticipantUndecided ? 'bg-blue-5' : 'bg-gray-3'
}`}
>
Expand All @@ -336,20 +336,20 @@ export default function Page() {
<label className="text-gray-9 text-[14px] leading-[1.571] font-semibold tracking-[0.203px] sm:text-[15px] md:text-[14px]">
참여 기한을 정해주세요.
</label>
<div className="border-gray-2 relative flex h-[60px] items-center rounded-[4px] border bg-white">
<div className="border-gray-2 relative flex h-15 items-center rounded border bg-white">
<button
type="button"
onClick={handleDecreaseDeadline}
disabled={isDeadlineFlexible || deadlineDays === 1}
className="bg-gray-1 absolute -top-px -left-px flex h-[60px] w-[44px] items-center justify-center rounded-tl-[4px] rounded-bl-[4px] disabled:opacity-50 sm:h-[60px] sm:w-[44px]"
className="bg-gray-1 absolute -top-px -left-px flex h-15 w-11 items-center justify-center rounded-tl-lg rounded-bl-lg disabled:opacity-50 sm:h-15 sm:w-11"
>
<Image src="/icon/minus.svg" alt="minus" width={20} height={20} />
</button>
<div className="absolute top-1/2 left-1/2 flex -translate-x-1/2 -translate-y-1/2 flex-col items-center">
<span className="text-blue-5 text-[12px] leading-[1.334] tracking-[0.3024px]">
{getDeadlineDate()}
</span>
<div className="text-gray-8 flex items-center gap-[2px] text-[15px] leading-[1.6] tracking-[0.144px]">
<div className="text-gray-8 flex items-center gap-0.5 text-[15px] leading-[1.6] tracking-[0.144px]">
<span>{deadlineDays}</span>
<span>일</span>
</div>
Expand All @@ -358,16 +358,16 @@ export default function Page() {
type="button"
onClick={handleIncreaseDeadline}
disabled={isDeadlineFlexible}
className="bg-gray-1 absolute -top-px -right-px flex h-[60px] w-[44px] items-center justify-center rounded-tr-[4px] rounded-br-[4px] disabled:opacity-50 sm:h-[60px] sm:w-[44px]"
className="bg-gray-1 absolute -top-px -right-px flex h-15 w-11 items-center justify-center rounded-tr-lg rounded-br-lg disabled:opacity-50 sm:h-15 sm:w-11"
>
<Image src="/icon/plus.svg" alt="plus" width={20} height={20} />
</button>
</div>
<div className="flex items-center gap-[10px]">
<div className="flex items-center gap-2.5">
<button
type="button"
onClick={handleDeadlineCheckbox}
className={`flex h-5 w-5 items-center justify-center rounded-[4px] border transition-colors ${
className={`flex h-5 w-5 items-center justify-center rounded border transition-colors ${
isDeadlineFlexible ? 'bg-blue-5' : 'bg-gray-3'
}`}
>
Expand All @@ -386,7 +386,7 @@ export default function Page() {
type="button"
disabled={!isFormValid || createMeeting.isPending}
onClick={handleGenerateMeeting}
className={`h-12 w-full rounded-[4px] text-[16px] leading-[1.445] font-semibold tracking-[-0.0036px] transition-colors sm:text-[17px] md:text-[18px] ${
className={`h-12 w-full rounded text-[16px] leading-[1.445] font-semibold tracking-[-0.0036px] transition-colors sm:text-[17px] md:text-[18px] ${
isFormValid && !createMeeting.isPending
? 'bg-blue-5 hover:bg-blue-8 text-white'
: 'bg-gray-4 text-gray-2 cursor-not-allowed'
Expand Down
11 changes: 7 additions & 4 deletions app/meeting/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@ import StationSearch from '@/components/meeting/stationSearch';
import { useOpenModal } from '@/hooks/useOpenModal';
import { MOCK_PARTICIPANTS } from '@/mock/mockData';
import StationData from '@/database/stations_info.json';
import { useRouter } from 'next/navigation';
import { useParams, useRouter } from 'next/navigation';

const STATION_DATA = StationData;

export default function Page() {
// 선택된 역 이름 상태 관리

const [selectedStation, setSelectedStation] = useState<string | null>(null);

const params = useParams();
const id = params?.id as string;

const openModal = useOpenModal();
const router = useRouter();

Expand Down Expand Up @@ -70,7 +73,7 @@ export default function Page() {
<button
className="text-blue-5 bg-blue-1 hover:bg-blue-2 flex h-6 w-30 cursor-pointer items-center gap-0.5 rounded px-3 py-1.5 text-[11px] font-semibold transition-colors"
type="button"
onClick={(e) => openModal('SHARE', e)}
onClick={(e) => openModal('SHARE', { meetingId: id }, e)}
>
<Image src="/icon/share.svg" alt="공유 아이콘" width={12} height={12} />
참여 링크 공유하기
Expand Down Expand Up @@ -108,7 +111,7 @@ export default function Page() {
<button
type="button"
className="bg-blue-5 hover:bg-blue-8 flex h-21 w-full cursor-pointer items-center justify-between rounded p-4 text-left text-white transition-transform active:scale-[0.98]"
onClick={(e) => openModal('NUDGE', e)}
onClick={(e) => openModal('NUDGE', { meetingId: id }, e)}
>
<div className="flex flex-col gap-0.5">
<span className="text-lg leading-[1.44] font-semibold">
Expand Down
8 changes: 5 additions & 3 deletions app/result/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
import { useState } from 'react';
import Image from 'next/image';
import { useOpenModal } from '@/hooks/useOpenModal';
import { useRouter } from 'next/navigation';
import { useParams, useRouter } from 'next/navigation';
import KakaoMapLine from '@/components/map/kakaoMapLine';
import { MOCK_LOCATION_RESULTS } from '@/mock/mockData';

export default function Page() {
const openModal = useOpenModal();
const router = useRouter();
const params = useParams();
const id = params?.id as string;

Comment on lines +6 to 15
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find app/result -type f -name "*.tsx" -o -name "*.ts" | head -20

Repository: SWYP-mingling/Frontend

Length of output: 85


🏁 Script executed:

cd app && find . -path "*result*" -type f | head -20

Repository: SWYP-mingling/Frontend

Length of output: 83


🏁 Script executed:

cat -n app/result/page.tsx 2>/dev/null | head -60

Repository: SWYP-mingling/Frontend

Length of output: 2601


🏁 Script executed:

ls -la app/result/

Repository: SWYP-mingling/Frontend

Length of output: 234


🏁 Script executed:

find app/result -type d | sort

Repository: SWYP-mingling/Frontend

Length of output: 76


🏁 Script executed:

rg "result/\[" --type tsx --type ts | head -20

Repository: SWYP-mingling/Frontend

Length of output: 93


🏁 Script executed:

rg "result" app --type js --type jsx -A 2 -B 2 | grep -E "(push|href|navigate|router\.|/result)" | head -30

Repository: SWYP-mingling/Frontend

Length of output: 93


🏁 Script executed:

rg "SHARE" app --type js --type jsx -A 3 | head -40

Repository: SWYP-mingling/Frontend

Length of output: 93


🏁 Script executed:

rg "useParams" app --type js --type jsx -B 2 -A 5 | head -60

Repository: SWYP-mingling/Frontend

Length of output: 93


🏁 Script executed:

rg "result" app -A 2 -B 2 | grep -E "(push|href|navigate|router\.|/result|meetingId)" | head -50

Repository: SWYP-mingling/Frontend

Length of output: 2494


🏁 Script executed:

rg "openModal" app -A 3 | head -80

Repository: SWYP-mingling/Frontend

Length of output: 1711


🏁 Script executed:

rg "/result" . -A 2 -B 2 | head -60

Repository: SWYP-mingling/Frontend

Length of output: 623


🏁 Script executed:

rg "SHARE" hooks -A 10 | head -60

Repository: SWYP-mingling/Frontend

Length of output: 48


🏁 Script executed:

find . -name "*.tsx" -o -name "*.ts" | xargs grep -l "SHARE" | grep -i modal

Repository: SWYP-mingling/Frontend

Length of output: 125


🏁 Script executed:

rg "useOpenModal" hooks -A 20 | head -80

Repository: SWYP-mingling/Frontend

Length of output: 628


🏁 Script executed:

cat -n components/modal/globalModal.tsx | head -100

Repository: SWYP-mingling/Frontend

Length of output: 1198


🏁 Script executed:

cat -n store/useModalStore.ts

Repository: SWYP-mingling/Frontend

Length of output: 976


🏁 Script executed:

rg "meetingId" components/modal -A 5 -B 5

Repository: SWYP-mingling/Frontend

Length of output: 2823


🌐 Web query:

Next.js App Router useParams() static route behavior empty segment

💡 Result:

In the App Router, useParams() only includes active dynamic segments. So:

  • If the current route has no dynamic segments, useParams() returns {}. (nextjs.org)
  • For an optional catch-all like app/shop/[[...slug]]/page.tsx:
    • Visiting /shop means the optional segment is not present, so in practice you should expect no slug key (i.e. useParams() is {}), and handle it as optional. (nextjs.org)
    • Visiting /shop/a/b means slug is active and you get { slug: ['a','b'] }. (nextjs.org)

Typical handling pattern:

'use client'
import { useParams } from 'next/navigation'

export default function Page() {
  const params = useParams<{ slug?: string[] }>()
  const slug = params.slug ?? []   // treat “missing” as empty
  // ...
}

If you’re seeing null on the first render, that’s a Pages Router static optimization behavior (not App Router): useParams() can be null during pre-rendering and then fill after hydration. (nextjs.org)

Citations:


🏁 Script executed:

rg "useSearchParams" app -B 2 -A 5 | head -40

Repository: SWYP-mingling/Frontend

Length of output: 48


meetingId가 undefined로 전달되어 SHARE 모달 기능이 작동하지 않습니다.

/result는 동적 세그먼트가 없는 정적 라우트이므로 useParams().id는 항상 undefined를 반환합니다. 이는 SHARE 모달에 빈 문자열 또는 undefined로 전달되어 링크 생성 API가 실패합니다. /result/[id]로 라우팅을 변경하거나 useSearchParams()로 쿼리 파라미터를 사용해 주세요. 또한 meetingId가 없을 때 SHARE 버튼을 비활성화하거나 클릭을 방지해야 합니다.

🔧 예시 수정안 (쿼리 기반)
-import { useParams, useRouter } from 'next/navigation';
+import { useSearchParams, useRouter } from 'next/navigation';

-const params = useParams();
-const id = params?.id as string;
+const searchParams = useSearchParams();
+const id = searchParams.get('meetingId');

- onClick={(e) => openModal('SHARE', { meetingId: id }, e)}
+ onClick={(e) => {
+   if (!id) return;
+   openModal('SHARE', { meetingId: id }, e);
+ }}

또는 라우트를 동적 세그먼트로 변경: /app/result/[id]/page.tsx

🤖 Prompt for AI Agents
In `@app/result/page.tsx` around lines 6 - 15, The Page currently reads an id via
useParams() which returns undefined because /result is a static route; update
Page to obtain meetingId from the query using useSearchParams() (e.g., const
search = useSearchParams(); const meetingId = search.get('id')) or change the
route to a dynamic segment (/result/[id]/page.tsx) so useParams() works; also
update the SHARE modal invocation (openModal / any share handler) to pass
meetingId only when present and disable or prevent clicks on the SHARE button
when meetingId is null/undefined/empty to avoid calling the link-generation API
with an invalid id.

// 현재 선택된 결과 카드 관리 (기본값: 첫 번째)
const [selectedResultId, setSelectedResultId] = useState<number>(1);
Expand Down Expand Up @@ -47,7 +49,7 @@ export default function Page() {
<button
className="text-blue-5 bg-blue-1 hover:bg-blue-2 flex h-7 cursor-pointer items-center gap-1 rounded px-2.5 text-[11px] font-semibold transition-colors"
type="button"
onClick={(e) => openModal('SHARE', e)}
onClick={(e) => openModal('SHARE', { meetingId: id }, e)}
>
<Image src="/icon/share.svg" alt="공유 아이콘" width={12} height={12} />
결과 공유하기
Expand Down Expand Up @@ -117,7 +119,7 @@ export default function Page() {
<button
onClick={(e) => {
e.stopPropagation();
openModal('TRANSFER', e);
openModal('TRANSFER', undefined, e);
}}
className="bg-gray-8 h-8 w-full cursor-pointer rounded py-1 text-[15px] font-normal text-white"
>
Expand Down
46 changes: 5 additions & 41 deletions app/share/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,54 +1,18 @@
'use client';

import { useEffect, useState } from 'react';
import { notFound, useParams, useRouter } from 'next/navigation';
import { notFound, useParams } from 'next/navigation';
import Link from 'next/link';
import Toast from '@/components/ui/toast';
import { useToast } from '@/hooks/useToast';
import { apiGet } from '@/lib/api';
import { useShareMeeting } from '@/hooks/api/query/useShareMeeting';

export default function SharePage() {
const params = useParams();
const id = params?.id as string;
const router = useRouter();
const { isVisible, show } = useToast();

const [shareUrl, setShareUrl] = useState('');
const [loading, setLoading] = useState(true);
const [isError, setIsError] = useState(false);

useEffect(() => {
if (!id) return;

const checkMeeting = async () => {
try {
// 모임이 존재하는지 확인
await apiGet(`${process.env.NEXT_PUBLIC_API_BASE_URL}/meeting/result/${id}`);

// 존재하면 URL 생성 (window 객체 사용 가능)
setShareUrl(`${window.location.origin}/meeting/${id}`);
setLoading(false);
} catch (error) {
console.error('실패:', error);
setIsError(true);
}
};

checkMeeting();
}, [id, router]);

const handleCopyLink = async () => {
if (!shareUrl) return;
try {
await navigator.clipboard.writeText(shareUrl);
show();
} catch (error) {
alert('복사 실패');
}
};
const { shareUrl, isError, isLoading, handleCopyLink, isVisible } = useShareMeeting(id);

if (isError) notFound();
if (loading) return null;
if (isLoading) return null;

return (
<div className="flex flex-col items-center justify-center bg-white px-5 py-10 md:py-25">
Expand Down Expand Up @@ -83,7 +47,7 @@ export default function SharePage() {
</div>

<Link
href={`/meeting/${id}`}
href={`/join/${id}`}
className="bg-blue-5 hover:bg-blue-8 h-12 w-full rounded-sm py-2.5 pt-3 text-center text-lg font-normal text-white transition-colors md:w-90"
>
내 출발지 등록하기
Expand Down
2 changes: 1 addition & 1 deletion components/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const Footer = () => {
</Link>
<button
type="button"
onClick={(e) => openModal('FEEDBACK', e)}
onClick={(e) => openModal('FEEDBACK', undefined, e)}
className="text-gray-7 cursor-pointer text-[16px]"
>
피드백남기기
Expand Down
2 changes: 1 addition & 1 deletion components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const Header = () => {
</Link>
<button
type="button"
onClick={(e) => openModal('FEEDBACK', e)}
onClick={(e) => openModal('FEEDBACK', undefined, e)}
className="text-gray-5 p-2 text-[16px] transition-colors hover:text-gray-900"
>
피드백남기기
Expand Down
6 changes: 3 additions & 3 deletions components/modal/globalModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import NudgeModal from '@/components/modal/nudgeModal';
import TransferModal from './transferModal';

export default function GlobalModal() {
const { type, isOpen, onClose } = useModalStore();
const { type, isOpen, onClose, data } = useModalStore();

// 닫혀있거나 타입이 없으면 모달 X
if (!isOpen || !type) return null;
Expand All @@ -17,9 +17,9 @@ export default function GlobalModal() {
case 'FEEDBACK':
return <FeedbackModal isOpen={isOpen} onClose={onClose} />;
case 'SHARE':
return <ShareModal isOpen={isOpen} onClose={onClose} />;
return <ShareModal isOpen={isOpen} onClose={onClose} meetingId={data?.meetingId || ''} />;
case 'NUDGE':
return <NudgeModal isOpen={isOpen} onClose={onClose} />;
return <NudgeModal isOpen={isOpen} onClose={onClose} meetingId={data?.meetingId || ''} />;
case 'TRANSFER':
return <TransferModal isOpen={isOpen} onClose={onClose} />;
default:
Expand Down
27 changes: 12 additions & 15 deletions components/modal/nudgeModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,23 @@ import {
DialogFooter,
DialogDescription,
} from '@/components/ui/dialog';
import { useState } from 'react';
import { useToast } from '@/hooks/useToast';
import Toast from '../ui/toast';
import { useShareMeeting } from '@/hooks/api/query/useShareMeeting';

interface NudgeModalProps {
isOpen: boolean;
onClose: () => void;
meetingId: string;
}

export default function NudgeModal({ isOpen, onClose }: NudgeModalProps) {
const [link, setLink] = useState('www.abcabc');
const { isVisible, show } = useToast();
export default function NudgeModal({ isOpen, onClose, meetingId }: NudgeModalProps) {
const { shareUrl, isError, isLoading, handleCopyLink, isVisible } = useShareMeeting(meetingId);

const handleCopyLink = async () => {
try {
await navigator.clipboard.writeText(link);
show();
} catch (error) {
console.error('복사 실패: ', error);
alert('복사에 실패했습니다. 링크를 직접 복사해주세요.');
}
};
const displayValue = isError
? '유효하지 않은 모임입니다.'
: isLoading
? '링크 생성 중...'
: shareUrl;

return (
<Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
Expand Down Expand Up @@ -60,13 +55,15 @@ export default function NudgeModal({ isOpen, onClose }: NudgeModalProps) {
type="text"
name="NudgeLink"
aria-label="모임 공유 링크"
value={link}
value={displayValue}
readOnly
disabled={isLoading || isError}
className="border-gray-1 grow rounded-l-sm border border-r-0 bg-white p-2.5 text-[15px] font-normal text-black focus:outline-none"
/>
<button
type="button"
onClick={handleCopyLink}
disabled={isLoading || isError || !shareUrl}
className="bg-gray-1 text-gray-6 border-gray-1 hover:bg-gray-2 cursor-pointer rounded-r-sm border px-3.5 py-3 text-sm font-semibold whitespace-nowrap transition-colors"
>
복사
Expand Down
Loading
Loading