// EvaluationSOC2.jsx — Verigo Global: SOC 2 online readiness evaluation (live, gated, resumable)
const { V: EV, MAXW: EMW, FONT: EFT } = window;
/* ── PROGRESS PERSISTENCE (localStorage) ─────────────────── */
const PROG_KEY = 'verigo_soc2_eval';
const loadProg = () => { try { return JSON.parse(localStorage.getItem(PROG_KEY) || 'null'); } catch (e) { return null; } };
const saveProg = (p) => { try { localStorage.setItem(PROG_KEY, JSON.stringify(p)); } catch (e) {} };
const clearProg = () => { try { localStorage.removeItem(PROG_KEY); } catch (e) {} };
const sPreviewThemes = [
['lock', 'CC6 · Access', 54],
['refresh', 'CC7 · Operations', 66],
['gauge', 'Availability', 78],
['users', 'Privacy', 46],
];
/* ── RESUME BANNER ───────────────────────────────────────── */
const SResumeBanner = ({ prog, onResume, onStartOver }) => {
const done = prog.stage === 'results';
const when = prog.ts ? new Date(prog.ts).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) + ' · ' + new Date(prog.ts).toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' }) : '';
return (
{done ? 'Your last evaluation is complete and saved.' : 'You have an evaluation in progress.'}
{prog.scope && prog.scope.company ? prog.scope.company + ' · ' : ''}{when ? 'Saved ' + when : 'Pick up where you left off.'}
{done ? 'View saved results' : 'Resume evaluation'}
Start over
);
};
/* ── PREVIEW DASHBOARD (illustrative, intro only) ────────── */
const SScorePreview = () => (
Overall maturity
Established
18 of 61 criteria need work before the examination.
{sPreviewThemes.map(([ic, name, pct], i) => (
))}
);
/* ── INTRO STAGE ─────────────────────────────────────────── */
const SEvalIntro = ({ onStart, onNav, resume, onResume, onStartOver }) => {
const steps = [
['clipboard', 'Self-assessment', 'Answer ~30 statements mapped to specific SOC 2 criteria — across the CC1–CC9 Common Criteria and every optional category you put in scope. About 12 minutes, no prep needed.'],
['gauge', 'Instant scoring', 'Get a weighted maturity score with a category-by-category breakdown showing exactly where you stand.'],
['flag', 'Mapped action plan', 'Every gap links straight to the toolkit document, policy, or procedure that closes it — so you know the next move.'],
];
return (
{resume &&
}
onNav('toolkit:soc2')} style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#7A7A8A', fontSize: 13, fontWeight: 600, padding: 0, fontFamily: EFT }}>SOC 2 Toolkit
/
Online Evaluation
~30 questions · ~12 minutes · free
Know where you stand on SOC 2.
Measure your current state against the Trust Services Criteria — criterion by criterion across CC1–CC9 and the optional categories you choose to scope in. Get an instant maturity score and a prioritized action plan mapped straight to the toolkit documents that close each gap.
{resume ? 'Start a new evaluation' : 'Start the evaluation'}
onNav('contact')}>Talk to a practitioner
Free and confidential. We'll ask for a few details so we can send your results.
{steps.map(([ic, t, d], i) => (
{String(i + 1).padStart(2, '0')}
{t}
{d}
))}
{resume ? 'Start a new evaluation' : 'Start the evaluation'}
);
};
/* ── SCOPE FORM ──────────────────────────────────────────── */
const SCOPE_FIELDS_SOC2 = {
industry: ['Software / SaaS', 'Financial services', 'Healthcare', 'Manufacturing', 'Professional services', 'Government / public sector', 'Retail / e-commerce', 'Other'],
employees: ['1–50', '51–200', '201–1,000', '1,000+'],
region: ['United States', 'India', 'United Kingdom', 'European Union', 'Singapore', 'Other'],
locations: ['1 site', '2–5 sites', '6–20 sites', '20+ sites'],
target: ['Type I — within 3 months', 'Type II — within 6 months', 'Type II — 6–12 months', '12+ months', 'Just exploring'],
};
// Trust Services categories selectable as in-scope parameters. Security is mandatory.
const CRITERIA_OPTIONS = [
['Security', 'lock', 'The mandatory Common Criteria (CC1–CC9). Always in scope.', true],
['Availability', 'gauge', 'Uptime, capacity, backup and disaster recovery commitments.', false],
['Confidentiality', 'shield', 'Protection of information designated as confidential.', false],
['Processing Integrity', 'check', 'Complete, valid, accurate, timely processing.', false],
['Privacy', 'users', 'Collection, use, retention and disposal of personal data.', false],
];
const evInput = { fontFamily: EFT, fontSize: 14, color: EV.black, background: '#fff', border: `1.5px solid ${EV.g200}`, borderRadius: 8, padding: '11px 14px', outline: 'none', width: '100%' };
const evLabel = { fontSize: 12.5, fontWeight: 600, color: EV.black, display: 'block', marginBottom: 7 };
const evSelect = { ...evInput, appearance: 'none', backgroundImage: "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%239A9AAA' stroke-width='2.5'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\")", backgroundRepeat: 'no-repeat', backgroundPosition: 'right 12px center', paddingRight: 32, cursor: 'pointer' };
const SScopeForm = ({ onSubmit, onBack, saved }) => {
const u = (() => { try { return window.TK_SOC2.getUser() || {}; } catch (e) { return {}; } })();
const sv = saved || {};
const [f, setF] = React.useState({
company: sv.company || u.company || '', name: sv.name || u.name || '', email: sv.email || u.email || '', phone: sv.phone || u.phone || '', role: sv.role || u.role || '',
industry: sv.industry || u.industry || SCOPE_FIELDS_SOC2.industry[0], employees: sv.employees || u.employees || SCOPE_FIELDS_SOC2.employees[0],
region: sv.region || u.region || SCOPE_FIELDS_SOC2.region[0], locations: sv.locations || u.locations || SCOPE_FIELDS_SOC2.locations[0],
systems: sv.systems || u.systems || '', certs: sv.certs || u.certs || '', target: sv.target || u.target || SCOPE_FIELDS_SOC2.target[1],
});
// Optional categories chosen as in scope (Security implicit & mandatory).
const [criteria, setCriteria] = React.useState(() => (saved && Array.isArray(saved.criteria)) ? saved.criteria : []);
const set = (k, v) => setF((p) => ({ ...p, [k]: v }));
const toggleCat = (name) => setCriteria((prev) => prev.indexOf(name) !== -1 ? prev.filter((c) => c !== name) : [...prev, name]);
const text = (k, lbl, props = {}) => {lbl} set(k, e.target.value)} {...props} />
;
const sel = (k, lbl) => {lbl} set(k, e.target.value)}>{SCOPE_FIELDS_SOC2[k].map((o) => {o} )}
;
return (
Step 1 of 2 · Scope your assessment
Tell us about your organization
A few details set the scope and let us tailor your results and quote. Then you’ll move on to the technical evaluation.
);
};
/* ── PAGE ────────────────────────────────────────────────── */
const EvaluationSOC2Page = ({ onNav }) => {
const [stage, setStage] = React.useState('intro'); // intro | scope | quiz | results
const [gate, setGate] = React.useState(false);
const [results, setResults] = React.useState(null);
const [user, setUser] = React.useState(null);
const [scopeData, setScopeData] = React.useState(null); // { form, criteria }
const [resume, setResume] = React.useState(null); // saved progress for the intro banner
const [seed, setSeed] = React.useState(null); // { answers, step } to resume the quiz
React.useEffect(() => {
try { setUser(window.TK_SOC2.getUser()); } catch (e) {}
const p = loadProg();
if (p && p.stage && p.stage !== 'intro') setResume(p);
}, []);
const persist = (patch) => { const base = loadProg() || {}; saveProg({ ...base, ...patch, ts: Date.now() }); };
const contactOk = () => { try { return !!sessionStorage.getItem('verigo_contact_ok'); } catch (e) { return false; } };
const requestStart = () => {
if (contactOk()) { setUser(window.TK_SOC2.getUser()); setStage('scope'); window.scrollTo({ top: 0 }); }
else { setGate(true); }
};
const onGateSubmit = (form) => {
window.TK_SOC2.storeLead({ ...form, source: 'evaluation' });
try { sessionStorage.setItem('verigo_contact_ok', '1'); } catch (e) {}
setUser(window.TK_SOC2.getUser());
setGate(false);
setStage('scope');
window.scrollTo({ top: 0 });
};
const onScopeSubmit = (form) => {
const criteria = Array.isArray(form.criteria) ? form.criteria : [];
window.TK_SOC2.storeLead({ ...form, criteria: ['Security'].concat(criteria).join(', '), source: 'evaluation-scope' });
setUser(window.TK_SOC2.getUser());
setScopeData({ form, criteria });
setSeed({ answers: {}, step: 0 });
saveProg({ stage: 'quiz', scope: form, criteria, answers: {}, step: 0, ts: Date.now() });
setStage('quiz');
window.scrollTo({ top: 0 });
};
const onQuizProgress = (answers, step) => { persist({ stage: 'quiz', answers, step }); };
const onComplete = (res) => { setResults(res); persist({ stage: 'results', results: res }); setStage('results'); window.scrollTo({ top: 0 }); };
const retake = () => { setResults(null); setSeed({ answers: {}, step: 0 }); persist({ stage: 'quiz', answers: {}, step: 0, results: null }); setStage('quiz'); window.scrollTo({ top: 0 }); };
const doResume = () => {
const p = resume; if (!p) return;
if (p.scope) setScopeData({ form: p.scope, criteria: Array.isArray(p.criteria) ? p.criteria : [] });
setSeed({ answers: p.answers || {}, step: p.step || 0 });
if (p.results) setResults(p.results);
setResume(null);
setStage(p.stage === 'results' ? 'results' : 'quiz');
window.scrollTo({ top: 0 });
};
const doStartOver = () => { clearProg(); setResume(null); setResults(null); setScopeData(null); setSeed(null); setStage('intro'); requestStart(); };
const scopeCriteria = scopeData ? scopeData.criteria : [];
return (
{stage === 'intro' && }
{stage === 'scope' && { setStage('intro'); window.scrollTo({ top: 0 }); }} saved={resume && resume.scope ? { ...resume.scope, criteria: resume.criteria } : null} />}
{stage === 'quiz' && { setStage('intro'); window.scrollTo({ top: 0 }); }} />}
{stage === 'results' && results && }
{gate && (
setGate(false)}
onSubmit={onGateSubmit} />
)}
);
};
Object.assign(window, { EvaluationSOC2Page });