feat:improved ui for login

This commit is contained in:
2025-09-28 15:02:54 +02:00
parent 925e7163b6
commit bc8a76b9f5
9 changed files with 306 additions and 39 deletions

View File

@@ -1,5 +1,30 @@
DATABASE_URL="postgres://root:mysecretpassword@localhost:5432/local"
POSTGRES_USER=root
POSTGRES_PASSWORD=mysecretpassword
POSTGRES_DB=local
PROJECT_TOKEN=CHROMATIC_PROJECT_TOKEN
# Chromatic token
PROJECT_TOKEN=****************************
# Recommended for most uses
DATABASE_URL=****************************
# For uses requiring a connection without pgbouncer
DATABASE_URL_UNPOOLED=****************************
# Parameters for constructing your own connection string
PGHOST=****************************
PGHOST_UNPOOLED=****************************
PGUSER=****************************
PGDATABASE=****************************
PGPASSWORD=****************************
# Parameters for Vercel Postgres Templates
POSTGRES_URL=****************************
POSTGRES_URL_NON_POOLING=****************************
POSTGRES_USER=****************************
POSTGRES_HOST=****************************
POSTGRES_PASSWORDL=****************************
POSTGRES_DATABASEL=****************************
POSTGRES_URL_NO_SSL=****************************
POSTGRES_PRISMA_URL=****************************
# Neon Auth environment variables for Next.js
NEXT_PUBLIC_STACK_PROJECT_ID=****************************
NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=****************************************
STACK_SECRET_SERVER_KEY=***********************

View File

@@ -4,7 +4,7 @@ COPY package.json ./
RUN npm install
COPY . .
RUN DATABASE_URL="postgres://user:pass@localhost:5432/db" npm run build
# DATABASE_URL is only needed at build time for Prisma to generate the client. It is not needed at runtime and will be replaced by the hosted neon database.
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app ./

View File

@@ -1,26 +1,7 @@
services:
db:
image: postgres
restart: always
ports:
- 5432:5432
env_file:
- .env
environment:
POSTGRES_USER: ${POSTGRES_USER:-root}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-mysecretpassword}
POSTGRES_DB: ${POSTGRES_DB:-local}
volumes:
- pgdata:/var/lib/postgresql/data
app:
build: .
ports:
- 3000:3000
env_file:
- .env
environment:
DATABASE_URL: postgres://${POSTGRES_USER:-root}:${POSTGRES_PASSWORD:-mysecretpassword}@db:5432/${POSTGRES_DB:-local}
depends_on:
- db
volumes:
pgdata:

View File

@@ -0,0 +1,43 @@
@font-face {
font-family: 'Washington';
src: url('/fonts/Washington.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.5;
background-color: #f8f8f8;
color: #333;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: 'Washington', serif;
font-weight: normal;
}
button {
cursor: pointer;
font-family: inherit;
}
input {
font-family: inherit;
}
a {
color: inherit;
text-decoration: none;
}

View File

@@ -1,21 +1,42 @@
<script lang="ts">
import { enhance } from '$app/forms';
import type { ActionData } from './$types';
import './login.css';
let { form }: { form: ActionData } = $props();
</script>
<h1>Login/Register</h1>
<form method="post" action="?/login" use:enhance>
<label>
Username
<input name="username" />
</label>
<label>
Password
<input type="password" name="password" />
</label>
<button>Login</button>
<button formaction="?/register">Register</button>
<div class="login-container">
<h1 class="login-title">Serengo</h1>
<form class="login-form" method="post" action="?/login" use:enhance>
<div class="input-group">
<input
class="input-field"
name="username"
type="text"
placeholder="Username or Email"
required
/>
</div>
<div class="input-group">
<input
class="input-field"
name="password"
type="password"
placeholder="Password"
required
/>
</div>
<div class="button-group">
<button class="login-button" type="submit">Login</button>
<button class="register-button" type="submit" formaction="?/register">Register</button>
</div>
</form>
<p style="color: red">{form?.message ?? ''}</p>
{#if form?.message}
<p class="error-message">{form.message}</p>
{/if}
</div>

115
src/routes/login/login.css Normal file
View File

@@ -0,0 +1,115 @@
.login-container {
display: flex;
flex-direction: column;
min-height: 100vh;
padding: 4rem 2rem;
background-color: #f8f8f8;
align-items: center;
justify-content: flex-start;
max-width: 400px;
margin: 0 auto;
}
.login-title {
font-family: 'Washington', serif;
font-size: 3.5rem;
color: #000000;
margin-bottom: 6rem;
text-align: center;
font-weight: normal;
letter-spacing: -0.02em;
}
.login-form {
display: flex;
flex-direction: column;
width: 100%;
gap: 1.5rem;
}
.input-group {
display: flex;
flex-direction: column;
}
.input-field {
padding: 1.25rem 1.5rem;
border: none;
border-radius: 2rem;
background-color: #e0e0e0;
font-size: 1rem;
color: #333;
outline: none;
transition: background-color 0.2s ease;
height: 3.5rem;
}
.input-field:focus {
background-color: #d5d5d5;
}
.input-field::placeholder {
color: #888;
}
.button-group {
display: flex;
gap: 1rem;
}
.login-button,
.register-button {
flex: 1;
padding: 1.25rem 2rem;
border: none;
border-radius: 2rem;
font-size: 1rem;
font-weight: 500;
transition: all 0.2s ease;
cursor: pointer;
height: 3.5rem;
}
.login-button {
background-color: #3a4553;
color: white;
}
.login-button:hover {
background-color: #2d3441;
transform: translateY(-1px);
}
.register-button {
background-color: #3a4553;
color: white;
}
.register-button:hover {
background-color: #2d3441;
transform: translateY(-1px);
}
.error-message {
color: #e53e3e;
font-size: 0.875rem;
text-align: center;
margin-top: 1rem;
padding: 0.5rem;
}
/* Mobile responsiveness */
@media (max-width: 480px) {
.login-container {
padding: 3rem 1.5rem;
}
.login-title {
font-size: 3rem;
margin-bottom: 4rem;
}
.button-group {
margin-top: 3rem;
}
}

View File

@@ -0,0 +1,35 @@
<script>
import Login from './Login.svelte';
export default {
title: 'Pages/Login',
component: Login,
argTypes: {
onSubmit: { action: 'submit' },
errorMessage: {
control: 'text',
description: 'Error message to display'
}
}
};
</script>
<script context="module">
export const Default = {
args: {}
};
export const WithError = {
args: {
errorMessage: 'Invalid username or password'
}
};
export const NetworkError = {
args: {
errorMessage: 'Unable to connect to server. Please try again.'
}
};
</script>
<Login {...$$props} />

47
src/stories/Login.svelte Normal file
View File

@@ -0,0 +1,47 @@
<script lang="ts">
import '../routes/login/login.css';
let { onSubmit = () => {}, errorMessage = '' }: { onSubmit?: (action: string) => void; errorMessage?: string } = $props();
function handleSubmit(event: Event & { currentTarget: EventTarget & HTMLFormElement }) {
event.preventDefault();
const formData = new FormData(event.currentTarget);
const action = (event.submitter as HTMLButtonElement)?.formAction?.includes('register') ? 'register' : 'login';
onSubmit(action);
}
</script>
<div class="login-container">
<h1 class="login-title">Serengo</h1>
<form class="login-form" on:submit={handleSubmit}>
<div class="input-group">
<input
class="input-field"
name="username"
type="text"
placeholder="Username or Email"
required
/>
</div>
<div class="input-group">
<input
class="input-field"
name="password"
type="password"
placeholder="Password"
required
/>
</div>
<div class="button-group">
<button class="login-button" type="submit">Login</button>
<button class="register-button" type="submit" formaction="?/register">Register</button>
</div>
</form>
{#if errorMessage}
<p class="error-message">{errorMessage}</p>
{/if}
</div>

BIN
static/fonts/Washington.ttf Normal file

Binary file not shown.