반응형
https://www.youtube.com/watch?v=Av9C7xlV0fA
07:35:14
* "Join Workspace" 버튼을 누르는 부분까지만 작업
1. 초대 코드 생성 API 엔드포인트
- reset-invite-code POST 메서드
src/features/workspaces/server/route.ts
- ADMIN 권한 확인 → 초대 코드 생성은 관리자만 가능
- generateInviteCode(6): 임의의 6자리 코드 생성
- 기존 초대 코드는 무효화되고 새로운 코드로 업데이트
- 이전 코드로 가입한 사람들은 더 이상 초대 링크 사용 불가
.post(
"/:workspaceId/reset-invite-code",
sessionMiddleware,
async (c) => {
const databases = c.get("databases");
const user = c.get("user");
const {workspaceId} = c.req.param();
// 1. 권한 확인 (ADMIN만 가능)
const member = await getMember({
databases,
workspaceId,
userId: user.$id,
})
if(!member || member.role !== MemberRole.ADMIN){
return c.json({error:"Unauthorized"}, 401)
}
// 2. 새로운 초대 코드 생성
const workspace = await databases.updateDocument(
DATABASE_ID,
WORKSPACES_ID,
workspaceId,
{
inviteCode: generateInviteCode(6)
}
)
return c.json({data:workspace});
}
)
2. 클라이언트 훅 - useResetInviteCode
src/features/workspaces/api/use-reset-invite-code.ts
- 초대 코드 재설정 요청 처리
- 성공 시 두 가지 캐시 무효화 (전체 워크스페이스 + 특정 워크스페이스)
- 캐시가 무효화되면 UI 자동 갱신
import { toast } from 'sonner';
import { InferRequestType, InferResponseType } from 'hono';
import {useMutation, useQueryClient} from '@tanstack/react-query'
import {client} from '@/lib/rpc'
type ResponseType = InferResponseType<typeof client.api.workspaces[':workspaceId']["reset-invite-code"]['$post'], 200>
type RequestType = InferRequestType<typeof client.api.workspaces[':workspaceId']["reset-invite-code"]['$post']>
export const useResetInviteCode = () => {
const queryClient = useQueryClient()
const mutation = useMutation<
ResponseType,
Error,
RequestType
>({
mutationFn: async({param}) => {
const response = await client.api.workspaces[':workspaceId']["reset-invite-code"]['$post']({param});
if(!response.ok){
throw new Error("Failed to reset invite code")
}
return await response.json()
},
onSuccess: ({data}) => {
toast.success("Invite code reset")
queryClient.invalidateQueries({queryKey : ['workspaces']})
queryClient.invalidateQueries({queryKey : ['workspace', data.$id]})
},
onError:()=>{
toast.error("Failed to reset invite code")
}
})
return mutation
}
3. EditWorkspaceForm에 초대 기능 통합
src/features/workspaces/component/edit-workspace-form.tsx
url 구성
- window.location.origin: 현재 도메인 (http://localhost:3000)
- /workspaces/{workspaceId}: 워크스페이스 ID
- /join/{inviteCode}: 초대 코드
초대 링크 복사 기능
- navigator.clipboard API 사용
'use client'
... 생략
interface EditWorkspaceFormProps{
onCancel?: () => void;
initialValues: Workspace
}
export const EditWorkspaceForm = ({onCancel, initialValues} : EditWorkspaceFormProps) =>{
const router = useRouter()
const {mutate: resetInviteCode, isPending:isResettingInviteCode} = useResetInviteCode()
... 생략
// ✅ 초대 코드 재설정 확인 다이얼로그
const [ResetDialog, confirmReset] = useConfirm(
"Reset invite link",
"This will invalidate the current invite link",
"destructive"
)
// 초대 링크 재설정 핸들러
const handleResetInviteCode = async () => {
const ok = await confirmReset();
if(!ok) return;
resetInviteCode({
param: {workspaceId:initialValues.$id},
},{
onSuccess: () => {
router.refresh() // 페이지 새로고침하여 새 코드 반영
}
})
}
// ✅ 초대 링크 생성
const fullInviteLink = `${window.location.origin}/workspaces/${initialValues.$id}/join/${initialValues.inviteCode}`
// 클립보드에 복사
const handleCopyInviteLink =() => {
navigator.clipboard.writeText(fullInviteLink)
.then(()=> toast.success("Invite link copied to clipboard"))
}
return(
<div className="flex flex-col gap-y-4">
<DeleteDialog/>
// 추가
<ResetDialog/>
<Card className="w-full h-full border-none shadow-none">
</Card>
{/* 초대 멤버 카드 */}
<Card className="w-full h-full border-none shadow-none">
<CardContent className="p-7">
<div className="flex flex-col">
<h3 className="font-bold">Invite Members</h3>
<p className="text-sm text-muted-foreground">
초대 링크를 사용하여 작업 공간에 멤버를 추가하세요
</p>
{/* 초대 링크 표시 및 복사 */}
<div className="mt-4">
<div className="flex items-center gap-x-2">
<Input disabled value={fullInviteLink}/>
<Button
onClick={handleCopyInviteLink}
variant="secondary"
className="size-12"
>
<CopyIcon className="size-5"/>
</Button>
</div>
</div>
<DottedSeparator className="py-7"/>
{/* 초대 링크 재설정 버튼 */}
<Button
className="mt-6 w-fit ml-auto"
size="sm"
variant="destructive"
type="button"
disabled={isPending || isResettingInviteCode}
onClick={handleResetInviteCode}
>
Reset invite link
</Button>
</div>
</CardContent>
</Card>
<Card className="w-full h-full border-none shadow-none">
</Card>
</div>
)
}
핵심 정리
POST 엔드포인트: 권한 확인 후 초대 코드 재생성 (API에서 특정 기능을 수행하는 URL 주소)
useResetInviteCode 훅: 초대 코드 재설정 요청 처리
초대 링크 생성: 워크스페이스 ID + 초대 코드 조합
클립보드 복사: navigator.clipboard API 활용
확인 다이얼로그: 실수로 인한 재설정 방지
router.refresh(): 최소한의 데이터만 새로고침
ADMIN 권한: 초대 기능은 관리자만 사용 가능
UI 구분: destructive 버튼으로 비가역적 작업 표시
반응형
'Clone Coding' 카테고리의 다른 글
| Jira-clone - 멤버 api 빌드 (0) | 2025.10.17 |
|---|---|
| Jira-clone - 워크스페이스 초대 링크 기능 (0) | 2025.10.12 |
| Jira-clone - 워크스페이스 삭제(Delete) 기능 구현 (0) | 2025.10.12 |
| Jira-clone - 서버 쿼리 리팩토링 (0) | 2025.10.11 |
| Jira-clone - 워크스페이스 수정(Update) 기능 구현 (0) | 2025.10.10 |