htmx and Tailwind CSS production setup
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
/target
|
/target
|
||||||
|
/node_modules
|
||||||
|
|||||||
31
Cargo.lock
generated
31
Cargo.lock
generated
@@ -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"
|
||||||
|
|||||||
@@ -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
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
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
1137
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
9
package.json
Normal file
9
package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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))
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
|
||||||
|
|||||||
@@ -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
1
templates/input.css
Normal file
@@ -0,0 +1 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
@@ -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 %}
|
||||||
|
|||||||
Reference in New Issue
Block a user