// DSO Portal product mockups — reusable React components rendered as inline SVG/HTML
// Styled to match ImmiHub DS. All are static visual mockups, not interactive.
const mockupCSS = `
.pm { font-family: var(--font-sans); color: var(--fg-1); font-size: 12px; }
.pm-app { display: grid; grid-template-columns: 200px 1fr; background: var(--bg-page); min-height: 560px; }
.pm-side { background: #fff; border-right: 1px solid var(--border-subtle); padding: 18px 14px; }
.pm-brand { display:flex; align-items:center; gap:8px; padding: 4px 8px 18px; border-bottom: 1px solid var(--border-subtle); margin-bottom: 14px; }
.pm-brand-plane { width: 22px; height:22px; background: var(--im-blue); border-radius: 6px; display:flex; align-items:center; justify-content:center; color:#fff; }
.pm-brand-wordmark { font-weight:600; font-size:14px; color: var(--fg-1); letter-spacing:-0.01em; }
.pm-brand-lockup { font-family: var(--font-serif); font-style: italic; font-size: 10.5px; color: var(--fg-3); padding-left: 6px; border-left: 1px solid var(--border-subtle); margin-left: 6px; }
.pm-nav { display:grid; gap:2px; }
.pm-nav-item { padding: 7px 10px; font-size: 12px; color: var(--fg-2); border-radius: 6px; display:flex; align-items:center; gap:8px; cursor: default; }
.pm-nav-item.on { background: var(--im-blue-ice); color: var(--im-blue-deep); font-weight: 500; }
.pm-nav-ico { width:14px; height: 14px; opacity: 0.9; }
.pm-nav-section { font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--fg-3); padding: 14px 10px 6px; font-weight: 500; }
.pm-main { padding: 20px 24px; overflow: hidden; }
.pm-topbar { display:flex; align-items:center; justify-content: space-between; margin-bottom: 20px; }
.pm-title { font-family: var(--font-serif); font-size: 20px; font-weight: 500; color: var(--fg-1); letter-spacing: -0.01em; }
.pm-crumb { font-size: 11px; color: var(--fg-3); margin-bottom: 4px; }
.pm-search { background: var(--bg-alt); border-radius: 6px; padding: 6px 12px; font-size: 11px; color: var(--fg-3); width: 220px; }
.pm-avatar { width: 26px; height: 26px; border-radius: 50%; background: var(--im-blue); color:#fff; font-size: 10px; font-weight:500; display:flex; align-items:center; justify-content:center; }
.pm-kpis { display:grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 18px; }
.pm-kpi { background: #fff; border:1px solid var(--border-subtle); border-radius: 10px; padding: 14px; }
.pm-kpi-label { font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--fg-3); font-weight:500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pm-kpi-num { font-family: var(--font-serif); font-size: 28px; font-weight: 500; color: var(--fg-1); letter-spacing: -0.02em; margin-top: 4px; }
.pm-kpi-delta { font-size: 10px; color: var(--fg-3); margin-top: 4px; }
.pm-kpi-delta.up { color: var(--im-status-danger-fg); }
.pm-card { background: #fff; border: 1px solid var(--border-subtle); border-radius: 10px; padding: 16px; }
.pm-card h4 { margin: 0 0 10px; font-family: var(--font-serif); font-size: 14px; font-weight: 500; color: var(--fg-1); letter-spacing: -0.01em; }
.pm-two-col { display: grid; grid-template-columns: 1.3fr 1fr; gap: 12px; }
.pm-pill { display:inline-flex; align-items:center; gap:4px; padding: 2px 8px; border-radius: 999px; font-size: 10px; font-weight:500; }
.pm-pill.crit { background: var(--im-status-danger-bg); color: var(--im-status-danger-fg); }
.pm-pill.warn { background: var(--im-status-warn-bg); color: var(--im-status-warn-fg); }
.pm-pill.ok { background: var(--im-status-valid-bg); color: var(--im-status-valid-fg); }
.pm-pill.muted { background: var(--bg-alt); color: var(--fg-2); }
.pm-pill.info { background: var(--im-blue-ice); color: var(--im-blue-deep); }
.pm-row { display:flex; align-items:center; padding: 8px 0; gap: 10px; border-bottom: 1px solid var(--border-subtle); font-size: 11.5px; }
.pm-row:last-child { border-bottom: 0; }
.pm-days { font-family: var(--font-mono); font-size: 11px; color: var(--fg-2); min-width: 52px; }
.pm-days.red { color: var(--im-status-danger-fg); }
.pm-days.amber { color: var(--im-status-warn-fg); }
.pm-stu { display:flex; align-items:center; gap:8px; flex: 1; min-width: 0; }
.pm-stu-avatar { width:22px; height:22px; border-radius: 50%; font-size: 9px; font-weight:500; color:#fff; display:flex; align-items:center; justify-content:center; flex-shrink: 0; }
.pm-stu-name { font-weight: 500; color: var(--fg-1); font-size: 11.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pm-stu-sub { color: var(--fg-3); font-size: 10.5px; }
.pm-tbl { width: 100%; border-collapse: collapse; font-size: 11.5px; }
.pm-tbl th { text-align:left; padding: 6px 8px 10px; font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--fg-3); border-bottom: 1px solid var(--border-subtle); font-weight: 500; }
.pm-tbl td { padding: 9px 8px; border-bottom: 1px solid var(--border-subtle); color: var(--fg-2); }
.pm-cal { display:grid; grid-template-columns: repeat(30, 1fr); gap: 2px; margin-top: 10px; }
.pm-cal-day { aspect-ratio: 1; border-radius: 2px; background: var(--bg-alt); position: relative; }
.pm-cal-day.e1 { background: #B8D5EB; }
.pm-cal-day.e2 { background: var(--im-blue-sky); }
.pm-cal-day.e3 { background: var(--im-blue); }
.pm-cal-day.crit { background: var(--im-status-danger-fg); }
.pm-cal-day.warn { background: var(--im-status-warn-fg); }
.pm-cal-leg { display:flex; gap: 14px; margin-top: 10px; font-size: 10px; color: var(--fg-3); }
.pm-cal-leg span { display:inline-flex; align-items:center; gap:5px; }
.pm-cal-leg i { width:8px; height:8px; border-radius: 2px; background: var(--im-blue); display:inline-block; }
.pm-btn { display:inline-flex; align-items:center; gap:5px; padding: 5px 10px; border-radius: 6px; font-size: 11px; font-weight:500; border:1px solid var(--border-default); background:#fff; color: var(--fg-1); cursor: default; }
.pm-btn.primary { background: var(--im-blue); color: #fff; border-color: transparent; }
.pm-btn.ghost { background: transparent; border-color: var(--border-default); }
@media (max-width: 900px) {
.pm-app { grid-template-columns: 1fr; min-height: auto; }
.pm-side { display: none; }
.pm-main { padding: 14px 16px; }
.pm-topbar { flex-wrap: wrap; gap: 10px; align-items: flex-start; }
.pm-topbar > div:last-child { flex: 1 1 100%; justify-content: flex-end; }
.pm-search { width: 100%; max-width: none; flex: 1; min-width: 0; }
.pm-title { font-size: 17px; }
.pm-kpis { grid-template-columns: repeat(2, 1fr); }
.pm-two-col { grid-template-columns: 1fr; }
}
@media (max-width: 480px) {
.pm-kpis { grid-template-columns: 1fr; }
.pm-kpi-num { font-size: 22px; }
.pm-tbl { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; }
}
`;
// Deterministic avatar colors
const avatarColors = ['#4F9ED6', '#2B7AB8', '#7BB8E2', '#34B87C', '#E5A318', '#8896A6'];
const avatarColor = (name) => {
let h = 0;
for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) >>> 0;
return avatarColors[h % avatarColors.length];
};
const initials = (name) => name.split(' ').map(s => s[0]).slice(0,2).join('').toUpperCase();
function SideNav({ active }) {
const items = [
['Overview', 'Calendar', 'overview'],
['SEVIS Queue', 'AlertTriangle', 'sevis'],
['New Arrivals', 'Users', 'arrivals'],
['Students', 'User', 'students'],
['Requests', 'Inbox', 'requests'],
['Batch I-20', 'FileText', 'batch'],
['What-If Simulator', 'GitBranch', 'whatif'],
['Reports', 'BarChart', 'reports'],
['Audit Trail', 'Shield', 'audit'],
];
return (
{items.map(([label, icon, id]) => {
const Ic = Ico[icon];
return (
{label}
);
})}
Administration
DSO team
Settings
);
}
function TopBar({ crumb, title, user = 'Priya Raman' }) {
return (
Search students, I-20 numbers, SEVIS IDs…
{initials(user)}
);
}
// ---------- OVERVIEW ----------
function OverviewMockup() {
const students = [
['Yuki Tanaka', 'PhD · Mech Eng · Program ends 17d', 'crit', 'i20-expiring', 17],
['Aisha Patel', 'STEM OPT · E-Verify 19d overdue', 'crit', 'everify-overdue', -19],
['Jian Li', 'F-1 · Termination report due', 'crit', 'termination', 3],
['Naledi Mokoena', 'OPT ending · STEM OPT eligible', 'warn', 'ead-expiring', 89],
['Rohan Mehta', 'CPT request · Summer 2026', 'warn', 'cpt-pending', 6],
['Ming Chen', 'Travel signature stale', 'warn', 'travel-sig-stale', 11],
['Sara Okonkwo', 'New arrival · Registration due', 'info', 'registration-due', 8],
];
return (
Active F-1 / J-1
2,847
+12 this week
SEVIS events · 30d
142
11 due this week
Open requests
38
8 awaiting DSO
30-day SEVIS reporting calendar
{Array.from({length: 30}).map((_, i) => {
const map = { 2:'e1', 3:'crit', 5:'e2', 7:'warn', 9:'e1', 10:'e3', 12:'e2', 14:'warn', 17:'crit', 19:'e1', 21:'e2', 24:'warn', 27:'e1', 28:'e2', 29:'e3' };
return
;
})}
Overdue
Due this week
Reportable
Clear
Next 7 days: 3 termination reports, 2 program extensions, 6 travel signature reissues, 2 STEM OPT validations.
Risk-first · students
{students.map((s, i) => (
{s[4] < 0 ? `${-s[4]}d over` : `${s[4]}d left`}
{s[3]}
))}
);
}
// ---------- SEVIS QUEUE ----------
function SevisQueueMockup() {
const rows = [
['3d', 'red', 'Termination report', 'Jian Li', 'N0034567890', 'Unauthorized employment', 'Registrar confirm pending'],
['5d', 'amber', 'Program extension', 'Yuki Tanaka', 'N0045612388', 'New end date 2027-09-01', 'Advisor form pending'],
['6d', 'amber', 'CPT authorization', 'Rohan Mehta', 'N0038821104', 'Grafton Capital · ECON 498', 'Ready to file'],
['9d', '', 'Initial registration', 'Sara Okonkwo', 'N0049110023', '—', 'Arrived · check-in complete'],
['11d', '', 'Travel signature', 'Ming Chen', 'N0031290045', '12-month window', 'Ready to sign'],
['14d', '', 'STEM OPT validation', 'Naledi Mokoena', 'N0029887712', '6-month E-Verify check', 'Employer verified'],
['19d', '', 'Program extension', 'Aditya Singh', 'N0045921667', 'Dept approval on file', 'Ready to file'],
['23d', '', 'CPT authorization', 'Hana Kim', 'N0046110885', 'Boston Sci · BIO 590', 'Course map pending'],
];
return (
All · 142
Overdue · 2
Due this week · 11
Next 30 days · 129
| Due |
Event |
Student |
SEVIS ID |
Detail |
Blocker |
|
{rows.map((r, i) => (
| {r[0]} |
{r[2]} |
|
{r[4]} |
{r[5]} |
{r[6]} |
File in SEVIS → |
))}
);
}
// ---------- STUDENT DRAWER ----------
function StudentDrawerMockup() {
const timeline = [
['2023-08-21', 'I-20 issued', 'Active · Initial'],
['2023-09-04', 'Program start · check-in', 'Active'],
['2024-05-12', 'Travel signature', '12-mo window'],
['2025-04-03', 'Travel signature (reissue)', 'Current'],
['2026-04-02', 'Program extension requested', 'In review'],
];
return (
Students · PhD candidates · Active
YT
Yuki Tanaka
F-1 · PhD Mechanical Engineering · SEVIS N0045612388 · Cohort AY23
17 days to program end
Approve extension
Sign travel endorsement
Send message
···
Overview
I-20 lifecycle
Requests
Employment
Messages
Audit
F-1 journey timeline
{timeline.map((t, i) => (
))}
Risk signals
i20-expiring
Program end 2026-05-03 · extension filed but advisor form pending
advisor-form-pending
Prof. Harding · overdue 4 days · 2 reminders sent
At a glance
StatusActive
Program end2026-05-03
Last travel sig2025-04-03
I-94 admit untilD/S
DSO assignedP. Raman
DeptMech Engineering
FundingRA · NSF GRFP
Next action: Reissue I-20 once advisor form arrives. Estimated SEVIS report in 3 days.
);
}
// ---------- WHAT-IF SIMULATOR ----------
function WhatIfMockup() {
return (
Proposed change
Current program end
2026-05-03
New program end
2026-09-01
+4 months · advisor-approved
I want preview. No change will be filed with SEVIS until you confirm.
Reset
Commit change →
Downstream effects
5 derived changes
{[
['Grace period', '60 days ends 2026-07-02', '60 days ends 2026-10-31', 'ok'],
['OPT earliest start', '2026-02-03 (90d before)', '2026-06-03 (shifted)', 'warn'],
['OPT latest start', '2026-08-02', '2026-11-30', 'ok'],
['SEVIS report', 'Program extension · due by 2026-05-03', 'Extended to 2026-09-01', 'info'],
['CPT eligibility', '4 CPT-eligible courses summer 2026', 'Remains eligible', 'ok'],
].map((r, i) => (
{r[0]}
{r[1]}
{r[2]}
{r[3] === 'ok' ? 'safe' : r[3] === 'warn' ? 'review' : 'report'}
))}
Risk signal impact
i20-expiring
cleared
+ new: opt-window-shift
);
}
// ---------- BATCH I-20 ----------
function BatchI20Mockup() {
const steps = ['Select', 'Review & sign', 'Generate PDFs', 'Ready for pickup'];
return (
{steps.map((s, i) => (
))}
45 students selected · pre-departure window · spring break 2026
Filter
Sign all 45 travel endorsements
|
Student |
Program |
Last signature |
Travel window |
Page 2 text |
Status |
{[
['Ming Chen', 'MS Computer Science', '2025-03-11', 'Mar 14 – Apr 4', '12-mo · OK', 'Signed · Ready'],
['Aditya Singh', 'PhD Chemistry', '2024-12-02', 'Mar 14 – Mar 28', '12-mo · OK', 'Signed · Ready'],
['Priya Iyer', 'MBA', '2025-04-18', 'Mar 15 – Mar 29', '12-mo · OK', 'Signed · Ready'],
['Jin Park', 'PhD Economics', '2025-02-07', 'Mar 16 – Apr 12', '12-mo · OK', 'Signed · Ready'],
['Liam O\'Brien', 'MS Data Science', '2024-09-14', 'Mar 14 – Mar 23', 'Expires mid-trip', 'Needs review'],
['Fatima Al-Sayed', 'PhD Biology', '2025-03-02', 'Mar 14 – Apr 3', '12-mo · OK', 'Signed · Ready'],
].map((r, i) => (
|
{r[0]} |
{r[1]} |
{r[2]} |
{r[3]} |
{r[4]} |
{r[5]} |
))}
Showing 6 of 45
Generated PDFs will be available in ~90s · audit-logged
);
}
// ---------- AUDIT TRAIL ----------
function AuditMockup() {
const rows = [
['2026-04-16 08:14:02', '10.24.19.88', 'P. Raman', 'Signed travel endorsement', 'Ming Chen · N0031290045'],
['2026-04-16 08:11:47', '10.24.19.88', 'P. Raman', 'Approved CPT', 'Rohan Mehta · N0038821104 · ECON 498'],
['2026-04-16 08:04:21', '10.24.19.88', 'P. Raman', 'Viewed student record', 'Yuki Tanaka · N0045612388'],
['2026-04-15 17:42:09', '10.24.19.44', 'M. Delacroix', 'Filed termination report', 'Jian Li · N0034567890 · unauthorized-employment'],
['2026-04-15 15:22:36', '10.24.19.44', 'M. Delacroix', 'Issued I-20', 'Sara Okonkwo · N0049110023 · Initial'],
['2026-04-15 14:18:02', '10.24.19.88', 'P. Raman', 'Batch signed 42 endorsements', 'Pre-departure spring 2026'],
['2026-04-15 11:03:58', '10.24.19.12', 'D. Watanabe', 'Updated program dates', 'Yuki Tanaka · N0045612388 · +4mo'],
['2026-04-14 16:31:14', '10.24.19.44', 'M. Delacroix', 'Auto-assigned to DSO queue', '12 new arrivals'],
];
return (
Filter · all DSOs
Range · last 30 days
Action · all
Export PDF
Export CSV
| Timestamp |
IP |
Actor |
Action |
Target |
{rows.map((r, i) => (
| {r[0]} |
{r[1]} |
{r[2]} |
{r[3]} |
{r[4]} |
))}
Showing 8 of 1,284 entries · AY 2025–26 · SHA-256 integrity verified
);
}
// ---------- NEW ARRIVALS ----------
function NewArrivalsMockup() {
return (
{[
['Issued', '214', 'I-20s'],
['In transit', '57', 'not yet arrived'],
['Arrived', '128', 'awaiting check-in'],
['Registered', '89', 'active in SEVIS'],
['Window open', '23', 'days remaining'],
].map(([l, n, s], i) => (
))}
Initial registration · 30-day federal window
Day 7 of 30
| Student | Program | Arrival | Check-in | SEVIS registered | Days left |
{[
['Sara Okonkwo', 'PhD Public Health', '2026-04-09', 'Complete', '—', 8, 'warn'],
['Takeshi Yamamoto', 'MS Civil Eng', '2026-04-10', 'Complete', 'Active', 0, 'ok'],
['Diya Chatterjee', 'PhD Physics', '2026-04-11', 'Complete', '—', 9, 'warn'],
['Lee Min-jun', 'MBA', '2026-04-12', 'Scheduled 04-17', '—', 11, ''],
['Ngozi Adebayo', 'MS Biotech', '2026-04-13', 'Complete', 'Active', 0, 'ok'],
['Raúl Vázquez', 'PhD History', '2026-04-14', 'No-show', '—', 4, 'crit'],
].map((r, i) => (
| {r[0]} |
{r[1]} |
{r[2]} |
{r[3]} |
{r[4]} |
{r[5] === 0 ? 'Registered' : `${r[5]}d`} |
))}
);
}
// ---------- REQUESTS INBOX ----------
function RequestsMockup() {
return (
All requests · 388 awaiting DSO
{[
['Rohan Mehta', 'CPT · Summer 2026', 'high', 'on', '2h'],
['Naledi Mokoena', 'STEM OPT extension', 'high', '', '4h'],
['Ming Chen', 'Travel signature', 'med', '', '1d'],
['Fatima Al-Sayed', 'Program extension', 'high', '', '1d'],
['Liam O\'Brien', 'Reduced course load', 'med', '', '2d'],
['Jin Park', 'Travel signature', 'low', '', '3d'],
].map((r, i) => (
{r[1]}
Submitted {r[4]} ago
))}
RM
Rohan Mehta · CPT request
MS Computer Science · submitted 2h ago · 3 documents attached
Priority
Employer · Grafton Capital, New York NY
Role · Quantitative Analyst Intern (Summer 2026)
Dates · 2026-05-19 → 2026-08-15 · 20hrs/wk academic year, 40hrs/wk summer
Course link · ECON 498 · Directed Research · Department confirmed
Summary: First CPT · eligible (completed 1 academic year) · offer letter, course syllabus, faculty advisor approval on file. Course map validates directly with ECON 498 spring-26 enrollment. Ready to authorize.
Approve and issue I-20
Request more info
Decline
);
}
function Mockup({ kind }) {
switch (kind) {
case 'overview': return
;
case 'sevis': return
;
case 'drawer': return
;
case 'whatif': return
;
case 'batch': return
;
case 'audit': return
;
case 'arrivals': return
;
case 'requests': return
;
default: return
;
}
}
const mockupStyleTag = () => ;
Object.assign(window, { Mockup, mockupStyleTag });