page.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. 'use client';
  2. import './style.scss';
  3. import { useState } from 'react';
  4. import { useRouter } from 'next/navigation';
  5. import { fetchApi } from '@/lib/utils/client';
  6. import { Button } from '@/components/ui/button';
  7. import { Input } from '@/components/ui/input';
  8. import { Label } from '@/components/ui/label';
  9. import { Checkbox } from '@/components/ui/checkbox';
  10. export default function CrewJoinPage()
  11. {
  12. const router = useRouter();
  13. const [inviteCode, setInviteCode] = useState('');
  14. const [nickname, setNickname] = useState('');
  15. const [consent, setConsent] = useState(false);
  16. const [processing, setProcessing] = useState(false);
  17. const [done, setDone] = useState<string|null>(null);
  18. const handleJoin = async () => {
  19. if (processing) {
  20. return;
  21. }
  22. if (!inviteCode.trim()) {
  23. alert('초대 코드를 입력해 주세요.');
  24. return;
  25. }
  26. if (!nickname.trim()) {
  27. alert('크루 안에서 사용할 별명을 입력해 주세요.');
  28. return;
  29. }
  30. if (!consent) {
  31. alert('크루 방송 합류 동의가 필요합니다.');
  32. return;
  33. }
  34. setProcessing(true);
  35. try {
  36. const res = await fetchApi<{ crewMemberID: number; crewName: string }>('/api/crew/join', {
  37. method: 'POST',
  38. body: { inviteCode: inviteCode.trim(), nickname: nickname.trim() }
  39. });
  40. setDone(res.data?.crewName ?? '크루');
  41. } catch (err) {
  42. alert(err instanceof Error ? err.message : '가입에 실패했습니다.');
  43. } finally {
  44. setProcessing(false);
  45. }
  46. };
  47. if (done) {
  48. return (
  49. <div className="crew-join-page">
  50. <h1 className="crew-join-page__title">✅ 크루 가입이 완료되었습니다</h1>
  51. <p className="crew-join-page__desc">
  52. <strong>{done}</strong> 크루의 크루원으로 등록되었습니다.<br />
  53. 이제 해당 크리에이터의 크루 방송에 참여하실 수 있습니다.
  54. </p>
  55. <Button type="button" onClick={() => router.push('/')}>홈으로</Button>
  56. </div>
  57. );
  58. }
  59. return (
  60. <div className="crew-join-page">
  61. <h1 className="crew-join-page__title">크루 합류</h1>
  62. <p className="crew-join-page__desc">
  63. 크리에이터로부터 받은 <strong>초대 코드</strong>를 입력해 크루에 합류합니다.<br />
  64. 코드 입력 시 해당 크리에이터의 <strong>크루 방송 합류에 동의</strong>하는 것으로 처리됩니다.
  65. </p>
  66. <div className="crew-join-page__form">
  67. <div className="crew-join-page__field">
  68. <Label htmlFor="invite-code">초대 코드</Label>
  69. <Input
  70. id="invite-code"
  71. value={inviteCode}
  72. onChange={e => setInviteCode(e.target.value)}
  73. placeholder="발급받은 초대 코드를 붙여 넣어 주세요"
  74. autoComplete="off"
  75. />
  76. </div>
  77. <div className="crew-join-page__field">
  78. <Label htmlFor="nickname">크루 내 별명</Label>
  79. <Input
  80. id="nickname"
  81. value={nickname}
  82. onChange={e => setNickname(e.target.value)}
  83. placeholder="크루 안에서 사용할 별명"
  84. maxLength={80}
  85. />
  86. </div>
  87. <label className="crew-join-page__consent" htmlFor="consent">
  88. <Checkbox id="consent" checked={consent} onCheckedChange={v => setConsent(!!v)} />
  89. <span>크루 방송 합류에 동의합니다.</span>
  90. </label>
  91. </div>
  92. <div className="crew-join-page__actions">
  93. <Button type="button" variant="outline" onClick={() => router.push('/')}>취소</Button>
  94. <Button type="button" onClick={handleJoin} disabled={processing}>
  95. {processing ? '처리 중...' : '합류'}
  96. </Button>
  97. </div>
  98. </div>
  99. );
  100. }