Skip to content
Merged
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
2 changes: 1 addition & 1 deletion api/languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@ export default async (req: VercelRequest, res: VercelResponse): Promise<void> =>
res.send(renderLangPercent(data, color ?? ""));
} catch {
res.setHeader("Content-Type", "image/svg+xml");
res.status(500).send(renderErrorCard("Could not fetch data"));
res.status(500).send(await renderErrorCard("Could not fetch data"));
}
};
2 changes: 1 addition & 1 deletion api/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ export default async (req: VercelRequest, res: VercelResponse): Promise<void> =>
res.send(renderStatCard(data, color ?? "", peng));
} catch {
res.setHeader("Content-Type", "image/svg+xml");
res.status(500).send(renderErrorCard("Could not fetch data"));
res.status(500).send(await renderErrorCard("Could not fetch data"));
}
};
Binary file added scripts/assets/fonts/inter-400.woff
Binary file not shown.
Binary file added scripts/assets/fonts/inter-600.woff
Binary file not shown.
72 changes: 72 additions & 0 deletions scripts/renderers/ErrorCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { CARD_WIDTH, CARD_HEIGHT, ERROR_DIVIDER_Y, COLOR_DARK, COLOR_SUBTLE } from '../utils/constants';

const DIVIDER_WIDTH = CARD_WIDTH - 2 * Math.round(CARD_WIDTH / 10);

interface ErrorCardProps {
message: string;
}

const ErrorCard = ({ message }: ErrorCardProps) => (
<div
style={{
display: 'flex',
flexDirection: 'column',
width: CARD_WIDTH,
height: CARD_HEIGHT,
backgroundColor: COLOR_DARK,
borderRadius: 10,
border: `1px solid ${COLOR_SUBTLE}`,
}}
>
<div
style={{
display: 'flex',
height: ERROR_DIVIDER_Y,
alignItems: 'flex-end',
justifyContent: 'center',
paddingBottom: 14,
}}
>
<span
style={{
fontFamily: 'Inter',
fontWeight: 600,
fontSize: 14,
color: '#FF6B6B',
}}
>
Error
</span>
</div>
<div
style={{
height: 1,
width: DIVIDER_WIDTH,
backgroundColor: COLOR_SUBTLE,
alignSelf: 'center',
}}
/>
<div
style={{
display: 'flex',
flex: 1,
alignItems: 'flex-start',
justifyContent: 'center',
paddingTop: 12,
}}
>
<span
style={{
fontFamily: 'Inter',
fontWeight: 400,
fontSize: 12,
color: COLOR_SUBTLE,
}}
>
{message}
</span>
</div>
</div>
);

export { ErrorCard };
20 changes: 10 additions & 10 deletions scripts/renderers/renderErrorCard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@ import { renderErrorCard } from './renderErrorCard';
import { CARD_WIDTH, CARD_HEIGHT } from '../utils/constants';

describe('renderErrorCard', () => {
test('returns an SVG string', () => {
const result = renderErrorCard('Test error');
test('returns an SVG string', async () => {
const result = await renderErrorCard('Test error');
expect(result).toContain('<svg');
expect(result).toContain('</svg>');
});

test('includes the error message', () => {
const message = 'Could not fetch data';
const result = renderErrorCard(message);
expect(result).toContain(message);
test('renders different output for different messages', async () => {
const result1 = await renderErrorCard('Could not fetch data');
const result2 = await renderErrorCard('Invalid username');
expect(result1).not.toBe(result2);
});

test('uses the correct card dimensions', () => {
const result = renderErrorCard('Test');
expect(result).toContain(`width="${ CARD_WIDTH }"`);
expect(result).toContain(`height="${ CARD_HEIGHT }"`);
test('uses the correct card dimensions', async () => {
const result = await renderErrorCard('Test');
expect(result).toContain(`width="${CARD_WIDTH}"`);
expect(result).toContain(`height="${CARD_HEIGHT}"`);
});
});
43 changes: 28 additions & 15 deletions scripts/renderers/renderErrorCard.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
import { CARD_WIDTH, CARD_HEIGHT, ERROR_DIVIDER_Y } from "../utils/constants";
import satori from 'satori';
import { readFileSync } from 'fs';
import { join } from 'path';
import React from 'react';
import { ErrorCard } from './ErrorCard';
import { CARD_WIDTH, CARD_HEIGHT } from '../utils/constants';

const renderErrorCard = (message: string): string => {
return `<svg
width="${ CARD_WIDTH }"
height="${ CARD_HEIGHT }"
viewBox="0 0 ${ CARD_WIDTH } ${ CARD_HEIGHT }"
fill="none"
xmlns="http://www.w3.org/2000/svg"
style="border-radius: 10px;"
>
<rect rx="10" ry="10" height="100%" width="100%" fill="#161B22" stroke-opacity="1" style="stroke:#A4A5A6;stroke-width:1;" />
<text x="${ CARD_WIDTH / 2 }" y="70" text-anchor="middle" style="font: 600 14px 'Segoe UI', Ubuntu, Sans-Serif; fill:#FF6B6B;">Error</text>
<line x1="${ CARD_WIDTH / 10 }" x2="${ CARD_WIDTH - (CARD_WIDTH / 10) }" y1="${ ERROR_DIVIDER_Y }" y2="${ ERROR_DIVIDER_Y }" stroke="#A4A5A6" />
<text x="${ CARD_WIDTH / 2 }" y="108" text-anchor="middle" style="font: 400 12px 'Segoe UI', Ubuntu, Sans-Serif; fill:#A4A5A6;">${message}</text>
</svg>`;
const fontRegular = readFileSync(join(__dirname, '../assets/fonts/inter-400.woff'));
const fontBold = readFileSync(join(__dirname, '../assets/fonts/inter-600.woff'));

const renderErrorCard = async (message: string): Promise<string> => {
return satori(React.createElement(ErrorCard, { message }), {
width: CARD_WIDTH,
height: CARD_HEIGHT,
fonts: [
{
name: 'Inter',
data: fontRegular,
weight: 400,
style: 'normal',
},
{
name: 'Inter',
data: fontBold,
weight: 600,
style: 'normal',
},
],
});
};

export { renderErrorCard };