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
863 changes: 543 additions & 320 deletions src/app/(app)/issues/issues-list.tsx

Large diffs are not rendered by default.

43 changes: 29 additions & 14 deletions src/app/(app)/issues/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type SearchParams = {
claimed?: string;
page?: string;
sort?: string;
category?: string;
};

export default async function IssuesPage({
Expand All @@ -27,7 +28,7 @@ export default async function IssuesPage({
const sb = await getServerSupabase();
if (!sb)
return (
<div className="min-h-screen bg-[#111318] p-12 font-mono text-white">Not configured</div>
<div className="min-h-screen bg-[#0D0E12] p-12 font-mono text-white">Not configured</div>
);

const {
Expand All @@ -38,27 +39,35 @@ export default async function IssuesPage({
const filters = {
search: resolvedSearchParams.q,
state: (resolvedSearchParams.state === 'closed' ? 'closed' : 'open') as 'open' | 'closed',
difficulty: (['E', 'M', 'H'].includes(resolvedSearchParams.difficulty ?? '')
? resolvedSearchParams.difficulty
: undefined) as 'E' | 'M' | 'H' | undefined,
difficulty: resolvedSearchParams.difficulty,
repo: resolvedSearchParams.repo,
showClaimed: resolvedSearchParams.claimed === 'true',
page: Math.max(1, parseInt(resolvedSearchParams.page ?? '1') || 1),
sort: (['newest', 'xp_desc', 'xp_asc'].includes(resolvedSearchParams.sort ?? '')
? resolvedSearchParams.sort
: undefined) as 'newest' | 'xp_desc' | 'xp_asc' | undefined,
category: (['all', 'bugs', 'docs', 'feature', 'tests'].includes(
resolvedSearchParams.category ?? '',
)
? resolvedSearchParams.category
: 'all') as 'all' | 'bugs' | 'docs' | 'feature' | 'tests',
};

const service = getServiceSupabase();

let currentUserLevel = 0;
let completedCount = 0;
if (service) {
const { data: profile } = await service
.from('profiles')
.select('level')
.eq('id', user.id)
.single();
const [{ data: profile }, { count }] = await Promise.all([
service.from('profiles').select('level').eq('id', user.id).single(),
service
.from('recommendations')
.select('id', { count: 'exact', head: true })
.eq('user_id', user.id)
.eq('status', 'completed'),
]);
currentUserLevel = profile?.level ?? 0;
completedCount = count ?? 0;
}

const maintainedRepoNames = new Set<string>();
Expand Down Expand Up @@ -144,10 +153,10 @@ export default async function IssuesPage({
const repoOptions: RepoOption[] = repoResult.ok ? repoResult.data : [];

return (
<div className="min-h-screen bg-[#111318] p-12 font-mono text-white">
<div className="mx-auto max-w-6xl">
<header className="mb-12 border-b border-[#2d333b] pb-6">
<div className="mb-4 text-[11px] uppercase tracking-widest text-zinc-500">
<div className="min-h-screen bg-[#0D0E12] p-8 font-mono text-white lg:p-12">
<div className="mx-auto max-w-7xl">
<header className="mb-12 border-b border-zinc-800 pb-6">
<div className="mb-4 text-[11px] uppercase tracking-widest text-[#00FF87]">
02 / ISSUES
</div>
<h1 className="font-serif text-4xl text-white">Browse Issues</h1>
Expand All @@ -160,7 +169,13 @@ export default async function IssuesPage({
/>
)}

<IssuesList initialData={pageData} initialFilters={filters} repoOptions={repoOptions} />
<IssuesList
initialData={pageData}
initialFilters={filters}
repoOptions={repoOptions}
completedCount={completedCount}
currentUserLevel={currentUserLevel}
/>
</div>
</div>
);
Expand Down
4 changes: 2 additions & 2 deletions src/app/(app)/nav-items.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import {
LayoutDashboard,
CheckCircle2,
GitPullRequest,
Shield,
User,
Trophy,
Inbox,
Shield,
Activity,
Settings,
} from 'lucide-react';
Expand All @@ -18,7 +18,7 @@ import { isActiveRoute } from '@/lib/nav-utils';
const CORE_NAV = [
{ name: 'DASHBOARD', href: '/dashboard', icon: LayoutDashboard },
{ name: 'ISSUES', href: '/issues', icon: CheckCircle2 },
{ name: 'MY PRS', href: '/my-prs', icon: GitPullRequest },
{ name: 'PULL REQUESTS', href: '/my-prs', icon: GitPullRequest },
];

export function NavItems({
Expand Down
15 changes: 15 additions & 0 deletions src/app/(app)/pipelines/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default function PipelinesPage() {
return (
<div className="flex min-h-screen flex-col items-center justify-center bg-[#0D0E12] p-8 font-mono text-white lg:p-12">
<div className="max-w-md rounded-lg border border-zinc-800 bg-[#161b22]/30 p-8 text-center shadow-xl">
<h1 className="mb-4 font-serif text-3xl text-white">Pipelines</h1>
<p className="mb-6 text-sm text-zinc-400">
Track test suites, compilation, and deployment pipelines.
</p>
<span className="inline-block border border-[#00FF87]/20 bg-[#00FF87]/5 px-3 py-1.5 font-mono text-[10px] uppercase tracking-widest text-[#00FF87]">
Under Construction
</span>
</div>
</div>
);
}
15 changes: 15 additions & 0 deletions src/app/(app)/repositories/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default function RepositoriesPage() {
return (
<div className="flex min-h-screen flex-col items-center justify-center bg-[#0D0E12] p-8 font-mono text-white lg:p-12">
<div className="max-w-md rounded-lg border border-zinc-800 bg-[#161b22]/30 p-8 text-center shadow-xl">
<h1 className="mb-4 font-serif text-3xl text-white">Repositories</h1>
<p className="mb-6 text-sm text-zinc-400">
Browse and manage your linked repositories and organizations.
</p>
<span className="inline-block border border-[#00FF87]/20 bg-[#00FF87]/5 px-3 py-1.5 font-mono text-[10px] uppercase tracking-widest text-[#00FF87]">
Under Construction
</span>
</div>
</div>
);
}
15 changes: 15 additions & 0 deletions src/app/(app)/security/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default function AppSecurityPage() {
return (
<div className="flex min-h-screen flex-col items-center justify-center bg-[#0D0E12] p-8 font-mono text-white lg:p-12">
<div className="max-w-md rounded-lg border border-zinc-800 bg-[#161b22]/30 p-8 text-center shadow-xl">
<h1 className="mb-4 font-serif text-3xl text-white">Security</h1>
<p className="mb-6 text-sm text-zinc-400">
Review repository security scans, dependency updates, and alerts.
</p>
<span className="inline-block border border-[#00FF87]/20 bg-[#00FF87]/5 px-3 py-1.5 font-mono text-[10px] uppercase tracking-widest text-[#00FF87]">
Under Construction
</span>
</div>
</div>
);
}
52 changes: 45 additions & 7 deletions src/app/actions/issues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,30 @@ import { repoFilterPattern } from './issues-helpers';
import { getInstallOctokit } from '@/lib/github/app';

const PAGE_SIZE = 10;
const DIFFICULTY_VALUES = ['E', 'M', 'H'] as const;
type DifficultyValue = (typeof DIFFICULTY_VALUES)[number];

function isDifficultyValue(value: string): value is DifficultyValue {
return (DIFFICULTY_VALUES as readonly string[]).includes(value);
}

export type IssueFilter = {
search?: string;
state?: 'open' | 'closed';
difficulty?: 'E' | 'M' | 'H';
difficulty?: string;
repo?: string;
showClaimed?: boolean;
page?: number;
sort?: 'newest' | 'xp_desc' | 'xp_asc';
category?: 'all' | 'bugs' | 'docs' | 'feature' | 'tests';
};

export type IssueWithStatus = {
id: number;
repoFullName: string;
githubIssueNumber: number;
title: string;
bodyExcerpt: string | null;
difficulty: 'E' | 'M' | 'H' | null;
xpReward: number | null;
labels: string[] | null;
Expand Down Expand Up @@ -160,7 +168,13 @@ export async function getIssuesPage(filters: IssueFilter): Promise<Result<Issues
if (!repoOptionsRes.ok) {
return err(repoOptionsRes.error.code, repoOptionsRes.error.message);
}
const allowedRepos = repoOptionsRes.data.map((opt) => opt.value);
let allowedRepos = repoOptionsRes.data.map((opt) => opt.value);
if (filters.repo) {
const selectedRepos = filters.repo.split(',').filter(Boolean);
if (selectedRepos.length > 0) {
allowedRepos = allowedRepos.filter((r) => selectedRepos.includes(r));
}
}
if (allowedRepos.length === 0) {
return ok({
issues: [],
Expand All @@ -177,7 +191,7 @@ export async function getIssuesPage(filters: IssueFilter): Promise<Result<Issues

query = query
.select(
'id, repo_full_name, github_issue_number, title, difficulty, xp_reward, labels, state, url, fetched_at',
'id, repo_full_name, github_issue_number, title, body_excerpt, difficulty, xp_reward, labels, state, url, fetched_at',
{ count: 'exact' },
)
.eq('state', filters.state ?? 'open')
Expand All @@ -195,11 +209,33 @@ export async function getIssuesPage(filters: IssueFilter): Promise<Result<Issues
}

if (filters.difficulty) {
query = query.eq('difficulty', filters.difficulty);
const diffs = filters.difficulty.split(',').filter(Boolean);
const standardDiffs = diffs.filter(isDifficultyValue);
const diffParts: string[] = [];
if (standardDiffs.length > 0) {
diffParts.push(`difficulty.in.(${standardDiffs.join(',')})`);
}
if (diffs.includes('L0')) {
diffParts.push('difficulty.is.null');
}
if (diffParts.length > 0) {
query = query.or(diffParts.join(','));
}
}
const repoPattern = repoFilterPattern(filters.repo);
if (repoPattern) {
query = query.ilike('repo_full_name', repoPattern);

if (filters.category && filters.category !== 'all') {
const cat = filters.category;
if (cat === 'bugs') {
query = query.or('labels.cs.{"bug","bugs","kind/bug"},title.ilike.%bug%');
} else if (cat === 'docs') {
query = query.or(
'labels.cs.{"documentation","docs","doc","kind/documentation"},title.ilike.%doc%',
);
} else if (cat === 'feature') {
query = query.or('labels.cs.{"feature","enhancement","kind/feature"},title.ilike.%feature%');
} else if (cat === 'tests') {
query = query.or('labels.cs.{"test","tests","testing","kind/test"},title.ilike.%test%');
}
}

const { data, count, error } = await query;
Expand All @@ -210,6 +246,7 @@ export async function getIssuesPage(filters: IssueFilter): Promise<Result<Issues
repo_full_name: string;
github_issue_number: number;
title: string;
body_excerpt: string | null;
difficulty: string | null;
xp_reward: number | null;
labels: string[] | null;
Expand Down Expand Up @@ -238,6 +275,7 @@ export async function getIssuesPage(filters: IssueFilter): Promise<Result<Issues
repoFullName: i.repo_full_name,
githubIssueNumber: i.github_issue_number,
title: i.title,
bodyExcerpt: i.body_excerpt,
difficulty: i.difficulty as 'E' | 'M' | 'H' | null,
xpReward: i.xp_reward,
labels: i.labels,
Expand Down
Loading