This commit is contained in:
Bryce
2025-08-03 19:37:23 -07:00
parent f5938c5586
commit 21d3a710f2
5 changed files with 144 additions and 134 deletions

View File

@@ -8,3 +8,4 @@ Here are special rules you must follow:
7. Always print unhandled exceptions to the console.
8. Follow best practices for jinja template partials. That is, separate out components where appropriate.
9. Ask the user when there is something unclear.
10. I always run the server locally on port 5000, so you can use curl to test. No need to start it yourself.

View File

@@ -1,4 +1,4 @@
from flask import Blueprint, render_template, request, jsonify
from flask import Blueprint, render_template, request, jsonify, make_response
from app import db
from app.models import Folder, User
import uuid
@@ -9,6 +9,13 @@ main = Blueprint('main', __name__)
# For prototype, use a fixed user ID
MOCK_USER_ID = '123e4567-e89b-12d3-a456-426614174000'
@main.route('/api/folders/new', methods=['GET'])
def new_folder_modal():
# Return the add folder modal
response = make_response(render_template('partials/folder_modal.html'))
response.headers['HX-Trigger'] = 'open-modal'
return response
@main.route('/')
def index():
# Ensure the mock user exists
@@ -50,7 +57,10 @@ def add_folder():
folders = Folder.query.filter_by(user_id=MOCK_USER_ID).all()
# Return the updated folders list HTML
return render_template('partials/folders_list.html', folders=folders)
response = make_response(render_template('partials/folders_list.html', folders=folders))
response.headers['HX-Trigger'] = 'close-modal'
return response
except Exception as e:
# Print unhandled exceptions to the console as required
@@ -120,8 +130,9 @@ def update_folder(folder_id):
# Get updated list of folders
folders = Folder.query.filter_by(user_id=MOCK_USER_ID).all()
# Return the updated folders list HTML
return render_template('partials/folders_list.html', folders=folders)
response = make_response(render_template('partials/folders_list.html', folders=folders))
response.headers['HX-Trigger'] = 'close-modal'
return response
except Exception as e:
# Print unhandled exceptions to the console as required
@@ -152,3 +163,23 @@ def get_folder(folder_id):
# Print unhandled exceptions to the console as required
logging.exception("Error getting folder: %s", e)
return jsonify({'error': 'Error retrieving folder'}), 500
@main.route('/api/folders/<folder_id>/edit', methods=['GET'])
def edit_folder_modal(folder_id):
try:
# Find the folder by ID
folder = Folder.query.filter_by(id=folder_id, user_id=MOCK_USER_ID).first()
if not folder:
return jsonify({'error': 'Folder not found'}), 404
# Return the edit folder modal with folder data
response = make_response(render_template('partials/folder_modal.html', folder=folder))
response.headers['HX-Trigger'] = 'open-modal'
return response
except Exception as e:
# Print unhandled exceptions to the console as required
logging.exception("Error getting folder for edit: %s", e)
return jsonify({'error': 'Error retrieving folder'}), 500

View File

@@ -11,69 +11,57 @@
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
</head>
<body class="min-h-screen flex"
x-data="{
editFolderData: { id: null, name: '', rule_text: '', priority: 0 },
openEditFolderModal(folderData) {
console.log(folderData)
this.editFolderData = {
id: folderData.detail.id,
name: folderData.detail.name,
rule_text: folderData.detail.rule_text,
priority: folderData.detail.priority
};
document.getElementById('edit-folder-modal').showModal();
}
}"
@open-add-folder-modal.window="document.getElementById('add-folder-modal').showModal()"
@open-edit-folder-modal.window="openEditFolderModal($event.detail)">
<body class="min-h-screen flex bg-base-200"
x-data "{}"
x-on:open-modal.window="$refs.modal.showModal()"
x-on:close-modal.window="$refs.modal.close()">
<!-- Sidebar -->
<div class="sidebar w-64 min-h-screen p-4 flex flex-col">
<div class="sidebar w-64 min-h-screen p-4 flex flex-col bg-base-100 shadow-lg">
<div class="mb-8">
<h1 class="text-2xl font-bold text-primary flex items-center">
<i class="fas fa-envelope mr-2"></i>
Email Organizer
</h1>
<p class="text-sm mt-1">AI-powered email organization</p>
<p class="text-sm mt-1 text-base-content/70">AI-powered email organization</p>
</div>
<nav class="flex-grow menu bg-transparent">
<nav class="flex-grow menu bg-transparent rounded-lg">
<div class="active">
<a class="btn btn-ghost justify-start">
<i class="fas fa-folder mr-3"></i>
<i class="fas fa-folder mr-3 text-primary"></i>
Folders
</a>
</div>
<div>
<a class="btn btn-ghost justify-start">
<i class="fas fa-inbox mr-3"></i>
<i class="fas fa-inbox mr-3 text-secondary"></i>
Inbox
</a>
</div>
<div>
<a class="btn btn-ghost justify-start">
<i class="fas fa-cog mr-3"></i>
<i class="fas fa-cog mr-3 text-accent"></i>
Settings
</a>
</div>
<div>
<a class="btn btn-ghost justify-start">
<i class="fas fa-chart-bar mr-3"></i>
<i class="fas fa-chart-bar mr-3 text-info"></i>
Analytics
</a>
</div>
<div>
<a class="btn btn-ghost justify-start">
<i class="fas fa-question-circle mr-3"></i>
<i class="fas fa-question-circle mr-3 text-warning"></i>
Help
</a>
</div>
</nav>
<div class="mt-auto pt-4 border-t border-slate-700">
<div class="mt-auto pt-4 border-t border-base-300">
<div>
<a class="btn btn-ghost justify-start">
<i class="fas fa-sign-out-alt mr-3"></i>
<i class="fas fa-sign-out-alt mr-3 text-error"></i>
Logout
</a>
</div>
@@ -83,45 +71,49 @@
<!-- Main Content -->
<div class="flex-1 flex flex-col">
<!-- Top Bar -->
<header class="border-b border-slate-700 p-4 flex justify-between items-center">
<header class="bg-base-100 border-b border-base-300 p-4 flex justify-between items-center shadow-sm">
<div>
<h2 class="text-xl font-semibold">Folders</h2>
<p class="text-sm">Manage your email organization rules</p>
<p class="text-sm text-base-content/70">Manage your email organization rules</p>
</div>
<div class="flex items-center space-x-4">
<button class="relative p-2 rounded-full hover:bg-slate-700">
<button class="relative p-2 rounded-full hover:bg-base-300 btn-circle">
<i class="fas fa-bell"></i>
<span class="notification-badge absolute top-0 right-0">3</span>
<span class="badge badge-sm badge-secondary absolute -top-1 -right-1">3</span>
</button>
<div class="relative" x-data="{ open: false }" @click.outside="open = false">
<button id="user-menu-button" class="flex items-center space-x-2 p-2 rounded-lg hover:bg-slate-700" @click="open = !open">
<button id="user-menu-button" class="flex items-center space-x-2 p-2 rounded-lg hover:bg-base-300" @click="open = !open">
<div class="w-8 h-8 rounded-full bg-primary flex items-center justify-center">
<span class="font-semibold">U</span>
<span class="font-semibold text-primary-content">U</span>
</div>
<span>User Name</span>
<span class="hidden md:inline">User Name</span>
<i class="fas fa-chevron-down"></i>
</button>
<!-- User Dropdown -->
<div id="user-dropdown" class="user-dropdown absolute right-0 mt-2 w-48 rounded-md shadow-lg py-1" x-show="open">
<a href="#" class="block px-4 py-2 text-sm hover:bg-slate-700">Profile</a>
<a href="#" class="block px-4 py-2 text-sm hover:bg-slate-700">Settings</a>
<a href="#" class="block px-4 py-2 text-sm hover:bg-slate-700">Logout</a>
<div id="user-dropdown" class="user-dropdown absolute right-0 mt-2 w-48 rounded-md shadow-lg py-1 bg-base-100 z-10" x-show="open">
<a href="#" class="block px-4 py-2 text-sm hover:bg-base-300">Profile</a>
<a href="#" class="block px-4 py-2 text-sm hover:bg-base-300">Settings</a>
<a href="#" class="block px-4 py-2 text-sm hover:bg-base-300">Logout</a>
</div>
</div>
</div>
</header>
<!-- Main Content Area -->
<main class="flex-1 p-6 overflow-auto">
<main class="flex-1 p-6 overflow-auto bg-base-200">
<div class="flex justify-between items-center mb-6">
<div>
<h2 class="text-2xl font-semibold">Email Folders</h2>
<p class="">Create and manage your email organization rules</p>
<h2 class="text-2xl font-bold">Email Folders</h2>
<p class="text-base-content/70">Create and manage your email organization rules</p>
</div>
<button class="btn btn-primary" onclick="document.getElementById('add-folder-modal').showModal()">
<button class="btn btn-primary"
hx-get="/api/folders/new"
hx-target="#modal-holder"
hx-swap="innerHTML"
>
<i class="fas fa-plus mr-2"></i>
Add Folder
</button>
@@ -133,68 +125,9 @@
</main>
</div>
<!-- Add Folder Modal -->
<dialog id="add-folder-modal" class="modal">
<div class="modal-box">
<h3 class="font-bold text-lg mb-4">Add New Folder</h3>
<form id="add-folder-form" hx-post="/api/folders" hx-target="#folders-list" hx-swap="innerHTML" hx-on:htmx:after-request="document.getElementById('add-folder-modal').close(); document.getElementById('add-folder-form').reset();">
<div class="mb-4">
<label for="folder-name" class="block text-sm font-medium mb-1">Name</label>
<input type="text" id="folder-name" name="name" class="input input-bordered w-full" placeholder="e.g., Work, Personal, Newsletters" required>
</div>
<div class="mb-4">
<label for="folder-rule" class="block text-sm font-medium mb-1">Rule (Natural Language)</label>
<textarea id="folder-rule" name="rule_text" class="textarea textarea-bordered w-full h-24" placeholder="e.g., Move emails from 'newsletter@company.com' to this folder" required></textarea>
</div>
<div class="mb-4">
<label for="folder-priority" class="block text-sm font-medium mb-1">Priority</label>
<select id="folder-priority" name="priority" class="select select-bordered w-full">
<option value="1">High</option>
<option value="0" selected>Normal</option>
<option value="-1">Low</option>
</select>
</div>
<div class="modal-action">
<button type="button" class="btn btn-outline" onclick="document.getElementById('add-folder-modal').close()">Cancel</button>
<button type="submit" class="btn btn-primary">Add Folder</button>
</div>
</form>
</div>
</dialog>
<!-- Edit Folder Modal -->
<dialog id="edit-folder-modal" class="modal">
<div class="modal-box">
<h3 class="font-bold text-lg mb-4">Edit Folder</h3>
<p x-text="`/api/folders/${editFolderData.id}`" />
<form id="edit-folder-form"
:hx-put="`/api/folders/${editFolderData.id}`"
x-effect="editFolderData.id; htmx.process($el)"
hx-target="#folders-list"
hx-swap="innerHTML"
hx-on:htmx:after-request="document.getElementById('edit-folder-modal').close()">
<div class="mb-4">
<label for="edit-folder-name" class="block text-sm font-medium mb-1">Name</label>
<input type="text" id="edit-folder-name" name="name" class="input input-bordered w-full" placeholder="e.g., Work, Personal, Newsletters" required x-model="editFolderData.name">
</div>
<div class="mb-4">
<label for="edit-folder-rule" class="block text-sm font-medium mb-1">Rule (Natural Language)</label>
<textarea id="edit-folder-rule" name="rule_text" class="textarea textarea-bordered w-full h-24" placeholder="e.g., Move emails from 'newsletter@company.com' to this folder" required x-model="editFolderData.rule_text"></textarea>
</div>
<div class="mb-4">
<label for="edit-folder-priority" class="block text-sm font-medium mb-1">Priority</label>
<select id="edit-folder-priority" name="priority" class="select select-bordered w-full" x-model="editFolderData.priority">
<option value="1" :selected="editFolderData.priority == 1">High</option>
<option value="0" :selected="editFolderData.priority == 0">Normal</option>
<option value="-1" :selected="editFolderData.priority == -1">Low</option>
</select>
</div>
<div class="modal-action">
<button type="button" class="btn btn-outline" onclick="document.getElementById('edit-folder-modal').close()">Cancel</button>
<button type="submit" class="btn btn-primary">Update Folder</button>
</div>
</form>
</div>
<!-- Modal Holder -->
<dialog id="modal-holder" x-ref="modal" class="modal" >
<!-- Modals will be loaded here via HTMX -->
</dialog>
</body>

View File

@@ -0,0 +1,39 @@
<div id="folder-modal" @click.away="$refs.modal.close()" class="modal-box">
<h3 class="font-bold text-lg mb-4" id="modal-title">
{% if folder %}Edit Folder{% else %}Add New Folder{% endif %}
</h3>
<form id="folder-form" {% if folder %} hx-put="/api/folders/{{ folder.id }}" {% else %} hx-post="/api/folders" {%
endif %} hx-target="#folders-list" hx-swap="innerHTML">
{% if folder %}
<input type="hidden" id="folder-id" name="id" value="{{ folder.id }}">
{% endif %}
<div class="mb-4">
<label for="folder-name" class="block text-sm font-medium mb-1">Name</label>
<input type="text" id="folder-name" name="name" class="input input-bordered w-full"
placeholder="e.g., Work, Personal, Newsletters" required
value="{% if folder %}{{ folder.name }}{% endif %}">
</div>
<div class="mb-4">
<label for="folder-rule" class="block text-sm font-medium mb-1">Rule (Natural Language)</label>
<textarea id="folder-rule" name="rule_text" class="textarea textarea-bordered w-full h-24"
placeholder="e.g., Move emails from 'newsletter@company.com' to this folder"
required>{% if folder %}{{ folder.rule_text }}{% endif %}</textarea>
</div>
<div class="mb-4">
<label for="folder-priority" class="block text-sm font-medium mb-1">Priority</label>
<select id="folder-priority" name="priority" class="select select-bordered w-full">
<option value="1" {% if folder and folder.priority==1 %}selected{% endif %}>High</option>
<option value="0" {% if not folder or (folder and folder.priority==0) %}selected{% endif %}>Normal
</option>
<option value="-1" {% if folder and folder.priority==-1 %}selected{% endif %}>Low</option>
</select>
</div>
<div class="modal-action">
<button type="button" class="btn btn-outline"
@click="$dispatch('close-modal')">Cancel</button>
<button type="submit" class="btn btn-primary" id="submit-btn">
{% if folder %}Update Folder{% else %}Add Folder{% endif %}
</button>
</div>
</form>
</div>

View File

@@ -1,23 +1,18 @@
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{% for folder in folders %}
<div class="card bg-base-100 shadow-sm">
<div class="card bg-base-100 shadow-xl border border-base-300">
<div class="card-body">
<div class="flex justify-between items-start mb-2">
<h3 class="text-xl font-bold">{{ folder.name }}</h3>
<div class="flex space-x-2">
<button class="btn"
@click="$dispatch('open-edit-folder-modal', {
detail: {
id: '{{ folder.id }}',
name: '{{ folder.name }}',
rule_text: '{{ folder.rule_text }}',
priority: '{{ folder.priority or 0 }}'
}
})">
Edit
<button class="btn btn-sm btn-outline"
hx-get="/api/folders/{{ folder.id }}/edit"
hx-target="#modal-holder"
hx-swap="innerHTML"
hx-trigger="click">
<i class="fas fa-edit"></i>
</button>
<button class="text-red-400 btn"
<button class="btn btn-sm btn-outline btn-error"
hx-delete="/api/folders/{{ folder.id }}"
hx-target="#folders-list"
hx-swap="innerHTML"
@@ -27,23 +22,34 @@
</div>
</div>
<ul class="list bg-base-200 rounded">
<li class="list-row">{{ folder.rule_text }}</li>
</ul>
<div class="flex justify-between items-center">
<span class="badge badge-primary">Priority: {{ folder.priority or 'Normal' }}</span>
<div class="bg-base-200 rounded-box p-4 mb-4">
<p class="text-base-content/80">{{ folder.rule_text }}</p>
</div>
<div class="flex justify-between items-center mt-2">
{% if folder.priority == 1 %}
<span class="badge badge-error">High Priority</span>
{% elif folder.priority == -1 %}
<span class="badge badge-info">Low Priority</span>
{% else %}
<span class="badge badge-primary">Normal Priority</span>
{% endif %}
<span class="text-xs badge badge-secondary">0 emails</span>
</div>
</div>
</div>
{% else %}
<div class="col-span-full text-center py-12">
<div class="text-5xl mb-4">
<div class="col-span-full text-center py-12 bg-base-100 rounded-box shadow-lg">
<div class="text-5xl mb-4 text-primary">
<i class="fas fa-folder-open"></i>
</div>
<h3 class="text-xl font-semibold mb-2">No folders yet</h3>
<p class="mb-4">Add your first folder to get started organizing your emails.</p>
<button class="btn btn-primary" @click="$dispatch('open-add-folder-modal')">
<h3 class="text-2xl font-bold mb-2">No folders yet</h3>
<p class="mb-6 text-base-content/70">Add your first folder to get started organizing your emails.</p>
<button class="btn btn-primary btn-lg"
hx-get="/api/folders/new"
hx-target="#modal-holder"
hx-swap="innerHTML"
hx-trigger="click">
<i class="fas fa-plus mr-2"></i>
Create Folder
</button>