Added authentication forms and routes

This commit is contained in:
2025-10-27 22:19:34 +01:00
parent e12e4389b6
commit b21161a273
27 changed files with 3336 additions and 337 deletions

View File

@@ -0,0 +1,88 @@
from fasthtml.components import *
class LoginPage:
def __init__(self, error_message=None, success_message=None):
self.error_message = error_message
self.success_message = success_message
def render(self):
message_alert = None
if self.error_message:
message_alert = Div(
P(self.error_message, cls="text-sm"),
cls="bg-error border border-red-400 text-red-700 px-4 py-3 rounded mb-4"
)
elif self.success_message:
message_alert = Div(
P(self.success_message, cls="text-sm"),
cls="bg-success border border-green-400 text-green-700 px-4 py-3 rounded mb-4"
)
return Div(
# Page title
H1("Sign In", cls="text-3xl font-bold text-center mb-6"),
# Login Form
Div(
# Message alert
message_alert if message_alert else "",
# Email login form
Form(
# Email field
Div(
Label("Email", For="email", cls="block text-sm font-medium text-gray-700 mb-1"),
Input(
type="email",
id="email",
name="email",
placeholder="you@example.com",
required=True,
cls="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2"
),
cls="mb-4"
),
# Password field
Div(
Label("Password", For="password", cls="block text-sm font-medium text-gray-700 mb-1"),
Input(
type="password",
id="password",
name="password",
placeholder="Your password",
required=True,
cls="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2"
),
cls="mb-6"
),
# Submit button
Button(
"Sign In",
type="submit",
cls="btn w-full font-bold py-2 px-4 rounded"
),
action="/login-p",
method="post",
cls="mb-6"
),
# Registration link
Div(
P(
"Don't have an account? ",
A("Register here", href="/register", cls="text-blue-600 hover:underline"),
cls="text-sm text-gray-600 text-center"
)
),
cls="p-8 rounded-lg shadow-2xl max-w-md mx-auto"
)
)
def __ft__(self):
return self.render()

View File

@@ -0,0 +1,125 @@
from fasthtml.components import *
class RegisterPage:
def __init__(self, error_message: str = None):
self.error_message = error_message
def register_page(self, error_message: str):
self.error_message = error_message
return self.__ft__()
def __ft__(self):
"""
Create the registration page.
Args:
error_message: Optional error message to display
Returns:
Components representing the registration page
"""
# Create alert for error message
error_alert = None
if self.error_message:
error_alert = Div(
P(self.error_message, cls="text-sm"),
cls="bg-soft bg-error border border-red-400 text-red-700 px-4 py-3 rounded mb-4"
)
return Div(
# Page title
H1("Create an Account", cls="text-3xl font-bold text-center mb-6"),
# Registration Form
Div(
# Error alert
error_alert if error_alert else "",
Form(
# Username field
Div(
Label("Username", For="username", cls="block text-sm font-medium text-gray-700 mb-1"),
Input(
type="text",
id="username",
name="username",
placeholder="Choose a username",
required=True,
minlength=3,
maxlength=30,
pattern="[a-zA-Z0-9_-]+",
cls="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2"
),
P("Only letters, numbers, underscores, and hyphens", cls="text-xs text-gray-500 mt-1"),
cls="mb-4"
),
# Email field
Div(
Label("Email", For="email", cls="block text-sm font-medium text-gray-700 mb-1"),
Input(
type="email",
id="email",
name="email",
placeholder="you@example.com",
required=True,
cls="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2"
),
cls="mb-4"
),
# Password field
Div(
Label("Password", For="password", cls="block text-sm font-medium text-gray-700 mb-1"),
Input(
type="password",
id="password",
name="password",
placeholder="Create a password",
required=True,
minlength=8,
cls="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2"
),
P("At least 8 characters with uppercase, lowercase, and number", cls="text-xs text-gray-500 mt-1"),
cls="mb-4"
),
# Confirm password field
Div(
Label("Confirm Password", For="confirm_password", cls="block text-sm font-medium text-gray-700 mb-1"),
Input(
type="password",
id="confirm_password",
name="confirm_password",
placeholder="Confirm your password",
required=True,
cls="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2"
),
cls="mb-6"
),
# Submit button
Button(
"Create Account",
type="submit",
cls="btn w-full font-bold py-2 px-4 rounded"
),
# Registration link
Div(
P(
"Already have an account? ",
A("Sign in here", href="/login", cls="text-blue-600 hover:underline"),
cls="text-sm text-gray-600 text-center"
)
),
action="register-p",
method="post",
cls="mb-6"
),
cls="p-8 rounded-lg shadow-2xl max-w-md mx-auto"
)
)

View File

@@ -0,0 +1,216 @@
"""
Register page component for FastHTML application.
Provides a styled registration form using DaisyUI 5 components.
"""
from fasthtml.common import Div, Form, Input, Button, H1, P, A, Label
class RegisterPage:
"""
Register page component with email and password fields.
Attributes:
error_message: Optional error message to display
redirect_url: URL to redirect after successful registration
"""
def __init__(self, error_message: str = None, redirect_url: str = "/login"):
"""
Initialize register page.
Args:
error_message: Optional error message to display
redirect_url: URL to redirect after successful registration (default: "/login")
"""
self.error_message = error_message
self.redirect_url = redirect_url
def _render(self):
"""
Render the register page HTML structure.
Returns:
FastHTML component tree for the register page
"""
# Error alert component (only shown if error_message exists)
error_alert = None
if self.error_message:
error_alert = Div(
Div(
self._svg_icon(),
self.error_message,
cls="flex items-center gap-2"
),
role="alert",
cls="alert alert-error mb-4",
id="error-alert"
)
# Success message container (empty by default, filled by HTMX on success)
success_alert = Div(id="success-alert")
# Register form
register_form = Form(
# Hidden field for redirect URL
Input(type="hidden", name="redirect_url", value=self.redirect_url),
# Email input
Label(
Div("Email", cls="label-text"),
cls="form-control w-full mb-4"
)(
Input(
type="email",
name="email",
placeholder="[email protected]",
required=True,
cls="input input-bordered w-full"
)
),
# Password input
Label(
Div("Password", cls="label-text"),
Div("Must be at least 8 characters", cls="label-text-alt text-base-content/60"),
cls="form-control w-full mb-4"
)(
Input(
type="password",
name="password",
placeholder="Create a password",
required=True,
minlength="8",
cls="input input-bordered w-full"
)
),
# Confirm password input
Label(
Div("Confirm Password", cls="label-text"),
cls="form-control w-full mb-4"
)(
Input(
type="password",
name="confirm_password",
placeholder="Confirm your password",
required=True,
minlength="8",
cls="input input-bordered w-full"
)
),
# Submit button
Button(
"Create Account",
type="submit",
cls="btn btn-primary w-full"
),
method="post",
action="/register",
hx_post="/register",
hx_target="#register-card",
hx_swap="outerHTML"
)
# Main card container
card = Div(
Div(
# Card title
H1("Create Account", cls="text-3xl font-bold text-center mb-2"),
P("Sign up for a new account", cls="text-center text-base-content/70 mb-6"),
# Success alert
success_alert,
# Error alert
error_alert if error_alert else Div(),
# Register form
register_form,
# Login link
Div(
P(
"Already have an account? ",
A("Sign in", href="/login", cls="link link-primary"),
cls="text-center text-sm"
),
cls="mt-4"
),
cls="card-body"
),
cls="card bg-base-100 w-96 shadow-xl",
id="register-card"
)
# Full page container
return Div(
card,
cls="flex items-center justify-center min-h-screen bg-base-200"
)
def _svg_icon(self):
"""
Helper method to create error icon SVG.
Returns:
SVG icon as NotStr
"""
from fasthtml.common import NotStr
return NotStr('''
<svg xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6 shrink-0 stroke-current"
fill="none"
viewBox="0 0 24 24">
<path stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
''')
def __ft__(self):
"""
FastHTML rendering protocol method.
Returns:
Rendered register page component
"""
return self._render()
def create_success_message(message: str = "Account created successfully!"):
"""
Create a success alert component for HTMX response.
Args:
message: Success message to display
Returns:
Success alert component
"""
from fasthtml.common import NotStr
return Div(
Div(
NotStr('''
<svg xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6 shrink-0 stroke-current"
fill="none"
viewBox="0 0 24 24">
<path stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
'''),
message,
cls="flex items-center gap-2"
),
role="alert",
cls="alert alert-success mb-4"
)

View File

@@ -0,0 +1,185 @@
"""
Welcome page component for FastHTML application.
Provides a protected welcome page that displays user information.
This page is only accessible to authenticated users.
"""
from fasthtml.common import Div, H1, H2, P, Button, A
from typing import Optional, Dict, Any
class WelcomePage:
"""
Welcome page component for authenticated users.
Attributes:
user_info: Dictionary containing user information (email, id, etc.)
redirect_url: URL for logout redirect
"""
def __init__(self, user_info: Optional[Dict[str, Any]] = None, redirect_url: str = "/login"):
"""
Initialize welcome page.
Args:
user_info: Dictionary containing user information
redirect_url: URL to redirect after logout (default: "/login")
"""
self.user_info = user_info or {}
self.redirect_url = redirect_url
def _render(self):
"""
Render the welcome page HTML structure.
Returns:
FastHTML component tree for the welcome page
"""
# Extract user info
email = self.user_info.get('email', 'Guest')
user_id = self.user_info.get('id', 'N/A')
# Hero section
hero = Div(
Div(
# Welcome message
H1(
f"Welcome back, {email}!",
cls="text-5xl font-bold"
),
P(
"You have successfully logged in to your account.",
cls="py-6 text-lg"
),
# Action buttons
Div(
Button(
"Logout",
cls="btn btn-primary",
hx_post="/logout",
hx_target="body",
hx_swap="outerHTML"
),
cls="flex gap-2"
),
cls="max-w-md"
),
cls="hero min-h-screen bg-base-200"
)
# User info card
info_card = Div(
Div(
H2("Your Information", cls="card-title mb-4"),
# User details
Div(
self._info_row("Email", email),
self._info_row("User ID", str(user_id)),
cls="space-y-2"
),
cls="card-body"
),
cls="card bg-base-100 shadow-xl w-full max-w-md"
)
# Main container
return Div(
# Hero section
hero,
# Info card (positioned absolutely over hero)
Div(
info_card,
cls="absolute top-20 right-20 hidden lg:block"
),
cls="relative"
)
def _info_row(self, label: str, value: str):
"""
Create an information row for displaying user details.
Args:
label: Label for the information
value: Value to display
Returns:
Div containing label and value
"""
return Div(
Div(
P(label, cls="font-semibold text-sm text-base-content/70"),
P(value, cls="text-base"),
cls="flex flex-col"
),
cls="py-2 border-b border-base-300 last:border-b-0"
)
def __ft__(self):
"""
FastHTML rendering protocol method.
Returns:
Rendered welcome page component
"""
return self._render()
class SimpleWelcomePage:
"""
Simpler version of welcome page without user info card.
Useful for quick prototyping or minimal designs.
"""
def __init__(self, user_email: str = "Guest"):
"""
Initialize simple welcome page.
Args:
user_email: User's email address
"""
self.user_email = user_email
def _render(self):
"""
Render the simple welcome page.
Returns:
FastHTML component tree
"""
return Div(
Div(
Div(
H1(f"Welcome, {self.user_email}!", cls="text-4xl font-bold mb-4"),
P("You are now logged in.", cls="mb-6 text-lg"),
Button(
"Logout",
cls="btn btn-primary",
hx_post="/logout",
hx_target="body",
hx_swap="outerHTML"
),
cls="text-center"
),
cls="hero-content"
),
cls="hero min-h-screen bg-base-200"
)
def __ft__(self):
"""
FastHTML rendering protocol method.
Returns:
Rendered simple welcome page
"""
return self._render()

View File