Rename md files
This commit is contained in:
parent
0575847263
commit
6d66d56b62
927
PLAN.md
Normal file
927
PLAN.md
Normal file
@ -0,0 +1,927 @@
|
||||
# SimpleShop Theme - Implementation Plan
|
||||
|
||||
## Overview
|
||||
|
||||
Integrate the complete theme feature from `theme-demo-v28.html` into the Phoenix LiveView application. The system will provide 9 curated presets and extensive customization options with real-time preview. All data (including uploaded images) will be stored in a single SQLite database file.
|
||||
|
||||
**Key Concept:** This codebase IS the shop (like WordPress is one site). Multiple users can manage it, but there's only one shop/site per deployment.
|
||||
|
||||
## Core Architectural Decisions
|
||||
|
||||
### 1. Database Storage Strategy
|
||||
- **Images as BLOBs in SQLite** - Complete self-containment in single database file
|
||||
- **Application-level settings** - NO Shop model, just Settings tables for the site
|
||||
- **Multiple admin users** - Any authenticated user can manage theme (no ownership model)
|
||||
- **SVG stored as both BLOB and TEXT** - Enable recoloring feature
|
||||
|
||||
### 2. CSS Architecture
|
||||
- **Three-layer CSS system** using Custom Properties:
|
||||
- Layer 1: Primitives (static) - spacing, fonts, radii
|
||||
- Layer 2: Theme tokens (dynamic) - generated from settings
|
||||
- Layer 3: Semantic aliases (static) - friendly variable names
|
||||
- **Inline `<style>` generation** in LiveView for instant preview
|
||||
- **ETS cache** for storefront CSS (invalidate on save)
|
||||
|
||||
### 3. LiveView Structure
|
||||
- **Single LiveView** (`ThemeLive.Index`) with nested function components
|
||||
- **No iframe** - Direct preview in same LiveView using data attributes
|
||||
- **Smart preview data** - Mock data initially, switch to real products when available
|
||||
- **Optimistic UI** - Update preview immediately, persist on "Save" button
|
||||
- **Idiomatic Phoenix/LiveView** - Use Tailwind utilities where possible, CSS variables for dynamic theming
|
||||
|
||||
### 4. Development Workflow
|
||||
- **Semantic git commits** - Commit each logical step for easy rollback
|
||||
- **Test-driven** - Write Phoenix tests alongside implementation (ExUnit, ConnCase, LiveViewTest)
|
||||
- **Manual verification** - Test in browser after each phase
|
||||
- **Progressive enhancement** - Each commit should leave app in working state
|
||||
|
||||
## Database Schema
|
||||
|
||||
**Philosophy:** No Shop model - this app IS the shop. Settings are site-wide, managed by any admin user.
|
||||
|
||||
### Migration 1: Create Settings Table
|
||||
|
||||
**File:** `priv/repo/migrations/YYYYMMDDHHMMSS_create_settings.exs`
|
||||
|
||||
```elixir
|
||||
defmodule SimpleshopTheme.Repo.Migrations.CreateSettings do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create table(:settings, primary_key: false) do
|
||||
add :id, :binary_id, primary_key: true
|
||||
add :key, :string, null: false # e.g., "site_name", "theme_settings"
|
||||
add :value, :text, null: false # JSON or plain text
|
||||
add :value_type, :string, null: false, default: "string" # string, json, integer, boolean
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
||||
create unique_index(:settings, [:key])
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Migration 2: Create Images Table
|
||||
|
||||
**File:** `priv/repo/migrations/YYYYMMDDHHMMSS_create_images.exs`
|
||||
|
||||
```elixir
|
||||
defmodule SimpleshopTheme.Repo.Migrations.CreateImages do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create table(:images, primary_key: false) do
|
||||
add :id, :binary_id, primary_key: true
|
||||
|
||||
add :image_type, :string, null: false # "logo" | "header" | "product"
|
||||
add :filename, :string, null: false
|
||||
add :content_type, :string, null: false
|
||||
add :file_size, :integer, null: false
|
||||
|
||||
# BLOB storage
|
||||
add :data, :binary, null: false
|
||||
|
||||
# SVG support
|
||||
add :is_svg, :boolean, default: false
|
||||
add :svg_content, :text # For recoloring
|
||||
|
||||
# Optional: thumbnail for admin UI performance
|
||||
add :thumbnail_data, :binary
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
||||
create index(:images, [:image_type])
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## File Structure
|
||||
|
||||
### New Contexts and Schemas
|
||||
|
||||
```
|
||||
lib/simpleshop_theme/
|
||||
├── settings/
|
||||
│ ├── setting.ex # Setting schema (key-value store)
|
||||
│ └── theme_settings.ex # Embedded schema for theme settings
|
||||
├── settings.ex # Settings context (site-wide config API)
|
||||
├── media/
|
||||
│ ├── image.ex # Image schema (replaces ShopImage)
|
||||
│ └── svg_recolorer.ex # Recolor SVG logos
|
||||
├── media.ex # Media context (image uploads/serving)
|
||||
└── theme/
|
||||
├── presets.ex # Preset definitions (9 presets)
|
||||
├── css_generator.ex # Generate CSS from settings
|
||||
├── css_cache.ex # ETS cache GenServer
|
||||
└── preview_data.ex # Smart preview (mock → real products)
|
||||
```
|
||||
|
||||
### LiveView Components
|
||||
|
||||
```
|
||||
lib/simpleshop_theme_web/live/theme_live/
|
||||
├── index.ex # Main LiveView
|
||||
├── index.html.heex # Template
|
||||
├── preset_selector.ex # 9 preset buttons (function component)
|
||||
├── branding_section.ex # Logo/header uploads
|
||||
├── customization_panel.ex # Accordion with all options
|
||||
├── preview_frame.ex # Preview area with page switcher
|
||||
└── preview_pages/
|
||||
├── home.html.heex # Preview homepage
|
||||
├── collection.html.heex # Preview product grid
|
||||
├── pdp.html.heex # Preview product detail page
|
||||
├── cart.html.heex # Preview cart
|
||||
├── about.html.heex # Preview about page
|
||||
├── contact.html.heex # Preview contact page
|
||||
└── error.html.heex # Preview 404 page
|
||||
```
|
||||
|
||||
### Controllers
|
||||
|
||||
```
|
||||
lib/simpleshop_theme_web/controllers/
|
||||
└── image_controller.ex # Serve images from BLOBs
|
||||
```
|
||||
|
||||
### Static Assets & Tests
|
||||
|
||||
```
|
||||
priv/static/css/
|
||||
├── theme-primitives.css # Layer 1: Fixed CSS variables
|
||||
└── theme-semantic.css # Layer 3: Semantic aliases
|
||||
|
||||
assets/css/
|
||||
└── app.css # Import theme CSS here
|
||||
|
||||
test/simpleshop_theme/
|
||||
├── settings_test.exs # Settings context tests
|
||||
├── media_test.exs # Media context tests
|
||||
└── theme/
|
||||
├── css_generator_test.exs # CSS generation tests
|
||||
├── presets_test.exs # Preset validation tests
|
||||
└── preview_data_test.exs # Preview data tests
|
||||
|
||||
test/simpleshop_theme_web/
|
||||
├── controllers/
|
||||
│ └── image_controller_test.exs
|
||||
└── live/
|
||||
└── theme_live_test.exs # LiveView integration tests
|
||||
```
|
||||
|
||||
## Implementation Phases
|
||||
|
||||
### Phase 1: Database Foundation ✓
|
||||
**Goal:** Set up Settings and Media models for site-wide configuration
|
||||
|
||||
**Steps:**
|
||||
1. Create migrations for `settings` and `images` tables
|
||||
2. Run migrations
|
||||
3. Create `lib/simpleshop_theme/settings/setting.ex` schema
|
||||
4. Create `lib/simpleshop_theme/settings/theme_settings.ex` embedded schema
|
||||
5. Create `lib/simpleshop_theme/settings.ex` context with:
|
||||
- `get_setting/2` - Get setting by key with default
|
||||
- `put_setting/3` - Set a setting value
|
||||
- `get_theme_settings/0` - Get theme settings as struct
|
||||
- `update_theme_settings/1` - Update theme settings
|
||||
6. Create `lib/simpleshop_theme/media/image.ex` schema
|
||||
7. Create `lib/simpleshop_theme/media.ex` context with:
|
||||
- `upload_image/2` - Upload image with type
|
||||
- `get_image/1` - Get image by ID
|
||||
- `delete_image/1` - Delete image
|
||||
8. Create `lib/simpleshop_theme/theme/presets.ex` with all 9 preset definitions
|
||||
9. Create seed data in `priv/repo/seeds.exs` for default theme settings
|
||||
10. Write tests:
|
||||
- `test/simpleshop_theme/settings_test.exs`
|
||||
- `test/simpleshop_theme/media_test.exs`
|
||||
|
||||
**Files to create:**
|
||||
- `priv/repo/migrations/*_create_settings.exs`
|
||||
- `priv/repo/migrations/*_create_images.exs`
|
||||
- `lib/simpleshop_theme/settings/setting.ex`
|
||||
- `lib/simpleshop_theme/settings/theme_settings.ex`
|
||||
- `lib/simpleshop_theme/settings.ex`
|
||||
- `lib/simpleshop_theme/media/image.ex`
|
||||
- `lib/simpleshop_theme/media.ex`
|
||||
- `lib/simpleshop_theme/theme/presets.ex`
|
||||
- `test/simpleshop_theme/settings_test.exs`
|
||||
- `test/simpleshop_theme/media_test.exs`
|
||||
|
||||
**Files to modify:**
|
||||
- `priv/repo/seeds.exs` - Add default theme settings
|
||||
|
||||
**Git commit:** `feat: add Settings and Media contexts with theme settings schema`
|
||||
|
||||
**Validation:**
|
||||
- Run tests: `mix test`
|
||||
- Run `iex -S mix` and test Settings API
|
||||
- Run seeds: `mix run priv/repo/seeds.exs`
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: CSS Architecture ✓
|
||||
**Goal:** Set up CSS generation system using Phoenix/Tailwind best practices
|
||||
|
||||
**Steps:**
|
||||
1. Create `priv/static/css/theme-primitives.css` with primitive CSS variables from demo
|
||||
2. Create `priv/static/css/theme-semantic.css` with semantic aliases
|
||||
3. Update `assets/css/app.css` to import theme CSS files
|
||||
4. Create `lib/simpleshop_theme/theme/css_generator.ex` module:
|
||||
- `generate/1` - Takes ThemeSettings, returns CSS custom properties string
|
||||
- Helper functions for each theme token (mood, typography, shape, density)
|
||||
- `hex_to_hsl/1` for accent color conversion
|
||||
- Use Tailwind-friendly approach (CSS variables that Tailwind can reference)
|
||||
5. Create `lib/simpleshop_theme/theme/css_cache.ex` GenServer with ETS table
|
||||
6. Add `CSSCache` to supervision tree in `lib/simpleshop_theme/application.ex`
|
||||
7. Write tests:
|
||||
- `test/simpleshop_theme/theme/css_generator_test.exs`
|
||||
- `test/simpleshop_theme/theme/presets_test.exs`
|
||||
|
||||
**Files to create:**
|
||||
- `priv/static/css/theme-primitives.css`
|
||||
- `priv/static/css/theme-semantic.css`
|
||||
- `lib/simpleshop_theme/theme/css_generator.ex`
|
||||
- `lib/simpleshop_theme/theme/css_cache.ex`
|
||||
- `test/simpleshop_theme/theme/css_generator_test.exs`
|
||||
- `test/simpleshop_theme/theme/presets_test.exs`
|
||||
|
||||
**Files to modify:**
|
||||
- `assets/css/app.css`
|
||||
- `lib/simpleshop_theme/application.ex`
|
||||
|
||||
**Git commit:** `feat: add CSS generation system with custom properties and ETS cache`
|
||||
|
||||
**Validation:**
|
||||
- Run tests: `mix test test/simpleshop_theme/theme/`
|
||||
- Generate CSS for each preset in IEx, verify output
|
||||
- Check CSS variables work with Tailwind
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: Smart Preview Data ✓
|
||||
**Goal:** Create preview data system that uses real data when available, falls back to mock
|
||||
|
||||
**Steps:**
|
||||
1. Create `lib/simpleshop_theme/theme/preview_data.ex` with:
|
||||
- `products/0` - Real products if exist, else mock products
|
||||
- `cart_items/0` - Mock cart contents
|
||||
- `testimonials/0` - Mock testimonials for homepage
|
||||
- `categories/0` - Mock product categories
|
||||
- `has_real_products?/0` - Check if shop has products
|
||||
- Mock data uses placeholder images from `https://placehold.co/`
|
||||
2. Write tests:
|
||||
- `test/simpleshop_theme/theme/preview_data_test.exs`
|
||||
|
||||
**Files to create:**
|
||||
- `lib/simpleshop_theme/theme/preview_data.ex`
|
||||
- `test/simpleshop_theme/theme/preview_data_test.exs`
|
||||
|
||||
**Git commit:** `feat: add smart preview data system with mock fallback`
|
||||
|
||||
**Validation:**
|
||||
- Run tests: `mix test test/simpleshop_theme/theme/preview_data_test.exs`
|
||||
- Call functions in IEx, verify data structure
|
||||
- Verify falls back to mock when no products exist
|
||||
|
||||
---
|
||||
|
||||
### Phase 4: Theme LiveView (Basic) ✓
|
||||
**Goal:** Get basic theme editor working with preset switching
|
||||
|
||||
**Steps:**
|
||||
1. Create `lib/simpleshop_theme_web/live/theme_live/index.ex`:
|
||||
- Mount: Load theme settings from Settings context
|
||||
- Assign: `:theme_settings`, `:preview_page`, `:generated_css`
|
||||
- Event handlers: `apply_preset`, `change_preview_page`, `save_theme`
|
||||
2. Create `lib/simpleshop_theme_web/live/theme_live/index.html.heex`:
|
||||
- Two-column layout (controls sidebar + preview area)
|
||||
- Render preset buttons using Tailwind/daisyUI components
|
||||
- Render preview frame with generated CSS in `<style>` tag
|
||||
- Use Phoenix.Component for reusable pieces
|
||||
3. Add route to `lib/simpleshop_theme_web/router.ex`:
|
||||
- `live "/admin/theme", ThemeLive.Index` (authenticated)
|
||||
4. Add "Theme" link to user menu/navigation
|
||||
5. Write tests:
|
||||
- `test/simpleshop_theme_web/live/theme_live_test.exs`
|
||||
|
||||
**Files to create:**
|
||||
- `lib/simpleshop_theme_web/live/theme_live/index.ex`
|
||||
- `lib/simpleshop_theme_web/live/theme_live/index.html.heex`
|
||||
- `test/simpleshop_theme_web/live/theme_live_test.exs`
|
||||
|
||||
**Files to modify:**
|
||||
- `lib/simpleshop_theme_web/router.ex`
|
||||
- `lib/simpleshop_theme_web/components/layouts.ex` (add nav link)
|
||||
|
||||
**Git commit:** `feat: add Theme LiveView with preset switching`
|
||||
|
||||
**Validation:**
|
||||
- Run tests: `mix test test/simpleshop_theme_web/live/theme_live_test.exs`
|
||||
- Visit `/admin/theme`, click presets, verify CSS changes
|
||||
- Test authentication requirement
|
||||
|
||||
---
|
||||
|
||||
### Phase 5: Preview Pages ✓
|
||||
**Goal:** Create all 7 preview page templates
|
||||
|
||||
**Steps:**
|
||||
1. Create preview page templates by porting HTML from `theme-demo-v28.html`:
|
||||
- `lib/simpleshop_theme_web/live/theme_studio_live/preview_pages/home.html.heex`
|
||||
- `lib/simpleshop_theme_web/live/theme_studio_live/preview_pages/collection.html.heex`
|
||||
- `lib/simpleshop_theme_web/live/theme_studio_live/preview_pages/pdp.html.heex`
|
||||
- `lib/simpleshop_theme_web/live/theme_studio_live/preview_pages/cart.html.heex`
|
||||
- `lib/simpleshop_theme_web/live/theme_studio_live/preview_pages/about.html.heex`
|
||||
- `lib/simpleshop_theme_web/live/theme_studio_live/preview_pages/contact.html.heex`
|
||||
- `lib/simpleshop_theme_web/live/theme_studio_live/preview_pages/error.html.heex`
|
||||
2. Port CSS from demo to theme CSS files (primitives, semantic)
|
||||
3. Wire up mock data in templates
|
||||
4. Implement page switcher buttons in preview frame
|
||||
5. Add data attributes system (`data-mood`, `data-typography`, etc.) to preview container
|
||||
|
||||
**Files to create:**
|
||||
- 7 preview page `.html.heex` files in `preview_pages/` directory
|
||||
|
||||
**Files to modify:**
|
||||
- `lib/simpleshop_theme_web/live/theme_studio_live/index.html.heex` - Add page switcher
|
||||
- CSS files - Port all styles from demo
|
||||
|
||||
**Git commit:** `feat: add preview page templates with theme styling`
|
||||
|
||||
**Validation:**
|
||||
- Run tests: `mix test test/simpleshop_theme_web/live/theme_live_test.exs`
|
||||
- Switch between all 7 pages, verify they render correctly
|
||||
- Verify preview data shows (mock or real)
|
||||
|
||||
---
|
||||
|
||||
### Phase 6: Customization Options ✓
|
||||
**Goal:** Add all customization controls beyond presets
|
||||
|
||||
**Steps:**
|
||||
1. Create option group components in `index.html.heex`:
|
||||
- Mood selector (4 options: neutral, warm, cool, dark)
|
||||
- Typography selector (7 options: clean, editorial, modern, classic, friendly, minimal, impulse)
|
||||
- Shape selector (4 options: sharp, soft, round, pill)
|
||||
- Density selector (3 options: spacious, balanced, compact)
|
||||
- Grid columns selector (3 options: 2, 3, 4)
|
||||
- Header layout selector (3 options: standard, centered, minimal)
|
||||
2. Add color pickers:
|
||||
- Accent color (with live preview)
|
||||
- Secondary accent color
|
||||
- Sale color
|
||||
3. Add advanced options accordion:
|
||||
- Font size (small, medium, large)
|
||||
- Heading weight (light, regular, bold)
|
||||
- Layout width (contained, wide, full)
|
||||
- Button style (filled, outline, soft)
|
||||
- Card shadow (none, subtle, pronounced)
|
||||
- Product text align (left, center)
|
||||
- Image aspect ratio (square, portrait, landscape)
|
||||
4. Add feature toggles:
|
||||
- Announcement bar
|
||||
- Sticky header
|
||||
- Hover image
|
||||
- Quick add button
|
||||
- Show prices
|
||||
- PDP trust badges
|
||||
- PDP reviews
|
||||
- PDP related products
|
||||
5. Implement `update_setting` event handler in LiveView
|
||||
6. Wire all controls to update theme settings and regenerate CSS
|
||||
|
||||
**Files to modify:**
|
||||
- `lib/simpleshop_theme_web/live/theme_live/index.ex` - Add `update_setting` handler
|
||||
- `lib/simpleshop_theme_web/live/theme_live/index.html.heex` - Add all controls
|
||||
- `test/simpleshop_theme_web/live/theme_live_test.exs` - Add tests for customization
|
||||
|
||||
**Git commit:** `feat: add full customization controls with real-time preview`
|
||||
|
||||
**Validation:**
|
||||
- Run tests: `mix test test/simpleshop_theme_web/live/theme_live_test.exs`
|
||||
- Change each option, verify preview updates instantly
|
||||
- Test all toggles work correctly
|
||||
|
||||
---
|
||||
|
||||
### Phase 6.5: Fix Theme Visual Application 🔧
|
||||
**Goal:** Fix the theme controls so they actually visually affect the preview pages
|
||||
|
||||
**Problem:** Theme controls update the database but don't visually change the preview. The Phoenix implementation is missing the data-attribute-based CSS system that the demo uses.
|
||||
|
||||
**Root Cause Analysis:**
|
||||
The demo HTML uses data attributes (`data-mood="warm"`, `data-typography="editorial"`, etc.) on the `.preview-frame` element, and the CSS uses attribute selectors like `.preview-frame[data-mood="warm"] { ... }` to apply styles. The Phoenix LiveView implementation is missing these data attributes entirely.
|
||||
|
||||
**Discovered Issues:**
|
||||
|
||||
| Issue | Status | Root Cause |
|
||||
|-------|--------|------------|
|
||||
| Fonts don't change | ❌ Broken | 1. Google Fonts not loaded<br>2. No data attributes on preview-frame |
|
||||
| Mood/colors don't work (except dark) | ❌ Broken | No data attributes on preview-frame |
|
||||
| Shape doesn't work | ❌ Broken | No data attributes on preview-frame |
|
||||
| Density doesn't work | ❌ Broken | No data attributes on preview-frame |
|
||||
| Grid columns 4 shows as 2 | ❌ Broken | Tailwind can't process dynamic class interpolation |
|
||||
| Accent color doesn't update | ⚠️ Partial | CSS generated but may be overridden |
|
||||
| Header layout has no component | ❌ Missing | No header component in preview pages |
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. **Add Google Fonts to root layout** (`lib/simpleshop_theme_web/components/layouts/root.html.heex`):
|
||||
```heex
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,500;9..144,600;9..144,700&family=Inter:wght@400;500;600;700&family=Libre+Baskerville:wght@400;700&family=Nunito:wght@400;600;700&family=Nunito+Sans:opsz,wght@6..12,300;6..12,400;6..12,500;6..12,600&family=Outfit:wght@300;400;500;600&family=Source+Sans+3:wght@400;500;600&family=Space+Grotesk:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
```
|
||||
|
||||
2. **Add data attributes to preview frame** (`lib/simpleshop_theme_web/live/theme_live/index.html.heex:205`):
|
||||
```heex
|
||||
<div class="preview-frame bg-white overflow-auto"
|
||||
data-mood={@theme_settings.mood}
|
||||
data-typography={@theme_settings.typography}
|
||||
data-shape={@theme_settings.shape}
|
||||
data-density={@theme_settings.density}
|
||||
data-grid={@theme_settings.grid_columns}
|
||||
data-header={@theme_settings.header_layout}
|
||||
style="min-height: 600px; max-height: calc(100vh - 200px);">
|
||||
```
|
||||
|
||||
3. **Copy CSS from demo file** (`theme-demo-v28.html` lines 81-217) to create `priv/static/css/theme-layer2-attributes.css`:
|
||||
- All the `.preview-frame[data-mood="..."]` rules
|
||||
- All the `.preview-frame[data-typography="..."]` rules
|
||||
- All the `.preview-frame[data-shape="..."]` rules
|
||||
- All the `.preview-frame[data-density="..."]` rules
|
||||
- All the `.preview-frame[data-grid="..."]` rules
|
||||
- All the `.preview-frame[data-header="..."]` rules
|
||||
|
||||
4. **Import the new CSS file** in `assets/css/app.css`:
|
||||
```css
|
||||
@import "../priv/static/css/theme-primitives.css";
|
||||
@import "../priv/static/css/theme-layer2-attributes.css";
|
||||
@import "../priv/static/css/theme-semantic.css";
|
||||
```
|
||||
|
||||
5. **Fix grid columns dynamic classes** - Replace string interpolation with conditional logic in all preview pages:
|
||||
```heex
|
||||
<!-- BEFORE (broken): -->
|
||||
<div class={"grid gap-6 grid-cols-1 sm:grid-cols-2 lg:grid-cols-#{@theme_settings.grid_columns}"}>
|
||||
|
||||
<!-- AFTER (working): -->
|
||||
<div class={["grid gap-6 grid-cols-1 sm:grid-cols-2",
|
||||
case @theme_settings.grid_columns do
|
||||
"2" -> "lg:grid-cols-2"
|
||||
"3" -> "lg:grid-cols-3"
|
||||
"4" -> "lg:grid-cols-4"
|
||||
_ -> "lg:grid-cols-3"
|
||||
end]}>
|
||||
```
|
||||
|
||||
6. **Add header component** to preview pages - Create a header section that responds to `data-header` attribute:
|
||||
```heex
|
||||
<header class="theme-header" style="background-color: var(--t-surface-raised); border-bottom: 1px solid var(--t-border-subtle);">
|
||||
<!-- Header content that adapts based on data-header="standard|centered|minimal" -->
|
||||
</header>
|
||||
```
|
||||
|
||||
7. **Update CSSGenerator** to work alongside attribute-based CSS:
|
||||
- Keep generating accent color HSL variables (these are used by all themes)
|
||||
- Remove mood/typography/shape/density generation (now handled by attribute CSS)
|
||||
- Focus on dynamic values like accent_color, secondary_accent_color, sale_color
|
||||
|
||||
8. **Test all controls** in browser to verify visual changes work
|
||||
|
||||
**Files to create:**
|
||||
- `priv/static/css/theme-layer2-attributes.css` - Attribute-based theme CSS from demo
|
||||
|
||||
**Files to modify:**
|
||||
- `lib/simpleshop_theme_web/components/layouts/root.html.heex` - Add Google Fonts
|
||||
- `lib/simpleshop_theme_web/live/theme_live/index.html.heex` - Add data attributes to preview-frame
|
||||
- `assets/css/app.css` - Import new CSS file
|
||||
- `lib/simpleshop_theme/theme/css_generator.ex` - Simplify to focus on color variables
|
||||
- All preview page files - Fix grid columns dynamic classes
|
||||
- All preview page files - Add header component
|
||||
|
||||
**Git commit:** `fix: add data attributes and attribute-based CSS to make theme controls work visually`
|
||||
|
||||
**Validation:**
|
||||
- Change mood - verify background colors change
|
||||
- Change typography - verify fonts change
|
||||
- Change shape - verify button/card border radius changes
|
||||
- Change density - verify spacing changes
|
||||
- Change grid columns - verify all 3 options (2, 3, 4) work correctly
|
||||
- Change accent color - verify primary buttons change color
|
||||
- Change header layout - verify header structure changes
|
||||
|
||||
---
|
||||
|
||||
### Phase 7: File Uploads (Logo & Header) ✓
|
||||
**Goal:** Enable logo and header image uploads with database storage
|
||||
|
||||
**Steps:**
|
||||
1. Add dependency to `mix.exs`: `{:image, "~> 0.54"}` for thumbnail generation
|
||||
2. Implement SVG detection and text storage in `image.ex` changeset
|
||||
3. Add Media context functions for logo/header:
|
||||
- Update `upload_image/2` to handle thumbnails
|
||||
- Add `get_logo/0`, `get_header/0` convenience functions
|
||||
4. Create `lib/simpleshop_theme_web/controllers/image_controller.ex`:
|
||||
- `show/2` - Serve image BLOB with proper content-type and caching
|
||||
- `recolored_svg/2` - Serve recolored SVG
|
||||
5. Create `lib/simpleshop_theme/media/svg_recolorer.ex` for SVG color manipulation
|
||||
6. Add routes for image serving in router
|
||||
7. Configure LiveView uploads in `index.ex`:
|
||||
- `:logo_upload` - Accept PNG, JPG, WebP, SVG (max 2MB)
|
||||
- `:header_upload` - Accept PNG, JPG, WebP (max 5MB)
|
||||
8. Add upload controls to branding section in template using daisyUI components:
|
||||
- Logo mode selector (text-only, logo+text, logo-only, header-image, logo+header)
|
||||
- Logo upload with preview
|
||||
- Logo size slider (24-120px)
|
||||
- SVG recolor toggle + color picker
|
||||
- Header image upload with preview
|
||||
- Header zoom slider (100-200%)
|
||||
- Header position sliders (X and Y: 0-100%)
|
||||
9. Implement upload event handlers
|
||||
10. Display uploaded images in preview
|
||||
11. Write tests:
|
||||
- `test/simpleshop_theme_web/controllers/image_controller_test.exs`
|
||||
- `test/simpleshop_theme/media/svg_recolorer_test.exs`
|
||||
|
||||
**Files to create:**
|
||||
- `lib/simpleshop_theme_web/controllers/image_controller.ex`
|
||||
- `lib/simpleshop_theme/media/svg_recolorer.ex`
|
||||
- `test/simpleshop_theme_web/controllers/image_controller_test.exs`
|
||||
- `test/simpleshop_theme/media/svg_recolorer_test.exs`
|
||||
|
||||
**Files to modify:**
|
||||
- `lib/simpleshop_theme/media.ex` - Add logo/header helpers
|
||||
- `lib/simpleshop_theme/media/image.ex` - SVG handling
|
||||
- `lib/simpleshop_theme_web/live/theme_live/index.ex` - Upload config and handlers
|
||||
- `lib/simpleshop_theme_web/live/theme_live/index.html.heex` - Branding section UI
|
||||
- `lib/simpleshop_theme_web/router.ex` - Image serving routes
|
||||
- `mix.exs` - Add `:image` dependency
|
||||
- `test/simpleshop_theme_web/live/theme_live_test.exs` - Add upload tests
|
||||
|
||||
**Git commit:** `feat: add image uploads with BLOB storage and SVG recoloring`
|
||||
|
||||
**Validation:**
|
||||
- Run tests: `mix test`
|
||||
- Upload logo and header images
|
||||
- Verify BLOB storage in database
|
||||
- Test SVG recoloring works
|
||||
- Verify images display in preview
|
||||
- Test image serving with proper cache headers
|
||||
|
||||
---
|
||||
|
||||
### Phase 8: Persistence & Polish ✓
|
||||
**Goal:** Complete save functionality and UX polish
|
||||
|
||||
**Steps:**
|
||||
1. Implement "Save Theme" button and handler:
|
||||
- Show loading state during save
|
||||
- Flash success/error message
|
||||
- Invalidate CSS cache
|
||||
2. Add "Reset to Preset" functionality
|
||||
3. Implement unsaved changes warning
|
||||
4. Add keyboard shortcuts (Cmd+S to save)
|
||||
5. Debounce color picker inputs (300ms) to avoid excessive renders
|
||||
6. Add loading skeletons for preview
|
||||
7. Handle edge cases:
|
||||
- File too large error
|
||||
- Invalid file type error
|
||||
- Network errors
|
||||
8. Add client-side validation
|
||||
9. Improve mobile responsiveness of editor
|
||||
10. Add tooltips/help text for complex options
|
||||
|
||||
**Files to modify:**
|
||||
- `lib/simpleshop_theme_web/live/theme_live/index.ex` - Save logic, debouncing
|
||||
- `lib/simpleshop_theme_web/live/theme_live/index.html.heex` - UI polish
|
||||
- JavaScript hooks for keyboard shortcuts (if needed)
|
||||
- `test/simpleshop_theme_web/live/theme_live_test.exs` - Test persistence
|
||||
|
||||
**Git commit:** `feat: add theme persistence with UX polish and validation`
|
||||
|
||||
**Validation:**
|
||||
- Run tests: `mix test`
|
||||
- Save theme, reload page, verify settings persist
|
||||
- Test unsaved changes warning
|
||||
- Test keyboard shortcuts
|
||||
- Verify error handling for edge cases
|
||||
- Test mobile responsiveness
|
||||
|
||||
---
|
||||
|
||||
### Phase 9: Storefront Integration ✓
|
||||
**Goal:** Apply theme to actual public-facing shop
|
||||
|
||||
**Steps:**
|
||||
1. Create public shop routes in router:
|
||||
- `get "/:shop_slug", ShopController, :show` (shop homepage)
|
||||
- Or use domain-based routing for custom domains
|
||||
2. Create `lib/simpleshop_theme_web/live/shop_live/` directory for storefront LiveViews
|
||||
3. Create storefront page templates that respect theme settings:
|
||||
- Use same CSS system as preview
|
||||
- Load theme CSS from cache
|
||||
- Render actual products (when they exist) or empty states
|
||||
4. Create `lib/simpleshop_theme_web/plugs/load_shop_theme.ex`:
|
||||
- Load shop by domain/slug
|
||||
- Load theme CSS from cache
|
||||
- Assign to `conn.assigns.theme_css`
|
||||
5. Add theme CSS to root layout for storefront
|
||||
6. Implement proper caching headers for theme CSS
|
||||
7. Handle theme CSS cache warming on app startup
|
||||
|
||||
**Files to create:**
|
||||
- `lib/simpleshop_theme_web/live/shop_live/*` (storefront pages)
|
||||
- `lib/simpleshop_theme_web/plugs/load_theme.ex`
|
||||
- `test/simpleshop_theme_web/live/shop_live_test.exs`
|
||||
- `test/simpleshop_theme_web/plugs/load_theme_test.exs`
|
||||
|
||||
**Files to modify:**
|
||||
- `lib/simpleshop_theme_web/router.ex` - Public shop routes
|
||||
- `lib/simpleshop_theme_web/components/layouts.ex` - Theme CSS injection
|
||||
- Root layout template
|
||||
|
||||
**Git commit:** `feat: integrate theme system with public storefront`
|
||||
|
||||
**Validation:**
|
||||
- Run tests: `mix test`
|
||||
- Visit public shop URL, verify theme applies correctly
|
||||
- Change theme in admin, verify storefront updates
|
||||
- Test CSS cache invalidation
|
||||
- Verify performance with caching headers
|
||||
|
||||
---
|
||||
|
||||
## Technical Implementation Details
|
||||
|
||||
### CSS Generation Example
|
||||
|
||||
```elixir
|
||||
# lib/simpleshop_theme/theme/css_generator.ex
|
||||
defmodule SimpleshopTheme.Theme.CSSGenerator do
|
||||
alias SimpleshopTheme.Settings.ThemeSettings
|
||||
|
||||
def generate(%ThemeSettings{} = settings) do
|
||||
"""
|
||||
.preview-frame, .shop-root {
|
||||
#{generate_accent(settings.accent_color)}
|
||||
#{generate_mood(settings.mood)}
|
||||
#{generate_typography(settings.typography)}
|
||||
#{generate_shape(settings.shape)}
|
||||
#{generate_density(settings.density)}
|
||||
}
|
||||
"""
|
||||
end
|
||||
|
||||
defp generate_accent(hex) do
|
||||
{h, s, l} = hex_to_hsl(hex)
|
||||
"""
|
||||
--t-accent-h: #{h};
|
||||
--t-accent-s: #{s}%;
|
||||
--t-accent-l: #{l}%;
|
||||
--t-accent: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));
|
||||
"""
|
||||
end
|
||||
|
||||
defp hex_to_hsl("#" <> hex), do: hex_to_hsl(hex)
|
||||
defp hex_to_hsl(hex) when byte_size(hex) == 6 do
|
||||
{r, ""} = Integer.parse(String.slice(hex, 0..1), 16)
|
||||
{g, ""} = Integer.parse(String.slice(hex, 2..3), 16)
|
||||
{b, ""} = Integer.parse(String.slice(hex, 4..5), 16)
|
||||
|
||||
r = r / 255
|
||||
g = g / 255
|
||||
b = b / 255
|
||||
|
||||
max = Enum.max([r, g, b])
|
||||
min = Enum.min([r, g, b])
|
||||
|
||||
l = (max + min) / 2
|
||||
|
||||
if max == min do
|
||||
{0, 0, round(l * 100)}
|
||||
else
|
||||
d = max - min
|
||||
s = if l > 0.5, do: d / (2 - max - min), else: d / (max + min)
|
||||
|
||||
h = cond do
|
||||
max == r -> (g - b) / d + (if g < b, do: 6, else: 0)
|
||||
max == g -> (b - r) / d + 2
|
||||
max == b -> (r - g) / d + 4
|
||||
end
|
||||
|
||||
{round(h * 60), round(s * 100), round(l * 100)}
|
||||
end
|
||||
end
|
||||
|
||||
defp generate_mood("neutral"), do: """
|
||||
--t-surface-base: #ffffff;
|
||||
--t-surface-raised: #ffffff;
|
||||
--t-text-primary: #171717;
|
||||
--t-border-default: #e5e5e5;
|
||||
"""
|
||||
|
||||
defp generate_mood("warm"), do: """
|
||||
--t-surface-base: #fdf8f3;
|
||||
--t-surface-raised: #fffcf8;
|
||||
--t-text-primary: #1c1917;
|
||||
--t-border-default: #e7e0d8;
|
||||
"""
|
||||
|
||||
defp generate_mood("cool"), do: """
|
||||
--t-surface-base: #f4f7fb;
|
||||
--t-surface-raised: #f8fafc;
|
||||
--t-text-primary: #0f172a;
|
||||
--t-border-default: #d4dce8;
|
||||
"""
|
||||
|
||||
defp generate_mood("dark"), do: """
|
||||
--t-surface-base: #0a0a0a;
|
||||
--t-surface-raised: #171717;
|
||||
--t-text-primary: #fafafa;
|
||||
--t-border-default: #262626;
|
||||
--p-shadow-strength: 0.25;
|
||||
"""
|
||||
|
||||
# Similar for typography, shape, density...
|
||||
end
|
||||
```
|
||||
|
||||
### LiveView Upload Configuration
|
||||
|
||||
```elixir
|
||||
# In mount/3
|
||||
socket =
|
||||
socket
|
||||
|> allow_upload(:logo_upload,
|
||||
accept: ~w(.png .jpg .jpeg .webp .svg),
|
||||
max_entries: 1,
|
||||
max_file_size: 2_000_000,
|
||||
auto_upload: true
|
||||
)
|
||||
|> allow_upload(:header_upload,
|
||||
accept: ~w(.png .jpg .jpeg .webp),
|
||||
max_entries: 1,
|
||||
max_file_size: 5_000_000,
|
||||
auto_upload: true
|
||||
)
|
||||
|
||||
# Handle upload completion
|
||||
def handle_event("logo_uploaded", _params, socket) do
|
||||
uploaded_files =
|
||||
consume_uploaded_entries(socket, :logo_upload, fn %{path: path}, entry ->
|
||||
file_binary = File.read!(path)
|
||||
|
||||
{:ok, image} = Media.upload_image(%{
|
||||
image_type: "logo",
|
||||
filename: entry.client_name,
|
||||
content_type: entry.client_type,
|
||||
file_size: entry.client_size,
|
||||
data: file_binary
|
||||
})
|
||||
|
||||
{:ok, image}
|
||||
end)
|
||||
|
||||
case uploaded_files do
|
||||
[image | _] ->
|
||||
# Update theme settings to reference this image
|
||||
settings = socket.assigns.theme_settings
|
||||
{:ok, _} = Settings.update_theme_settings(%{settings | logo_image_id: image.id})
|
||||
{:noreply, assign(socket, :logo_image, image)}
|
||||
[] ->
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Image Controller Serving
|
||||
|
||||
```elixir
|
||||
defmodule SimpleshopThemeWeb.ImageController do
|
||||
use SimpleshopThemeWeb, :controller
|
||||
alias SimpleshopTheme.Media
|
||||
|
||||
def show(conn, %{"id" => id}) do
|
||||
case Media.get_image(id) do
|
||||
nil ->
|
||||
send_resp(conn, 404, "Image not found")
|
||||
|
||||
image ->
|
||||
conn
|
||||
|> put_resp_content_type(image.content_type)
|
||||
|> put_resp_header("cache-control", "public, max-age=31536000, immutable")
|
||||
|> put_resp_header("etag", "\"#{image.id}\"")
|
||||
|> send_resp(200, image.data)
|
||||
end
|
||||
end
|
||||
|
||||
def recolored_svg(conn, %{"id" => id, "color" => color}) do
|
||||
with %{is_svg: true, svg_content: svg} <- Media.get_image(id),
|
||||
clean_color <- String.trim_leading(color, "#"),
|
||||
recolored <- SimpleshopTheme.Media.SVGRecolorer.recolor(svg, "##{clean_color}") do
|
||||
conn
|
||||
|> put_resp_content_type("image/svg+xml")
|
||||
|> put_resp_header("cache-control", "public, max-age=3600")
|
||||
|> send_resp(200, recolored)
|
||||
else
|
||||
_ -> send_resp(conn, 400, "Invalid request")
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Key Dependencies to Add
|
||||
|
||||
```elixir
|
||||
# mix.exs
|
||||
{:image, "~> 0.54"} # Thumbnail generation (uses libvips)
|
||||
{:fast_sanitize, "~> 0.2"} # SVG sanitization (security)
|
||||
```
|
||||
|
||||
## Router Updates
|
||||
|
||||
```elixir
|
||||
# lib/simpleshop_theme_web/router.ex
|
||||
|
||||
# Theme editor (authenticated)
|
||||
scope "/admin", SimpleshopThemeWeb do
|
||||
pipe_through [:browser, :require_authenticated_user]
|
||||
|
||||
live_session :theme_editor,
|
||||
on_mount: [{SimpleshopThemeWeb.UserAuth, :require_authenticated}] do
|
||||
live "/theme", ThemeLive.Index, :index
|
||||
end
|
||||
end
|
||||
|
||||
# Image serving (public)
|
||||
scope "/", SimpleshopThemeWeb do
|
||||
pipe_through :browser
|
||||
|
||||
get "/images/:id", ImageController, :show
|
||||
get "/images/:id/recolored/:color", ImageController, :recolored_svg
|
||||
end
|
||||
|
||||
# Public storefront (future - Phase 9)
|
||||
# scope "/", SimpleshopThemeWeb do
|
||||
# pipe_through :browser
|
||||
# live "/", ShopLive.Home, :index
|
||||
# live "/products/:slug", ShopLive.Product, :show
|
||||
# end
|
||||
```
|
||||
|
||||
## Critical Files Reference
|
||||
|
||||
### To Create
|
||||
- `lib/simpleshop_theme/settings.ex` - Settings context
|
||||
- `lib/simpleshop_theme/settings/setting.ex` - Setting schema (key-value)
|
||||
- `lib/simpleshop_theme/settings/theme_settings.ex` - Embedded theme settings
|
||||
- `lib/simpleshop_theme/media.ex` - Media context
|
||||
- `lib/simpleshop_theme/media/image.ex` - Image schema
|
||||
- `lib/simpleshop_theme/media/svg_recolorer.ex` - SVG manipulation
|
||||
- `lib/simpleshop_theme/theme/presets.ex` - 9 preset definitions
|
||||
- `lib/simpleshop_theme/theme/css_generator.ex` - CSS generation
|
||||
- `lib/simpleshop_theme/theme/css_cache.ex` - ETS caching
|
||||
- `lib/simpleshop_theme/theme/preview_data.ex` - Smart preview data
|
||||
- `lib/simpleshop_theme_web/live/theme_live/index.ex` - Main LiveView
|
||||
- `lib/simpleshop_theme_web/controllers/image_controller.ex` - Image serving
|
||||
- `priv/static/css/theme-primitives.css` - CSS Layer 1
|
||||
- `priv/static/css/theme-semantic.css` - CSS Layer 3
|
||||
- Comprehensive test files for all contexts and LiveViews
|
||||
|
||||
### To Modify
|
||||
- `lib/simpleshop_theme/application.ex` - Add CSS cache to supervision tree
|
||||
- `lib/simpleshop_theme_web/router.ex` - Add theme and image routes
|
||||
- `lib/simpleshop_theme_web/components/layouts.ex` - Add theme nav link
|
||||
- `assets/css/app.css` - Import theme CSS
|
||||
- `priv/repo/seeds.exs` - Add default theme settings
|
||||
- `mix.exs` - Add dependencies
|
||||
|
||||
### Reference Files
|
||||
- `SIMPLESHOP_THEME_STUDIO_SPEC.md` - Complete specification
|
||||
- `theme-demo-v28.html` - Working prototype (CSS, HTML structure, JavaScript logic)
|
||||
|
||||
## Success Criteria
|
||||
|
||||
✅ User can access theme studio at `/admin/theme`
|
||||
✅ All 9 presets work with real-time preview
|
||||
✅ All customization options update preview instantly
|
||||
✅ Logo and header images upload and store as BLOBs
|
||||
✅ SVG recoloring works correctly
|
||||
✅ Preview shows all 7 mock pages
|
||||
✅ Settings persist to database
|
||||
✅ "Save" button works with success feedback
|
||||
✅ Images served efficiently from database
|
||||
✅ CSS cached in ETS for performance
|
||||
✅ Everything self-contained in SQLite file
|
||||
|
||||
## Next Steps After Implementation
|
||||
|
||||
1. **Add Products Context** - So users can add real products
|
||||
2. **Build Storefront** - Public-facing shop pages using saved theme
|
||||
3. **Add Orders/Cart** - E-commerce functionality
|
||||
4. **Multi-admin Support** - Invite additional users to manage shop
|
||||
5. **Custom Domains** - Allow users to point their own domain
|
||||
6. **Theme Export/Import** - Share themes between shops
|
||||
7. **Advanced Features** - Custom CSS, code injection, etc.
|
||||
Loading…
Reference in New Issue
Block a user