htmx and Tailwind CSS production setup

This commit is contained in:
Alphonse Paix
2025-09-16 19:42:35 +02:00
parent 38208654dc
commit 1d027b5460
13 changed files with 1376 additions and 308 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
/target /target
/node_modules

31
Cargo.lock generated
View File

@@ -1181,6 +1181,12 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
] ]
[[package]]
name = "http-range-header"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c"
[[package]] [[package]]
name = "httparse" name = "httparse"
version = "1.10.1" version = "1.10.1"
@@ -1590,6 +1596,16 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mime_guess"
version = "2.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
dependencies = [
"mime",
"unicase",
]
[[package]] [[package]]
name = "minimal-lexical" name = "minimal-lexical"
version = "0.2.1" version = "0.2.1"
@@ -3093,11 +3109,20 @@ checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"bytes", "bytes",
"futures-core",
"futures-util", "futures-util",
"http", "http",
"http-body", "http-body",
"http-body-util",
"http-range-header",
"httpdate",
"iri-string", "iri-string",
"mime",
"mime_guess",
"percent-encoding",
"pin-project-lite", "pin-project-lite",
"tokio",
"tokio-util",
"tower", "tower",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
@@ -3302,6 +3327,12 @@ version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
[[package]]
name = "unicase"
version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.18" version = "0.3.18"

View File

@@ -43,7 +43,7 @@ sqlx = { version = "0.8.6", features = [
] } ] }
thiserror = "2.0.16" thiserror = "2.0.16"
tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] } tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] }
tower-http = { version = "0.6.6", features = ["trace"] } tower-http = { version = "0.6.6", features = ["fs", "trace"] }
tower-sessions = "0.14.0" tower-sessions = "0.14.0"
tower-sessions-redis-store = "0.16.0" tower-sessions-redis-store = "0.16.0"
tracing = "0.1.41" tracing = "0.1.41"

2
assets/css/main.css Normal file

File diff suppressed because one or more lines are too long

1
assets/js/htmx.min.js vendored Normal file

File diff suppressed because one or more lines are too long

1137
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

9
package.json Normal file
View File

@@ -0,0 +1,9 @@
{
"scripts": {
"build-css": "tailwindcss -i ./templates/input.css -o ./assets/css/main.css --minify --watch"
},
"dependencies": {
"@tailwindcss/cli": "^4.1.13",
"tailwindcss": "^4.1.13"
}
}

View File

@@ -13,7 +13,7 @@ use axum_server::tls_rustls::RustlsConfig;
use secrecy::ExposeSecret; use secrecy::ExposeSecret;
use sqlx::{PgPool, postgres::PgPoolOptions}; use sqlx::{PgPool, postgres::PgPoolOptions};
use std::{net::TcpListener, sync::Arc}; use std::{net::TcpListener, sync::Arc};
use tower_http::trace::TraceLayer; use tower_http::{services::ServeDir, trace::TraceLayer};
use tower_sessions::SessionManagerLayer; use tower_sessions::SessionManagerLayer;
use tower_sessions_redis_store::{ use tower_sessions_redis_store::{
RedisStore, RedisStore,
@@ -126,6 +126,7 @@ pub fn app(
.route("/logout", post(logout)) .route("/logout", post(logout))
.layer(middleware::from_fn(require_auth)); .layer(middleware::from_fn(require_auth));
Router::new() Router::new()
.nest_service("/assets", ServeDir::new("assets"))
.route("/", get(home)) .route("/", get(home))
.route("/login", get(get_login).post(post_login)) .route("/login", get(get_login).post(post_login))
.route("/health_check", get(health_check)) .route("/health_check", get(health_check))

View File

@@ -2,10 +2,14 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="description" content="zero2prod newsletter" />
<meta name="keywords" content="newsletter, rust, axum, htmx" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{% block title %}zero2prod{% endblock %}</title> <title>
<script src="https://cdn.tailwindcss.com"></script> {% block title %}zero2prod{% endblock %}
<script src="https://unpkg.com/htmx.org@1.9.6"></script> </title>
<link href="/assets/css/main.css" rel="stylesheet" />
<script src="../assets/js/htmx.min.js"></script>
</head> </head>
<body class="bg-gray-50 min-h-screen flex flex-col"> <body class="bg-gray-50 min-h-screen flex flex-col">
<header class="bg-white shadow-sm border-b border-gray-200 top-0 z-40"> <header class="bg-white shadow-sm border-b border-gray-200 top-0 z-40">
@@ -18,19 +22,15 @@
</h1> </h1>
</a> </a>
</div> </div>
<nav> <nav>
<a <a href="/admin/dashboard"
href="/admin/dashboard" class="bg-blue-600 text-white hover:bg-blue-700 px-4 py-2 rounded-md text-sm font-medium transition-colors">
class="bg-blue-600 text-white hover:bg-blue-700 px-4 py-2 rounded-md text-sm font-medium transition-colors"
>
Dashboard Dashboard
</a> </a>
</nav> </nav>
</div> </div>
</div> </div>
</header> </header>
<div class="flex flex-1"> <div class="flex flex-1">
<main class="flex-1 lg:ml-0"> <main class="flex-1 lg:ml-0">
<div class="py-8 px-4 sm:px-6 lg:px-8"> <div class="py-8 px-4 sm:px-6 lg:px-8">
@@ -38,41 +38,26 @@
</div> </div>
</main> </main>
</div> </div>
<footer class="bg-white border-t border-gray-200 mt-auto"> <footer class="bg-white border-t border-gray-200 mt-auto">
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8"> <div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
<div class="flex flex-col md:flex-row justify-between items-center"> <div class="flex flex-col md:flex-row justify-between items-center">
<div <div class="flex flex-col md:flex-row items-center space-y-2 md:space-y-0 md:space-x-6">
class="flex flex-col md:flex-row items-center space-y-2 md:space-y-0 md:space-x-6"
>
<div class="flex items-center space-x-4"> <div class="flex items-center space-x-4">
<a <a href="https://gitea.alphonsepaix.xyz/alphonse/zero2prod"
href="https://gitea.alphonsepaix.xyz/alphonse/zero2prod" target="_blank"
target="_blank" class="text-sm text-gray-500 hover:text-gray-900 transition-colors flex items-center">
class="text-sm text-gray-500 hover:text-gray-900 transition-colors flex items-center"
>
Code repository Code repository
<svg <svg class="ml-1 h-3 w-3"
class="ml-1 h-3 w-3" fill="none"
fill="none" viewBox="0 0 24 24"
viewBox="0 0 24 24" stroke="currentColor">
stroke="currentColor" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
/>
</svg> </svg>
</a> </a>
</div> </div>
</div> </div>
<div class="mt-4 md:mt-0"> <div class="mt-4 md:mt-0">
<p class="text-xs text-gray-500"> <p class="text-xs text-gray-500">Built with ❤️ using Rust, axum & htmx</p>
Built with ❤️ using Rust, axum & htmx
</p>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,44 +1,27 @@
{% extends "base.html" %} {% block title %}zero2prod{% endblock %} {% block {% extends "base.html" %}
content %} {% block title %}zero2prod{% endblock %}
<div class="min-h-[60vh] flex items-center justify-center"> {% block content %}
<div class="max-w-md w-full space-y-8"> <div class="min-h-[60vh] flex items-center justify-center">
<div class="text-center"> <div class="max-w-md w-full space-y-8">
<div
class="mx-auto flex items-center justify-center h-16 w-16 rounded-full bg-green-100 mb-6"
>
<svg
class="h-8 w-8 text-green-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M5 13l4 4L19 7"
></path>
</svg>
</div>
<h1 class="text-3xl font-bold text-gray-900 mb-4">
Subscription confirmed
</h1>
<p class="text-lg text-gray-600 mb-8">
Your email has been confirmed! You're all set to receive our newsletter
updates.
</p>
</div>
<div class="text-center"> <div class="text-center">
<a <div class="mx-auto flex items-center justify-center h-16 w-16 rounded-full bg-green-100 mb-6">
href="/" <svg class="h-8 w-8 text-green-600"
class="text-sm text-blue-600 hover:text-blue-500 transition-colors" fill="none"
> stroke="currentColor"
← Back to homepage viewBox="0 0 24 24">
</a> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
</div>
<h1 class="text-3xl font-bold text-gray-900 mb-4">Subscription confirmed</h1>
<p class="text-lg text-gray-600 mb-8">
Your email has been confirmed! You're all set to receive our newsletter
updates.
</p>
</div>
<div class="text-center">
<a href="/"
class="text-sm text-blue-600 hover:text-blue-500 transition-colors">← Back to homepage</a>
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
</div>

View File

@@ -1,174 +1,117 @@
{% extends "base.html" %} {% block title %}Home - zero2prod{% endblock %} {% {% extends "base.html" %}
block content %} {% block title %}Home - zero2prod{% endblock %}
<div class="max-w-4xl mx-auto"> {% block content %}
<div <div class="max-w-4xl mx-auto">
class="bg-gradient-to-r from-blue-600 to-indigo-700 rounded-lg shadow-lg text-white p-8 mb-8" <div class="bg-gradient-to-r from-blue-600 to-indigo-700 rounded-lg shadow-lg text-white p-8 mb-8">
> <div class="max-w-3xl">
<div class="max-w-3xl"> <h1 class="text-4xl font-bold mb-4">zero2prod</h1>
<h1 class="text-4xl font-bold mb-4">zero2prod</h1> <p class="text-xl text-blue-100 mb-6">
<p class="text-xl text-blue-100 mb-6"> Welcome to our newsletter! Stay updated on our latest projects and
Welcome to our newsletter! Stay updated on our latest projects and thoughts. Unsubscribe at any time.
thoughts. Unsubscribe at any time. </p>
</p> <div class="flex flex-col sm:flex-row gap-4">
<div class="flex flex-col sm:flex-row gap-4"> <a href="#newsletter-signup"
<a class="bg-white text-blue-600 hover:bg-gray-100 font-semibold py-3 px-6 rounded-md transition-colors text-center">
href="#newsletter-signup"
class="bg-white text-blue-600 hover:bg-gray-100 font-semibold py-3 px-6 rounded-md transition-colors text-center"
>
Subscribe
</a>
<a
href="https://gitea.alphonsepaix.xyz/alphonse/zero2prod"
target="_blank"
class="border border-white text-white hover:bg-white hover:text-blue-600 font-semibold py-3 px-6 rounded-md transition-colors text-center"
>
View code
</a>
</div>
</div>
</div>
<div class="grid md:grid-cols-3 gap-6 mb-8">
<div class="bg-white rounded-lg shadow-md p-6 border border-gray-200">
<div
class="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center mb-4"
>
<svg
class="w-6 h-6 text-blue-600"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13 10V3L4 14h7v7l9-11h-7z"
/>
</svg>
</div>
<h3 class="text-lg font-semibold text-gray-900 mb-2">Idempotent</h3>
<p class="text-gray-600 text-sm">
Smart duplicate prevention ensures you'll never receive the same email
twice.
</p>
</div>
<div class="bg-white rounded-lg shadow-md p-6 border border-gray-200">
<div
class="w-12 h-12 bg-green-100 rounded-lg flex items-center justify-center mb-4"
>
<svg
class="w-6 h-6 text-green-600"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
/>
</svg>
</div>
<h3 class="text-lg font-semibold text-gray-900 mb-2">Privacy first</h3>
<p class="text-gray-600 text-sm">
Zero spam, zero tracking, zero data sharing. Your email stays private
and secure. Unsubscribe at any time.
</p>
</div>
<div class="bg-white rounded-lg shadow-md p-6 border border-gray-200">
<div
class="w-12 h-12 bg-purple-100 rounded-lg flex items-center justify-center mb-4"
>
<svg
class="w-6 h-6 text-purple-600"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"
/>
</svg>
</div>
<h3 class="text-lg font-semibold text-gray-900 mb-2">Quality content</h3>
<p class="text-gray-600 text-sm">
Curated insights on Rust backend development, performance tips, and
production war stories.
</p>
</div>
</div>
<div
id="newsletter-signup"
class="bg-white rounded-lg shadow-md p-8 border border-gray-200"
>
<div class="max-w-2xl mx-auto text-center">
<h2 class="text-2xl font-bold text-gray-900 mb-4">Stay updated</h2>
<p class="text-gray-600 mb-6">
Subscribe to our newsletter to get the latest updates.
</p>
<form action="/subscriptions" method="post" class="max-w-md mx-auto">
<div class="flex flex-col sm:flex-row gap-3">
<input
type="email"
name="email"
placeholder="Enter your email address"
required
class="flex-1 px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
<button
type="submit"
class="bg-blue-600 text-white hover:bg-blue-700 font-medium py-3 px-6 rounded-md transition-colors"
>
Subscribe Subscribe
</button> </a>
<a href="https://gitea.alphonsepaix.xyz/alphonse/zero2prod"
target="_blank"
class="border border-white text-white hover:bg-white hover:text-blue-600 font-semibold py-3 px-6 rounded-md transition-colors text-center">
View code
</a>
</div> </div>
</form>
<div class="mt-4">{{ message }}</div>
</div>
</div>
<div class="mt-8 bg-gray-50 rounded-lg p-6">
<h3 class="text-lg font-semibold text-gray-900 mb-4 text-center">Stats</h3>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 text-center">
<div>
<div class="text-2xl font-bold text-blue-600" id="subscriber-count">
2
</div>
<div class="text-sm text-gray-600">subscribers</div>
</div> </div>
<div> </div>
<div class="grid md:grid-cols-3 gap-6 mb-8">
<div class="bg-white rounded-lg shadow-md p-6 border border-gray-200">
<div class="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center mb-4">
<svg class="w-6 h-6 text-blue-600"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</div>
<h3 class="text-lg font-semibold text-gray-900 mb-2">Idempotent</h3>
<p class="text-gray-600 text-sm">
Smart duplicate prevention ensures you'll never receive the same email
twice.
</p>
</div>
<div class="bg-white rounded-lg shadow-md p-6 border border-gray-200">
<div class="w-12 h-12 bg-green-100 rounded-lg flex items-center justify-center mb-4">
<svg class="w-6 h-6 text-green-600"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
</svg>
</div>
<h3 class="text-lg font-semibold text-gray-900 mb-2">Privacy first</h3>
<p class="text-gray-600 text-sm">
Zero spam, zero tracking, zero data sharing. Your email stays private
and secure. Unsubscribe at any time.
</p>
</div>
<div class="bg-white rounded-lg shadow-md p-6 border border-gray-200">
<div class="w-12 h-12 bg-purple-100 rounded-lg flex items-center justify-center mb-4">
<svg class="w-6 h-6 text-purple-600"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
</svg>
</div>
<h3 class="text-lg font-semibold text-gray-900 mb-2">Quality content</h3>
<p class="text-gray-600 text-sm">
Curated insights on Rust backend development, performance tips, and
production war stories.
</p>
</div>
</div>
<div id="newsletter-signup"
class="bg-white rounded-lg shadow-md p-8 border border-gray-200">
<div class="max-w-2xl mx-auto text-center">
<h2 class="text-2xl font-bold text-gray-900 mb-4">Stay updated</h2>
<p class="text-gray-600 mb-6">Subscribe to our newsletter to get the latest updates.</p>
<form action="/subscriptions" method="post" class="max-w-md mx-auto">
<div class="flex flex-col sm:flex-row gap-3">
<input type="email"
name="email"
placeholder="Enter your email address"
required
class="flex-1 px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" />
<button type="submit"
class="bg-blue-600 text-white hover:bg-blue-700 font-medium py-3 px-6 rounded-md transition-colors">
Subscribe
</button>
</div>
</form>
<div class="mt-4">{{ message }}</div>
</div>
</div>
<div class="mt-8 bg-gray-50 rounded-lg p-6">
<h3 class="text-lg font-semibold text-gray-900 mb-4 text-center">Stats</h3>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 text-center">
<div> <div>
<div class="text-2xl font-bold text-orange-600">23</div> <div class="text-2xl font-bold text-blue-600" id="subscriber-count">2</div>
<div class="text-sm text-gray-600">emails sent</div> <div class="text-sm text-gray-600">subscribers</div>
</div>
<div>
<div>
<div class="text-2xl font-bold text-orange-600">23</div>
<div class="text-sm text-gray-600">emails sent</div>
</div>
</div>
<div>
<div class="text-2xl font-bold text-green-600">0</div>
<div class="text-sm text-gray-600">email opened</div>
</div>
<div>
<div class="text-2xl font-bold text-purple-600">3</div>
<div class="text-sm text-gray-600">issues delivered</div>
</div> </div>
</div>
<div>
<div class="text-2xl font-bold text-green-600">0</div>
<div class="text-sm text-gray-600">email opened</div>
</div>
<div>
<div class="text-2xl font-bold text-purple-600">3</div>
<div class="text-sm text-gray-600">issues delivered</div>
</div> </div>
</div> </div>
</div> </div>
{% endblock %}
<!-- <script>
document.addEventListener("DOMContentLoaded", function () {
htmx.ajax("GET", "/api/stats/subscribers", {
target: "#subscriber-count",
});
});
</script> -->
{% endblock %}
</div>

1
templates/input.css Normal file
View File

@@ -0,0 +1 @@
@import "tailwindcss";

View File

@@ -1,69 +1,43 @@
{% extends "base.html" %} {% block title %}Login - zero2prod{% endblock %} {% {% extends "base.html" %}
block content %} {% block title %}Login - zero2prod{% endblock %}
<div class="min-h-[60vh] flex items-center justify-center"> {% block content %}
<div class="max-w-md w-full space-y-8"> <div class="min-h-[60vh] flex items-center justify-center">
<div class="text-center"> <div class="max-w-md w-full space-y-8">
<h2 class="text-3xl font-bold text-gray-900">Login</h2> <div class="text-center">
<p class="mt-2 text-sm text-gray-600"> <h2 class="text-3xl font-bold text-gray-900">Login</h2>
Sign in to access the admin dashboard. <p class="mt-2 text-sm text-gray-600">Sign in to access the admin dashboard.</p>
</p> </div>
</div> <div class="bg-white rounded-lg shadow-md p-8 border border-gray-200">
<form action="/login" method="post" class="space-y-6">
<div class="bg-white rounded-lg shadow-md p-8 border border-gray-200"> <div>
<form action="/login" method="post" class="space-y-6"> <label for="username" class="block text-sm font-medium text-gray-700 mb-2">Username</label>
<div> <input type="text"
<label id="username"
for="username" name="username"
class="block text-sm font-medium text-gray-700 mb-2" required
> class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" />
Username </div>
</label> <div>
<input <label for="password" class="block text-sm font-medium text-gray-700 mb-2">Password</label>
type="text" <input type="password"
id="username" id="password"
name="username" name="password"
required required
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" />
/> </div>
</div> <div>
<button type="submit"
<div> class="w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors">
<label Continue
for="password" </button>
class="block text-sm font-medium text-gray-700 mb-2" </div>
> </form>
Password <div class="mt-4 text-center">{{ error }}</div>
</label> </div>
<input <div class="text-center">
type="password" <a href="/"
id="password" class="text-sm text-blue-600 hover:text-blue-500 transition-colors">← Back to homepage</a>
name="password" </div>
required
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<button
type="submit"
class="w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors"
>
Continue
</button>
</div>
</form>
<div class="mt-4 text-center">{{ error }}</div>
</div>
<div class="text-center">
<a
href="/"
class="text-sm text-blue-600 hover:text-blue-500 transition-colors"
>
← Back to homepage
</a>
</div> </div>
</div> </div>
</div>
{% endblock %} {% endblock %}