Skip to content

Commit 6554b8a

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

5 files changed

Lines changed: 41 additions & 36 deletions

File tree

app/customize/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client';
22

33
import { useCallback, useEffect, useRef, useState, type ReactElement } from 'react';
4-
import { validateGitHubUsername } from '@/lib/github';
4+
import { validateGitHubUsername } from '@/lib/validations';
55
import Link from 'next/link';
66
import { motion } from 'framer-motion';
77
import { ControlsPanel } from './components/ControlsPanel';

lib/github.test.ts

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
buildCommitClock,
1111
clearGitHubApiCacheForTests,
1212
GITHUB_CACHE_TTL_MS,
13-
validateGitHubUsername,
1413
cacheKey,
1514
displayName,
1615
fetchOrgMembers,
@@ -1690,35 +1689,6 @@ describe('displayName', () => {
16901689
});
16911690
});
16921691

1693-
describe('validateGitHubUsername', () => {
1694-
it('returns true for a valid username', () => {
1695-
expect(validateGitHubUsername('valid-username-123')).toBe(true);
1696-
});
1697-
1698-
it('returns false for a too long username', () => {
1699-
expect(validateGitHubUsername('a'.repeat(40))).toBe(false);
1700-
});
1701-
1702-
it('returns false for a username with underscore', () => {
1703-
expect(validateGitHubUsername('invalid_username')).toBe(false);
1704-
});
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-
});
1721-
});
17221692
describe('cacheKey', () => {
17231693
it('creates key without year', () => {
17241694
expect(cacheKey('profile', 'DeepSikha')).toBe('profile:deepsikha');

lib/github.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -315,10 +315,6 @@ const getHeaders = () => ({
315315
'Content-Type': 'application/json',
316316
});
317317

318-
export function validateGitHubUsername(username: string): boolean {
319-
return /^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i.test(username);
320-
}
321-
322318
export function displayName(profile: GitHubUserProfile): string {
323319
if (typeof profile.name === 'string' && profile.name.trim() !== '') return profile.name;
324320
return profile.login;

lib/validations.test.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { describe, expect, it } from 'vitest';
2-
import { githubParamsSchema, ogParamsSchema, streakParamsSchema } from './validations';
2+
import {
3+
githubParamsSchema,
4+
ogParamsSchema,
5+
streakParamsSchema,
6+
validateGitHubUsername,
7+
} from './validations';
38

49
describe('streakParamsSchema — grace fallback behavior', () => {
510
it('accepts "0" as a valid grace value', () => {
@@ -40,6 +45,36 @@ describe('streakParamsSchema — grace fallback behavior', () => {
4045
});
4146
});
4247

48+
describe('validateGitHubUsername', () => {
49+
it('returns true for a valid username', () => {
50+
expect(validateGitHubUsername('valid-username-123')).toBe(true);
51+
});
52+
53+
it('returns false for a too long username', () => {
54+
expect(validateGitHubUsername('a'.repeat(40))).toBe(false);
55+
});
56+
57+
it('returns false for a username with underscore', () => {
58+
expect(validateGitHubUsername('invalid_username')).toBe(false);
59+
});
60+
61+
it('returns false for empty string', () => {
62+
expect(validateGitHubUsername('')).toBe(false);
63+
});
64+
65+
it('returns false for leading hyphen', () => {
66+
expect(validateGitHubUsername('-octocat')).toBe(false);
67+
});
68+
69+
it('returns false for trailing hyphen', () => {
70+
expect(validateGitHubUsername('octocat-')).toBe(false);
71+
});
72+
73+
it('returns false for consecutive hyphens', () => {
74+
expect(validateGitHubUsername('octo--cat')).toBe(false);
75+
});
76+
});
77+
4378
describe('githubParamsSchema', () => {
4479
it('should pass when username is valid', () => {
4580
const result = githubParamsSchema.safeParse({

lib/validations.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ export function toDimensionValue(val?: string): number | undefined {
4747
return val === undefined ? undefined : Number(val);
4848
}
4949

50+
export function validateGitHubUsername(username: string): boolean {
51+
return /^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i.test(username);
52+
}
53+
5054
function dimensionParam(name: string, min: number, max: number) {
5155
return z
5256
.string()

0 commit comments

Comments
 (0)