Skip to content

Commit 1d476ef

Browse files
add: enhance new installs ui components and skeleton loaders for better ux
1 parent d516dcd commit 1d476ef

8 files changed

Lines changed: 253 additions & 127 deletions

File tree

src/client/components/features/dashboard/updates-email-prompt.tsx

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,20 +49,21 @@ export function UpdatesEmailPrompt() {
4949

5050
return (
5151
<section className="surface overflow-hidden">
52-
<div className="flex flex-col gap-4 px-4 py-4 sm:px-5 lg:flex-row lg:items-center lg:justify-between">
52+
<div className="flex flex-col gap-4 px-4 py-4 sm:px-5 md:flex-row md:items-center md:justify-between md:gap-6">
5353
<div className="flex min-w-0 items-start gap-3">
54-
<span className="mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-primary/10 text-primary">
54+
<span className="mt-0.5 hidden sm:flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-primary/10 text-primary">
5555
<Mail size={16} strokeWidth={2.1} />
5656
</span>
5757
<div className="min-w-0">
5858
<h2 className="text-sm font-semibold text-foreground">Get important Codra updates</h2>
59-
<p className="mt-1 max-w-2xl text-sm text-muted-foreground">
60-
Add an email for release notes, security fixes, and upgrade heads-up. You can opt out from any update email later. No spam.
59+
<p className="mt-0.5 text-sm text-muted-foreground leading-relaxed">
60+
Add an email for release notes, security fixes, and upgrade heads-up.{' '}
61+
<span className="hidden sm:inline">You can opt out from any update email later. No spam.</span>
6162
</p>
6263
</div>
6364
</div>
6465

65-
<form onSubmit={subscribe} className="flex min-w-0 flex-col gap-2 sm:flex-row sm:items-center lg:w-[31rem]">
66+
<form onSubmit={subscribe} className="flex w-full min-w-0 flex-col gap-2 sm:flex-row sm:items-center md:w-auto md:shrink-0 md:basis-[26rem]">
6667
<Input
6768
type="email"
6869
required
@@ -72,12 +73,10 @@ export function UpdatesEmailPrompt() {
7273
className="h-9 min-w-0 flex-1"
7374
aria-label="Email for Codra release updates"
7475
/>
75-
<div className="flex shrink-0 gap-2">
76-
<Button type="submit" size="sm" disabled={submitting} className="flex-1 gap-2 sm:flex-none">
77-
{submitting ? <RefreshCw size={13} className="animate-spin" /> : <Check size={13} />}
78-
Save email
79-
</Button>
80-
</div>
76+
<Button type="submit" size="sm" disabled={submitting} className="w-full gap-2 sm:w-auto sm:shrink-0">
77+
{submitting ? <RefreshCw size={13} className="animate-spin" /> : <Check size={13} />}
78+
Save email
79+
</Button>
8180
</form>
8281
</div>
8382
</section>

src/client/components/layout/page-header.tsx

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { cn } from '@client/lib/utils';
33
import { UpdatesEmailPrompt } from '@client/components/features/dashboard/updates-email-prompt';
44

55
interface PageHeaderProps extends React.HTMLAttributes<HTMLElement> {
6-
category: string;
6+
category?: string;
77
title: string;
88
description?: React.ReactNode;
99
actions?: React.ReactNode;
@@ -21,16 +21,13 @@ export function PageHeader({
2121
return (
2222
<>
2323
<header
24-
className={cn('flex flex-col sm:flex-row sm:items-end justify-between gap-4 sm:gap-0', className)}
24+
className={cn('flex flex-col sm:flex-row sm:items-center justify-between gap-3', className)}
2525
{...props}
2626
>
2727
<div>
28-
<p className="text-xs font-semibold uppercase tracking-widest text-primary/70 mb-1">
29-
{category}
30-
</p>
3128
<h1
32-
className="flex items-center gap-3 text-xl md:text-2xl font-bold text-foreground"
33-
style={{ letterSpacing: '-0.025em' }}
29+
className="flex items-center gap-3 text-xl font-bold text-foreground"
30+
style={{ letterSpacing: '-0.02em' }}
3431
>
3532
{title}
3633
{props.versionBadge && (
@@ -40,13 +37,13 @@ export function PageHeader({
4037
)}
4138
</h1>
4239
{description && (
43-
<div className="mt-1 text-sm text-muted-foreground">
40+
<div className="mt-0.5 text-sm text-muted-foreground">
4441
{description}
4542
</div>
4643
)}
4744
</div>
4845
{actions && (
49-
<div className="flex flex-wrap items-center gap-2 sm:gap-3 w-full sm:w-auto">
46+
<div className="flex flex-wrap items-center gap-2 sm:gap-2.5 w-full sm:w-auto sm:shrink-0">
5047
{actions}
5148
</div>
5249
)}
@@ -55,3 +52,4 @@ export function PageHeader({
5552
</>
5653
);
5754
}
55+

src/client/components/shared/empty-state.tsx

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,88 @@
11
import React from 'react';
2-
import { Button } from '@client/components/ui/button';
32
import { cn } from '@client/lib/utils';
43

54
interface EmptyStateProps {
65
icon?: React.ReactNode;
76
title: string;
87
description?: string;
8+
/** Bullet-point hints shown with green dot prefix (Beetle-style) */
9+
hints?: string[];
10+
/** Renders an outlined link button below the hints */
11+
linkAction?: {
12+
label: string;
13+
href?: string;
14+
onClick?: () => void;
15+
};
16+
/** Legacy single action button */
917
action?: {
1018
label: string;
1119
onClick: () => void;
1220
};
1321
className?: string;
1422
}
1523

16-
export function EmptyState({ icon, title, description, action, className }: EmptyStateProps) {
24+
export function EmptyState({ icon, title, description, hints, linkAction, action, className }: EmptyStateProps) {
1725
return (
1826
<div
1927
className={cn(
20-
'flex flex-col items-center justify-center gap-4 rounded-md border border-dashed border-border/70 bg-card/30 px-8 py-16 text-center',
28+
'flex flex-col items-center justify-center gap-4 px-8 py-16 text-center',
2129
className,
2230
)}
2331
>
2432
{icon && (
25-
<div className="flex h-16 w-16 items-center justify-center rounded-full bg-accent/10 text-accent [&_svg]:h-8 [&_svg]:w-8">
33+
<div className="flex h-14 w-14 items-center justify-center text-muted-foreground/40 [&_svg]:h-9 [&_svg]:w-9 [&_svg]:stroke-[1.35]">
2634
{icon}
2735
</div>
2836
)}
2937
<div className="space-y-1.5">
30-
<h3 className="text-lg font-semibold text-foreground">{title}</h3>
38+
<h3 className="text-base font-semibold text-foreground">{title}</h3>
3139
{description && (
3240
<p className="max-w-sm text-sm text-muted-foreground leading-relaxed">{description}</p>
3341
)}
3442
</div>
43+
44+
{hints && hints.length > 0 && (
45+
<ul className="mt-1 flex flex-col gap-1.5 text-left">
46+
{hints.map((hint, i) => (
47+
<li key={i} className="flex items-start gap-2.5 text-sm text-muted-foreground">
48+
<span className="mt-[7px] h-1.5 w-1.5 shrink-0 rounded-full bg-primary opacity-80" />
49+
<span>{hint}</span>
50+
</li>
51+
))}
52+
</ul>
53+
)}
54+
55+
{linkAction && (
56+
<div className="mt-3">
57+
{linkAction.href ? (
58+
<a
59+
href={linkAction.href}
60+
target="_blank"
61+
rel="noopener noreferrer"
62+
className="inline-flex items-center rounded-md border border-border bg-card px-4 py-2 text-xs font-medium text-foreground shadow-sm transition-colors hover:bg-secondary hover:border-border/80"
63+
>
64+
{linkAction.label}
65+
</a>
66+
) : (
67+
<button
68+
type="button"
69+
onClick={linkAction.onClick}
70+
className="inline-flex items-center rounded-md border border-border bg-card px-4 py-2 text-xs font-medium text-foreground shadow-sm transition-colors hover:bg-secondary hover:border-border/80"
71+
>
72+
{linkAction.label}
73+
</button>
74+
)}
75+
</div>
76+
)}
77+
3578
{action && (
36-
<Button onClick={action.onClick} className="mt-2">
79+
<button
80+
type="button"
81+
onClick={action.onClick}
82+
className="mt-2 inline-flex items-center rounded border border-border/60 bg-transparent px-4 py-1.5 text-xs font-medium text-muted-foreground transition-colors hover:border-border hover:text-foreground"
83+
>
3784
{action.label}
38-
</Button>
85+
</button>
3986
)}
4087
</div>
4188
);

src/client/components/shared/jobs-table.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,16 @@ interface JobsTableProps {
2929
}
3030

3131
const DEFAULT_COLUMNS: Column[] = [
32-
'repo',
3332
'pr',
33+
'repo',
3434
'status',
3535
'verdict',
3636
'created',
3737
'action',
3838
];
3939

4040
const thCls =
41-
'px-4 py-3 text-left text-[10px] font-bold uppercase tracking-[0.16em] text-muted-foreground select-none';
41+
'px-4 py-3 text-left text-xs font-semibold text-muted-foreground/70 select-none';
4242

4343
const COLUMN_CLASSES: Record<Column, string> = {
4444
repo: 'w-[190px] max-w-[190px]',
@@ -53,12 +53,12 @@ const COLUMN_CLASSES: Record<Column, string> = {
5353

5454
const COLUMN_HEADERS: Record<Column, string> = {
5555
repo: 'Repository',
56-
pr: 'Pull request',
56+
pr: 'Title',
5757
status: 'Status',
5858
verdict: 'Verdict',
5959
files: 'Files',
6060
tokens: 'Tokens',
61-
created: 'Created',
61+
created: 'Last Updated',
6262
action: '',
6363
};
6464

@@ -228,7 +228,7 @@ export function JobsTable({ jobs, loading, columns }: JobsTableProps) {
228228
<div className="hidden max-w-full overflow-x-auto sm:block">
229229
<table className={cn('w-full border-separate border-spacing-0 text-sm', tableMinWidth)}>
230230
<thead>
231-
<tr className="bg-secondary/60">
231+
<tr className="border-b border-border/60 bg-muted/30">
232232
{cols.map((col) => (
233233
<th
234234
key={col}

src/client/pages/dashboard.tsx

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { useState } from 'react';
22
import { api } from '@client/lib/api';
33
import type { StatsPayload } from '@shared/schema';
44
import type { JobSummary } from '@shared/schema';
5-
import { ArrowRight } from 'lucide-react';
5+
import { ArrowRight, GitPullRequest } from 'lucide-react';
66
import { JobsTable } from '@client/components/shared/jobs-table';
7+
import { EmptyState } from '@client/components/shared/empty-state';
78
import { PageHeaderActions } from '@client/components/shared/page-header-actions';
89
import { Link } from 'react-router-dom';
910

@@ -47,7 +48,6 @@ export function DashboardPage() {
4748
<section className="page-enter flex flex-col gap-6">
4849

4950
<PageHeader
50-
category="Home"
5151
title="Dashboard"
5252
description="Totals and recent review jobs for the selected time range."
5353
actions={
@@ -84,6 +84,23 @@ export function DashboardPage() {
8484
<div className="surface min-w-0 overflow-hidden">
8585
<JobsTable jobs={recentJobs} loading={loading} />
8686

87+
{!loading && recentJobs.length === 0 && (
88+
<EmptyState
89+
icon={<GitPullRequest />}
90+
title="No jobs yet"
91+
description="Your pull request analysis logs will appear here"
92+
hints={[
93+
'Once you open a PR in any of the connected repos, analysis triggers automatically',
94+
'To trigger manually, comment @codra on any PR',
95+
]}
96+
linkAction={{
97+
label: 'See how to interact with Codra',
98+
href: 'https://github.com/devarshishimpi/codra#readme',
99+
}}
100+
className="rounded-none border-0"
101+
/>
102+
)}
103+
87104
{!loading && recentJobs.length > 0 && (
88105
<div className="px-5 py-2.5 bg-muted/20 border-t border-border/50">
89106
<p className="text-[10px] font-semibold uppercase tracking-[0.2em] text-muted-foreground/40 text-center">
@@ -96,3 +113,4 @@ export function DashboardPage() {
96113
</section>
97114
);
98115
}
116+

0 commit comments

Comments
 (0)