Skip to content

Commit 5855740

Browse files
committed
Merge remote-tracking branch 'origin/main' into test/streak-templates-tests
# Conflicts: # components/DailySummaryCard.js
2 parents c677564 + 275863b commit 5855740

31 files changed

Lines changed: 417 additions & 179 deletions

.github/workflows/ci.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
build-and-lint:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- uses: actions/setup-node@v4
16+
with:
17+
node-version: 20
18+
cache: npm
19+
20+
- run: npm ci
21+
22+
- run: npx next build
23+
env:
24+
NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }}
25+
NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }}
26+
27+
- run: npm run lint
28+
29+
e2e:
30+
runs-on: ubuntu-latest
31+
needs: build-and-lint
32+
env:
33+
NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }}
34+
NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }}
35+
steps:
36+
- uses: actions/checkout@v4
37+
38+
- uses: actions/setup-node@v4
39+
with:
40+
node-version: 20
41+
cache: npm
42+
43+
- run: npm ci
44+
45+
- run: npx playwright install --with-deps chromium
46+
47+
- run: npm run test:e2e

app/dashboard/DashboardLayout.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export default function DashboardLayout({ user, children }) {
4444
<main
4545
id="main-content"
4646
className={styles.main}
47-
style={{ marginLeft: sidebarExpanded ? 260 : 72 }}
47+
style={{ marginLeft: sidebarExpanded ? 260 : 80 }}
4848
>
4949
<ErrorBoundary message="Something went wrong loading this page.">
5050
<motion.div

app/dashboard/focus/FocusPageClient.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export default function FocusPageClient({ user }) {
3838
const { data, error } = await supabase
3939
.from('focus_sessions')
4040
.select('*')
41+
.eq('user_id', user.id)
4142
.order('started_at', { ascending: false })
4243
.limit(10);
4344

@@ -128,7 +129,7 @@ export default function FocusPageClient({ user }) {
128129
</svg>
129130
</div>
130131
<div className={styles.sessionInfo}>
131-
<span className={styles.sessionDuration}>{session.duration_minutes} minutes</span>
132+
<span className={styles.sessionDuration}>{session.duration_minutes} {session.duration_minutes === 1 ? 'minute' : 'minutes'}</span>
132133
<span className={styles.sessionDate}>{formatDate(session.started_at)}</span>
133134
</div>
134135
</div>

app/dashboard/focus/page.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { createClient } from '@/lib/supabase/server';
22
import { redirect } from 'next/navigation';
33
import FocusPageClient from './FocusPageClient';
44

5+
export const metadata = { title: 'Focus Timer | LockIn' };
6+
57
export default async function FocusPage() {
68
const supabase = await createClient();
79
const { data: { user } } = await supabase.auth.getUser();

app/dashboard/pacts/PactsPageClient.js

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use client';
22

33
import { useState, useEffect, useCallback, useMemo } from 'react';
4+
import { motion, AnimatePresence, LayoutGroup } from 'framer-motion';
45
import { createClient } from '@/lib/supabase/client';
56
import styles from './PactsPage.module.css';
67
import CreatePactModal from '@/components/CreatePactModal';
@@ -170,16 +171,28 @@ export default function PactsPageClient({ user }) {
170171
)}
171172
</div>
172173
) : (
173-
<div className={styles.pactsGrid}>
174-
{filteredPacts.map((pact) => (
175-
<PactCard
176-
key={pact.id}
177-
pact={pact}
178-
onUpdate={handlePactUpdate}
179-
onDelete={handleDeletePact}
180-
/>
181-
))}
182-
</div>
174+
<LayoutGroup>
175+
<motion.div className={styles.pactsGrid}>
176+
<AnimatePresence mode="popLayout">
177+
{filteredPacts.map((pact) => (
178+
<motion.div
179+
key={pact.id}
180+
layout
181+
initial={{ opacity: 0, scale: 0.9 }}
182+
animate={{ opacity: 1, scale: 1 }}
183+
exit={{ opacity: 0, scale: 0.9, transition: { duration: 0.2 } }}
184+
transition={{ type: "spring", stiffness: 500, damping: 30 }}
185+
>
186+
<PactCard
187+
pact={pact}
188+
onUpdate={handlePactUpdate}
189+
onDelete={handleDeletePact}
190+
/>
191+
</motion.div>
192+
))}
193+
</AnimatePresence>
194+
</motion.div>
195+
</LayoutGroup>
183196
)}
184197

185198
{/* Create Pact Modal */}

app/dashboard/settings/page.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { createClient } from '@/lib/supabase/server';
22
import { redirect } from 'next/navigation';
33
import SettingsPageClient from './SettingsPageClient';
44

5+
export const metadata = { title: 'Settings | LockIn' };
6+
57
export default async function SettingsPage() {
68
const supabase = await createClient();
79
const { data: { user } } = await supabase.auth.getUser();

app/dashboard/stats/page.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { createClient } from '@/lib/supabase/server';
22
import { redirect } from 'next/navigation';
33
import StatsPageClient from './StatsPageClient';
44

5+
export const metadata = { title: 'Stats | LockIn' };
6+
57
export default async function StatsPage() {
68
const supabase = await createClient();
79
const { data: { user } } = await supabase.auth.getUser();

app/globals.css

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
/* Text colors — Rich, readable */
2626
--text-primary: #161529;
2727
--text-secondary: #524E68;
28-
--text-tertiary: #6E6A82;
28+
--text-tertiary: #656180;
2929
--text-muted: #A9A5BC;
3030
--text-inverse: #FFFFFF;
3131

@@ -185,11 +185,11 @@
185185
--surface-overlay: rgba(91, 94, 245, 0.04);
186186
--surface-glass: rgba(18, 22, 34, 0.35);
187187

188-
/* Text colors */
188+
/* Text colors — adjusted for WCAG AA contrast (4.5:1 min against #08090D) */
189189
--text-primary: #F0F0FF;
190190
--text-secondary: #A8A3C0;
191-
--text-tertiary: #6E6990;
192-
--text-muted: #4A4568;
191+
--text-tertiary: #9590AD;
192+
--text-muted: #7E79A0;
193193
--text-inverse: #08090D;
194194

195195
/* Gradient adjustments — more luminous in dark */
@@ -849,6 +849,11 @@ p {
849849
100% { transform: translateX(250%); }
850850
}
851851

852+
@keyframes shimmerSweep {
853+
0% { background-position: -200% center; }
854+
100% { background-position: 200% center; }
855+
}
856+
852857
@keyframes breathe {
853858
0%, 100% { opacity: 0.4; transform: scale(1); }
854859
50% { opacity: 0.8; transform: scale(1.02); }

app/not-found.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import Link from 'next/link';
2+
import styles from './not-found.module.css';
3+
4+
export default function NotFound() {
5+
return (
6+
<div className={styles.container}>
7+
<main className={styles.content}>
8+
<h1 className={styles.heading}>404</h1>
9+
<p className={styles.message}>This page could not be found.</p>
10+
<Link href="/" className={styles.link}>
11+
Go home
12+
</Link>
13+
</main>
14+
</div>
15+
);
16+
}

app/not-found.module.css

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
.container {
2+
min-height: 100vh;
3+
min-height: 100dvh;
4+
display: flex;
5+
align-items: center;
6+
justify-content: center;
7+
background: var(--bg-primary);
8+
color: var(--text-primary);
9+
font-family: var(--font-sans);
10+
}
11+
12+
.content {
13+
text-align: center;
14+
}
15+
16+
.heading {
17+
font-size: var(--text-6xl);
18+
font-weight: var(--font-extrabold);
19+
margin-bottom: 16px;
20+
background: var(--gradient-primary);
21+
-webkit-background-clip: text;
22+
-webkit-text-fill-color: transparent;
23+
background-clip: text;
24+
}
25+
26+
.message {
27+
font-size: var(--text-xl);
28+
color: var(--text-secondary);
29+
margin-bottom: 32px;
30+
}
31+
32+
.link {
33+
display: inline-block;
34+
padding: 12px 24px;
35+
background: var(--gradient-primary);
36+
color: white;
37+
border-radius: var(--radius-full);
38+
font-weight: var(--font-semibold);
39+
font-size: var(--text-base);
40+
text-decoration: none;
41+
box-shadow: var(--shadow-md);
42+
transition: transform var(--transition-base), box-shadow var(--transition-base);
43+
}
44+
45+
.link:hover,
46+
.link:focus-visible {
47+
transform: translateY(-2px);
48+
box-shadow: var(--shadow-lg);
49+
}
50+
51+
.link:focus-visible {
52+
outline: 2px solid var(--text-primary, #fff);
53+
outline-offset: 2px;
54+
}
55+
56+
@media (forced-colors: active) {
57+
.heading {
58+
-webkit-text-fill-color: currentColor;
59+
background: none;
60+
}
61+
}

0 commit comments

Comments
 (0)