DevAdd SEO module, blogging editor improvements, and navigation UX enhancements - #187
Merged
Conversation
* introduce PhoenixKit.Modules.SEO plus defaults and persistence keys * expose new `/admin/settings/seo` LiveView, router entry, and sidebar link * update modules dashboard card/toggle and inject robots meta tags when enabled
* update admin_nav_item to use conditional hover classes * active items now show hover:bg-primary/90 instead of hover:bg-base-200 * prevents grey hover background from overriding blue active background
…tabs
* introduce exact_match_only attribute to admin_nav_item component
* update nav_item_active? to skip hierarchical matching when exact_match_only is true
* apply exact_match_only={true} to blogging nav item
* blogging parent now highlights only on /admin/blogging, not on /admin/blogging/{blog-slug}
* individual blog subtabs continue to highlight correctly as nested items
* update blogging index grid from xl:grid-cols-4 to xl:grid-cols-5 * move drafts from stat description to its own dedicated stat square * improve visual hierarchy by giving drafts equal prominence with published posts
* change both slug-based and timestamp-based badges to use badge-ghost * remove conditional styling that made slug-based primary and timestamp-based ghost * apply consistent light grey appearance across blogging index and settings pages
Implements proper timezone handling for blog post timestamps:
- Store all times in UTC internally
- Display times in logged-in user's timezone
- Respect user's date/time format preferences from Settings
- Allow users to publish at "5pm their time" without UTC math
Performance optimizations:
- Load date_time_settings once per mount using Settings.get_settings_cached/2
- Pass settings through call chain to cached formatter functions
- Add cached timezone conversion helpers in PhoenixKit.Utils.Date:
* shift_to_user_timezone_cached/3
* format_datetime_with_timezone_cached/4
* format_date_with_timezone_cached/4
* format_time_with_timezone_cached/4
- These use get_user_timezone_cached/2 to avoid per-row Settings queries
Resilience improvements:
- Add nil user fallback: user = current_user || %{user_timezone: nil}
- Graceful handling during tests or unauthenticated access
Files modified:
- lib/phoenix_kit/utils/date.ex: Add cached timezone conversion helpers
- lib/phoenix_kit_web/live/modules/blogging/index.ex: Load settings, use cached formatters
- lib/phoenix_kit_web/live/modules/blogging/blog.ex: Load settings, use cached formatters
- lib/phoenix_kit_web/live/modules/blogging/blog.html.heex: Pass user and settings to formatter
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed label from "PhoenixKit Storage file ID" to "Phoenix Kit Media ID" in the blogging editor's advanced manual file ID input section for better clarity and consistency with product naming. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Implements automatic saving with debouncing to prevent accidental data loss: Features: - 2-second debounced autosave triggers on content or metadata changes - Proper timer management to prevent multiple simultaneous saves - Visual status indicators replacing manual save button: * "Saving..." badge (blue, with spinner) during autosave * "Unsaved changes" badge (yellow) when changes pending * "Saved" badge (green, with checkmark) when all saved - Silent autosave (no flash messages for automatic saves) - Manual save events still show confirmation messages - Browser exit protection remains for in-progress saves Technical implementation: - Added :is_autosaving and :autosave_timer assigns to track state - schedule_autosave/1 helper cancels old timers and schedules new ones - perform_save/1 extracted from save event for reuse in autosave - handle_info(:autosave) processes debounced autosave requests - Conditional flash messages based on is_autosaving flag Benefits: - Users no longer need to remember to click save - Work is automatically preserved during editing - Reduced cognitive load with clear visual feedback - Prevents data loss from browser crashes or accidental navigation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Resolves issues where component insertion and unsaved changes popup weren't working in environments with Content Security Policy enabled. Key fixes: - Add CSP nonce support to inline scripts for policy compliance - Add visible warning system if JavaScript features fail to initialize - Improve error handling with helpful messages for blocked scripts - Remove excessive console.log debugging statements - Better DOM initialization handling for LiveView updates - Add MutationObserver to manage warning visibility CSP Compliance: - Extract nonce from assigns (script_csp_nonce or csp_nonce) - Apply nonce attribute to all inline script tags - Scripts now work in strict CSP environments User Experience: - Warning banner hidden automatically when JS works - Shows helpful message if inline scripts blocked - Noscript fallback for fully disabled JavaScript - Init retry with timeout and error reporting Technical improvements: - Proper quote escaping in template strings - Cleaner event listener parameter naming - Warning element reference caching with validation - 20-attempt retry limit before showing error This fixes production/staging issues while maintaining local dev compatibility. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Prefix unused session parameter with underscore in SEO mount - Remove default value from nav_item_active?/4 (always called with 4 args) This resolves all compilation warnings. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
RateLimiter fixes for Hammer 7.x API:
- Backend.set/3 returns integer count, not :ok/{:error, reason}
- Backend.get/2 returns integer directly, not {:ok, count}
- Backend.hit/3 only returns {:allow, count} or {:deny, retry_after}
- Remove unreachable error handling for non-existent {:error, reason} case
Autosave pattern match fix:
- Remove unreachable 'other ->' clause in handle_info(:autosave)
- perform_save/1 always returns {:noreply, socket}, so pattern match directly
All Dialyzer warnings resolved.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Hammer 7.x removed delete_buckets with no replacement, and Backend.set/3 requires positive integers (cannot set to 0 to reset). Changes: - Mark reset_rate_limit/2 as @deprecated with explanation - Return {:error, :not_supported} instead of attempting reset - Add migration guide in documentation - Log warning when function is called Alternative approaches for users: - For testing: Use Application.put_env to disable rate limiting - For admin intervention: Wait for time window to expire naturally - For immediate reset: Restart application (clears ETS tables) This resolves Dialyzer errors about invalid Backend.set/3 arguments. See: https://hexdocs.pm/hammer/upgrade-v7.html 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add alias for PhoenixKit.Users.RateLimiter.Backend at module level to avoid repeated fully-qualified module references. This resolves all Credo software design suggestions about nested module aliasing in rate_limiter.ex. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Extract error handling logic into helper functions to bring complexity down from 14 to acceptable levels: - Add handle_post_update_result/4 to centralize update success path - Add handle_post_update_error/2 for update error handling - Add handle_post_creation_error/3 for creation error handling - Refactor create_new_post/2 to use new helpers - Refactor create_new_translation/2 to use new helpers This eliminates the Credo cyclomatic complexity warnings while maintaining the same functionality and improving code maintainability. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TL;DR
This PR adds a new SEO module with noindex configuration, implements autosave for the blogging editor, fixes timezone handling for timestamps,
improves navigation UX with better hover states and active highlighting, and resolves CSP compatibility issues for production environments.
SEO Module
Blogging Editor Enhancements
Autosave Functionality
Timezone-Aware Timestamps
CSP Compatibility Fixes
UI Polish
Navigation & Dashboard Improvements
Navigation UX
Blogging Dashboard
Technical Details
Files Modified:
Breaking Changes: None