()
| 166 | } |
| 167 | |
| 168 | export function App() { |
| 169 | const [config, setConfig] = useState<AppConfig | null>(null); |
| 170 | const [participant, setParticipant] = useState<Participant | null>(null); |
| 171 | const [challenges, setChallenges] = useState<Challenge[]>([]); |
| 172 | const [activeChallengeId, setActiveChallengeId] = useState<string>(""); |
| 173 | const [view, setView] = useState<View>("home"); |
| 174 | const [loading, setLoading] = useState(true); |
| 175 | const [error, setError] = useState(""); |
| 176 | |
| 177 | useEffect(() => { |
| 178 | const params = new URLSearchParams(window.location.search); |
| 179 | const challengeId = params.get("challenge"); |
| 180 | if (challengeId) { |
| 181 | setActiveChallengeId(challengeId); |
| 182 | setView("challenge"); |
| 183 | } |
| 184 | Promise.all([api.config(), api.me(), api.listChallenges()]) |
| 185 | .then(([cfg, me, list]) => { |
| 186 | setConfig(cfg); |
| 187 | setParticipant(me.participant); |
| 188 | setChallenges(list.challenges); |
| 189 | if (!challengeId && list.challenges[0]) { |
| 190 | setActiveChallengeId(list.challenges[0].id); |
| 191 | } |
| 192 | }) |
| 193 | .catch((err) => setError(String(err))) |
| 194 | .finally(() => setLoading(false)); |
| 195 | }, []); |
| 196 | |
| 197 | async function refreshChallenges() { |
| 198 | const list = await api.listChallenges(); |
| 199 | setChallenges(list.challenges); |
| 200 | if (!activeChallengeId && list.challenges[0]) { |
| 201 | setActiveChallengeId(list.challenges[0].id); |
| 202 | } |
| 203 | } |
| 204 | |
| 205 | const activeChallenge = challenges.find((item) => item.id === activeChallengeId); |
| 206 | const challengePage = view === "challenge"; |
| 207 | |
| 208 | return ( |
| 209 | <main className={`appShell ${challengePage ? "challengeShell" : ""}`}> |
| 210 | {!challengePage ? ( |
| 211 | <header className="topBar"> |
| 212 | <button className="brand" onClick={() => setView("home")}> |
| 213 | <span className="brandMark"> |
| 214 | <Compass size={19} /> |
| 215 | </span> |
| 216 | <span> |
| 217 | <strong>点亮挑战 (MapGoGoGo)</strong> |
| 218 | <small>勾选地图区域,一起点亮</small> |
| 219 | </span> |
| 220 | </button> |
| 221 | <nav> |
| 222 | <button className={view === "home" ? "active" : ""} onClick={() => setView("home")}> |
| 223 | 挑战 |
| 224 | </button> |
| 225 | <button className={view === "create" ? "active" : ""} onClick={() => setView("create")}> |
nothing calls this directly
no test coverage detected