page.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. 'use client';
  2. import { useEffect, useState } from 'react';
  3. import { fetchApi } from '@/lib/utils/client';
  4. import { Input } from '@/components/ui/input';
  5. import { Label } from '@/components/ui/label';
  6. import { Button } from '@/components/ui/button';
  7. import type { StudioSettingsResponse } from '@/types/response/studio/settings';
  8. const CODE_PATTERN = /^[A-Za-z0-9]{4,7}$/;
  9. type IssueResponse = {
  10. donationCode: string;
  11. };
  12. export default function StudioDonationCodePage()
  13. {
  14. const [loading, setLoading] = useState(true);
  15. const [existingCode, setExistingCode] = useState<string|null>(null);
  16. const [input, setInput] = useState('');
  17. const [submitting, setSubmitting] = useState(false);
  18. const [error, setError] = useState<string|null>(null);
  19. useEffect(() => {
  20. fetchApi<StudioSettingsResponse>('/api/studio/settings')
  21. .then(res => {
  22. if (res.data) {
  23. setExistingCode(res.data.donationCode);
  24. }
  25. })
  26. .catch(() => {})
  27. .finally(() => {
  28. setLoading(false);
  29. });
  30. }, []);
  31. const handleSubmit = async (e: React.FormEvent) => {
  32. e.preventDefault();
  33. const trimmed = input.trim();
  34. if (!CODE_PATTERN.test(trimmed)) {
  35. setError('후원 코드는 4~7자 영문/숫자만 가능합니다.');
  36. return;
  37. }
  38. const normalized = trimmed.toUpperCase();
  39. const ok = window.confirm(`후원 코드 "${normalized}" 로 등록합니다.\n한 번 등록하면 변경할 수 없습니다. 진행하시겠습니까?`);
  40. if (!ok) {
  41. return;
  42. }
  43. setSubmitting(true);
  44. setError(null);
  45. const res = await fetchApi<IssueResponse>('/api/studio/donation-code', {
  46. method: 'POST',
  47. body: { code: normalized },
  48. silent: true
  49. });
  50. if (res.success && res.data) {
  51. setExistingCode(res.data.donationCode);
  52. setInput('');
  53. window.alert(`후원 코드 "${res.data.donationCode}" 가 정상적으로 등록되었습니다.`);
  54. }
  55. else {
  56. setError(res.message || '후원 코드 발급에 실패했습니다.');
  57. }
  58. setSubmitting(false);
  59. };
  60. if (loading) {
  61. return (
  62. <div className="studio-page">
  63. <p className="text-sm text-muted-foreground">불러오는 중...</p>
  64. </div>
  65. );
  66. }
  67. return (
  68. <div className="studio-page">
  69. <div className="mb-6">
  70. <h1 className="text-2xl font-bold mb-3">후원 코드</h1>
  71. <p className="mt-1 text-sm text-muted-foreground leading-relaxed">
  72. 후원 코드는 시청자가 후원 채널을 빠르게 찾도록 도와줍니다. 4~7자 영문/숫자만 가능하며, 대소문자 구분 없이 대문자로 저장됩니다.
  73. <br />
  74. 한 번 등록하면 <span className="font-semibold text-foreground">변경할 수 없습니다.</span>
  75. </p>
  76. </div>
  77. {existingCode !== null ? (
  78. <div className="max-w-md rounded-md border border-input bg-background p-5">
  79. <Label htmlFor="code-readonly" className="text-sm">
  80. 등록된 후원 코드
  81. </Label>
  82. <Input
  83. id="code-readonly"
  84. value={existingCode}
  85. readOnly
  86. className="mt-2 font-mono text-base tracking-widest"
  87. />
  88. <p className="mt-2 text-xs text-muted-foreground">
  89. * 이미 등록되어 변경할 수 없습니다.
  90. </p>
  91. </div>
  92. ) : (
  93. <form onSubmit={handleSubmit} className="max-w-md space-y-4 rounded-md border border-input bg-background p-5">
  94. <div>
  95. <Label htmlFor="code" className="text-sm">
  96. 후원 코드
  97. </Label>
  98. <Input
  99. id="code"
  100. value={input}
  101. onChange={(e) => {
  102. setInput(e.target.value);
  103. setError(null);
  104. }}
  105. placeholder="예: ABC123"
  106. maxLength={7}
  107. autoComplete="off"
  108. spellCheck={false}
  109. className="mt-2 font-mono text-base tracking-widest uppercase"
  110. disabled={submitting}
  111. />
  112. <p className="mt-1 text-xs text-muted-foreground">
  113. * 4~7자 영문/숫자만 사용 가능. 특수문자·한글 불가.
  114. </p>
  115. </div>
  116. {error && (
  117. <p className="text-xs text-destructive">{error}</p>
  118. )}
  119. <Button
  120. type="submit"
  121. disabled={submitting || !CODE_PATTERN.test(input.trim())}
  122. className="w-full"
  123. >
  124. {submitting ? '등록 중...' : '등록하기'}
  125. </Button>
  126. </form>
  127. )}
  128. </div>
  129. );
  130. }