|
1 | | -// branding.templ renders the backdrop upload section and accent color picker |
2 | | -// for campaign settings. |
| 1 | +// branding.templ renders the backdrop upload section, accent color picker, |
| 2 | +// and appearance customization tab for campaign settings. |
3 | 3 |
|
4 | 4 | package campaigns |
5 | 5 |
|
6 | | -import "fmt" |
| 6 | +import ( |
| 7 | + "fmt" |
| 8 | + "github.com/keyxmakerx/chronicle/internal/templates/layouts" |
| 9 | +) |
7 | 10 |
|
8 | 11 | // BackdropUploadSection renders the backdrop image upload/remove UI fragment. |
9 | 12 | // Used as both the initial render and the HTMX swap target after upload/remove. |
@@ -123,3 +126,148 @@ templ AccentColorPicker(campaignID string, currentColor string, csrfToken string |
123 | 126 | } |
124 | 127 | </div> |
125 | 128 | } |
| 129 | + |
| 130 | +// appearanceTab renders the visual customization editor in the Customization Hub. |
| 131 | +// Contains: faux site outline, brand name/logo, accent color, and topbar styling. |
| 132 | +templ appearanceTab(cc *CampaignContext, csrfToken string) { |
| 133 | + <h2 class="text-base font-semibold text-fg mb-1">Appearance</h2> |
| 134 | + <p class="text-xs text-fg-secondary mb-6">Customize the visual style of your campaign.</p> |
| 135 | + |
| 136 | + <div |
| 137 | + data-widget="appearance-editor" |
| 138 | + data-campaign-id={ cc.Campaign.ID } |
| 139 | + data-csrf={ csrfToken } |
| 140 | + data-brand-name={ cc.Campaign.ParseSettings().BrandName } |
| 141 | + data-brand-logo={ cc.Campaign.ParseSettings().BrandLogo } |
| 142 | + data-accent-color={ cc.Campaign.ParseSettings().AccentColor } |
| 143 | + data-topbar-style={ topbarStyleJSON(cc.Campaign.ParseSettings().TopbarStyle) } |
| 144 | + > |
| 145 | + <!-- Faux site outline preview --> |
| 146 | + <div class="rounded-lg border border-edge overflow-hidden mb-6 bg-bg-secondary"> |
| 147 | + <div class="flex"> |
| 148 | + <!-- Faux sidebar --> |
| 149 | + <div class="w-12 bg-[#1a1c23] shrink-0 flex flex-col items-center py-3 gap-2"> |
| 150 | + <div class="w-5 h-5 rounded bg-gray-600"></div> |
| 151 | + <div class="w-5 h-5 rounded bg-gray-700"></div> |
| 152 | + <div class="w-5 h-5 rounded bg-gray-700"></div> |
| 153 | + </div> |
| 154 | + <!-- Faux main area --> |
| 155 | + <div class="flex-1 flex flex-col min-h-[160px]"> |
| 156 | + <!-- Faux topbar (reflects live customization) --> |
| 157 | + <div |
| 158 | + id="appearance-preview-topbar" |
| 159 | + class="h-8 bg-surface border-b border-edge flex items-center px-3" |
| 160 | + > |
| 161 | + <span class="text-[10px] text-fg-muted font-medium" id="appearance-preview-brand"> |
| 162 | + if cc.Campaign.ParseSettings().BrandName != "" { |
| 163 | + { cc.Campaign.ParseSettings().BrandName } |
| 164 | + } else { |
| 165 | + { cc.Campaign.Name } |
| 166 | + } |
| 167 | + </span> |
| 168 | + </div> |
| 169 | + <!-- Faux content --> |
| 170 | + <div class="flex-1 p-4"> |
| 171 | + <div class="w-3/4 h-2 bg-edge rounded mb-2"></div> |
| 172 | + <div class="w-1/2 h-2 bg-edge rounded mb-4"></div> |
| 173 | + <div class="grid grid-cols-2 gap-2"> |
| 174 | + <div class="h-8 bg-edge rounded"></div> |
| 175 | + <div class="h-8 bg-edge rounded"></div> |
| 176 | + </div> |
| 177 | + </div> |
| 178 | + </div> |
| 179 | + </div> |
| 180 | + </div> |
| 181 | + |
| 182 | + <!-- Brand name --> |
| 183 | + <div class="card p-4 mb-4"> |
| 184 | + <h3 class="text-sm font-semibold text-fg mb-2"> |
| 185 | + <i class="fa-solid fa-tag text-xs mr-1.5 text-fg-muted"></i> Brand Name |
| 186 | + </h3> |
| 187 | + <p class="text-xs text-fg-secondary mb-3">Replace the campaign name in the sidebar with a custom brand name.</p> |
| 188 | + <div class="flex gap-2"> |
| 189 | + <input |
| 190 | + type="text" |
| 191 | + id="appearance-brand-name" |
| 192 | + class="input text-sm flex-1" |
| 193 | + placeholder={ cc.Campaign.Name } |
| 194 | + maxlength="40" |
| 195 | + value={ cc.Campaign.ParseSettings().BrandName } |
| 196 | + /> |
| 197 | + <button |
| 198 | + type="button" |
| 199 | + id="appearance-brand-clear" |
| 200 | + class="btn-secondary text-xs px-3" |
| 201 | + title="Clear brand name" |
| 202 | + > |
| 203 | + <i class="fa-solid fa-xmark"></i> |
| 204 | + </button> |
| 205 | + </div> |
| 206 | + </div> |
| 207 | + |
| 208 | + <!-- Accent color --> |
| 209 | + <div class="card p-4 mb-4"> |
| 210 | + <h3 class="text-sm font-semibold text-fg mb-2"> |
| 211 | + <i class="fa-solid fa-droplet text-xs mr-1.5 text-fg-muted"></i> Accent Color |
| 212 | + </h3> |
| 213 | + <p class="text-xs text-fg-secondary mb-3">Theme color used for buttons, links, and highlights.</p> |
| 214 | + @AccentColorPicker(cc.Campaign.ID, cc.Campaign.ParseSettings().AccentColor, csrfToken) |
| 215 | + </div> |
| 216 | + |
| 217 | + <!-- Topbar style --> |
| 218 | + <div class="card p-4 mb-4"> |
| 219 | + <h3 class="text-sm font-semibold text-fg mb-2"> |
| 220 | + <i class="fa-solid fa-paintbrush text-xs mr-1.5 text-fg-muted"></i> Top Bar Style |
| 221 | + </h3> |
| 222 | + <p class="text-xs text-fg-secondary mb-3">Customize the top navigation bar appearance.</p> |
| 223 | + <div class="flex gap-2 mb-3" id="appearance-topbar-mode"> |
| 224 | + <button type="button" data-mode="" class="btn-secondary text-xs px-3 py-1.5">Default</button> |
| 225 | + <button type="button" data-mode="solid" class="btn-secondary text-xs px-3 py-1.5">Solid Color</button> |
| 226 | + <button type="button" data-mode="gradient" class="btn-secondary text-xs px-3 py-1.5">Gradient</button> |
| 227 | + </div> |
| 228 | + <!-- Solid color options --> |
| 229 | + <div id="appearance-topbar-solid" class="hidden space-y-2"> |
| 230 | + <label class="text-xs text-fg-secondary">Color</label> |
| 231 | + <input type="color" id="appearance-topbar-color" class="w-10 h-8 rounded border border-edge cursor-pointer"/> |
| 232 | + </div> |
| 233 | + <!-- Gradient options --> |
| 234 | + <div id="appearance-topbar-gradient" class="hidden space-y-2"> |
| 235 | + <div class="flex gap-3"> |
| 236 | + <div> |
| 237 | + <label class="text-xs text-fg-secondary block mb-1">From</label> |
| 238 | + <input type="color" id="appearance-topbar-gradient-from" class="w-10 h-8 rounded border border-edge cursor-pointer"/> |
| 239 | + </div> |
| 240 | + <div> |
| 241 | + <label class="text-xs text-fg-secondary block mb-1">To</label> |
| 242 | + <input type="color" id="appearance-topbar-gradient-to" class="w-10 h-8 rounded border border-edge cursor-pointer"/> |
| 243 | + </div> |
| 244 | + <div class="flex-1"> |
| 245 | + <label class="text-xs text-fg-secondary block mb-1">Direction</label> |
| 246 | + <select id="appearance-topbar-gradient-dir" class="input text-xs py-1.5"> |
| 247 | + <option value="to-r">Left to Right</option> |
| 248 | + <option value="to-br">Top-Left to Bottom-Right</option> |
| 249 | + <option value="to-b">Top to Bottom</option> |
| 250 | + </select> |
| 251 | + </div> |
| 252 | + </div> |
| 253 | + </div> |
| 254 | + </div> |
| 255 | + </div> |
| 256 | +} |
| 257 | + |
| 258 | +// topbarStyleJSON serializes a TopbarStyle to a JSON data attribute value. |
| 259 | +func topbarStyleJSON(style *TopbarStyle) string { |
| 260 | + if style == nil { |
| 261 | + return "{}" |
| 262 | + } |
| 263 | + // Manual construction to avoid importing encoding/json in templ file. |
| 264 | + return fmt.Sprintf( |
| 265 | + `{"mode":"%s","color":"%s","gradient_from":"%s","gradient_to":"%s","gradient_dir":"%s","image_path":"%s"}`, |
| 266 | + layouts.EscapeJSONString(style.Mode), |
| 267 | + layouts.EscapeJSONString(style.Color), |
| 268 | + layouts.EscapeJSONString(style.GradientFrom), |
| 269 | + layouts.EscapeJSONString(style.GradientTo), |
| 270 | + layouts.EscapeJSONString(style.GradientDir), |
| 271 | + layouts.EscapeJSONString(style.ImagePath), |
| 272 | + ) |
| 273 | +} |
0 commit comments