### Render bonus payment calculator Source: https://talent.docs.mercor.com/how-to/payments Provides input fields for bonus amounts and approval dates, displaying the calculated release window. ```jsx
Bonuses, work trials, referrals

Which release window does your bonus hit?

Bonuses, work-trial pay, and referral payouts use a different schedule than weekly wages — there are two cutoffs per week (Wednesday and Friday in PST). Enter the bonus details below to find out when it lands.

setBonusAmount(e.target.value)} />
Total bonus or work-trial pay
setBonusDateTime(e.target.value)} />
SPL or PM approval timestamp; defaults to right now
{bonusResult &&
${bonusResult.amount.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
Released on {fmtFullDate(bonusResult.release, tz)} · {bonusResult.windowLabel} (~12:00 PM PST). Bank arrival follows your payout speed selection above.
}
``` -------------------------------- ### Payment Timeline CSS Source: https://talent.docs.mercor.com/how-to/payments Styles for a multi-step payment progress timeline. ```css .pc-timeline { padding: 20px 30px 24px; position: relative; border-top: 1px solid rgba(255,255,255,0.18); } .pc-timeline-rail { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; position: relative; } .pc-timeline-line { position: absolute; left: 12.5%; right: 12.5%; top: 12px; height: 2px; background: rgba(255,255,255,0.25); } .pc-step { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; position: relative; } .pc-step-dot { width: 24px; height: 24px; border-radius: 50%; background: rgba(255,255,255,0.18); border: 2px solid rgba(255,255,255,0.4); display: inline-flex; align-items: center; justify-content: center; font-size: 11px; color: white; position: relative; z-index: 1; font-weight: 700; } .pc-step.pc-active .pc-step-dot { background: white; color: var(--pc-indigo-dark); border-color: white; box-shadow: 0 0 0 4px rgba(255,255,255,0.25); } .pc-step.pc-done .pc-step-dot { background: rgba(255,255,255,0.7); border-color: rgba(255,255,255,0.7); color: var(--pc-indigo-dark); } .pc-step-label { font-size: 10.5px; font-weight: 600; opacity: 0.9; text-transform: uppercase; letter-spacing: 0.08em; margin-top: 6px; } .pc-step-val { font-size: 13.5px; font-weight: 600; line-height: 1.35; } .pc-step-meta { font-size: 11.5px; opacity: 0.75; line-height: 1.35; } ``` -------------------------------- ### Payment and Bonus Calculation Logic Source: https://talent.docs.mercor.com/how-to/payments React state hooks and calculation logic for determining payment release dates based on country, provider, and bonus timing. ```javascript const [country, setCountry] = React.useState(defaultCountry.tz); const [rate, setRate] = React.useState(''); const [hours, setHours] = React.useState(''); const [provider, setProvider] = React.useState('stripe-bank'); const [checkDateTime, setCheckDateTime] = React.useState(nowInTzAsLocalString(defaultCountry.tz)); const [adjustOpen, setAdjustOpen] = React.useState(false); const [bonusAmount, setBonusAmount] = React.useState(''); const [bonusDateTime, setBonusDateTime] = React.useState(nowInTzAsLocalString(defaultCountry.tz)); const [faqOpenIdx, setFaqOpenIdx] = React.useState(null); React.useEffect(() => { setCheckDateTime(nowInTzAsLocalString(country)); setBonusDateTime(nowInTzAsLocalString(country)); }, [country]); const now = new Date(); const tz = country; const tzName = COUNTRIES.find(c => c.tz === tz)?.name || tz; const provInfo = providerInfo(provider); const liveWeekStart = workWeekStartFor(now); const liveWeekEnd = workWeekEnd(liveWeekStart); const adjustedMoment = adjustOpen && checkDateTime ? parseLocalDateTime(checkDateTime, tz) : null; const referenceMoment = adjustedMoment || now; const refWeekStart = workWeekStartFor(referenceMoment); const refWeekEnd = workWeekEnd(refWeekStart); const release = nextWedReleaseAfter(refWeekEnd); const arrival = provInfo.instant ? new Date(release.getTime() + (provInfo.minutes || 30) * 60000) : provInfo.minutes ? new Date(release.getTime() + provInfo.minutes * 60000) : addBusinessDays(release, provInfo.businessDays); const rateNum = parseFloat(rate); const hoursNum = parseFloat(hours); const hasInput = rateNum > 0 && hoursNum > 0; const gross = hasInput ? rateNum * hoursNum : 0; let adjustStatus = null; if (adjustedMoment) { if (refWeekStart.getTime() === liveWeekStart.getTime()) adjustStatus = 'current'; else if (refWeekStart < liveWeekStart) adjustStatus = 'past'; else adjustStatus = 'future'; } const stepStates = [{ id: 'now', moment: referenceMoment }, { id: 'cutoff', moment: refWeekEnd }, { id: 'release', moment: release }, { id: 'bank', moment: arrival }]; let activeMarked = false; const stepClass = stepStates.map(step => { if (step.moment < now) return 'done'; if (!activeMarked) { activeMarked = true; return 'active'; } return ''; }); let bonusResult = null; const bonusAmt = parseFloat(bonusAmount); if (bonusAmt > 0 && bonusDateTime) { const bonusMoment = parseLocalDateTime(bonusDateTime, tz); if (bonusMoment) { const pstParts = zonedParts(bonusMoment, 'America/Los_Angeles'); const dayIdx = ({ Sun: 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6 })[pstParts.weekday]; const inWindowA = dayIdx === 3 && pstParts.hour >= 12 || dayIdx === 4 || dayIdx === 5 || dayIdx === 6 || dayIdx === 0; let bonusRelease; let windowLabel; const base = new Date(Date.UTC(pstParts.year, pstParts.month - 1, pstParts.day)); if (inWindowA) { const daysToWed = (3 - dayIdx + 7) % 7 || 7; base.setUTCDate(base.getUTCDate() + daysToWed); bonusRelease = dateFromZonedWallClock('America/Los_Angeles', base.getUTCFullYear(), base.getUTCMonth() + 1, base.getUTCDate(), 12, 0); windowLabel = 'Wednesday window'; } else { const daysToFri = (5 - dayIdx + 7) % 7; base.setUTCDate(base.getUTCDate() + daysToFri); bonusRelease = dateFromZonedWallClock('America/Los_Angeles', base.getUTCFullYear(), base.getUTCMonth() + 1, base.getUTCDate(), 12, 0); windowLabel = 'Friday window'; } bonusResult = { amount: bonusAmt, release: bonusRelease, windowLabel }; } } ``` -------------------------------- ### Render Payment Timeline and Adjustment Panel Source: https://talent.docs.mercor.com/how-to/payments Displays a multi-step timeline for payment processing and an optional panel for testing specific log times. ```jsx
●
{adjustedMoment ? 'Log time' : 'Now'}
{fmtShortDate(referenceMoment, tz)}
{fmtTime(referenceMoment, tz)}
◆
IST cutoff
{fmtShortDate(refWeekEnd, tz)}
{fmtTime(refWeekEnd, tz)} your time · Fri 11:59 PM IST
$
Mercor releases
{fmtShortDate(release, tz)}
~12:00 PM PST
✓
In your bank
{fmtShortDate(arrival, tz)}
{provInfo.instant ? '~30 min after release' : provInfo.isWise ? '~1 hr after you accept email' : '2–3 business days'}
{adjustStatus && {adjustStatus === 'current' ? 'Current week' : adjustStatus === 'past' ? 'Past week' : 'Future week'} }
{adjustOpen &&
setCheckDateTime(e.target.value)} />
If you worked late at night or early morning, check which work week your hours land in
}
``` -------------------------------- ### Troubleshooting Item CSS Source: https://talent.docs.mercor.com/how-to/payments Styles for FAQ or troubleshooting list items. ```css .pc-trouble { display: grid; gap: 10px; margin-top: 4px; } .pc-trouble-item { padding: 14px 16px; background: var(--pc-gray-50); border: 1px solid var(--pc-gray-200); border-radius: 12px; } .pc-trouble-q { font-size: 14px; font-weight: 600; color: var(--pc-black); margin-bottom: 4px; } .pc-trouble-a { font-size: 13.5px; color: var(--pc-gray-600); line-height: 1.6; } .pc-trouble-a a { color: var(--pc-indigo); text-decoration: none; } .pc-trouble-a a:hover { text-decoration: underline; } ``` -------------------------------- ### CSS Styles for FAQ and Layout Source: https://talent.docs.mercor.com/how-to/payments Contains styling for FAQ lists, footer elements, and responsive media queries for mobile devices. ```css .pc-faq-list { margin-top: 4px; } .pc-faq-item { border-top: 1px solid var(--pc-gray-200); } .pc-faq-item:first-child { border-top: none; } .pc-faq-trigger { width: 100%; text-align: left; background: none; border: none; padding: 16px 0; font-size: 14.5px; font-weight: 600; color: var(--pc-black); cursor: pointer; display: flex; justify-content: space-between; align-items: center; gap: 14px; } .pc-faq-trigger:hover { color: var(--pc-indigo-dark); } .pc-faq-trigger .pc-chev-sm { width: 14px; height: 14px; flex-shrink: 0; transition: transform 0.15s; color: var(--pc-gray-400); } .pc-faq-item.pc-open .pc-faq-trigger .pc-chev-sm { transform: rotate(180deg); color: var(--pc-indigo); } .pc-faq-content { padding: 0 0 16px; font-size: 14px; color: var(--pc-gray-600); line-height: 1.65; } .pc-faq-content a { color: var(--pc-indigo); text-decoration: none; } .pc-faq-content a:hover { text-decoration: underline; } .pc-foot { text-align: center; color: var(--pc-gray-400); font-size: 12px; margin: 24px 0 0; line-height: 1.6; } .pc-foot a { color: var(--pc-indigo); text-decoration: none; font-weight: 500; } @media (max-width: 720px) { .pc-fields { grid-template-columns: 1fr; } .pc-h1 { font-size: 28px; } .pc-card-head { padding: 18px 18px 0; } .pc-card-body { padding: 14px 18px 20px; } .pc-result-top { padding: 20px 18px 14px; } .pc-timeline { padding: 16px 18px 18px; } .pc-result-amount { font-size: 40px; } .pc-timeline-rail { grid-template-columns: 1fr; gap: 12px; } .pc-timeline-line { display: none; } .pc-step { flex-direction: row; align-items: center; gap: 12px; flex-wrap: wrap; } .pc-step-label { margin-top: 0; } .pc-step > div:last-child { flex: 1; min-width: 140px; } .pc-ww-line { flex-direction: column; gap: 2px; padding: 4px 0; } .pc-ww-key { min-width: 0; } .pc-ww-val { font-size: 14px; } } ``` -------------------------------- ### Wise Card CSS Source: https://talent.docs.mercor.com/how-to/payments Styles for informational cards with numbered steps. ```css .pc-wise-card { background: linear-gradient(135deg, #FFFBEB 0%, #FEF3C7 100%); border: 1px solid #FDE68A; } .pc-wise-card .pc-heading { color: #92400E; } .pc-wise-card .pc-eyebrow { color: #D97706; } .pc-wise-card .pc-sub { color: #78350F; } .pc-wise-steps { display: grid; gap: 10px; margin-top: 14px; counter-reset: pc-wise-step; } .pc-wise-step { display: grid; grid-template-columns: 32px 1fr; gap: 12px; align-items: flex-start; padding: 10px 0; } .pc-wise-step + .pc-wise-step { border-top: 1px solid rgba(217, 119, 6, 0.18); } .pc-wise-step::before { counter-increment: pc-wise-step; content: counter(pc-wise-step); width: 26px; height: 26px; border-radius: 50%; background: white; border: 1px solid #FCD34D; color: #92400E; font-weight: 600; font-size: 13px; display: inline-flex; align-items: center; justify-content: center; } .pc-wise-step-content { color: #78350F; font-size: 14px; line-height: 1.55; } ``` -------------------------------- ### Payment Interface CSS Styles Source: https://talent.docs.mercor.com/how-to/payments CSS variables and base styles for the payment shell component. ```css :root { --pc-indigo: #4F46E5; --pc-indigo-dark: #4338CA; --pc-indigo-light: #EEF2FF; --pc-indigo-soft: #F5F3FF; --pc-black: #020817; --pc-gray-50: #F8FAFC; --pc-gray-100: #F1F5F9; --pc-gray-200: #E2E8F0; --pc-gray-300: #CBD5E1; --pc-gray-400: #94A3B8; --pc-gray-500: #64748B; --pc-gray-600: #475569; --pc-gray-700: #334155; } .pc-shell { font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; color: var(--pc-black); -webkit-font-smoothing: antialiased; line-height: 1.5; max-width: 880px; margin: 24px 0; } .pc-shell *, .pc-shell *::before, .pc-shell *::after { box-sizing: border-box; } .pc-shell button { font-family: inherit; } .pc-mono { font-family: 'JetBrains Mono', ui-monospace, monospace; } ``` -------------------------------- ### Timezone Configuration List Source: https://talent.docs.mercor.com/how-to/payments A collection of country names mapped to their respective IANA timezone identifiers. ```javascript }, { name: 'Malta', tz: 'Europe/Malta' }, { name: 'Mauritius', tz: 'Indian/Mauritius' }, { name: 'Mexico (CDMX)', tz: 'America/Mexico_City' }, { name: 'Mexico (Cancún)', tz: 'America/Cancun' }, { name: 'Mexico (Tijuana)', tz: 'America/Tijuana' }, { name: 'Moldova', tz: 'Europe/Chisinau' }, { name: 'Mongolia', tz: 'Asia/Ulaanbaatar' }, { name: 'Montenegro', tz: 'Europe/Podgorica' }, { name: 'Morocco', tz: 'Africa/Casablanca' }, { name: 'Mozambique', tz: 'Africa/Maputo' }, { name: 'Myanmar', tz: 'Asia/Yangon' }, { name: 'Namibia', tz: 'Africa/Windhoek' }, { name: 'Nepal', tz: 'Asia/Kathmandu' }, { name: 'Netherlands', tz: 'Europe/Amsterdam' }, { name: 'New Zealand', tz: 'Pacific/Auckland' }, { name: 'Nicaragua', tz: 'America/Managua' }, { name: 'Nigeria', tz: 'Africa/Lagos' }, { name: 'North Macedonia', tz: 'Europe/Skopje' }, { name: 'Norway', tz: 'Europe/Oslo' }, { name: 'Oman', tz: 'Asia/Muscat' }, { name: 'Pakistan', tz: 'Asia/Karachi' }, { name: 'Palestine', tz: 'Asia/Gaza' }, { name: 'Panama', tz: 'America/Panama' }, { name: 'Papua New Guinea', tz: 'Pacific/Port_Moresby' }, { name: 'Paraguay', tz: 'America/Asuncion' }, { name: 'Peru', tz: 'America/Lima' }, { name: 'Philippines', tz: 'Asia/Manila' }, { name: 'Poland', tz: 'Europe/Warsaw' }, { name: 'Portugal', tz: 'Europe/Lisbon' }, { name: 'Puerto Rico', tz: 'America/Puerto_Rico' }, { name: 'Qatar', tz: 'Asia/Qatar' }, { name: 'Romania', tz: 'Europe/Bucharest' }, { name: 'Russia (Moscow)', tz: 'Europe/Moscow' }, { name: 'Russia (Novosibirsk)', tz: 'Asia/Novosibirsk' }, { name: 'Russia (Vladivostok)', tz: 'Asia/Vladivostok' }, { name: 'Rwanda', tz: 'Africa/Kigali' }, { name: 'Saudi Arabia', tz: 'Asia/Riyadh' }, { name: 'Senegal', tz: 'Africa/Dakar' }, { name: 'Serbia', tz: 'Europe/Belgrade' }, { name: 'Singapore', tz: 'Asia/Singapore' }, { name: 'Slovakia', tz: 'Europe/Bratislava' }, { name: 'Slovenia', tz: 'Europe/Ljubljana' }, { name: 'Somalia', tz: 'Africa/Mogadishu' }, { name: 'South Africa', tz: 'Africa/Johannesburg' }, { name: 'South Korea', tz: 'Asia/Seoul' }, { name: 'Spain', tz: 'Europe/Madrid' }, { name: 'Sri Lanka', tz: 'Asia/Colombo' }, { name: 'Sudan', tz: 'Africa/Khartoum' }, { name: 'Sweden', tz: 'Europe/Stockholm' }, { name: 'Switzerland', tz: 'Europe/Zurich' }, { name: 'Syria', tz: 'Asia/Damascus' }, { name: 'Taiwan', tz: 'Asia/Taipei' }, { name: 'Tajikistan', tz: 'Asia/Dushanbe' }, { name: 'Tanzania', tz: 'Africa/Dar_es_Salaam' }, { name: 'Thailand', tz: 'Asia/Bangkok' }, { name: 'Trinidad and Tobago', tz: 'America/Port_of_Spain' }, { name: 'Tunisia', tz: 'Africa/Tunis' }, { name: 'Turkey', tz: 'Europe/Istanbul' }, { name: 'Turkmenistan', tz: 'Asia/Ashgabat' }, { name: 'Uganda', tz: 'Africa/Kampala' }, { name: 'Ukraine', tz: 'Europe/Kyiv' }, { name: 'United Arab Emirates', tz: 'Asia/Dubai' }, { name: 'United Kingdom', tz: 'Europe/London' }, { name: 'United States (Alaska)', tz: 'America/Anchorage' }, { name: 'United States (Central)', tz: 'America/Chicago' }, { name: 'United States (Eastern)', tz: 'America/New_York' }, { name: 'United States (Hawaii)', tz: 'Pacific/Honolulu' }, { name: 'United States (Mountain)', tz: 'America/Denver' }, { name: 'United States (Pacific)', tz: 'America/Los_Angeles' }, { name: 'Uruguay', tz: 'America/Montevideo' }, { name: 'Uzbekistan', tz: 'Asia/Tashkent' }, { name: 'Venezuela', tz: 'America/Caracas' }, { name: 'Vietnam', tz: 'Asia/Ho_Chi_Minh' }, { name: 'Yemen', tz: 'Asia/Aden' }, { name: 'Zambia', tz: 'Africa/Lusaka' }, { name: 'Zimbabwe', tz: 'Africa/Harare' }]; ``` -------------------------------- ### Implement Payment Estimator UI Source: https://talent.docs.mercor.com/how-to/payments A React-based component for calculating and displaying estimated weekly payouts based on user-provided contract details. ```jsx
Weekly payout

Estimate your hourly contract payout

Enter your contract rate and hours from the Mercor Spend page. The estimator will translate IST cutoffs into your local time and show the full journey of your money.

setRate(e.target.value)} />
From your contract on work.mercor.com
setHours(e.target.value)} />
Use the total from your Mercor Spend page — Insightful's "this week" may differ
Used to translate IST cutoffs into your local time
The amount is identical — speed only affects bank arrival
Estimated gross payout {hasInput && {rateNum.toFixed(2)} × {hoursNum}h}
${gross.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
{hasInput ? `Released by Mercor • Via ${provInfo.label}` : 'Enter your hourly rate and hours above to see the full payment journey'}
``` -------------------------------- ### Adjustment Panel CSS Source: https://talent.docs.mercor.com/how-to/payments Styles for toggleable adjustment panels and reset links. ```css .pc-adjust-row { display: flex; align-items: center; justify-content: space-between; margin-top: 16px; padding-top: 14px; border-top: 1px solid rgba(255,255,255,0.2); flex-wrap: wrap; gap: 10px; } .pc-toggle { background: rgba(255,255,255,0.14); border: 1px solid rgba(255,255,255,0.25); padding: 8px 14px; border-radius: 999px; font-size: 12.5px; font-weight: 500; color: white; cursor: pointer; display: inline-flex; align-items: center; gap: 6px; transition: background 0.15s; } .pc-toggle:hover { background: rgba(255,255,255,0.22); } .pc-chev { transition: transform 0.15s; } .pc-toggle.pc-open .pc-chev { transform: rotate(180deg); } .pc-adjust-panel { margin-top: 14px; padding: 16px; background: rgba(255,255,255,0.95); border-radius: 12px; border: 1px solid rgba(255,255,255,0.4); } .pc-reset-link { background: none; border: none; padding: 10px 0; font-size: 13px; font-weight: 500; color: var(--pc-indigo); cursor: pointer; text-align: left; } .pc-reset-link:hover { text-decoration: underline; } ``` -------------------------------- ### Bonus Result CSS Source: https://talent.docs.mercor.com/how-to/payments Styles for displaying bonus amount results. ```css .pc-bonus-result { margin-top: 16px; padding: 16px 18px; background: var(--pc-indigo-soft); border: 1px solid #E0E7FF; border-radius: 12px; } .pc-bonus-amount { font-size: 22px; font-weight: 700; color: var(--pc-indigo-dark); letter-spacing: -0.01em; margin-bottom: 4px; } .pc-bonus-detail { font-size: 13.5px; color: var(--pc-gray-700); line-height: 1.55; } ``` -------------------------------- ### Date and Time Utility Functions Source: https://talent.docs.mercor.com/how-to/payments Functions for time zone-aware date manipulation, including work week calculations, business day addition, and localized date formatting. ```javascript const zMin = +parts.find(p => p.type === 'minute').value; return new Date(guessMs - (Date.UTC(zY, zM - 1, zD, zH, zMin) - guessMs)); }; const zonedParts = (date, tz) => { const fmt = new Intl.DateTimeFormat('en-US', { timeZone: tz, year: 'numeric', month: '2-digit', day: '2-digit', weekday: 'short', hour: '2-digit', minute: '2-digit', hourCycle: 'h23' }); const p = fmt.formatToParts(date); return { year: +p.find(x => x.type === 'year').value, month: +p.find(x => x.type === 'month').value, day: +p.find(x => x.type === 'day').value, weekday: p.find(x => x.type === 'weekday').value, hour: +p.find(x => x.type === 'hour').value, minute: +p.find(x => x.type === 'minute').value }; }; const workWeekStartFor = moment => { const p = zonedParts(moment, 'Asia/Kolkata'); const daysSinceSat = ({ Sat: 0, Sun: 1, Mon: 2, Tue: 3, Wed: 4, Thu: 5, Fri: 6 })[p.weekday]; const base = new Date(Date.UTC(p.year, p.month - 1, p.day)); base.setUTCDate(base.getUTCDate() - daysSinceSat); return dateFromZonedWallClock('Asia/Kolkata', base.getUTCFullYear(), base.getUTCMonth() + 1, base.getUTCDate(), 0, 0); }; const workWeekEnd = satStart => { const p = zonedParts(satStart, 'Asia/Kolkata'); const base = new Date(Date.UTC(p.year, p.month - 1, p.day)); base.setUTCDate(base.getUTCDate() + 6); return dateFromZonedWallClock('Asia/Kolkata', base.getUTCFullYear(), base.getUTCMonth() + 1, base.getUTCDate(), 23, 59); }; const nextWedReleaseAfter = moment => { const p = zonedParts(moment, 'America/Los_Angeles'); const dayIdx = ({ Sun: 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6 })[p.weekday]; const daysToWed = (3 - dayIdx + 7) % 7; const base = new Date(Date.UTC(p.year, p.month - 1, p.day)); base.setUTCDate(base.getUTCDate() + daysToWed); let candidate = dateFromZonedWallClock('America/Los_Angeles', base.getUTCFullYear(), base.getUTCMonth() + 1, base.getUTCDate(), 12, 0); if (candidate <= moment) { base.setUTCDate(base.getUTCDate() + 7); candidate = dateFromZonedWallClock('America/Los_Angeles', base.getUTCFullYear(), base.getUTCMonth() + 1, base.getUTCDate(), 12, 0); } return candidate; }; const addBusinessDays = (date, days) => { const d = new Date(date); let added = 0; while (added < days) { d.setUTCDate(d.getUTCDate() + 1); const dow = d.getUTCDay(); if (dow !== 0 && dow !== 6) added++; } return d; }; const fmtFullDate = (date, tz) => new Intl.DateTimeFormat('en-US', { timeZone: tz, weekday: 'short', month: 'short', day: 'numeric', year: 'numeric' }).format(date); const fmtShortDate = (date, tz) => new Intl.DateTimeFormat('en-US', { timeZone: tz, weekday: 'short', month: 'short', day: 'numeric' }).format(date); const fmtTime = (date, tz) => new Intl.DateTimeFormat('en-US', { timeZone: tz, hour: 'numeric', minute: '2-digit', hour12: true }).format(date); const fmtDateTimeLong = (date, tz) => new Intl.DateTimeFormat('en-US', { timeZone: tz, weekday: 'short', month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit', hour12: true }).format(date); const nowInTzAsLocalString = tz => { const fmt = new Intl.DateTimeFormat('en-CA', { timeZone: tz, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hourCycle: 'h23' }); const p = fmt.formatToParts(new Date()); return `${p.find(x => x.type === 'year').value}-${p.find(x => x.type === 'month').value}-${p.find(x => x.type === 'day').value}T${p.find(x => x.type === 'hour').value}:${p.find(x => x.type === 'minute').value}`; }; const parseLocalDateTime = (value, tz) => { if (!value) return null; const [date, time] = value.split('T'); if (!date || !time) return null; const [y, m, d] = date.split('-').map(Number); const [h, mn] = time.split(':').map(Number); return dateFromZonedWallClock(tz, y, m, d, h, mn); }; const providerInfo = p => { if (p === 'stripe-instant') return { label: 'Stripe Instant Payout', businessDays: 0, instant: true, minutes: 30, isWise: false }; if (p === 'wise') return { label: 'Wise (after you accept)', businessDays: 0, instant: false, minutes: 60, isWise: true }; return { label: 'Stripe bank transfer', businessDays: 3, instant: false, isWise: false }; }; const detectedTz = typeof Intl !== 'undefined' ? Intl.DateTimeFormat().resolvedOptions().timeZone : 'America/Los_Angeles'; const defaultCountry = COUNTRIES.find(c => c.tz === detectedTz) || COUNTRIES[0]; ``` -------------------------------- ### Status Badge CSS Source: https://talent.docs.mercor.com/how-to/payments Styles for status indicators representing current, past, or future states. ```css .pc-status { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: 999px; font-size: 11px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; } .pc-status::before { content: ''; width: 6px; height: 6px; border-radius: 50%; } .pc-status.pc-current { background: rgba(255,255,255,0.18); color: white; } .pc-status.pc-current::before { background: white; } .pc-status.pc-past { background: rgba(255,255,255,0.18); color: white; opacity: 0.85; } .pc-status.pc-past::before { background: rgba(255,255,255,0.7); } .pc-status.pc-future { background: rgba(255,255,255,0.18); color: white; } .pc-status.pc-future::before { background: #BAE6FD; } ``` -------------------------------- ### Render Wise payout notification Source: https://talent.docs.mercor.com/how-to/payments Displays a conditional section informing users about the manual action required for Wise payouts. ```jsx {provInfo.isWise &&
Wise process — important

Your Wise payout requires action from you

Unlike Stripe, which auto-deposits to your linked account, Wise sends you an email after Mercor releases the payout. You must accept it for the transfer to actually happen.

Mercor releases the payout on Wednesday ~12 PM PST. Wise then emails you at the address registered with your Mercor account.
Open the email from wise.com and click the link. Check your spam folder if you don't see it within 24h.
Register (if first time) or log in. Select Personal account, set 2FA, and add your bank details in your local currency.
Accept the transfer. Money typically arrives ~1 hour later, though some countries can take longer based on local banking.
If you ignore the email, the payout stalls on Wise's side — Mercor cannot push it through for you. Always action the email.
} ``` -------------------------------- ### Date Conversion Utility Source: https://talent.docs.mercor.com/how-to/payments A function that converts a wall-clock time in a specific timezone to a UTC timestamp using Intl.DateTimeFormat. ```javascript const dateFromZonedWallClock = (tz, year, month, day, hour, minute) => { const guessMs = Date.UTC(year, month - 1, day, hour, minute); const fmt = new Intl.DateTimeFormat('en-US', { timeZone: tz, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hourCycle: 'h23' }); const parts = fmt.formatToParts(new Date(guessMs)); const zY = +parts.find(p => p.type === 'year').value; const zM = +parts.find(p => p.month === 'month').value; const zD = +parts.find(p => p.type === 'day').value; const zH = +parts.find(p => p.type === 'hour').value; ```