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
6 changes: 6 additions & 0 deletions compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,11 @@ services:
ports:
- "8000:8000"

tailwind:
<<: *common-settings
command: ["uv", "run", "-m", "manage", "tailwind", "--skip-checks", "watch"]
depends_on: []
init: true

volumes:
postgres-data:
38 changes: 38 additions & 0 deletions frontend/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,41 @@

/* Use legacy config for content sources */
@config "../tailwind.config.js";

/* Base form input styles */
@layer base {
input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
input[type="url"],
input[type="tel"],
input[type="date"],
input[type="time"],
input[type="datetime-local"],
input[type="search"],
textarea,
select {
@apply w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm;
@apply focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500;
@apply text-gray-900 text-base;
}

input[type="checkbox"] {
@apply h-4 w-4 text-blue-600 border-gray-300 rounded;
@apply focus:ring-blue-500;
}

input[type="file"] {
@apply block w-full text-sm text-gray-500;
@apply file:mr-4 file:py-2 file:px-4;
@apply file:rounded-md file:border-0;
@apply file:text-sm file:font-medium;
@apply file:bg-blue-50 file:text-blue-700;
@apply hover:file:bg-blue-100;
}

textarea {
@apply min-h-[120px];
}
}
28 changes: 18 additions & 10 deletions templates/_field.html
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
<div class="field">
<label for="{{ field.id_for_label }}">
<span>{{ field.label }} {% if field.field.required %}<span class="required" aria-hidden="true">(required)</span>{% endif %}</span>
{{ field }}
{% if field.help_text %}<p class="help-text" id="help-{{ field.id_for_label }}">{{ field.help_text }}</p>{% endif %}
{% if field.errors %}
<div class="field-errors" role="alert">
{{ field.errors }}
</div>
{% endif %}
<div class="mb-4">
<label for="{{ field.id_for_label }}" class="block text-sm font-medium text-gray-700 mb-1">
{{ field.label }}
{% if field.field.required %}<span class="text-red-500 text-xs ml-1">(required)</span>{% endif %}
</label>
{% if field.field.widget.input_type == 'checkbox' %}
<div class="flex items-center">
{{ field }}
{% if field.help_text %}<span class="ml-2 text-sm text-gray-500">{{ field.help_text }}</span>{% endif %}
</div>
{% else %}
{{ field }}
{% if field.help_text %}<p class="mt-1 text-sm text-gray-500" id="help-{{ field.id_for_label }}">{{ field.help_text }}</p>{% endif %}
{% endif %}
{% if field.errors %}
<div class="mt-1 text-sm text-red-600" role="alert">
{{ field.errors }}
</div>
{% endif %}
</div>
22 changes: 16 additions & 6 deletions templates/_form.html
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
<form action="{{ action|default:"." }}" method="POST">
<form action="{{ action|default:'.' }}" method="POST" class="space-y-4">
{% csrf_token %}
{{ form.non_field_errors }}
{% if form.non_field_errors %}
<div class="p-4 bg-red-50 border border-red-200 rounded-md text-red-700 text-sm">
{{ form.non_field_errors }}
</div>
{% endif %}
{% for field in form.visible_fields %}
{% include "_field.html" %}
{% endfor %}
{% for field in form.hidden_fields %}
{{ field }}
{% endfor %}
<div class="buttons">
<input class="button" type="submit" value="{{ submit_verb|default:"Save" }}" aria-label="{{ submit_verb|default:"Save" }}">
<div class="flex flex-wrap gap-3 pt-4">
<button type="submit" class="inline-flex items-center px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-md transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
{{ submit_verb|default:"Save" }}
</button>
{% if include_delete %}
<input class="button serious" type="submit" name="delete" value="{{ delete_verb|default:"Delete" }}" aria-label="{{ delete_verb|default:"Delete" }}">
<button type="submit" name="delete" class="inline-flex items-center px-4 py-2 bg-red-600 hover:bg-red-700 text-white font-medium rounded-md transition-colors focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2">
{{ delete_verb|default:"Delete" }}
</button>
{% endif %}
{% if include_random %}
<input class="button boring" type="submit" name="random_after" accesskey="n" value="{{ random_verb|default:"Save & Next" }}" aria-label="{{ random_verb|default:"Save and go to next item" }}">
<button type="submit" name="random_after" accesskey="n" class="inline-flex items-center px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white font-medium rounded-md transition-colors focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2">
{{ random_verb|default:"Save & Next" }}
</button>
{% endif %}
</div>
</form>
13 changes: 6 additions & 7 deletions templates/account/base.html
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
{% extends "base.html" %}


{% block content-wrapper %}
<div class="section">
<div class="main-column">
{% block content %}
{% endblock content %}
{% block content %}
<div class="max-w-md mx-auto">
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-8">
{% block account_content %}
{% endblock account_content %}
</div>
</div>
{% endblock content-wrapper %}
{% endblock content %}
120 changes: 66 additions & 54 deletions templates/account/email.html
Original file line number Diff line number Diff line change
@@ -1,63 +1,75 @@
{% extends "account/base.html" %}
{% extends "base.html" %}

{% load i18n %}

{% block title %}Email Addresses{% endblock %}

{% block content %}
<h2>Email Addresses</h2>

{% if emailaddresses %}
<p>The following email addresses are associated with your account:</p>

<ul class="real">
{% for emailaddress in emailaddresses %}
<li>
{{ emailaddress.email }}
{% if emailaddress.verified %}
<span class="verified">(verified)</span>
{% else %}
<span class="unverified">(unverified)</span>
{% endif %}
{% if emailaddress.primary %}
<span class="primary">(primary)</span>
{% endif %}
</li>
{% endfor %}
</ul>

<form method="POST" action="{% url 'account_email' %}">
{% csrf_token %}
<select name="email">
{% for emailaddress in emailaddresses %}
<option value="{{ emailaddress.email }}">{{ emailaddress.email }}</option>
<div class="max-w-xl mx-auto">
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-8">
<h2 class="text-2xl font-bold text-gray-900 mb-6">Email Addresses</h2>

{% if emailaddresses %}
<p class="text-gray-600 mb-4">The following email addresses are associated with your account:</p>

<div class="space-y-2 mb-6">
{% for emailaddress in emailaddresses %}
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-md border border-gray-200">
<span class="font-medium text-gray-900">{{ emailaddress.email }}</span>
<div class="flex gap-2">
{% if emailaddress.verified %}
<span class="text-xs px-2 py-1 bg-green-100 text-green-700 rounded">Verified</span>
{% else %}
<span class="text-xs px-2 py-1 bg-yellow-100 text-yellow-700 rounded">Unverified</span>
{% endif %}
{% if emailaddress.primary %}
<span class="text-xs px-2 py-1 bg-blue-100 text-blue-700 rounded">Primary</span>
{% endif %}
</div>
</div>
{% endfor %}
</div>

<form method="POST" action="{% url 'account_email' %}" class="mb-8">
{% csrf_token %}
<select name="email" class="w-full p-2 border border-gray-300 rounded-md mb-3">
{% for emailaddress in emailaddresses %}
<option value="{{ emailaddress.email }}">{{ emailaddress.email }}</option>
{% endfor %}
</select>
<div class="flex flex-wrap gap-2">
<button type="submit" name="action_primary" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-md transition-colors">
Make Primary
</button>
<button type="submit" name="action_send" class="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white text-sm font-medium rounded-md transition-colors">
Re-send Verification
</button>
<button type="submit" name="action_remove" class="px-4 py-2 bg-red-600 hover:bg-red-700 text-white text-sm font-medium rounded-md transition-colors">
Remove
</button>
</div>
</form>
{% else %}
<p class="text-gray-500 italic mb-6">No email addresses are associated with your account.</p>
{% endif %}

<h3 class="text-lg font-semibold text-gray-900 mb-4 pt-4 border-t border-gray-200">Add Email Address</h3>

{% if form.errors %}
<div class="mb-4 p-4 bg-red-50 border border-red-200 rounded-md text-red-700 text-sm">
{{ form.non_field_errors }}
</div>
{% endif %}

<form method="POST" action="{% url 'account_email' %}" class="space-y-4">
{% csrf_token %}
{% for field in form.visible_fields %}
{% include "_field.html" %}
{% endfor %}
</select>
<div class="buttons">
<button class="button" type="submit" name="action_primary">Make Primary</button>
<button class="button" type="submit" name="action_send">Re-send Verification</button>
<button class="button serious" type="submit" name="action_remove">Remove</button>
</div>
</form>
{% else %}
<p>No email addresses are associated with your account.</p>
{% endif %}

<h3>Add Email Address</h3>

{% if form.errors %}
<div class="errors">
{{ form.non_field_errors }}
</div>
{% endif %}

<form method="POST" action="{% url 'account_email' %}">
{% csrf_token %}
{% for field in form.visible_fields %}
{% include "_field.html" %}
{% endfor %}
<div class="buttons">
<input class="button" type="submit" name="action_add" value="Add Email">
<button type="submit" name="action_add" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-md transition-colors">
Add Email
</button>
</form>
</div>
</form>
</div>
{% endblock %}
42 changes: 23 additions & 19 deletions templates/account/email_confirm.html
Original file line number Diff line number Diff line change
@@ -1,28 +1,32 @@
{% extends "account/base.html" %}
{% extends "base.html" %}

{% load i18n %}

{% block title %}Confirm Email Address{% endblock %}

{% block content %}
<h2>Confirm Email Address</h2>
<div class="max-w-md mx-auto">
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-8 text-center">
<h2 class="text-2xl font-bold text-gray-900 mb-4">Confirm Email Address</h2>

{% if confirmation %}
<p>
Please confirm that <strong>{{ confirmation.email_address.email }}</strong>
is an email address for user <strong>{{ confirmation.email_address.user }}</strong>.
</p>
{% if confirmation %}
<p class="text-gray-600 mb-6">
Please confirm that <strong class="text-gray-900">{{ confirmation.email_address.email }}</strong>
is an email address for user <strong class="text-gray-900">{{ confirmation.email_address.user }}</strong>.
</p>

<form method="POST" action="{% url 'account_confirm_email' confirmation.key %}">
{% csrf_token %}
<div class="buttons">
<input class="button" type="submit" value="Confirm">
</div>
</form>
{% else %}
<p class="error">
This email confirmation link has expired or is invalid.
Please <a href="{% url 'account_email' %}">request a new confirmation email</a>.
</p>
{% endif %}
<form method="POST" action="{% url 'account_confirm_email' confirmation.key %}">
{% csrf_token %}
<button type="submit" class="w-full py-2.5 px-4 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-md transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
Confirm
</button>
</form>
{% else %}
<div class="p-4 bg-red-50 border border-red-200 rounded-md text-red-700">
<p class="mb-2">This email confirmation link has expired or is invalid.</p>
<a href="{% url 'account_email' %}" class="text-blue-600 hover:text-blue-800 font-medium">Request a new confirmation email</a>
</div>
{% endif %}
</div>
</div>
{% endblock %}
62 changes: 35 additions & 27 deletions templates/account/login.html
Original file line number Diff line number Diff line change
@@ -1,38 +1,46 @@
{% extends "account/base.html" %}
{% extends "base.html" %}

{% load i18n socialaccount %}

{% block title %}Login{% endblock %}

{% block content %}
<h2>Login</h2>
<div class="max-w-md mx-auto">
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-8">
<h2 class="text-2xl font-bold text-gray-900 mb-6 text-center">Log In</h2>

{% if form.errors %}
<div class="errors">
{{ form.non_field_errors }}
</div>
{% endif %}
{% if form.errors %}
<div class="mb-4 p-4 bg-red-50 border border-red-200 rounded-md text-red-700 text-sm">
{{ form.non_field_errors }}
</div>
{% endif %}

<form method="POST" action="{% url 'account_login' %}">
{% csrf_token %}
{% for field in form.visible_fields %}
{% include "_field.html" %}
{% endfor %}
<div class="buttons">
<input class="button" type="submit" value="Login">
</div>
</form>
<form method="POST" action="{% url 'account_login' %}" class="space-y-4">
{% csrf_token %}
{% for field in form.visible_fields %}
{% include "_field.html" %}
{% endfor %}
<div class="pt-2">
<button type="submit" class="w-full py-2.5 px-4 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-md transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
Log In
</button>
</div>
</form>

<p class="login-links">
<a href="{% url 'account_reset_password' %}">Forgot Password?</a>
<a href="{% url 'register' %}">Register New Account</a>
</p>
<div class="mt-4 flex justify-between text-sm">
<a href="{% url 'account_reset_password' %}" class="text-blue-600 hover:text-blue-800">Forgot Password?</a>
<a href="{% url 'register' %}" class="text-blue-600 hover:text-blue-800">Register</a>
</div>

{% get_providers as socialaccount_providers %}
{% if socialaccount_providers %}
<hr>
<p>
<a class="button" href="{% provider_login_url 'github' %}">Login with GitHub</a>
</p>
{% endif %}
{% get_providers as socialaccount_providers %}
{% if socialaccount_providers %}
<div class="mt-6 pt-6 border-t border-gray-200">
<a href="{% provider_login_url 'github' %}" class="w-full flex items-center justify-center gap-2 py-2.5 px-4 bg-gray-800 hover:bg-gray-900 text-white font-medium rounded-md transition-colors">
<i class="fab fa-github"></i>
Log in with GitHub
</a>
</div>
{% endif %}
</div>
</div>
{% endblock %}
Loading