Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | frontend/src/screens/AcceptInvite.tsx |
| Original SHA-256 | 45a475c6813aa360ecc8132138e5c9387fa8c2ad58e985d163641faa2da0c475 |
| Git blob SHA | c76c649ae9fc91a15fd9a9866bb754a4402acf50 |
| Reading-copy SHA-256 | 45a475c6813aa360ecc8132138e5c9387fa8c2ad58e985d163641faa2da0c475 |
Every recorded occurrence
These 35 records contain identical original source bytes. Their paths and revisions remain part of the archive.
| Original record | Branch or archive | Revision / identifier | Original location |
|---|---|---|---|
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | feature/opportunity-to-outcome | 720d47fae476b0d38477ae4401697d372b1ea2bc | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | per-tenant-routing | 9bde0f9a74b89d01980fa658e5253acda7528d0c | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | collision-deconfliction | 4ee5a09972608ed89f8377c6665542d5f450f0a6 | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | ws6-retrieval | 6c56e29231ec05735927fa265c97d517499dfeaf | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | workstream-a-shared-core | e0de03ac3a1ec826bd15d0e048e3b02baa23eeff | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | m5-price-to-win | 41000ad30fff4129fdaf5ec84e15ba14c27d4890 | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | workstream-c-slatematch | 5cd60c918301b46ecd6ed3e03e20c395da0062e2 | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | ws1-ontology-sync | b596f449a32a1016fbd403c36505cc00a1ac9ee6 | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | master | 1e9249840a4302ec82e123b9c9cb6f75badea3e4 | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | ws8-teaching-loop | c823f0fb6bb2aba0f39001eb6de96f4000d9321f | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | w3-ai-staffing-recommendations | 43d56387270ef4da4cabd794d97ca6a49909bc7d | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | mag-substrate | b82c7100a492dec67dfd1eda6b3f53def859be35 | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | ws3-clearance | 4b8e7ce594c068621867f9c9deed0ec64ae6c553 | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | workstream-d-command-center | 255e7d1d420abf5e277d0cd8c9b14f6f72b6acb9 | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | phase2-eval-fairness | d361af254701975fe239927656ababb56374655e | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | workstream-e-dataflow-deploy | 433815a042f9741411e60fa553fe72143e9b10db | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | ruthless-debug-sweep | b7d742f8f032984be3817efffa3eb8b13b651c1f | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | time-to-fill-survival | b66e1e505863d4f17741a4b9ede73509e45bd6c6 | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | ai-intelligence-layer | 5d5ddff70393a197c5f54a45280bb5dd42b24755 | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | graphrag-foundation | b0ddeb9f633fd138d09309d1a17b7d13bc3a67f7 | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | workstream-a-opportunity-intelligence | 1675031db74316e100782d063e9eda1a597fe3a1 | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | deploy-and-ops | bdacd9d0b4d5998d25fc74bf4007f4c3ec538c7a | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | ws9-bench-ingestion | f585a9ef32fa554663ce1fba9a414a54b6e49cf0 | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | ws2-alias-relationship | e4dada4601c6eb101830f9d223a9655dfa120616 | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | presentation-readiness | eb574acaa29d71ce4d0f852572e87c275c79fdb7 | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | vendor-intelligence | fe9796a19c261951d3f2565206c0c1b5ac294465 | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | workstream-b-screenmatch | 09015496d565a172bcaa15cfe9a8e4b072b13df0 | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | ws7-federal-signal | 6a9f6ce9cfb13345ffb47770d4960e7c2d9c17c2 | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | workstream-e-commercial-stubs | 4437da660539284a400ee380b9b427a300c369ee | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | ai-brain-live | aac32fc49c878cc5bca099459307611562d0bac1 | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | workstream-d-missionmatch | 3be46ba89edb737182075a2b320509b42dc916f8 | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | phase2-connectors | e038d4c687aec16f8bd9bc4d907eea14d91f8704 | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | workstream-b-sam-contract-awards | d5101cceb98e70000cebb31d3e7ac73d747d3a6d | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | feature/aioe-upgrade | 2178e7298d0e5d9af83afa12402fd587f0ba6ed8 | Open original |
| Janady13/missionmatch / frontend/src/screens/AcceptInvite.tsx | capture-readiness | bae6af7cb2997e819174a9e9cfff5e16067a1975 | Open original |
Recovered source transcript
Download complete source text ↓158 lines · 5.3 KB
import { useState } from 'react'
import { useNavigate, useSearchParams } from 'react-router-dom'
import { Loader2, ShieldCheck } from 'lucide-react'
import { authApi, type EnrollResult } from '../lib/auth'
import { useAuth } from '../lib/useAuth'
type Step = 'set' | 'enroll'
// AcceptInvite handles an authority bounded invite link (/accept?token=...): set a password, then enroll
// the mandatory TOTP. On an active session it navigates to the app.
export function AcceptInvite() {
const { refresh } = useAuth()
const nav = useNavigate()
const [params] = useSearchParams()
const token = params.get('token') ?? ''
const [step, setStep] = useState<Step>('set')
const [password, setPassword] = useState('')
const [confirm, setConfirm] = useState('')
const [code, setCode] = useState('')
const [enroll, setEnroll] = useState<EnrollResult | null>(null)
const [err, setErr] = useState('')
const [busy, setBusy] = useState(false)
async function onSet(e: React.FormEvent) {
e.preventDefault()
setErr('')
if (password.length < 8) {
setErr('Use at least 8 characters.')
return
}
if (password !== confirm) {
setErr('Passwords do not match.')
return
}
setBusy(true)
try {
const r = await authApi.inviteAccept(token, password)
if (!r.mfa_required) {
// MFA is disabled for this pilot: the account is active after setting a password, go straight in.
await refresh()
nav('/')
return
}
setEnroll(await authApi.mfaEnroll())
setStep('enroll')
} catch {
setErr('This invite link is invalid or expired.')
} finally {
setBusy(false)
}
}
async function onActivate(e: React.FormEvent) {
e.preventDefault()
setErr('')
setBusy(true)
try {
await authApi.mfaActivate(code.trim())
await refresh()
nav('/')
} catch {
setErr('Invalid code. Make sure your authenticator time is correct.')
setBusy(false)
}
}
if (!token) {
return (
<div className="grid min-h-screen place-items-center bg-navy-950 p-4 text-ink">
<p className="text-sm text-risk">This invite link is missing its token.</p>
</div>
)
}
return (
<div className="grid min-h-screen place-items-center bg-navy-950 p-4 text-ink">
<div className="w-full max-w-sm rounded-xl border border-line/70 bg-surface p-6 shadow-2xl">
<div className="mb-5 flex items-center gap-2">
<ShieldCheck className="text-gold" size={22} />
<div className="text-lg font-extrabold tracking-tight">missionMATCH</div>
</div>
{step === 'set' && (
<form onSubmit={onSet} className="space-y-3">
<h1 className="text-sm font-bold">Accept your invitation</h1>
<p className="text-xs text-muted">Set a password to create your account.</p>
<Input label="Password" type="password" value={password} onChange={setPassword} autoFocus maxLength={64} />
<Input label="Confirm password" type="password" value={confirm} onChange={setConfirm} maxLength={64} />
{err && <p className="text-sm text-risk">{err}</p>}
<Go busy={busy} label="Continue" />
</form>
)}
{step === 'enroll' && enroll && (
<form onSubmit={onActivate} className="space-y-3">
<h1 className="text-sm font-bold">Set up two factor authentication</h1>
<p className="text-xs text-muted">Add this account to your authenticator app, then enter the code.</p>
<div className="rounded-lg bg-navy-950/60 p-3">
<div className="text-[11px] uppercase tracking-wider text-faint">Setup key</div>
<div className="mt-1 break-all font-mono text-sm text-blue-soft">{enroll.secret}</div>
<a href={enroll.otpauth_uri} className="mt-2 inline-block text-[11px] text-muted underline">
Open in authenticator
</a>
</div>
<Input label="Authentication code" value={code} onChange={setCode} autoFocus />
{err && <p className="text-sm text-risk">{err}</p>}
<Go busy={busy} label="Activate and sign in" />
</form>
)}
</div>
</div>
)
}
function Input({
label,
value,
onChange,
type = 'text',
autoFocus,
maxLength,
}: {
label: string
value: string
onChange: (v: string) => void
type?: string
autoFocus?: boolean
maxLength?: number
}) {
return (
<label className="block">
<span className="mb-1 block text-[11px] font-semibold uppercase tracking-wider text-muted">{label}</span>
<input
type={type}
value={value}
autoFocus={autoFocus}
maxLength={maxLength}
onChange={(e) => onChange(e.target.value)}
className="w-full rounded-lg border border-line/70 bg-navy-950/60 px-3 py-2 text-sm text-ink outline-none focus:border-blue/60"
/>
</label>
)
}
function Go({ busy, label }: { busy: boolean; label: string }) {
return (
<button
type="submit"
disabled={busy}
className="flex w-full items-center justify-center gap-2 rounded-lg bg-gradient-to-r from-blue to-navy-700 px-4 py-2 text-sm font-bold text-white hover:brightness-110 disabled:opacity-50"
>
{busy ? <Loader2 className="animate-spin" size={15} /> : <ShieldCheck size={15} />}
{label}
</button>
)
}