Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@ import { PHASE_DEVELOPMENT_SERVER } from 'next/constants.js';

export default function nextConfig(phase) {
const isDev = phase === PHASE_DEVELOPMENT_SERVER;
const zoneAssetPrefix =
process.env.NEXT_PUBLIC_ZONE_ASSET_PREFIX ??
(isDev ? '' : '/_zones/household-api-docs');

/** @type {import('next').NextConfig} */
return {
output: 'export',
assetPrefix: isDev ? undefined : '/_zones/household-api-docs',
assetPrefix: zoneAssetPrefix || undefined,
env: {
NEXT_PUBLIC_ZONE_ASSET_PREFIX: zoneAssetPrefix,
},
images: {
unoptimized: true,
},
Expand Down
2 changes: 2 additions & 0 deletions src/components/ApiDocsContent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import AccessModeSelector from './AccessModeSelector';
import AuthSection from './AuthSection';
import RequestSection from './RequestSection';
import HouseholdSection from './HouseholdSection';
import OpenApiReferenceSection from './OpenApiReferenceSection';
import ModelLink from './ModelLink';
import TermsLinkSection from './TermsLinkSection';

Expand All @@ -17,6 +18,7 @@ export default function ApiDocsContent({ country }) {
<AuthSection country={country} accessMode={accessMode} />
<RequestSection country={country} accessMode={accessMode} />
<HouseholdSection country={country} accessMode={accessMode} />
<OpenApiReferenceSection country={country} />
<ModelLink country={country} />
<TermsLinkSection country={country} />
</>
Expand Down
4 changes: 3 additions & 1 deletion src/components/CodeBlock.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default function CodeBlock({ code, language = 'python', title, output, ou
};

return (
<div className="rounded-lg border border-border-light overflow-hidden my-4">
<div className="my-4 min-w-0 overflow-hidden rounded-lg border border-border-light">
<div className="flex items-center justify-between px-4 py-2 bg-gray-50 border-b border-border-light">
<span className="text-sm font-medium text-text-secondary">{title || language}</span>
<button
Expand All @@ -38,6 +38,8 @@ export default function CodeBlock({ code, language = 'python', title, output, ou
borderRadius: 0,
fontSize: '14px',
lineHeight: '1.6',
maxWidth: '100%',
overflowX: 'auto',
}}
>
{code}
Expand Down
180 changes: 180 additions & 0 deletions src/components/OpenApiReferenceSection.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
'use client';

import { useState } from 'react';
import CodeBlock from './CodeBlock';
import {
getCalculateRequestExamples,
getCalculateResponseExamples,
} from '@/utils/countryDocs';

export default function OpenApiReferenceSection({ country }) {
const requestExamples = getCalculateRequestExamples(country);
const [selectedRequestId, setSelectedRequestId] = useState(
requestExamples[0]?.id
);
const responseExamples = getCalculateResponseExamples(country);
const [selectedResponseId, setSelectedResponseId] = useState(
responseExamples[0]?.id
);
const selectedRequest =
requestExamples.find(({ id }) => id === selectedRequestId) ??
requestExamples[0];
const selectedResponse =
responseExamples.find(({ id }) => id === selectedResponseId) ??
responseExamples[0];

return (
<section
id="endpoint-reference"
className="py-16 border-b border-border-light"
>
<div className="max-w-4xl mx-auto px-6">
<div>
<h2 className="text-3xl font-bold text-text-primary mb-4">
Endpoint reference
</h2>
<p className="text-text-secondary mb-6 text-lg">
The calculate endpoint reference includes request body shape,
authentication, response codes, and examples.
</p>
</div>

<div className="mb-8 overflow-hidden rounded-lg border border-border-light">
<div className="border-b border-border-light bg-gray-50 px-4 py-3">
<h3 className="text-lg font-semibold text-text-primary">
Request sample
</h3>
</div>
<div className="grid lg:grid-cols-[240px_1fr]">
<div className="border-b border-border-light bg-bg-secondary p-3 lg:border-b-0 lg:border-r">
<div className="space-y-1">
{requestExamples.map(({ id, label, requirement }) => {
const isSelected = selectedRequest?.id === id;
return (
<button
key={id}
type="button"
onClick={() => setSelectedRequestId(id)}
className={`flex w-full items-start gap-3 rounded-md px-3 py-2 text-left text-sm transition-colors ${
isSelected
? 'bg-white font-semibold text-primary-800 shadow-sm'
: 'text-text-secondary hover:bg-white hover:text-text-primary'
}`}
aria-pressed={isSelected}
>
<span
className={`inline-flex min-w-[4.75rem] shrink-0 justify-center rounded-full px-2 py-0.5 text-xs font-semibold ${
requirement === 'Required'
? 'bg-green-100 text-green-800'
: 'bg-gray-100 text-text-primary'
}`}
>
{requirement}
</span>
<code className="min-w-0 break-all">{label}</code>
</button>
);
})}
</div>
</div>
<div className="min-w-0 p-4">
<div className="mb-4">
<div className="mb-2 flex flex-wrap items-center gap-2">
<h4 className="text-base font-semibold text-text-primary">
{selectedRequest?.title}
</h4>
<span
className={`inline-flex rounded-full px-2 py-0.5 text-xs font-semibold ${
selectedRequest?.requirement === 'Required'
? 'bg-green-100 text-green-800'
: 'bg-gray-100 text-text-primary'
}`}
>
{selectedRequest?.requirement}
</span>
</div>
<p className="mb-3 text-sm text-text-secondary">
{selectedRequest?.description}
</p>
<dl className="mb-3 grid gap-3 text-sm sm:grid-cols-2">
<div>
<dt className="font-semibold text-text-primary">Type</dt>
<dd className="text-text-secondary">
<code>{selectedRequest?.type}</code>
</dd>
</div>
<div>
<dt className="font-semibold text-text-primary">
Default
</dt>
<dd className="text-text-secondary">
<code>{selectedRequest?.defaultValue}</code>
</dd>
</div>
</dl>
<ul className="list-disc space-y-1 pl-5 text-sm text-text-secondary">
{(selectedRequest?.notes ?? []).map((note) => (
<li key={note}>{note}</li>
))}
</ul>
</div>
<CodeBlock
code={selectedRequest?.code ?? '{}'}
language="json"
title={`${selectedRequest?.title ?? 'Request'} example`}
/>
</div>
</div>
</div>

<div className="overflow-hidden rounded-lg border border-border-light bg-white">
<div className="border-b border-border-light bg-gray-50 px-4 py-3">
<h3 className="text-lg font-semibold text-text-primary">
Response sample
</h3>
</div>
<div className="grid lg:grid-cols-[240px_1fr]">
<div className="border-b border-border-light bg-bg-secondary p-3 lg:border-b-0 lg:border-r">
<div className="space-y-1">
{responseExamples.map(({ id, status, label }) => {
const isSelected = selectedResponse?.id === id;
return (
<button
key={id}
type="button"
onClick={() => setSelectedResponseId(id)}
className={`flex w-full items-center gap-3 rounded-md px-3 py-2 text-left text-sm transition-colors ${
isSelected
? 'bg-white font-semibold text-primary-800 shadow-sm'
: 'text-text-secondary hover:bg-white hover:text-text-primary'
}`}
aria-pressed={isSelected}
>
<span
className={`inline-flex min-w-12 justify-center rounded-full px-2 py-0.5 text-xs font-semibold ${
status === '200'
? 'bg-green-100 text-green-800'
: 'bg-gray-100 text-text-primary'
}`}
>
{status}
</span>
<span>{label}</span>
</button>
);
})}
</div>
</div>
<div className="min-w-0 p-4">
<CodeBlock
code={selectedResponse?.code ?? '{}'}
language="json"
title={selectedResponse?.title ?? 'Response sample'}
/>
</div>
</div>
</div>
</div>
</section>
);
}
14 changes: 10 additions & 4 deletions src/components/PythonDocsContent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ const REPRODUCIBILITY_TOPICS = [
{ id: 'trace', label: 'TRACE export' },
];

const ZONE_ASSET_PREFIX = process.env.NEXT_PUBLIC_ZONE_ASSET_PREFIX ?? '';

function zoneAssetPath(path) {
return `${ZONE_ASSET_PREFIX}${path}`;
}

const US_ENTITY_ROWS = [
{ entity: 'person', scope: 'Individual', examples: 'employment_income, age, is_disabled' },
{ entity: 'marital_unit', scope: 'Married couple or single adult', examples: 'joint return grouping' },
Expand Down Expand Up @@ -503,8 +509,8 @@ Name: household_net_income, dtype: float32`,
language: 'python',
code: getPolicyengineVisualizationExample(country),
outputImage: isUS
? '/_zones/household-api-docs/python-guide/us-variation-chart.png'
: '/_zones/household-api-docs/python-guide/uk-variation-chart.png',
? zoneAssetPath('/python-guide/us-variation-chart.png')
: zoneAssetPath('/python-guide/uk-variation-chart.png'),
outputImageAlt: isUS
? 'US household net income and EITC by employment income'
: 'UK household net income and universal credit by employment income',
Expand Down Expand Up @@ -764,8 +770,8 @@ CAGR: 2.68%`,
language: 'python',
code: getPolicyengineMicrosimVisualizationExample(country),
outputImage: isUS
? '/_zones/household-api-docs/python-guide/us-decile-impacts-chart.png'
: '/_zones/household-api-docs/python-guide/uk-decile-impacts-chart.png',
? zoneAssetPath('/python-guide/us-decile-impacts-chart.png')
: zoneAssetPath('/python-guide/uk-decile-impacts-chart.png'),
outputImageAlt: isUS
? 'US mean change in household net income by income decile under the reform'
: 'UK mean change in household net income by income decile under the reform',
Expand Down
Loading