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
3 changes: 0 additions & 3 deletions src/app/(dashboard)/courses/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import Link from 'next/link';
import { requireAuth } from '@/lib/auth';
import { db } from '@/lib/db';
import { Card, CardHeader, CardTitle, CardDescription, Badge } from '@/components/ui';
import { BottomNav } from '@/components/ui/BottomNav';
import { ProgressStatus } from '@/lib/enums';
import styles from './courses.module.css';

Expand Down Expand Up @@ -91,8 +90,6 @@ export default async function CoursesIndexPage() {
);
})}
</section>

<BottomNav active="courses" />
</main>
);
}
8 changes: 2 additions & 6 deletions src/app/(dashboard)/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { redirect } from 'next/navigation';
import Link from 'next/link';
import { requireAuth } from '@/lib/auth';
import { db } from '@/lib/db';
import { Card, CardHeader, CardTitle, CardDescription, Badge, Button } from '@/components/ui';
import { Icon } from '@/components/ui/Icon';
import { BottomNav } from '@/components/ui/BottomNav';
import { ProgressStatus } from '@/lib/enums';
import styles from './dashboard.module.css';

Expand Down Expand Up @@ -104,7 +102,7 @@ export default async function DashboardPage() {

return (
<Card key={course.id} variant="interactive" padding="md">
<Link href={`/dashboard/courses/${course.slug}` as never} className={styles.courseLink}>
<Link href={`/courses/${course.slug}` as never} className={styles.courseLink}>
<CardHeader>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
<CardTitle>{course.title}</CardTitle>
Expand Down Expand Up @@ -134,12 +132,10 @@ export default async function DashboardPage() {
<p style={{ color: 'var(--ink-500)', marginBottom: 'var(--space-4)' }}>
Practice real Amazon Advertising Console workflows with synthetic data.
</p>
<Link href="/dashboard/tools" className={styles.ctaPrimary}>
<Link href="/tools" className={styles.ctaPrimary}>
Open the tools
</Link>
</section>

<BottomNav active="home" />
</main>
);
}
28 changes: 28 additions & 0 deletions src/app/(dashboard)/layout.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.shell {
display: flex;
min-height: 100vh;
background: var(--surface-0);
}

.main {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
}

.content {
flex: 1;
padding: var(--space-8);
max-width: 1280px;
width: 100%;
}

@media (max-width: 768px) {
.shell {
flex-direction: column;
}
.content {
padding: var(--space-4);
}
}
37 changes: 37 additions & 0 deletions src/app/(dashboard)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { requireAuth } from '@/lib/auth';
import { NavSidebar, type NavItem } from '@/components/ui/NavSidebar';
import { TopBar } from '@/components/ui/TopBar';
import styles from './layout.module.css';

const STUDENT_NAV_ITEMS: NavItem[] = [
{ href: '/dashboard', label: 'Dashboard', icon: 'House' },
{ href: '/courses', label: 'Courses', icon: 'BookOpen' },
{ href: '/tools', label: 'Tools', icon: 'Gear' },
{ href: '/live-classes', label: 'Live classes', icon: 'Video' },
{ href: '/certificates', label: 'Certificates', icon: 'GraduationCap' },
{ href: '/payments', label: 'Payments', icon: 'CreditCard' },
];

export default async function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
// requireAuth redirects to /auth/signin if not authenticated.
const user = await requireAuth();

return (
<div className={styles.shell}>
<NavSidebar
items={STUDENT_NAV_ITEMS}
homeHref="/dashboard"
brandSuffix=""
ariaLabel="Student navigation"
/>
<div className={styles.main}>
<TopBar user={user} />
<div className={styles.content}>{children}</div>
</div>
</div>
);
}
4 changes: 2 additions & 2 deletions src/app/(dashboard)/tools/[tool]/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default async function ToolRunnerPage({ params }: PageProps) {
<main className="container" style={{ padding: 'var(--space-8) 0' }}>
<h1>Could not start session</h1>
<p style={{ color: 'var(--danger)' }}>{result.error}</p>
<Link href={`/dashboard/tools/${toolSlug}` as never}>← Back to scenarios</Link>
<Link href={`/tools/${toolSlug}` as never}>← Back to scenarios</Link>
</main>
);
}
Expand All @@ -60,7 +60,7 @@ export default async function ToolRunnerPage({ params }: PageProps) {
return (
<main id="main-content" className="container" style={{ padding: 'var(--space-6) 0' }}>
<Link
href={`/dashboard/tools/${toolSlug}` as never}
href={`/tools/${toolSlug}` as never}
style={{ color: 'var(--ink-500)', fontSize: 'var(--text-sm)' }}
>
← {toolMeta.name} scenarios
Expand Down
6 changes: 3 additions & 3 deletions src/app/(dashboard)/tools/[tool]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default async function ToolDetailPage({ params }: PageProps) {
return (
<main id="main-content" className="container" style={{ padding: 'var(--space-8) 0' }}>
<header style={{ marginBottom: 'var(--space-8)' }}>
<Link href="/dashboard/tools" style={{ color: 'var(--ink-500)', fontSize: 'var(--text-sm)' }}>
<Link href="/tools" style={{ color: 'var(--ink-500)', fontSize: 'var(--text-sm)' }}>
← All tools
</Link>
<h1 style={{ margin: 'var(--space-2) 0', fontSize: 'var(--text-3xl)' }}>{toolMeta.name}</h1>
Expand All @@ -35,11 +35,11 @@ export default async function ToolDetailPage({ params }: PageProps) {

<section>
<h2 style={{ marginBottom: 'var(--space-4)', fontSize: 'var(--text-xl)' }}>Scenarios</h2>
<div className={styles.toolGrid}>
<div className={styles.scenarioGrid}>
{toolMeta.scenarios.map((scenario) => (
<Card key={scenario.id} variant="interactive" padding="md">
<Link
href={`/dashboard/tools/${toolMeta.slug}/${scenario.slug}` as never}
href={`/tools/${toolMeta.slug}/${scenario.slug}` as never}
className={styles.toolLink}
>
<CardHeader>
Expand Down
30 changes: 21 additions & 9 deletions src/app/(dashboard)/tools/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { redirect } from 'next/navigation';
import Link from 'next/link';
import { requireAuth } from '@/lib/auth';
import { Card, CardHeader, CardTitle, CardDescription, Badge } from '@/components/ui';
import { Icon, type PhosphorIconName } from '@/components/ui/Icon';
import { BottomNav } from '@/components/ui/BottomNav';
import { SCENARIOS as CB_SCENARIOS, BTV_SCENARIOS } from '@/engine/campaign-builder/scenarios';
import { SCENARIOS as BE_SCENARIOS } from '@/engine/bid-elevator/scenarios';
import { SCENARIOS as STR_SCENARIOS } from '@/engine/str-triage/scenarios';
Expand Down Expand Up @@ -63,21 +61,37 @@ export const metadata = {

export default async function ToolsIndexPage() {
const user = await requireAuth();
const heroTool = TOOLS[0];
if (!heroTool) throw new Error('TOOLS must have at least one entry');
const restTools = TOOLS.slice(1);

return (
<main id="main-content" className="container" style={{ padding: 'var(--space-8) 0' }}>
<header style={{ marginBottom: 'var(--space-8)' }}>
<h1 style={{ marginBottom: 'var(--space-2)' }}>Practice tools</h1>
<p style={{ color: 'var(--ink-500)', maxWidth: '640px' }}>
Each tool mirrors a real Amazon Advertising Console workflow. Pick a tool, then pick a scenario.
Your work saves automatically come back anytime.
Each tool mirrors a real Amazon Advertising Console workflow. Pick a tool, then pick
a scenario. Your work saves automatically, so you can come back anytime.
</p>
</header>

<Card key={heroTool.id} variant="interactive" padding="lg" className={styles.heroCard}>
<Link href={`/tools/${heroTool.id}` as never} className={styles.toolLink}>
<CardHeader>
<div className={styles.toolHeader}>
<Icon name={heroTool.icon} size="xl" />
<Badge variant="default">{heroTool.scenarioCount} scenarios</Badge>
</div>
<CardTitle className={styles.heroTitle}>{heroTool.name}</CardTitle>
<CardDescription>{heroTool.description}</CardDescription>
</CardHeader>
</Link>
</Card>

<section className={styles.toolGrid}>
{TOOLS.map((tool) => (
{restTools.map((tool) => (
<Card key={tool.id} variant="interactive" padding="lg">
<Link href={`/dashboard/tools/${tool.id}` as never} className={styles.toolLink}>
<Link href={`/tools/${tool.id}` as never} className={styles.toolLink}>
<CardHeader>
<div className={styles.toolHeader}>
<Icon name={tool.icon} size="lg" />
Expand All @@ -90,8 +104,6 @@ export default async function ToolsIndexPage() {
</Card>
))}
</section>

<BottomNav active="tools" />
</main>
);
}
}
28 changes: 26 additions & 2 deletions src/app/(dashboard)/tools/tools.module.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
.heroCard {
margin-bottom: var(--space-6);
}

.heroTitle {
font-size: var(--text-2xl);
}

/* Remaining-tools grid on the index page, under the hero card: fixed at 2
columns since there are only 4 items. Scenario counts per tool vary up to
10 (see the detail page's own .scenarioGrid), so that one keeps 3 columns
at the wider breakpoint instead of sharing this class. */
.toolGrid {
display: grid;
grid-template-columns: 1fr;
Expand All @@ -10,8 +22,20 @@
}
}

.scenarioGrid {
display: grid;
grid-template-columns: 1fr;
gap: var(--space-4);
}

@media (min-width: 768px) {
.scenarioGrid {
grid-template-columns: repeat(2, 1fr);
}
}

@media (min-width: 1024px) {
.toolGrid {
.scenarioGrid {
grid-template-columns: repeat(3, 1fr);
}
}
Expand All @@ -31,4 +55,4 @@
align-items: center;
justify-content: space-between;
margin-bottom: var(--space-3);
}
}
2 changes: 2 additions & 0 deletions src/app/(public)/auth/signin/auth.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@
color: var(--ink-900);
margin: 0;
letter-spacing: -0.01em;
text-align: center;
}

.subtitle {
font-size: var(--text-sm);
color: var(--ink-500);
margin: 0;
text-align: center;
}

.form {
Expand Down
4 changes: 2 additions & 2 deletions src/app/(public)/pricing/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default async function PricingPage() {
<p className={styles.eyebrow}>PRICING</p>
<h1 className={styles.title}>Pick the tier that matches where you are.</h1>
<p className={styles.subtitle}>
Every tier teaches the same core skill — the difference is how much support and
Every tier teaches the same core skill. The difference is how much support and
speed you want. Pick based on how you learn best, not how fast you think you need
to move.
</p>
Expand Down Expand Up @@ -108,7 +108,7 @@ export default async function PricingPage() {
<summary>What if I don&apos;t have any Amazon ads experience yet?</summary>
<p>
Start with PPC Foundations. The first three modules build the mental model
from scratch you don&apos;t need prior campaign work to follow along.
from scratch, so you don&apos;t need prior campaign work to follow along.
</p>
</details>
</section>
Expand Down
17 changes: 15 additions & 2 deletions src/app/(public)/pricing/pricing.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
display: grid;
grid-template-columns: 1fr;
gap: var(--space-6);
align-items: start;
}

@media (min-width: 768px) {
Expand All @@ -44,22 +45,34 @@
}
}

/* Featured tier is the hero card here, not an equal third column. */
@media (min-width: 1024px) {
.tierGrid {
grid-template-columns: repeat(3, 1fr);
grid-template-columns: 0.85fr 1.15fr 0.85fr;
}
}

.tier,
.tierFeatured {
position: relative;
padding: var(--space-6);
padding: var(--space-5);
display: flex;
flex-direction: column;
gap: var(--space-4);
}

.tier {
background: var(--surface-2);
}

@media (min-width: 1024px) {
.tier {
margin-top: var(--space-6);
}
}

.tierFeatured {
padding: var(--space-8) var(--space-6);
border-color: var(--accent);
box-shadow: 0 0 0 1px var(--accent);
}
Expand Down
Loading