diff --git a/package-lock.json b/package-lock.json index f491b860..d337af16 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,6 +49,7 @@ "eslint": "^9.39.4", "eslint-config-next": "^16.2.6", "husky": "^9.1.7", + "kill-port": "^2.0.1", "lint-staged": "^15.2.10", "postcss": "^8.5.15", "prettier": "^3.3.3", @@ -8820,6 +8821,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-them-args": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/get-them-args/-/get-them-args-1.3.2.tgz", + "integrity": "sha512-LRn8Jlk+DwZE4GTlDbT3Hikd1wSHgLMme/+7ddlqKd7ldwR6LjJgTVWzBnR01wnYGe4KgrXjg287RaI22UHmAw==", + "dev": true, + "license": "MIT" + }, "node_modules/get-tsconfig": { "version": "4.14.0", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", @@ -10022,6 +10030,20 @@ "json-buffer": "3.0.1" } }, + "node_modules/kill-port": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/kill-port/-/kill-port-2.0.1.tgz", + "integrity": "sha512-e0SVOV5jFo0mx8r7bS29maVWp17qGqLBZ5ricNSajON6//kmb7qqqNnml4twNE8Dtj97UQD+gNFOaipS/q1zzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-them-args": "1.3.2", + "shell-exec": "1.0.2" + }, + "bin": { + "kill-port": "cli.js" + } + }, "node_modules/language-subtag-registry": { "version": "0.3.23", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", @@ -12343,6 +12365,13 @@ "node": ">=8" } }, + "node_modules/shell-exec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/shell-exec/-/shell-exec-1.0.2.tgz", + "integrity": "sha512-jyVd+kU2X+mWKMmGhx4fpWbPsjvD53k9ivqetutVW/BQ+WIZoDoP4d8vUMGezV6saZsiNoW2f9GIhg9Dondohg==", + "dev": true, + "license": "MIT" + }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", diff --git a/src/app/(app)/app-navbar.tsx b/src/app/(app)/app-navbar.tsx new file mode 100644 index 00000000..fdc0db54 --- /dev/null +++ b/src/app/(app)/app-navbar.tsx @@ -0,0 +1,39 @@ +import Link from 'next/link'; +import { Anchor } from 'lucide-react'; +import { CommandPalette } from '@/components/command-palette'; +import { LogoutButton } from './logout-button'; + +const NAV_LINKS = [ + { label: 'Dashboard', href: '/dashboard' }, + { label: 'Issues', href: '/issues' }, + { label: 'My PRs', href: '/my-prs' }, + { label: 'Leaderboard', href: '/leaderboard' }, +]; + +export function AppNavbar({ handle }: { handle: string | null }) { + return ( +
+
+ + + MergeShip + + +
+ +
+
+ +
+ {handle && {handle}} + +
+
+ ); +} diff --git a/src/app/(app)/app-shell.css b/src/app/(app)/app-shell.css new file mode 100644 index 00000000..0bec6942 --- /dev/null +++ b/src/app/(app)/app-shell.css @@ -0,0 +1,376 @@ +/* Authenticated app — split-panel shell matching landing hero */ + +.app-shell { + --ms-cream: #f2f0eb; + --ms-ink: #111110; + --ms-muted: #b8b3aa; + --ms-muted-dark: #8a877e; + --ms-green: #16a34a; + --ms-border: #2a2a28; + --ms-border-subtle: rgba(242, 240, 235, 0.08); + --ms-card: #1a1a18; + --ms-card-hover: #222220; + + min-height: 100%; + background: var(--ms-ink); + color: var(--ms-cream); + font-family: var(--font-dm-sans), 'DM Sans', sans-serif; + font-weight: 300; + font-size: 16px; + line-height: 1.55; + -webkit-font-smoothing: antialiased; +} + +.app-shell ::selection { + background: rgba(22, 163, 74, 0.35); + color: var(--ms-cream); +} + +.app-shell::-webkit-scrollbar { + width: 6px; +} +.app-shell::-webkit-scrollbar-track { + background: var(--ms-ink); +} +.app-shell::-webkit-scrollbar-thumb { + background: #3a3a36; + border-radius: 0; +} +.app-shell::-webkit-scrollbar-thumb:hover { + background: #4a4a46; +} + +/* Page layout */ +.app-page { + padding: 48px 56px 64px; + max-width: 1280px; +} + +@media (max-width: 768px) { + .app-page { + padding: 32px 24px 48px; + } +} + +.app-page-header { + display: flex; + flex-direction: column; + gap: 24px; + margin-bottom: 48px; + padding-bottom: 24px; + border-bottom: 1px solid var(--ms-border); +} + +@media (min-width: 768px) { + .app-page-header { + flex-direction: row; + align-items: flex-end; + justify-content: space-between; + } +} + +.app-eyebrow { + font-family: var(--font-dm-mono), 'DM Mono', monospace; + font-size: 11px; + font-weight: 400; + text-transform: uppercase; + letter-spacing: 0.12em; + color: var(--ms-muted-dark); + margin-bottom: 16px; +} + +.app-title { + font-family: var(--font-dm-serif), 'DM Serif Display', serif; + font-size: clamp(2.25rem, 4vw, 4.5rem); + font-weight: 400; + line-height: 0.95; + letter-spacing: -0.04em; + color: var(--ms-cream); +} + +.app-title-sm { + font-family: var(--font-dm-serif), 'DM Serif Display', serif; + font-size: clamp(1.75rem, 3vw, 2.5rem); + font-weight: 400; + line-height: 1; + letter-spacing: -0.03em; + color: var(--ms-cream); +} + +.app-lead { + font-size: 1.05rem; + line-height: 1.55; + color: var(--ms-muted); + max-width: 560px; +} + +.app-body { + color: var(--ms-muted); + font-size: 0.95rem; + line-height: 1.55; +} + +.app-section-label { + font-family: var(--font-dm-mono), 'DM Mono', monospace; + font-size: 10px; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.14em; + color: var(--ms-muted-dark); + margin-bottom: 12px; +} + +.app-card { + background: var(--ms-card); + border: 1px solid var(--ms-border); + transition: + border-color 0.2s ease, + background 0.2s ease; +} + +.app-card:hover { + border-color: #3a3a36; + background: var(--ms-card-hover); +} + +.app-divider { + border-color: var(--ms-border); +} + +.app-footer { + margin-top: 96px; + padding-top: 24px; + border-top: 1px solid var(--ms-border); + display: flex; + flex-wrap: wrap; + justify-content: space-between; + gap: 16px; + font-family: var(--font-dm-mono), 'DM Mono', monospace; + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.1em; + color: var(--ms-muted-dark); +} + +.app-footer a { + color: var(--ms-muted-dark); + text-decoration: none; + transition: color 0.15s ease; +} + +.app-footer a:hover { + color: var(--ms-cream); +} + +/* Split navbar — cream left / black right, aligned with sidebar + main */ +.app-navbar { + display: flex; + height: 64px; + z-index: 50; +} + +.app-navbar-left { + display: flex; + width: 16rem; + shrink: 0; + align-items: center; + gap: 28px; + background: var(--ms-cream); + color: var(--ms-ink); + padding: 0 20px 0 24px; + border-bottom: 1px solid #ddd9d0; +} + +.app-navbar-right { + display: flex; + flex: 1; + align-items: center; + justify-content: flex-end; + gap: 20px; + background: var(--ms-ink); + color: var(--ms-cream); + padding: 0 24px 0 32px; + border-bottom: 1px solid var(--ms-border); +} + +.app-navbar-logo { + display: flex; + align-items: center; + gap: 10px; + text-decoration: none; + color: var(--ms-ink); + flex-shrink: 0; +} + +.app-navbar-wordmark { + font-family: var(--font-dm-serif), 'DM Serif Display', serif; + font-size: 1.25rem; + letter-spacing: -0.01em; + line-height: 1; +} + +.app-navbar-links { + align-items: center; + gap: 28px; + min-width: 0; +} + +.app-navbar-link { + font-family: var(--font-dm-mono), 'DM Mono', monospace; + font-size: 0.7rem; + font-weight: 400; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--ms-ink); + text-decoration: none; + position: relative; + padding: 4px 0; + white-space: nowrap; + transition: color 0.15s ease; +} + +.app-navbar-link::after { + content: ''; + position: absolute; + left: 0; + bottom: 0; + height: 1px; + width: 0; + background: var(--ms-ink); + transition: width 220ms ease; +} + +.app-navbar-link:hover::after { + width: 100%; +} + +.app-navbar-user { + font-family: var(--font-dm-mono), 'DM Mono', monospace; + font-size: 0.72rem; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--ms-muted); + max-width: 140px; +} + +.app-navbar-btn { + font-family: var(--font-dm-mono), 'DM Mono', monospace; + font-size: 0.72rem; + font-weight: 400; + text-transform: uppercase; + letter-spacing: 0.1em; + padding: 11px 18px; + border: 1px solid var(--ms-cream); + background: transparent; + color: var(--ms-cream); + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 8px; + transition: + background 0.2s ease, + color 0.2s ease; + white-space: nowrap; +} + +.app-navbar-btn:hover { + background: var(--ms-cream); + color: var(--ms-ink); +} + +/* Sidebar */ +.app-sidebar { + background: var(--ms-cream); + color: var(--ms-ink); + border-right: 1px solid #ddd9d0; +} + +.app-sidebar-divider { + height: 1px; + background: #ddd9d0; +} + +.app-nav-section { + padding: 0 12px 10px; + font-family: var(--font-dm-mono), 'DM Mono', monospace; + font-size: 9px; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.14em; + color: var(--ms-green); +} + +.app-nav-item { + display: flex; + align-items: center; + gap: 9px; + padding: 7px 10px 7px 8px; + font-family: var(--font-dm-mono), 'DM Mono', monospace; + font-size: 11px; + font-weight: 400; + letter-spacing: 0.07em; + text-transform: uppercase; + text-decoration: none; + color: var(--ms-ink); + border-left: 2px solid transparent; + transition: + background 0.15s ease, + color 0.15s ease, + border-color 0.15s ease; +} + +.app-nav-item svg { + width: 14px; + height: 14px; + flex-shrink: 0; + color: #9b9790; + transition: color 0.15s ease; +} + +.app-nav-item:hover:not(.app-nav-item--active) { + background: rgba(17, 17, 16, 0.05); + color: var(--ms-ink); +} + +.app-nav-item:hover:not(.app-nav-item--active) svg { + color: #6b6860; +} + +.app-nav-item--active { + background: var(--ms-ink); + color: var(--ms-cream); + border-left-color: var(--ms-green); +} + +.app-nav-item--active svg { + color: var(--ms-green); +} + +.app-sidebar-action { + display: flex; + width: 100%; + align-items: center; + gap: 8px; + padding: 7px 10px; + font-family: var(--font-dm-mono), 'DM Mono', monospace; + font-size: 11px; + font-weight: 400; + letter-spacing: 0.07em; + text-transform: uppercase; + color: #6b6860; + background: none; + border: none; + cursor: pointer; + text-align: left; + transition: color 0.15s ease; +} + +.app-sidebar-action:hover { + color: var(--ms-ink); +} + +.app-sidebar-action svg { + width: 13px; + height: 13px; + flex-shrink: 0; +} diff --git a/src/app/(app)/dashboard/loading.tsx b/src/app/(app)/dashboard/loading.tsx index a0ee2b61..9067afd9 100644 --- a/src/app/(app)/dashboard/loading.tsx +++ b/src/app/(app)/dashboard/loading.tsx @@ -1,6 +1,6 @@ export default function DashboardLoading() { return ( -
+
diff --git a/src/app/(app)/dashboard/page.tsx b/src/app/(app)/dashboard/page.tsx index 09928dff..d23b4c42 100644 --- a/src/app/(app)/dashboard/page.tsx +++ b/src/app/(app)/dashboard/page.tsx @@ -115,68 +115,26 @@ export default async function DashboardPage() { - {/* Three-column layout */} -
- {/* ── Left Sidebar ── */} -
- }> - - -
- - {/* ── Center Feed ── */} -
- {/* Journey progress */} - }> - - - - {/* Recent XP activity */} - }> - - - - {/* Active issues */} + {/* Main Columns */} +
+ {/* Left Column */} +
}> - {/* GitHub PRs */} - }> - - - - {/* Contribution heatmap */} - }> - - - - {/* Daily challenge */} - - - {/* Course progression */} - - - {/* Mentees */} }> +
- {/* Trending Repos */} - }> - - -
- - {/* ── Right Sidebar ── */} -
- }> - + {/* Right Column */} +
+ }> + - - {/* Leaderboard */} }> - + {/* Repository Matches */} @@ -188,33 +146,26 @@ export default async function DashboardPage() {
- - {/* Footer */} -
- ©{new Date().getFullYear()} ARCH_06 / SYSTEM_v1.0 -
- - TERMS - - - PRIVACY - - - SECURITY - -
-
+ +
+ © {new Date().getFullYear()} MergeShip +
+ Terms + Privacy + Security +
+
); } function NotConfigured() { return ( -
+
-

Dashboard not configured

-

Auth isn't wired on this deployment yet.

+

Dashboard not configured

+

Auth isn't wired on this deployment yet.

); diff --git a/src/app/(app)/dashboard/sync-button.tsx b/src/app/(app)/dashboard/sync-button.tsx index d8857891..16cb2de9 100644 --- a/src/app/(app)/dashboard/sync-button.tsx +++ b/src/app/(app)/dashboard/sync-button.tsx @@ -87,14 +87,18 @@ export function SyncButton({ lastSyncedAt }: Props) { - + {formatSyncedAt(localSyncedAt)} diff --git a/src/app/(app)/error.tsx b/src/app/(app)/error.tsx index dafd0ad6..77fc30e9 100644 --- a/src/app/(app)/error.tsx +++ b/src/app/(app)/error.tsx @@ -2,21 +2,16 @@ export default function Error({ reset }: { reset: () => void }) { return ( -
-
-
- System Error -
- -

Something went wrong.

- -

- An unexpected error occurred while loading this page. -

- +
+
+

System Error

+

Something went wrong.

+

An unexpected error occurred while loading this page.

diff --git a/src/app/(app)/help-inbox/loading.tsx b/src/app/(app)/help-inbox/loading.tsx index e6ab74d8..bb8f1645 100644 --- a/src/app/(app)/help-inbox/loading.tsx +++ b/src/app/(app)/help-inbox/loading.tsx @@ -1,6 +1,6 @@ export default function HelpInboxPageLoading() { return ( -
+
diff --git a/src/app/(app)/help-inbox/page.tsx b/src/app/(app)/help-inbox/page.tsx index 73cbdd30..76eabaf4 100644 --- a/src/app/(app)/help-inbox/page.tsx +++ b/src/app/(app)/help-inbox/page.tsx @@ -56,7 +56,7 @@ export default async function HelpInboxPage() { const sb = await getServerSupabase(); if (!sb) { return ( -
+

Service not configured.

); @@ -70,7 +70,7 @@ export default async function HelpInboxPage() { const service = getServiceSupabase(); if (!service) { return ( -
+

Service role not configured.

); @@ -159,7 +159,7 @@ export default async function HelpInboxPage() { } return ( -
+

Help inbox

diff --git a/src/app/(app)/issues/loading.tsx b/src/app/(app)/issues/loading.tsx index 4e37d73d..b6fffc3f 100644 --- a/src/app/(app)/issues/loading.tsx +++ b/src/app/(app)/issues/loading.tsx @@ -1,6 +1,6 @@ export default function IssuesPageLoading() { return ( -

+
diff --git a/src/app/(app)/issues/page.tsx b/src/app/(app)/issues/page.tsx index eda5d2ce..bb40ce88 100644 --- a/src/app/(app)/issues/page.tsx +++ b/src/app/(app)/issues/page.tsx @@ -20,7 +20,9 @@ export default async function IssuesPage({ searchParams }: { searchParams: Searc const sb = await getServerSupabase(); if (!sb) return ( -
Not configured
+
+

Not configured

+
); const { @@ -111,24 +113,22 @@ export default async function IssuesPage({ searchParams }: { searchParams: Searc const repoOptions: RepoOption[] = repoResult.ok ? repoResult.data : []; return ( -
-
-
-
- 02 / ISSUES -
-

Browse Issues

-
- - {linkedRecs.length > 0 && ( - - )} - - -
+
+
+
+

02 / Issues

+

Browse Issues

+
+
+ + {linkedRecs.length > 0 && ( + + )} + +
); } diff --git a/src/app/(app)/layout.tsx b/src/app/(app)/layout.tsx index cbc0a441..41844dd6 100644 --- a/src/app/(app)/layout.tsx +++ b/src/app/(app)/layout.tsx @@ -1,4 +1,3 @@ -import Link from 'next/link'; import { redirect } from 'next/navigation'; import { getServerSupabase } from '@/lib/supabase/server'; import { getServiceSupabase } from '@/lib/supabase/service'; @@ -7,7 +6,8 @@ import { LogoutButton } from './logout-button'; import { CommandPalette } from '@/components/command-palette'; import { isUserMaintainer } from '@/lib/maintainer/detect'; import { ThemeToggle } from './theme-toggle'; -import { ToastProvider } from '@/components/toast'; +import { AppNavbar } from './app-navbar'; +import './app-shell.css'; export default async function AppLayout({ children }: { children: React.ReactNode }) { const sb = await getServerSupabase(); @@ -76,104 +76,58 @@ export default async function AppLayout({ children }: { children: React.ReactNod } return ( - -
- {/* Sidebar */} -