Skip to content

Commit 66d8212

Browse files
feat: add client-side GitHub username validation
1 parent e6b5723 commit 66d8212

2 files changed

Lines changed: 46 additions & 13 deletions

File tree

app/customize/page.tsx

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use client';
22

33
import { useCallback, useEffect, useRef, useState, type ReactElement } from 'react';
4+
import { validateGitHubUsername } from '@/lib/github';
45
import Link from 'next/link';
56
import { motion } from 'framer-motion';
67
import { ControlsPanel } from './components/ControlsPanel';
@@ -121,6 +122,12 @@ export default function CustomizePage(): ReactElement {
121122
setSvgState('idle');
122123
return;
123124
}
125+
if (!validateGitHubUsername(trimmedUsername)) {
126+
setSvgContent('');
127+
setSvgState('error');
128+
setErrorMessage("That doesn't look like a valid GitHub username");
129+
return;
130+
}
124131

125132
setSvgState('loading');
126133
const controller = new AbortController();
@@ -192,7 +199,7 @@ export default function CustomizePage(): ReactElement {
192199
});
193200

194201
return () => controller.abort();
195-
}, [previewSrc, hasUsername]);
202+
}, [previewSrc, hasUsername, trimmedUsername]);
196203

197204
const exportSnippet = getExportSnippet(exportFormat, queryString);
198205

@@ -395,6 +402,14 @@ export default function CustomizePage(): ReactElement {
395402
Loading preview...
396403
</div>
397404
)}
405+
{svgState === 'error' &&
406+
errorMessage === "That doesn't look like a valid GitHub username" && (
407+
<div className="flex flex-col items-center justify-center gap-2 text-center py-8">
408+
<p className="text-sm font-semibold text-red-500 dark:text-red-400">
409+
{errorMessage}
410+
</p>
411+
</div>
412+
)}
398413
{svgState === 'error' && errorMessage === 'GitHub user not found' && (
399414
<div className="flex flex-col items-center justify-center gap-4 py-12 text-center">
400415
<div className="flex h-16 w-16 items-center justify-center rounded-3xl border border-red-500/20 bg-red-500/10 shadow-inner">
@@ -422,16 +437,18 @@ export default function CustomizePage(): ReactElement {
422437
</div>
423438
</div>
424439
)}
425-
{svgState === 'error' && errorMessage !== 'GitHub user not found' && (
426-
<div className="flex flex-col items-center justify-center gap-2 text-center py-8">
427-
<p className="text-sm font-semibold text-red-500 dark:text-red-400">
428-
Failed to load badge
429-
</p>
430-
<p className="text-xs text-gray-500 dark:text-white/45">
431-
The API may be unavailable. Please try again.
432-
</p>
433-
</div>
434-
)}
440+
{svgState === 'error' &&
441+
errorMessage !== 'GitHub user not found' &&
442+
errorMessage !== "That doesn't look like a valid GitHub username" && (
443+
<div className="flex flex-col items-center justify-center gap-2 text-center py-8">
444+
<p className="text-sm font-semibold text-red-500 dark:text-red-400">
445+
Failed to load badge
446+
</p>
447+
<p className="text-xs text-gray-500 dark:text-white/45">
448+
The API may be unavailable. Please try again.
449+
</p>
450+
</div>
451+
)}
435452
{svgState === 'loaded' && svgContent && (
436453
<motion.div
437454
initial={{ opacity: 0, scale: 0.95 }}
@@ -446,8 +463,8 @@ export default function CustomizePage(): ReactElement {
446463
)}
447464
</div>
448465
) : (
449-
<div className="relative z-10 flex w-full max-w-xl flex-col items-center justify-center rounded-[1.25rem] border border-dashed border-black/10 bg-gray-100/80 backdrop-blur-md dark:border-white/10 dark:bg-white/[0.03] px-6 py-12 text-center">
450-
<div className="mb-4 flex h-14 w-14 items-center justify-center rounded-2xl border border-black/10 bg-gray-100/80 dark:border-white/10 dark:bg-white/[0.04] text-gray-500 dark:text-emerald-300/70">
466+
<div className="relative z-10 flex w-full max-w-xl flex-col items-center justify-center rounded-[1.25rem] border border-dashed border-black/10 bg-gray-100/80 backdrop-blur-md dark:border-white/10 dark:bg-white/3 px-6 py-12 text-center">
467+
<div className="mb-4 flex h-14 w-14 items-center justify-center rounded-2xl border border-black/10 bg-gray-100/80 dark:border-white/10 dark:bg-white/4 text-gray-500 dark:text-emerald-300/70">
451468
<svg
452469
xmlns="http://www.w3.org/2000/svg"
453470
className="h-6 w-6"

lib/github.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1702,6 +1702,22 @@ describe('validateGitHubUsername', () => {
17021702
it('returns false for a username with underscore', () => {
17031703
expect(validateGitHubUsername('invalid_username')).toBe(false);
17041704
});
1705+
1706+
it('returns false for empty string', () => {
1707+
expect(validateGitHubUsername('')).toBe(false);
1708+
});
1709+
1710+
it('returns false for leading hyphen', () => {
1711+
expect(validateGitHubUsername('-octocat')).toBe(false);
1712+
});
1713+
1714+
it('returns false for trailing hyphen', () => {
1715+
expect(validateGitHubUsername('octocat-')).toBe(false);
1716+
});
1717+
1718+
it('returns false for consecutive hyphens', () => {
1719+
expect(validateGitHubUsername('octo--cat')).toBe(false);
1720+
});
17051721
});
17061722
describe('cacheKey', () => {
17071723
it('creates key without year', () => {

0 commit comments

Comments
 (0)