## Backend Changes:
- Enhanced PluginTemplate.save() to auto-parse template variables from plugin code
- Updated PluginInstallationSerializer to expose template metadata (description, category, version, author, logo, template_variables)
- Fixed template variable parser to handle nested {{ }} braces in default values
- Added brace-counting algorithm to properly extract variables with insertion codes
- Fixed explicit type parameter detection (textarea, text, email, etc.)
- Made scheduled_task optional on PluginInstallation model
- Added EventPlugin through model for event-plugin relationships
- Added Event.execute_plugins() method for plugin automation
## Frontend Changes:
- Created Tasks.tsx page for managing scheduled tasks
- Enhanced MyPlugins page with clickable plugin cards
- Added edit configuration modal with dynamic form generation
- Implemented escape sequence handling (convert \n, \', etc. for display)
- Added plugin logos to My Plugins page
- Updated type definitions for PluginInstallation interface
- Added insertion code documentation to Plugin Docs
## Plugin System:
- All platform plugins now have editable email templates with textarea support
- Template variables properly parsed with full default values
- Insertion codes ({{CUSTOMER_NAME}}, {{BUSINESS_NAME}}, etc.) documented
- Plugin logos displayed in marketplace and My Plugins
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
156 lines
4.8 KiB
Python
156 lines
4.8 KiB
Python
import os
|
|
import sys
|
|
|
|
try:
|
|
from PIL import Image, ImageDraw, ImageFont
|
|
except ImportError:
|
|
print("Error: Pillow library not found.")
|
|
print("Please install it using: pip install Pillow")
|
|
sys.exit(1)
|
|
|
|
# Configuration
|
|
OUTPUT_DIR = "/home/poduck/Desktop/smoothschedule2/frontend/public/plugin-logos"
|
|
SIZE = (144, 144)
|
|
RADIUS = 30
|
|
|
|
def create_base_logo(bg_color):
|
|
"""Creates the base image with rounded corners and background color."""
|
|
img = Image.new('RGBA', SIZE, (0, 0, 0, 0))
|
|
draw = ImageDraw.Draw(img)
|
|
rect_bounds = [0, 0, SIZE[0], SIZE[1]]
|
|
draw.rounded_rectangle(rect_bounds, radius=RADIUS, fill=bg_color)
|
|
return img, draw
|
|
|
|
def save_logo(img, filename):
|
|
"""Saves the image to the output directory."""
|
|
path = os.path.join(OUTPUT_DIR, filename)
|
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
img.save(path)
|
|
print(f"Saved: {path}")
|
|
|
|
# 1. No-Show Customer Tracker (Red, Person with X)
|
|
def create_no_show_logo():
|
|
img, draw = create_base_logo("#dc2626") # Red
|
|
|
|
# Person Silhouette
|
|
cx, cy = 72, 72
|
|
# Head
|
|
draw.ellipse([cx-15, 35, cx+15, 65], fill="white")
|
|
# Shoulders/Body
|
|
draw.rounded_rectangle([cx-30, 70, cx+30, 130], radius=10, fill="white")
|
|
|
|
# Cancel Symbol (Red X on white circle or just overlaid)
|
|
# Let's use a thick Red X overlay
|
|
x_center_y = 85
|
|
half = 20
|
|
width = 8
|
|
|
|
# Draw X
|
|
draw.line([cx - half, x_center_y - half, cx + half, x_center_y + half], fill="#dc2626", width=width)
|
|
draw.line([cx + half, x_center_y - half, cx - half, x_center_y + half], fill="#dc2626", width=width)
|
|
|
|
save_logo(img, "no-show-tracker.png")
|
|
|
|
# 2. Birthday Greeting Campaign (Pink, Gift)
|
|
def create_birthday_logo():
|
|
img, draw = create_base_logo("#ec4899") # Pink
|
|
|
|
# Gift Box
|
|
box_w, box_h = 70, 60
|
|
box_x = (SIZE[0] - box_w) // 2
|
|
box_y = (SIZE[1] - box_h) // 2 + 10
|
|
|
|
# Box body
|
|
draw.rectangle([box_x, box_y, box_x + box_w, box_y + box_h], fill="white")
|
|
|
|
# Ribbons (Lighter Pink)
|
|
r_w = 12
|
|
ribbon_color = "#fbcfe8"
|
|
# Vertical
|
|
draw.rectangle([box_x + (box_w - r_w)//2, box_y, box_x + (box_w + r_w)//2, box_y + box_h], fill=ribbon_color)
|
|
# Horizontal
|
|
draw.rectangle([box_x, box_y + (box_h - r_w)//2, box_x + box_w, box_y + (box_h + r_w)//2], fill=ribbon_color)
|
|
|
|
# Bow
|
|
bow_w = 20
|
|
draw.ellipse([72 - bow_w, box_y - 15, 72, box_y], fill="white")
|
|
draw.ellipse([72, box_y - 15, 72 + bow_w, box_y], fill="white")
|
|
|
|
save_logo(img, "birthday-greetings.png")
|
|
|
|
# 3. Monthly Revenue Report (Green, Chart)
|
|
def create_revenue_logo():
|
|
img, draw = create_base_logo("#10b981") # Green
|
|
|
|
# Bar Chart
|
|
margin_bottom = 110
|
|
bar_w = 20
|
|
gap = 10
|
|
start_x = (144 - (3 * bar_w + 2 * gap)) // 2
|
|
|
|
# Bars
|
|
heights = [30, 50, 75]
|
|
for i, h in enumerate(heights):
|
|
x = start_x + i * (bar_w + gap)
|
|
draw.rectangle([x, margin_bottom - h, x + bar_w, margin_bottom], fill="white")
|
|
|
|
# Trend Arrow (Rising)
|
|
arrow_start = (start_x - 5, margin_bottom - 10)
|
|
arrow_end = (start_x + 3 * bar_w + 2 * gap + 5, margin_bottom - 90)
|
|
|
|
# Simple line for trend
|
|
# draw.line([arrow_start, arrow_end], fill="white", width=4)
|
|
|
|
save_logo(img, "monthly-revenue-report.png")
|
|
|
|
# 4. Appointment Reminder (24hr) (Amber, Bell)
|
|
def create_reminder_logo():
|
|
img, draw = create_base_logo("#f59e0b") # Amber
|
|
|
|
cx, cy = 72, 70
|
|
r = 30
|
|
|
|
# Bell Dome
|
|
draw.chord([cx - r, cy - r, cx + r, cy + r], 180, 0, fill="white")
|
|
draw.rectangle([cx - r, cy, cx + r, cy + 20], fill="white")
|
|
|
|
# Bell Flare
|
|
draw.polygon([(cx - r, cy + 20), (cx + r, cy + 20), (cx + r + 5, cy + 30), (cx - r - 5, cy + 30)], fill="white")
|
|
|
|
# Clapper
|
|
draw.ellipse([cx - 8, cy + 28, cx + 8, cy + 42], fill="white")
|
|
|
|
# Notification Dot
|
|
draw.ellipse([cx + 15, cy - 25, cx + 35, cy - 5], fill="#dc2626")
|
|
|
|
save_logo(img, "appointment-reminder-24hr.png")
|
|
|
|
# 5. Inactive Customer Re-engagement (Purple, Heart)
|
|
def create_inactive_logo():
|
|
img, draw = create_base_logo("#8b5cf6") # Purple
|
|
|
|
hx, hy = 72, 75
|
|
size = 18
|
|
|
|
# Heart Shape
|
|
# Left circle
|
|
draw.ellipse([hx - size * 2, hy - size, hx, hy + size], fill="white")
|
|
# Right circle
|
|
draw.ellipse([hx, hy - size, hx + size * 2, hy + size], fill="white")
|
|
# Bottom triangle
|
|
draw.polygon([(hx - size * 2 + 1, hy + 4), (hx + size * 2 - 1, hy + 4), (hx, hy + size * 2 + 8)], fill="white")
|
|
|
|
# Refresh/Loop Arrow (Subtle arc above)
|
|
bbox = [25, 25, 119, 119]
|
|
draw.arc(bbox, start=180, end=0, fill="#ddd6fe", width=5)
|
|
draw.polygon([(119, 72), (110, 60), (128, 60)], fill="#ddd6fe")
|
|
|
|
save_logo(img, "inactive-customer-reengagement.png")
|
|
|
|
if __name__ == "__main__":
|
|
create_no_show_logo()
|
|
create_birthday_logo()
|
|
create_revenue_logo()
|
|
create_reminder_logo()
|
|
create_inactive_logo()
|