chih.yikuan Claude Opus 4.6 commited on
Commit
459bded
·
1 Parent(s): bc45e54

Add invite code gate page before login

Browse files

Users must enter "taboola-npo-cz" to access the app. Code is saved in localStorage so it only needs to be entered once per browser.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

chatkit/frontend/src/App.tsx CHANGED
@@ -1,5 +1,6 @@
1
  import { useState, useEffect, useCallback } from "react";
2
  import { Header } from "./components/Header";
 
3
  import { LoginForm } from "./components/LoginForm";
4
  import { StepIndicator } from "./components/StepIndicator";
5
  import { FileUploadPanel } from "./components/step1/FileUploadPanel";
@@ -8,6 +9,8 @@ import { PromptEditor } from "./components/step2/PromptEditor";
8
  import { ReportViewer } from "./components/step3/ReportViewer";
9
  import { apiGet, apiPost, getToken, clearToken } from "./lib/api";
10
 
 
 
11
  interface User {
12
  id: number;
13
  email: string;
@@ -15,6 +18,7 @@ interface User {
15
  }
16
 
17
  export default function App() {
 
18
  const [user, setUser] = useState<User | null>(null);
19
  const [loading, setLoading] = useState(true);
20
  const [currentStep, setCurrentStep] = useState<1 | 2 | 3>(1);
@@ -131,6 +135,19 @@ export default function App() {
131
  );
132
  }
133
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  // Login gate
135
  if (!user) {
136
  return <LoginForm onLogin={setUser} />;
 
1
  import { useState, useEffect, useCallback } from "react";
2
  import { Header } from "./components/Header";
3
+ import { InviteGate } from "./components/InviteGate";
4
  import { LoginForm } from "./components/LoginForm";
5
  import { StepIndicator } from "./components/StepIndicator";
6
  import { FileUploadPanel } from "./components/step1/FileUploadPanel";
 
9
  import { ReportViewer } from "./components/step3/ReportViewer";
10
  import { apiGet, apiPost, getToken, clearToken } from "./lib/api";
11
 
12
+ const INVITE_CODE = "taboola-npo-cz";
13
+
14
  interface User {
15
  id: number;
16
  email: string;
 
18
  }
19
 
20
  export default function App() {
21
+ const [invited, setInvited] = useState(() => localStorage.getItem("invite_code") === INVITE_CODE);
22
  const [user, setUser] = useState<User | null>(null);
23
  const [loading, setLoading] = useState(true);
24
  const [currentStep, setCurrentStep] = useState<1 | 2 | 3>(1);
 
135
  );
136
  }
137
 
138
+ // Invite code gate
139
+ if (!invited) {
140
+ return (
141
+ <InviteGate
142
+ onSuccess={() => {
143
+ localStorage.setItem("invite_code", INVITE_CODE);
144
+ setInvited(true);
145
+ }}
146
+ correctCode={INVITE_CODE}
147
+ />
148
+ );
149
+ }
150
+
151
  // Login gate
152
  if (!user) {
153
  return <LoginForm onLogin={setUser} />;
chatkit/frontend/src/components/InviteGate.tsx ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from "react";
2
+ import { CLASSLENS_ICON } from "../lib/icon";
3
+
4
+ interface InviteGateProps {
5
+ onSuccess: () => void;
6
+ correctCode: string;
7
+ }
8
+
9
+ export function InviteGate({ onSuccess, correctCode }: InviteGateProps) {
10
+ const [code, setCode] = useState("");
11
+ const [error, setError] = useState("");
12
+
13
+ const handleSubmit = (e: React.FormEvent) => {
14
+ e.preventDefault();
15
+ if (code.trim() === correctCode) {
16
+ onSuccess();
17
+ } else {
18
+ setError("邀請碼不正確,請再試一次");
19
+ }
20
+ };
21
+
22
+ return (
23
+ <div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-[#0f1419] via-[#1a2332] to-[#0f1419]">
24
+ <div className="relative">
25
+ <div className="absolute -top-20 -left-20 w-40 h-40 bg-[var(--color-primary)]/20 rounded-full blur-3xl" />
26
+ <div className="absolute -bottom-20 -right-20 w-40 h-40 bg-[var(--color-accent)]/20 rounded-full blur-3xl" />
27
+
28
+ <div className="relative bg-[var(--color-surface)] border border-[var(--color-border)] rounded-2xl p-8 max-w-md w-full shadow-2xl">
29
+ <div className="text-center mb-8">
30
+ <img
31
+ src={CLASSLENS_ICON}
32
+ alt="ClassLens"
33
+ className="w-24 h-24 mx-auto mb-4 rounded-2xl shadow-lg"
34
+ />
35
+ <h1 className="text-2xl font-bold text-[var(--color-text)] font-display">
36
+ ClassLens
37
+ </h1>
38
+ <p className="text-[var(--color-text-muted)] mt-2">
39
+ 請輸入邀請碼以繼續
40
+ </p>
41
+ </div>
42
+
43
+ <form onSubmit={handleSubmit} className="space-y-4">
44
+ <div>
45
+ <label className="block text-sm font-medium text-[var(--color-text-muted)] mb-2">
46
+ 邀請碼
47
+ </label>
48
+ <input
49
+ type="text"
50
+ value={code}
51
+ onChange={(e) => { setCode(e.target.value); setError(""); }}
52
+ placeholder="請輸入邀請碼..."
53
+ required
54
+ className="input text-center tracking-widest"
55
+ autoFocus
56
+ />
57
+ </div>
58
+
59
+ {error && (
60
+ <p className="text-sm text-red-400 text-center flex items-center justify-center gap-1">
61
+ <svg className="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
62
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
63
+ </svg>
64
+ {error}
65
+ </p>
66
+ )}
67
+
68
+ <button
69
+ type="submit"
70
+ className="w-full py-3 px-4 bg-gradient-to-r from-[var(--color-primary)] to-[var(--color-accent)] text-white font-semibold rounded-xl hover:opacity-90 transition-opacity"
71
+ >
72
+ 進入
73
+ </button>
74
+ </form>
75
+ </div>
76
+ </div>
77
+ </div>
78
+ );
79
+ }