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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Dependencies and build output
node_modules/
.next/
next-env.d.ts
out/
dist/

Expand All @@ -9,6 +10,7 @@ coverage/
playwright-report/
test-results/
*.tsbuildinfo
generated/

# Environment and secrets
.env
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Status

This repository contains the development foundation and initial App Router shell for a public-facing Next.js application. The root landing page, layout, and global styles are ready for feature development; authentication, database models, migrations, API routes, and browser workflows have **not** been created yet. The approved product decisions are recorded in [docs/product-spec.md](docs/product-spec.md) and [docs/implementation-plan.md](docs/implementation-plan.md).
This repository contains the development foundation and initial App Router shell for a public-facing Next.js application. Members can register and sign in with a display name, email address, and password. The approved product decisions are recorded in [docs/product-spec.md](docs/product-spec.md) and [docs/implementation-plan.md](docs/implementation-plan.md).

## Repository map

Expand All @@ -21,7 +21,7 @@ This repository contains the development foundation and initial App Router shell
## Getting Started

1. Install [Git](https://git-scm.com/downloads), [Node.js 26.8.2](https://nodejs.org/en/download), and [Docker Desktop](https://www.docker.com/products/docker-desktop/) (or Docker Engine with the Compose plugin). Node 26 is the project’s active-LTS line; npm 11 is bundled with it.
2. Copy the local configuration template. It contains only safe local service credentials; replace `AUTH_SECRET` before authentication work begins.
2. Copy the local configuration template. It contains only safe local service credentials. Generate a unique `AUTH_SECRET` for your local environment with `npx auth secret`, then keep it out of version control.

```sh
cp .env.example .env
Expand Down Expand Up @@ -62,7 +62,7 @@ To install dependencies, start local services, and launch the development server

## Database migrations

Prisma is configured for PostgreSQL and reads `DATABASE_URL` from `.env`. No product schema exists yet. Once a model is approved, create and review a development migration with:
Prisma is configured for PostgreSQL and reads `DATABASE_URL` from `.env`. The initial account migration creates the `User` table with a unique email address and password hash. Once a model is approved, create and review a development migration with:

```sh
npm run prisma:migrate -- --name describe_the_change
Expand Down
67 changes: 42 additions & 25 deletions app/Navbar.css
Original file line number Diff line number Diff line change
@@ -1,25 +1,42 @@
nav {
background-color: rgba(75, 207, 141, 0.473);
color: black;
}

ul {
list-style-type: none;
margin: 1rem;
padding: 0;
display: flex;
justify-content: space-around;
}

li {
padding: 15px;
}

a {
color: black;
text-decoration: none;
}

a:hover {
text-decoration: underline;
}
nav {
background-color: rgba(75, 207, 141, 0.473);
color: black;
}

ul {
list-style-type: none;
margin: 1rem;
padding: 0;
display: flex;
justify-content: space-around;
}

li {
padding: 15px;
}

a {
color: black;
text-decoration: none;
}

a:hover {
text-decoration: underline;
}

nav form {
margin: 0;
}

nav button {
padding: 0;
border: 0;
background: none;
color: inherit;
cursor: pointer;
font: inherit;
}

nav button:hover {
text-decoration: underline;
}
66 changes: 44 additions & 22 deletions app/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,44 @@
import React from 'react';
import './Navbar.css';

type navbarProps = {
links: { text: string; url: string }[];
};

const Navbar: React.FC<navbarProps> = ({ links }) => {
return (
<nav>
<ul>
{links.map((link, index) => (
<li key={index}>
<a href={link.url}>{link.text}</a>
</li>
))}
</ul>
</nav>
);
};

export default Navbar;
import Link from 'next/link'
import { signOut } from '../auth'

import './Navbar.css'

type NavbarProps = {
session: { user?: { name?: string | null } } | null
}

export default function Navbar({ session }: NavbarProps) {
return (
<nav>
<ul>
<li>
<Link href="/">SideCause</Link>
</li>
<li>
<Link href="/">Posts</Link>
</li>
{session?.user ? (
<>
<li aria-label="Signed in member">
<Link href="/">{session.user.name ?? 'Member'}</Link>
</li>
<li>
<form
action={async () => {
'use server'
await signOut({ redirectTo: '/' })
}}
>
<button type="submit">Log out</button>
</form>
</li>
</>
) : (
<li>
<Link href="/login">Login</Link>
</li>
)}
</ul>
</nav>
)
}
3 changes: 3 additions & 0 deletions app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { handlers } from '../../../../auth'

export const { GET, POST } = handlers
115 changes: 114 additions & 1 deletion app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
--ink: #20251f;
--muted: #5d675d;
--line: #cdd5c8;
--accent: #476a45;
--accent: #3cb4bd;
--accent-soft: #dfe9d9;
}

Expand Down Expand Up @@ -132,6 +132,115 @@ h3 {
color: var(--muted);
}

.account-page {
display: grid;
min-height: calc(90vh - 7rem);
place-items: center;
padding: 1rem 1.5rem 5rem;
}

.account-panel {
width: min(100%, 31rem);
padding: 2rem;
border: 1px solid var(--line);
background: #fffdf8;
}

.account-panel__back {
display: inline-block;
margin-bottom: 3rem;
color: var(--muted);
font-size: 0.875rem;
}

.account-panel h2 {
max-width: 14ch;
margin-top: 0.5rem;
font-size: 2rem;
}

.account-panel__intro {
margin-top: .25rem;
color: var(--muted);
}

.account-form {
display: grid;
gap: 1.25rem;
margin-top: 2rem;
}

.form-error {
margin: 0;
padding: 0.75rem;
border-left: 4px solid #9b2c2c;
color: #6e2020;
background: #fff1f1;
}

.form-field {
display: grid;
gap: 0.5rem;
}

.form-field label {
font-size: 0.875rem;
font-weight: 700;
}

.form-field input {
width: 100%;
min-height: 2.5rem;
padding: 0.425rem 0.75rem;
border: 1px solid var(--line);
border-radius: 0;
color: var(--ink);
background: #fffdf8;
font: inherit;
}

.form-field input:focus-visible,
.account-form button:focus-visible,
.account-panel a:focus-visible {
outline: 3px solid #236e69;
outline-offset: 3px;
}

.form-field__hint {
color: var(--muted);
font-size: 0.875rem;
}

.account-form button {
min-height: 2.75rem;
border: 1px solid var(--accent);
border-radius: 0;
color: #fff;
background: var(--accent);
font: inherit;
font-weight: 700;
cursor: pointer;
}

.account-form button:hover {
background: #468a7b;
}

.account-form button:disabled {
cursor: wait;
opacity: 0.7;
}

.account-panel__switch {
margin-top: 1.75rem;
color: var(--muted);
}

.account-panel__switch a {
color: var(--accent);
font-weight: 700;
}

@media (min-width: 48rem) {
.hero {
padding: 5rem 1.5rem;
Expand Down Expand Up @@ -160,4 +269,8 @@ h3 {
.principles__list span {
grid-row: auto;
}

.account-panel {
padding: 2rem;
}
}
8 changes: 6 additions & 2 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,27 @@ import type { ReactNode } from 'react'

import './globals.css'
import Navbar from './Navbar'
import { auth } from '../auth'

export default async function RootLayout({

export const metadata: Metadata = {
title: 'SideCause',
description:
'Complete community service projects and level up!',
}


const navLinks = [
{ text: 'SideCause', url: '/' },
{ text: 'Posts', url: '/posts' },
{ text: 'Login', url: '/' }
{ text: 'Login', url: '/login' }
];

export default function RootLayout({
children,
}: Readonly<{ children: ReactNode }>) {
const session = await auth()

return (
<html lang="en">
<body>
Expand Down
49 changes: 49 additions & 0 deletions app/login/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use client'

import { useActionState, useState } from 'react'

import { initialLoginFormState } from '../../lib/auth/form-state'
import { loginAction } from './actions'

export function LoginForm() {
const [state, formAction, isPending] = useActionState(
loginAction,
initialLoginFormState,
)
const [email, setEmail] = useState(initialLoginFormState.values.email)

return (
<form action={formAction} className="account-form">
{state.error && (
<p className="form-error" role="alert">
{state.error}
</p>
)}
<div className="form-field">
<label htmlFor="login-email">Email address</label>
<input
id="login-email"
name="email"
type="email"
autoComplete="email"
value={email}
onChange={(event) => setEmail(event.target.value)}
required
/>
</div>
<div className="form-field">
<label htmlFor="login-password">Password</label>
<input
id="login-password"
name="password"
type="password"
autoComplete="current-password"
required
/>
</div>
<button type="submit" disabled={isPending}>
{isPending ? 'Logging in…' : 'Log in'}
</button>
</form>
)
}
Loading
Loading