Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
14 changes: 6 additions & 8 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,13 @@ jobs:
with:
node-version: 20
cache: 'npm'
cache-dependency-path: apps/web/package-lock.json
cache-dependency-path: package-lock.json
- name: Install dependencies
run: npm install --prefix apps/web
run: npm ci
- name: Lint
run: npm run lint --prefix apps/web
run: npm run lint -w web
- name: Build
run: npm run build --prefix apps/web
run: npm run build -w web

playwright-e2e:
name: E2E Tests (Playwright)
Expand All @@ -85,10 +85,8 @@ jobs:
node-version: 20
cache: 'npm'
cache-dependency-path: package-lock.json
- name: Install root dependencies
run: npm install
- name: Install web dependencies
run: npm install --prefix apps/web
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps chromium
- name: Run E2E tests
Expand Down
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
legacy-peer-deps=true
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

139 changes: 121 additions & 18 deletions apps/web/app/globals.css
Original file line number Diff line number Diff line change
@@ -1,38 +1,119 @@
@import "tailwindcss";

:root {
--background: #f8f5ef;
--foreground: #0f172a;
--font-sans-stack: "Avenir Next", "Segoe UI", sans-serif;
--background: #f7f4ed;
--foreground: #112032;
--card: rgba(255, 250, 243, 0.9);
--card-foreground: #112032;
--popover: rgba(255, 250, 243, 0.98);
--popover-foreground: #112032;
--primary: #0d7c66;
--primary-foreground: #effcf8;
--secondary: #e7dfd0;
--secondary-foreground: #112032;
--muted: #efe7db;
--muted-foreground: #536273;
--accent: #e3f6f1;
--accent-foreground: #0f4f43;
--destructive: #c65353;
--destructive-foreground: #fff8f7;
--border: rgba(17, 32, 50, 0.12);
--input: rgba(17, 32, 50, 0.16);
--ring: rgba(13, 124, 102, 0.42);
--chart-1: #0d7c66;
--chart-2: #e29a2f;
--chart-3: #173b63;
--chart-4: #58b89b;
--chart-5: #9d6132;
--radius: 1.25rem;
--font-sans-stack: "Sora", "Avenir Next", "Segoe UI", sans-serif;
--font-mono-stack: "SFMono-Regular", "SF Mono", "Roboto Mono", monospace;
}

@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--radius-sm: calc(var(--radius) - 8px);
--radius-md: calc(var(--radius) - 4px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--font-sans: var(--font-sans-stack);
--font-mono: var(--font-mono-stack);
}

@media (prefers-color-scheme: dark) {
:root {
--background: #0f172a;
--foreground: #f8fafc;
}
.dark {
--background: #09131f;
--foreground: #edf6f7;
--card: rgba(10, 21, 34, 0.84);
--card-foreground: #edf6f7;
--popover: rgba(9, 19, 31, 0.96);
--popover-foreground: #edf6f7;
--primary: #45c1a2;
--primary-foreground: #052c24;
--secondary: rgba(89, 111, 131, 0.22);
--secondary-foreground: #edf6f7;
--muted: rgba(89, 111, 131, 0.2);
--muted-foreground: #98a8b8;
--accent: rgba(69, 193, 162, 0.14);
--accent-foreground: #d8fbf2;
--destructive: #f28585;
--destructive-foreground: #20090a;
--border: rgba(237, 246, 247, 0.1);
--input: rgba(237, 246, 247, 0.12);
--ring: rgba(69, 193, 162, 0.3);
--chart-1: #45c1a2;
--chart-2: #f4b860;
--chart-3: #87aef2;
--chart-4: #4f8b7a;
--chart-5: #ff9770;
}

* {
box-sizing: border-box;
}
@layer base {
* {
@apply border-border;
}

html {
scroll-behavior: smooth;
}

body {
@apply bg-background text-foreground;
min-height: 100vh;
font-family: var(--font-sans-stack);
background-image:
radial-gradient(circle at top left, rgba(226, 154, 47, 0.18), transparent 24%),
radial-gradient(circle at 90% 18%, rgba(13, 124, 102, 0.14), transparent 22%),
linear-gradient(180deg, rgba(247, 244, 237, 0.98), rgba(240, 245, 247, 0.95));
}

html {
scroll-behavior: smooth;
.dark body {
background-image:
radial-gradient(circle at top left, rgba(242, 183, 90, 0.12), transparent 20%),
radial-gradient(circle at 88% 16%, rgba(69, 193, 162, 0.16), transparent 22%),
linear-gradient(180deg, rgba(9, 19, 31, 0.98), rgba(8, 17, 29, 0.96));
}
}

body {
background: var(--background);
color: var(--foreground);
font-family: var(--font-sans-stack);
* {
box-sizing: border-box;
}

a {
Expand All @@ -41,5 +122,27 @@ a {
}

::selection {
background: rgba(245, 158, 11, 0.28);
background: rgba(69, 193, 162, 0.28);
}

.glass-surface {
background: color-mix(in srgb, var(--card) 82%, transparent);
backdrop-filter: blur(18px);
}

.noise-overlay {
position: relative;
}

.noise-overlay::before {
content: "";
position: absolute;
inset: 0;
pointer-events: none;
background-image:
linear-gradient(rgba(255, 255, 255, 0.024) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.024) 1px, transparent 1px);
background-size: 24px 24px;
mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0.9), transparent);
opacity: 0.35;
}
6 changes: 3 additions & 3 deletions apps/web/app/jobs/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ export default function JobDetailsPage() {
setBusyAction(`accept-${bidId}`);

try {
await api.bids.accept(id, bidId, {
const acceptedJob = await api.bids.accept(id, bidId, {
client_address: workspace.job.client_address,
});
await workspace.refresh();
router.push(`/jobs/${id}/fund`);
void workspace.refresh();
router.push(`/jobs/${acceptedJob.id}/fund`);
} catch {
alert("Failed to accept bid");
} finally {
Expand Down
18 changes: 13 additions & 5 deletions apps/web/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import type { Metadata } from "next";
import "./globals.css";
import { DashboardLayout } from "@/components/layout/dashboard-layout";
import { Providers } from "@/components/providers";
import { ToastProvider } from "@/components/ui/toast-provider";

export const metadata: Metadata = {
title: "Lance - Decentralized Freelance Marketplace",
description: "Stellar-native freelance marketplace with AI-powered dispute resolution",
title: "Lance | Soroban Freelance Intelligence",
description:
"Soroban-native freelance operations with escrow, reputation, and dispute intelligence.",
};

export default function RootLayout({
Expand All @@ -13,10 +16,15 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className="antialiased">
<ToastProvider>{children}</ToastProvider>
<html lang="en" suppressHydrationWarning>
<body className="bg-background text-foreground antialiased">
<Providers>
<ToastProvider>
<DashboardLayout>{children}</DashboardLayout>
</ToastProvider>
</Providers>
</body>
</html>
);
}

111 changes: 2 additions & 109 deletions apps/web/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,5 @@
import Link from "next/link";
import { ArrowRight, BriefcaseBusiness, Gavel, ShieldCheck, Star } from "lucide-react";
import { SiteShell } from "@/components/site-shell";

const highlights = [
{
title: "Trustless Profiles",
description:
"Blend editable bios and portfolio links with Soroban reputation math so serious freelancers can market verified credibility everywhere.",
href: "/profile/GD...CLIENT",
icon: Star,
},
{
title: "Live Job Workspaces",
description:
"Keep both sides aligned around milestones, evidence, escrow state, and payout actions inside a single shared dashboard.",
href: "/jobs",
icon: BriefcaseBusiness,
},
{
title: "Neutral Dispute Center",
description:
"Explain evidence, AI reasoning, and final payout splits with courtroom-level clarity once cooperation breaks down.",
href: "/jobs",
icon: Gavel,
},
];
import { RoleOverview } from "@/components/dashboard/role-overview";

export default function Home() {
return (
Expand All @@ -33,89 +8,7 @@ export default function Home() {
title="Premium freelance execution with escrow, verifiable reputation, and transparent AI arbitration."
description="Lance is the surface layer for serious clients and elite independents who want payment security, immutable trust signals, and fast dispute resolution without losing clarity."
>
<div className="grid gap-6 lg:grid-cols-[1.4fr_0.9fr]">
<section className="rounded-[2rem] border border-slate-200 bg-white/85 p-8 shadow-[0_30px_80px_-48px_rgba(15,23,42,0.55)] sm:p-10">
<div className="flex flex-col gap-6">
<div className="inline-flex w-fit items-center gap-2 rounded-full border border-amber-200 bg-amber-50 px-4 py-2 text-xs font-semibold uppercase tracking-[0.24em] text-amber-800">
Trust by design
</div>
<h2 className="max-w-2xl text-3xl font-semibold tracking-tight text-slate-950 sm:text-4xl">
Every page is built to make strong operators look stronger.
</h2>
<p className="max-w-2xl text-base leading-7 text-slate-600">
Public profiles become acquisition funnels, active jobs become
command centers, and disputes become legible instead of chaotic.
</p>
<div className="flex flex-col gap-3 sm:flex-row">
<Link
href="/jobs"
className="inline-flex items-center justify-center gap-2 rounded-full bg-slate-950 px-6 py-3 text-sm font-semibold text-white transition hover:bg-slate-800"
>
Explore Job Board
<ArrowRight className="h-4 w-4" />
</Link>
<Link
href="/jobs/new"
className="inline-flex items-center justify-center rounded-full border border-slate-200 px-6 py-3 text-sm font-semibold text-slate-700 transition hover:border-amber-300 hover:text-slate-950"
>
Post a Job
</Link>
</div>
</div>
</section>

<aside className="rounded-[2rem] border border-slate-200/80 bg-slate-950 p-8 text-slate-50 shadow-[0_30px_80px_-48px_rgba(15,23,42,0.8)]">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-amber-300">
Release posture
</p>
<div className="mt-6 space-y-5">
<div>
<p className="text-4xl font-semibold">4</p>
<p className="mt-1 text-sm text-slate-300">
Core surfaces now aligned: profiles, marketplace, job overview,
and dispute resolution.
</p>
</div>
<div className="rounded-3xl border border-white/10 bg-white/5 p-5">
<div className="flex items-center gap-3">
<ShieldCheck className="h-5 w-5 text-amber-300" />
<p className="text-sm font-medium">Escrow-first workflow</p>
</div>
<p className="mt-3 text-sm leading-6 text-slate-300">
Fund milestones, upload proof, approve releases, or escalate
into a locked dispute flow with on-chain receipts.
</p>
</div>
</div>
</aside>
</div>

<section className="mt-10 grid gap-5 lg:grid-cols-3">
{highlights.map((item) => {
const Icon = item.icon;
return (
<Link
key={item.title}
href={item.href}
className="group rounded-[1.75rem] border border-slate-200 bg-white/80 p-6 transition hover:-translate-y-1 hover:border-amber-300 hover:shadow-[0_25px_60px_-40px_rgba(15,23,42,0.45)]"
>
<div className="flex h-12 w-12 items-center justify-center rounded-2xl bg-amber-100 text-amber-700">
<Icon className="h-5 w-5" />
</div>
<h3 className="mt-5 text-xl font-semibold text-slate-950">
{item.title}
</h3>
<p className="mt-3 text-sm leading-6 text-slate-600">
{item.description}
</p>
<span className="mt-5 inline-flex items-center gap-2 text-sm font-semibold text-slate-950">
Open surface
<ArrowRight className="h-4 w-4 transition group-hover:translate-x-1" />
</span>
</Link>
);
})}
</section>
<RoleOverview />
</SiteShell>
);
}
Loading
Loading