improvements

This commit is contained in:
2025-08-06 16:34:05 -07:00
parent 41ea8fb3bd
commit e9ed1c3de6
9 changed files with 111 additions and 170 deletions

View File

@@ -15,14 +15,8 @@ def index():
# Get folders for the current authenticated user
folders = Folder.query.filter_by(user_id=current_user.id).all()
# Separate folders by type
tidy_folders = [folder for folder in folders if folder.folder_type == 'tidy']
destination_folders = [folder for folder in folders if folder.folder_type == 'destination']
return render_template('index.html',
folders=folders,
tidy_folders=tidy_folders,
destination_folders=destination_folders)
folders=folders)
@main.route('/api/folders/new', methods=['GET'])
@login_required
@@ -82,16 +76,9 @@ def add_folder():
# Get updated list of folders for the current user
folders = Folder.query.filter_by(user_id=current_user.id).all()
# Get updated lists of folders by type
tidy_folders = [folder for folder in folders if folder.folder_type == 'tidy']
destination_folders = [folder for folder in folders if folder.folder_type == 'destination']
# Return both sections
tidy_section = render_template('partials/folders_to_tidy_section.html', tidy_folders=tidy_folders)
destination_section = render_template('partials/destination_folders_section.html', destination_folders=destination_folders)
response = make_response(tidy_section + destination_section)
response = make_response(render_template('partials/folders_list.html', folders=folders))
response.headers['HX-Trigger'] = 'close-modal'
response.headers["HX-Target"] = 'folder-list'
response.status_code = 201
return response
@@ -102,13 +89,6 @@ def add_folder():
# Return error in modal
errors = {'general': 'An unexpected error occurred. Please try again.'}
# Get updated lists of folders by type for error fallback
folders = Folder.query.filter_by(user_id=current_user.id).all()
tidy_folders = [folder for folder in folders if folder.folder_type == 'tidy']
destination_folders = [folder for folder in folders if folder.folder_type == 'destination']
# Return both sections as fallback
tidy_section = render_template('partials/folders_to_tidy_section.html', tidy_folders=tidy_folders)
destination_section = render_template('partials/destination_folders_section.html', destination_folders=destination_folders)
response = make_response(render_template('partials/folder_modal.html', errors=errors, name=name, rule_text=rule_text, priority=priority))
response.headers['HX-Retarget'] = '#folder-modal'
@@ -134,18 +114,8 @@ def delete_folder(folder_id):
db.session.delete(folder)
db.session.commit()
# Get updated list of folders for the current user
folders = Folder.query.filter_by(user_id=current_user.id).all()
# Get updated lists of folders by type
tidy_folders = [folder for folder in folders if folder.folder_type == 'tidy']
destination_folders = [folder for folder in folders if folder.folder_type == 'destination']
# Return both sections
tidy_section = render_template('partials/folders_to_tidy_section.html', tidy_folders=tidy_folders)
destination_section = render_template('partials/destination_folders_section.html', destination_folders=destination_folders)
return tidy_section + destination_section
return render_template('partials/folders_list.html', folders=folders)
except Exception as e:
# Print unhandled exceptions to the console as required
@@ -153,14 +123,8 @@ def delete_folder(folder_id):
db.session.rollback()
# Return the folders list unchanged
folders = Folder.query.filter_by(user_id=current_user.id).all()
tidy_folders = [folder for folder in folders if folder.folder_type == 'tidy']
destination_folders = [folder for folder in folders if folder.folder_type == 'destination']
# Return both sections
tidy_section = render_template('partials/folders_to_tidy_section.html', tidy_folders=tidy_folders)
destination_section = render_template('partials/destination_folders_section.html', destination_folders=destination_folders)
return tidy_section + destination_section
return render_template('partials/folders_list.html', folders=folders)
@main.route('/api/folders/<folder_id>/toggle', methods=['PUT'])
@login_required
@@ -245,15 +209,6 @@ def update_folder(folder_id):
# If there are validation errors, return the modal with errors
if errors:
# Get updated lists of folders by type for error fallback
folders = Folder.query.filter_by(user_id=current_user.id).all()
tidy_folders = [folder for folder in folders if folder.folder_type == 'tidy']
destination_folders = [folder for folder in folders if folder.folder_type == 'destination']
# Return both sections as fallback
tidy_section = render_template('partials/folders_to_tidy_section.html', tidy_folders=tidy_folders)
destination_section = render_template('partials/destination_folders_section.html', destination_folders=destination_folders)
response = make_response(render_template('partials/folder_modal.html', folder=folder, errors=errors, name=name, rule_text=rule_text, priority=priority))
response.headers['HX-Retarget'] = '#folder-modal'
response.headers['HX-Reswap'] = 'outerHTML'
@@ -275,15 +230,9 @@ def update_folder(folder_id):
# Get updated list of folders for the current user
folders = Folder.query.filter_by(user_id=current_user.id).all()
# Get updated lists of folders by type
tidy_folders = [folder for folder in folders if folder.folder_type == 'tidy']
destination_folders = [folder for folder in folders if folder.folder_type == 'destination']
# Return both sections
tidy_section = render_template('partials/folders_to_tidy_section.html', tidy_folders=tidy_folders)
destination_section = render_template('partials/destination_folders_section.html', destination_folders=destination_folders)
response = make_response(tidy_section + destination_section)
section = render_template('partials/folders_list.html', folders=folders)
response = make_response(section)
response.headers['HX-Trigger'] = 'close-modal'
return response
@@ -422,7 +371,6 @@ def sync_imap_folders():
# Process each unique folder
synced_count = 0
print("HELLOOO")
processed_emails_service = ProcessedEmailsService(current_user)
for imap_folder in unique_folders:
@@ -474,14 +422,8 @@ def sync_imap_folders():
# Get updated list of folders
folders = Folder.query.filter_by(user_id=current_user.id).all()
tidy_folders = [folder for folder in folders if folder.folder_type == 'tidy']
destination_folders = [folder for folder in folders if folder.folder_type == 'destination']
# Return both sections
tidy_section = render_template('partials/folders_to_tidy_section.html', tidy_folders=tidy_folders)
destination_section = render_template('partials/destination_folders_section.html', destination_folders=destination_folders)
return tidy_section + destination_section
return render_template('partials/folders_list.html', folders=folders)
except Exception as e:
logging.exception("Error syncing IMAP folders: %s", e)
@@ -511,11 +453,14 @@ def get_folders():
# Get all folders
folders = Folder.query.filter_by(user_id=current_user.id).all()
# Check if we need to return a specific section
if folder_type == 'tidy':
return render_template('partials/folders_to_tidy_section.html', tidy_folders=folders)
response = make_response(render_template('partials/folders_to_tidy_section.html', folders=folders))
response["HX-Retarget"] = "#folders-to-tidy"
return response
elif folder_type == 'destination':
return render_template('partials/destination_folders_section.html', destination_folders=folders)
response = make_response(render_template('partials/destination_folders_section.html', folders=folders))
response["HX-Retarget"] = "#destination-folders"
return response
else:
return render_template('partials/folders_list.html', folders=folders)

View File

@@ -26,9 +26,14 @@
.fade-in-htmx.htmx-added {
opacity: 0;
}
.htmx-added .fade-in-htmx {
opacity: 0;
}
.fade-in-htmx {
opacity: 1;
transition: opacity 1s ease-out;
transition: opacity 300ms ease-out;
}
/* Fade out transition for HTMX */
.fade-out-htmx.htmx-swapping {

View File

@@ -21,57 +21,41 @@
<h2 class="text-2xl font-bold">Email Folders</h2>
<p class="text-base-content/70">Create and manage your email organization rules</p>
</div>
<div data-loading-states>
<button class="btn btn-primary" hx-get="/api/folders/new" hx-target="#modal-holder" hx-swap="innerHTML"
data-loading-disable>
<i class="fas fa-plus mr-2" data-loading-class="!hidden"></i>
<span data-loading-class="!hidden">Add Folder</span>
<span class="loading loading-spinner loading-xs hidden" data-loading-class-remove="hidden"></span>
</button>
</div>
</div>
<!-- Welcome Section -->
<div class="mb-8 p-6 bg-base-100 rounded-box shadow-lg border border-base-300">
<h3 class="text-xl font-bold mb-2">Welcome to Email Organizer!</h3>
<p class="text-base-content/80 mb-4">Organize your emails automatically with AI-powered rules. Create folders and set up rules to categorize incoming emails.</p>
<div class="flex space-x-4" data-loading-states>
<button class="btn btn-primary" hx-get="/api/folders/new" hx-target="#modal-holder"
hx-swap="innerHTML"
data-loading-disable>
<i class="fas fa-plus mr-2" data-loading-class="!hidden"></i>
<span data-loading-class="!hidden">Create Your First Folder</span>
<span class="loading loading-spinner loading-xs hidden" data-loading-class-remove="hidden"></span>
</button>
<div class="flex space-x-2">
{% if current_user.imap_config %}
<div data-loading-states>
<button class="btn btn-outline" hx-post="/api/imap/sync" hx-target="#folders-list"
hx-swap="innerHTML"
data-loading-disable>
<i class="fas fa-sync mr-2" data-loading-class="!hidden"></i>
<span data-loading-class="!hidden">Sync Folders</span>
<span class="loading loading-spinner loading-xs hidden" data-loading-class-remove="hidden"></span>
</button>
</div>
<div data-loading-states >
<button class="btn btn-outline" hx-get="/api/imap/config" hx-target="#modal-holder" hx-swap="innerHTML"
data-loading-disable>
<i class="fas fa-cog" data-loading-class="!hidden"></i>
<span class="loading loading-spinner loading-xs hidden" data-loading-class-remove="hidden"></span>
</button>
<button class="btn btn-outline" hx-post="/api/imap/sync" hx-target="#folders-list" hx-swap="outerHTML settle:300ms"
data-loading-disable>
<i class="fas fa-sync mr-2" data-loading-class="!hidden"></i>
<span data-loading-class="!hidden">Sync Folders</span>
<span class="loading loading-spinner loading-xs hidden" data-loading-class-remove="hidden"></span>
</button>
</div>
<div data-loading-states>
<button class="btn btn-outline" hx-get="/api/imap/config" hx-target="#modal-holder" hx-swap="innerHTML"
data-loading-disable>
<i class="fas fa-cog" data-loading-class="!hidden"></i>
<span class="loading loading-spinner loading-xs hidden" data-loading-class-remove="hidden"></span>
</button>
</div>
{% else %}
<div data-loading-states>
<button class="btn btn-outline" hx-get="/api/imap/config" hx-target="#modal-holder" hx-swap="innerHTML"
data-loading-disable>
<i class="fas fa-cog mr-2" data-loading-class="!hidden"></i>
<span data-loading-class="!hidden">Configure IMAP</span>
<span class="loading loading-spinner loading-xs hidden" data-loading-class-remove="hidden"></span>
</button>
<button class="btn btn-outline" hx-get="/api/imap/config" hx-target="#modal-holder" hx-swap="innerHTML"
data-loading-disable>
<i class="fas fa-cog mr-2" data-loading-class="!hidden"></i>
<span data-loading-class="!hidden">Configure IMAP</span>
<span class="loading loading-spinner loading-xs hidden" data-loading-class-remove="hidden"></span>
</button>
</div>
{% endif %}
</div>
</div>
<!-- Welcome Section - Only shown when IMAP is not configured -->
{% if not current_user.imap_config %}
{% include "partials/welcome_section.html" %}
{% endif %}
<!-- Stats Section -->
<div class="mb-8 grid grid-cols-1 md:grid-cols-4 gap-4">
@@ -80,16 +64,16 @@
<div class="text-sm text-base-content/70">Total Folders</div>
</div>
<div class="card bg-base-100 shadow-md border border-base-300 p-4">
<div class="text-2xl font-bold text-warning">{{ tidy_folders|length }}</div>
<div class="text-2xl font-bold text-warning">{{ folders|selectattr('folder_type', 'equalto', 'tidy')|list|length }}</div>
<div class="text-sm text-base-content/70">Folders to Tidy</div>
</div>
<div class="card bg-base-100 shadow-md border border-base-300 p-4">
<div class="text-2xl font-bold text-secondary">{{ destination_folders|length }}</div>
<div class="text-2xl font-bold text-secondary">{{ folders|selectattr('folder_type', 'equalto', 'destination')|list|length }}</div>
<div class="text-sm text-base-content/70">Destination Folders</div>
</div>
<div class="card bg-base-100 shadow-md border border-base-300 p-4">
<div class="text-2xl font-bold text-info">
{{ tidy_folders|sum(attribute='pending_count') }}
{{ folders|selectattr('folder_type', 'equalto', 'tidy')|sum(attribute='pending_count') }}
</div>
<div class="text-sm text-base-content/70">Pending Emails</div>
</div>
@@ -102,16 +86,13 @@
<i class="fas fa-search absolute right-3 top-3 text-base-content/50"></i>
</div>
<div class="flex space-x-2">
<button class="btn btn-sm btn-outline" hx-get="/api/folders?filter=all" hx-target="#folders-list" hx-swap="innerHTML swap1:s">All</button>
<button class="btn btn-sm btn-outline" hx-get="/api/folders?filter=high" hx-target="#folders-list" hx-swap="innerHTML swap1:s">High Priority</button>
<button class="btn btn-sm btn-outline" hx-get="/api/folders?filter=normal" hx-target="#folders-list" hx-swap="innerHTML swap1:s">Normal</button>
<button class="btn btn-sm btn-outline" hx-get="/api/folders?filter=all" hx-target="#folders-list" hx-swap="outerHTML settle:300ms">All</button>
<button class="btn btn-sm btn-outline" hx-get="/api/folders?filter=high" hx-target="#folders-list" hx-swap="outerHTML settle:300ms">High Priority</button>
<button class="btn btn-sm btn-outline" hx-get="/api/folders?filter=normal" hx-target="#folders-list" hx-swap="outerHTML settle:300ms">Normal</button>
</div>
</div>
<section id="folders-list" class="mb-12">
{% include 'partials/folders_to_tidy_section.html' %}
{% include 'partials/destination_folders_section.html' %}
</section>
{% include 'partials/folders_list.html' %}
</main>
</div>
</div>

View File

@@ -1,18 +1,26 @@
<div class="destination-folders-section mb-8">
<div class="destination-folders-section mb-8 fade-in-htmx" id="destination-folders">
<div class="section-header bg-base-200 p-4 rounded-t-lg border-b border-base-300">
<div class="flex flex-col md:flex-row md:items-center md:justify-between">
<div>
<h2 class="text-2xl font-bold text-base-content">Destination Folders</h2>
<h2 class="text-2xl font-bold text-base-content">
<i class="fas fa-archive mr-3"></i>
Destination Folders</h2>
<p class="text-base-content/70 mt-1">Folders where emails are organized and stored</p>
</div>
<button class="btn btn-primary mt-4 md:mt-0" hx-get="/api/folders/new" hx-target="#modal-holder" hx-swap="innerHTML" hx-trigger="click">
<i class="fas fa-plus mr-2"></i>Add Destination Folder
</button>
<div data-loading-states>
<button class="btn btn-primary" hx-get="/api/folders/new" hx-target="#modal-holder" hx-swap="innerHTML"
hx-trigger="click">
<i class="fas fa-plus mr-2" data-loading-class="!hidden"></i>
<span data-loading-class="!hidden">Create Folder</span>
<span class="loading loading-spinner loading-xs hidden" data-loading-class-remove="hidden"></span>
</button>
</div>
</div>
</div>
<div id="destination-folders-list" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 p-6">
{% for folder in destination_folders %}
{% for folder in folders|selectattr('folder_type', 'equalto', 'destination') %}
{% include 'partials/folder_card_destination.html' %}
{% else %}
<div class="col-span-full text-center py-12 bg-base-100 rounded-box shadow-lg border border-dashed border-base-300">
@@ -32,4 +40,4 @@
</div>
{% endfor %}
</div>
</div>
</div>

View File

@@ -16,8 +16,8 @@
</button>
<button class="btn btn-sm btn-outline btn-error fade-me-out"
hx-delete="/api/folders/{{ folder.id }}"
hx-target="#destination-folders-list"
hx-swap="innerHTML swap:1s"
hx-target="#folders-list"
hx-swap="outerhtml settle:300ms"
hx-confirm="Are you sure you want to delete this folder?"
data-loading-disable
>

View File

@@ -1,25 +1,4 @@
<div id="folders-list" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 fade-in-htmx">
{% for folder in folders %}
{% include 'partials/folder_card.html' %}
{% else %}
<div class="col-span-full text-center py-12 bg-base-100 rounded-box shadow-lg border border-dashed border-base-300">
<div class="text-5xl mb-4 text-primary">
<i class="fas fa-folder-open"></i>
</div>
<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>
<div data-loading-states>
<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" data-loading-class="!hidden"></i>
<span data-loading-class="!hidden">Create Folder</span>
<span class="loading loading-spinner loading-xs hidden" data-loading-class-remove="hidden"></span>
</button>
</div>
<div class="mt-4 text-sm text-base-content/70">
<p>Need help setting up your first folder?</p>
<a href="#" class="link link-primary">View tutorial</a>
</div>
</div>
{% endfor %}
</div>
<section id="folders-list" class="mb-12">
{% include 'partials/folders_to_tidy_section.html' %}
{% include 'partials/destination_folders_section.html' %}
</section>

View File

@@ -1,31 +1,33 @@
<div class="folders-to-tidy-section mb-8">
<div class="section-header bg-base-200 p-4 rounded-t-lg border-b border-base-300">
<div class="folders-to-tidy-section mb-8 " id="folders-to-tidy">
<div class="section-header bg-base-200 p-4 rounded-t-lg border-b border-base-300 fade-in-htmx" >
<div class="flex flex-col md:flex-row md:items-center md:justify-between">
<div>
<h2 class="text-2xl font-bold text-base-content">Folders to Tidy</h2>
<p class="text-base-content/70 mt-1">Folders containing emails that need to be processed</p>
<h2 class="text-2xl font-bold text-base-content flex items-center">
<i class="fas fa-folder-open mr-3"></i>
Emails to organize
</h2>
<p class="text-base-content/70 mt-1">
Folders that need a little Marie Kondo
</p>
</div>
<button class="btn btn-primary mt-4 md:mt-0" hx-get="/api/folders/new" hx-target="#modal-holder" hx-swap="innerHTML" hx-trigger="click">
<i class="fas fa-plus mr-2"></i>Add Tidy Folder
</button>
</div>
</div>
<div id="folders-to-tidy-list" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 p-6">
{% for folder in tidy_folders %}
<div id="folders-to-tidy-list" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 p-6 fade-in-htmx">
{% for folder in folders|selectattr('folder_type', 'equalto', 'tidy') %}
{% include 'partials/folder_card_tidy.html' %}
{% else %}
<div class="col-span-full text-center py-12 bg-base-100 rounded-box shadow-lg border border-dashed border-base-300">
<div class="text-5xl mb-4 text-warning">
<i class="fas fa-inbox"></i>
<i class="fas fa-folder-open"></i>
</div>
<h3 class="text-2xl font-bold mb-2">No folders to tidy yet</h3>
<p class="mb-6 text-base-content/70">Add your first folder to start organizing your emails.</p>
<h3 class="text-2xl font-bold mb-2">Your email transformation area is empty!</h3>
<p class="mb-6 text-base-content/70">Let's bring in some folders to organize and watch the magic happen.</p>
<div data-loading-states>
<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" data-loading-class="!hidden"></i>
<span data-loading-class="!hidden">Create Folder</span>
<span data-loading-class="!hidden">Add Folder to Transform</span>
<span class="loading loading-spinner loading-xs hidden" data-loading-class-remove="hidden"></span>
</button>
</div>

View File

@@ -80,7 +80,7 @@
{% if success %}
<div class="mt-4 pt-4 border-t border-base-300" data-loading-states>
<button class="btn btn-success w-full" hx-post="/api/imap/sync" hx-target="#folders-list" hx-swap="innerHTML" data-loading-disable>
<button class="btn btn-success w-full" hx-post="/api/imap/sync" hx-target="#folders-list" hx-swap="outerHTML settle:300ms" data-loading-disable>
<span data-loading-class="!hidden"><i class="fas fa-sync mr-2"></i>Sync Folders</span>
<span class="loading loading-spinner loading-xs hidden" data-loading-class-remove="hidden"></span>
</button>

View File

@@ -0,0 +1,21 @@
<!-- Welcome Section - Only shown when IMAP is not configured -->
<div class="mb-8 p-6 bg-gradient-to-r from-primary/10 to-secondary/10 rounded-box shadow-lg border border-primary/20">
<div class="flex items-center mb-4">
<i class="fas fa-envelope-open-text text-3xl text-primary mr-4"></i>
<h3 class="text-xl font-bold">Welcome to Email Organizer! 🎉</h3>
</div>
<p class="text-base-content/80 mb-4">Ready to tame your email chaos? Let's get your email folders synchronized so we can start organizing your emails with AI-powered rules!</p>
<div class="flex items-center space-x-4" data-loading-states>
<button class="btn btn-primary" hx-get="/api/imap/config" hx-target="#modal-holder"
hx-swap="innerHTML"
data-loading-disable>
<i class="fas fa-cog mr-2" data-loading-class="!hidden"></i>
<span data-loading-class="!hidden">Set Up IMAP Sync</span>
<span class="loading loading-spinner loading-xs hidden" data-loading-class-remove="hidden"></span>
</button>
<div class="text-sm text-base-content/60">
<i class="fas fa-info-circle mr-1"></i>
Connect your email account to get started
</div>
</div>
</div>