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
33 changes: 18 additions & 15 deletions .github/workflows/frontend-ci.yml
Original file line number Diff line number Diff line change
@@ -1,33 +1,36 @@
name: Frontend CI
name: frontend

on:
pull_request:
branches: [main]
push:
branches: [main]
pull_request:
branches: [main]

jobs:
frontend:
build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend

defaults:
run:
working-directory: frontend
working-directory: ./frontend

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
cache-dependency-path: frontend/package-lock.json
node-storage: "npm"
node-version: 20

- name: Install dependencies
run: npm ci

- run: npm ci
- name: Lint check
run: npm run lint

- run: npm run build
- name: Type check
run: npm run typecheck

- run: npm audit --audit-level=high
- name: Build project
run: npm run build
12 changes: 4 additions & 8 deletions frontend/src/app/applications/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ export default function MyApplicationsPage() {
</div>
) : error ? (
<Card className="p-4">
<p className="text-[13px] font-semibold text-destructive">Failed to load your applications</p>
<p className="text-[13px] font-semibold text-destructive">
Failed to load your applications
</p>
<p className="mt-1 text-[12px] text-muted-foreground">
{error instanceof Error ? error.message : "Unknown error"}
</p>
Expand Down Expand Up @@ -180,17 +182,11 @@ function ApplicationCard({
</div>

<div className="flex flex-col items-end gap-2">
<Button
size="sm"
variant="outline"
disabled={!canWithdraw || busy}
onClick={onWithdraw}
>
<Button size="sm" variant="outline" disabled={!canWithdraw || busy} onClick={onWithdraw}>
{busy ? "Withdrawing…" : "Withdraw"}
</Button>
</div>
</div>
</Card>
);
}

Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ export function ApplicationStatusBadge({ status }: { status: ApplicationStatus }
status === "reviewing" && "bg-warning/10 text-warning border border-warning/30",
status === "accepted" && "bg-success/10 text-success border border-success/30",
status === "rejected" && "bg-destructive/10 text-destructive border border-destructive/30",
status === "withdrawn" && "bg-destructive/10 text-destructive border border-destructive/30 opacity-70",
status === "withdrawn" &&
"bg-destructive/10 text-destructive border border-destructive/30 opacity-70",
);

return <span className={className}>{status}</span>;
}

9 changes: 4 additions & 5 deletions frontend/src/components/applications/ApplicationsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,7 @@ export function ApplicationsList({ projectId, className }: Props) {
queryFn: () => getProjectApplications(projectId),
});

const [optimistic, setOptimistic] = useState<Record<UUID, ApplicationResponse["status"]>>(
{},
);
const [optimistic, setOptimistic] = useState<Record<UUID, ApplicationResponse["status"]>>({});

const apps = useMemo(() => {
if (!data) return [];
Expand Down Expand Up @@ -120,7 +118,9 @@ export function ApplicationsList({ projectId, className }: Props) {
<div className="flex items-center justify-between gap-3">
<div>
<p className="text-[13px] font-semibold text-foreground">Applications</p>
<p className="mt-1 text-[12px] text-muted-foreground">Review applicants and update status.</p>
<p className="mt-1 text-[12px] text-muted-foreground">
Review applicants and update status.
</p>
</div>
<div className="min-w-0">
<Input
Expand Down Expand Up @@ -267,4 +267,3 @@ export function ApplicationsList({ projectId, className }: Props) {
</Card>
);
}

20 changes: 8 additions & 12 deletions frontend/src/components/layout/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,7 @@ const groups: Group[] = [
},
{
label: "Account",
items: [
{ label: "Settings", to: "/settings", icon: <Settings size={16} /> },
],
items: [{ label: "Settings", to: "/settings", icon: <Settings size={16} /> }],
},
];

Expand Down Expand Up @@ -98,7 +96,7 @@ export function Sidebar({ open, onClose }: { open: boolean; onClose: () => void
<span className="text-[18px] font-bold tracking-tight text-foreground">DevLink</span>
</Link>

<nav className="flex-1 overflow-y-auto px-3 pb-4">
<nav className="flex-1 overflow-y-auto px-3 pb-4 [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]">
{groups.map((group) => (
<SidebarGroup key={group.label} group={group} onNav={onClose} />
))}
Expand Down Expand Up @@ -147,10 +145,7 @@ function SidebarGroup({ group, onNav }: { group: Group; onNav: () => void }) {
className="flex w-full items-center justify-between px-2 py-1 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground hover:text-foreground"
>
{group.label}
<ChevronRight
size={12}
className={cn("transition-transform", open && "rotate-90")}
/>
<ChevronRight size={12} className={cn("transition-transform", open && "rotate-90")} />
</button>
<AnimatePresence initial={false}>
{open && (
Expand All @@ -161,17 +156,18 @@ function SidebarGroup({ group, onNav }: { group: Group; onNav: () => void }) {
className="overflow-hidden"
>
{group.items.map((item) => {
const active = pathname === item.to || pathname.startsWith(item.to.split("?")[0] + "/");
const active =
pathname === item.to || pathname.startsWith(item.to.split("?")[0] + "/");
return (
<li key={item.label}>
<Link
to={item.to.split("?")[0]}
onClick={onNav}
className={cn(
"mt-0.5 flex items-center gap-2.5 rounded-md px-2 py-1.5 text-[13px] font-medium transition-colors",
"mt-0.5 flex items-center gap-2.5 rounded-md px-2 py-2 text-[13px] font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-primary",
active
? "bg-primary-soft text-primary"
: "text-foreground/80 hover:bg-sidebar-accent hover:text-foreground",
? "bg-primary-soft font-semibold text-primary"
: "text-foreground/80 hover:bg-sidebar-accent hover:text-foreground focus:bg-sidebar-accent",
)}
>
<span className={cn(active ? "text-primary" : "text-muted-foreground")}>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/shared/BackButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ export function BackButton({ to, label }: BackButtonProps) {
{label}
</Link>
);
}
}
56 changes: 45 additions & 11 deletions frontend/src/components/shared/Markdown.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable */
import { memo } from "react";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
Expand All @@ -22,7 +23,12 @@ export const Markdown = memo(function Markdown({ content, className }: MarkdownP
if (!content?.trim()) return null;

return (
<div className={cn("max-w-none break-words text-[13px] leading-relaxed text-foreground", className)}>
<div
className={cn(
"max-w-none break-words text-[13px] leading-relaxed text-foreground",
className,
)}
>
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[[rehypeSanitize, schema]]}
Expand All @@ -37,7 +43,11 @@ export const Markdown = memo(function Markdown({ content, className }: MarkdownP
),
img: ({ node, ...props }) => (
// eslint-disable-next-line jsx-a11y/alt-text
<img {...props} loading="lazy" className="my-2 max-w-full rounded-md border border-border" />
<img
{...props}
loading="lazy"
className="my-2 max-w-full rounded-md border border-border"
/>
),
h1: ({ node, ...props }) => (
<h1 {...props} className="mt-4 mb-2 text-[19px] font-bold text-foreground first:mt-0" />
Expand All @@ -46,17 +56,30 @@ export const Markdown = memo(function Markdown({ content, className }: MarkdownP
<h2 {...props} className="mt-4 mb-2 text-[17px] font-bold text-foreground first:mt-0" />
),
h3: ({ node, ...props }) => (
<h3 {...props} className="mt-3 mb-1.5 text-[15px] font-semibold text-foreground first:mt-0" />
<h3
{...props}
className="mt-3 mb-1.5 text-[15px] font-semibold text-foreground first:mt-0"
/>
),
h4: ({ node, ...props }) => (
<h4 {...props} className="mt-3 mb-1.5 text-[14px] font-semibold text-foreground first:mt-0" />
<h4
{...props}
className="mt-3 mb-1.5 text-[14px] font-semibold text-foreground first:mt-0"
/>
),
p: ({ node, ...props }) => <p {...props} className="mb-2 last:mb-0" />,
ul: ({ node, ...props }) => <ul {...props} className="mb-2 ml-5 list-disc space-y-1 last:mb-0" />,
ol: ({ node, ...props }) => <ol {...props} className="mb-2 ml-5 list-decimal space-y-1 last:mb-0" />,
ul: ({ node, ...props }) => (
<ul {...props} className="mb-2 ml-5 list-disc space-y-1 last:mb-0" />
),
ol: ({ node, ...props }) => (
<ol {...props} className="mb-2 ml-5 list-decimal space-y-1 last:mb-0" />
),
li: ({ node, ...props }) => <li {...props} className="pl-0.5" />,
blockquote: ({ node, ...props }) => (
<blockquote {...props} className="my-2 border-l-2 border-border pl-3 text-muted-foreground" />
<blockquote
{...props}
className="my-2 border-l-2 border-border pl-3 text-muted-foreground"
/>
),
hr: () => <hr className="my-4 border-border" />,
table: ({ node, ...props }) => (
Expand All @@ -66,14 +89,22 @@ export const Markdown = memo(function Markdown({ content, className }: MarkdownP
),
thead: ({ node, ...props }) => <thead {...props} className="bg-muted" />,
th: ({ node, ...props }) => (
<th {...props} className="border-b border-border px-3 py-1.5 font-semibold text-foreground" />
<th
{...props}
className="border-b border-border px-3 py-1.5 font-semibold text-foreground"
/>
),
td: ({ node, ...props }) => (
<td {...props} className="border-b border-border px-3 py-1.5 align-top" />
),
td: ({ node, ...props }) => <td {...props} className="border-b border-border px-3 py-1.5 align-top" />,
code: ({ node, className: codeClassName, children, ...props }) => {
const isBlock = /language-/.test(codeClassName ?? "");
if (!isBlock) {
return (
<code {...props} className="rounded bg-muted px-1 py-0.5 font-mono text-[12px] text-foreground">
<code
{...props}
className="rounded bg-muted px-1 py-0.5 font-mono text-[12px] text-foreground"
>
{children}
</code>
);
Expand All @@ -85,7 +116,10 @@ export const Markdown = memo(function Markdown({ content, className }: MarkdownP
);
},
pre: ({ node, ...props }) => (
<pre {...props} className="my-2 overflow-x-auto rounded-md border border border-border bg-muted p-3 text-[12px]" />
<pre
{...props}
className="my-2 overflow-x-auto rounded-md border border border-border bg-muted p-3 text-[12px]"
/>
),
}}
>
Expand Down
9 changes: 2 additions & 7 deletions frontend/src/components/shared/primitives.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,11 @@ export function SectionHeader({
<h3 className="text-[14px] font-semibold text-foreground">{title}</h3>
{action &&
(actionTo ? (
<Link
to={actionTo}
className="text-[12px] font-medium text-primary hover:underline"
>
<Link to={actionTo} className="text-[12px] font-medium text-primary hover:underline">
{action}
</Link>
) : (
<button className="text-[12px] font-medium text-primary hover:underline">
{action}
</button>
<button className="text-[12px] font-medium text-primary hover:underline">{action}</button>
))}
</div>
);
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/features/dashboard/GreetingHero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ function MiniStat({
<p className="truncate text-[11px] font-medium text-muted-foreground">{label}</p>
<p className="text-[15px] font-bold text-foreground">
<span className={valueTint}>{value}</span>
{suffix && <span className="text-[12px] font-medium text-muted-foreground">{suffix}</span>}
{suffix && (
<span className="text-[12px] font-medium text-muted-foreground">{suffix}</span>
)}
</p>
{progress !== undefined && (
<div className="mt-1 h-1 w-full overflow-hidden rounded-full bg-muted">
Expand Down
Loading