Remove EmailBranding component and fix Puck sidebar scroll
- Remove EmailBranding from Puck editor (branding auto-shows for free tier) - Fix sidebar scroll in email template editor with single scrollbar - Fix logo data URL format in email_renderer.py 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,15 +23,15 @@
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
html, body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
min-width: 320px;
|
min-width: 320px;
|
||||||
min-height: 100vh;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#root {
|
#root {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 100vh;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* React Grid Layout Dashboard Styling */
|
/* React Grid Layout Dashboard Styling */
|
||||||
@@ -190,3 +190,37 @@ body {
|
|||||||
background-color: #ffffff !important;
|
background-color: #ffffff !important;
|
||||||
border-color: #e5e7eb !important;
|
border-color: #e5e7eb !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* =============================================================================
|
||||||
|
Puck Editor Height Fix
|
||||||
|
Force Puck to fill container and enable sidebar scrolling
|
||||||
|
============================================================================= */
|
||||||
|
|
||||||
|
/* Container must be positioned for absolute child */
|
||||||
|
.email-editor-light-mode {
|
||||||
|
position: relative !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Force Puck root to fill container absolutely */
|
||||||
|
.email-editor-light-mode > [id^="_r_"] {
|
||||||
|
position: absolute !important;
|
||||||
|
inset: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Target Puck's main layout - all direct children need height constraints */
|
||||||
|
.email-editor-light-mode > [id^="_r_"] > div {
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-editor-light-mode > [id^="_r_"] > div > div {
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Force the left sidebar to be constrained and scrollable */
|
||||||
|
/* Use calc to account for the header areas above the editor */
|
||||||
|
.email-editor-light-mode [class*="_Sidebar_"][class*="--left"],
|
||||||
|
.email-editor-light-mode [class*="_Sidebar_"][class*="--right"] {
|
||||||
|
max-height: calc(100vh - 280px) !important;
|
||||||
|
overflow-y: auto !important;
|
||||||
|
border: 3px solid red !important; /* TEST - remove after confirming selector works */
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
* (welcome, appointment confirmations, reminders, etc.) using a Puck editor.
|
* (welcome, appointment confirmations, reminders, etc.) using a Puck editor.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useCallback, useMemo } from 'react';
|
import React, { useState, useCallback, useMemo, useEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { Puck, Render } from '@measured/puck';
|
import { Puck, Render } from '@measured/puck';
|
||||||
@@ -103,6 +103,36 @@ const SystemEmailTemplates: React.FC = () => {
|
|||||||
// Get email editor config
|
// Get email editor config
|
||||||
const editorConfig = getEmailEditorConfig();
|
const editorConfig = getEmailEditorConfig();
|
||||||
|
|
||||||
|
// Fix Puck sidebar scrolling when editor is open
|
||||||
|
useEffect(() => {
|
||||||
|
if (!editingTemplate) return;
|
||||||
|
|
||||||
|
const fixSidebars = () => {
|
||||||
|
// Target only the main left/right sidebar containers
|
||||||
|
const leftSidebar = document.querySelector('[class*="Sidebar--left"]') as HTMLElement;
|
||||||
|
const rightSidebar = document.querySelector('[class*="Sidebar--right"]') as HTMLElement;
|
||||||
|
|
||||||
|
[leftSidebar, rightSidebar].forEach((sidebar) => {
|
||||||
|
if (!sidebar) return;
|
||||||
|
|
||||||
|
// Make the main sidebar scroll
|
||||||
|
sidebar.style.maxHeight = 'calc(100vh - 300px)';
|
||||||
|
sidebar.style.overflowY = 'auto';
|
||||||
|
|
||||||
|
// Remove overflow from inner sections so only the main sidebar scrolls
|
||||||
|
const innerSections = sidebar.querySelectorAll('[class*="SidebarSection"]');
|
||||||
|
innerSections.forEach((section) => {
|
||||||
|
(section as HTMLElement).style.overflow = 'visible';
|
||||||
|
(section as HTMLElement).style.maxHeight = 'none';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run after Puck renders
|
||||||
|
const timer = setTimeout(fixSidebars, 200);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [editingTemplate]);
|
||||||
|
|
||||||
// Fetch all email templates
|
// Fetch all email templates
|
||||||
const { data: templates = [], isLoading } = useQuery<SystemEmailTemplate[]>({
|
const { data: templates = [], isLoading } = useQuery<SystemEmailTemplate[]>({
|
||||||
queryKey: ['system-email-templates'],
|
queryKey: ['system-email-templates'],
|
||||||
@@ -281,6 +311,7 @@ const SystemEmailTemplates: React.FC = () => {
|
|||||||
puck_data: editorData,
|
puck_data: editorData,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
setPreviewSubject(preview.subject);
|
setPreviewSubject(preview.subject);
|
||||||
setPreviewHtml(preview.html);
|
setPreviewHtml(preview.html);
|
||||||
setPreviewText(preview.text);
|
setPreviewText(preview.text);
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import type { ComponentConfig } from '@measured/puck';
|
|
||||||
|
|
||||||
export interface EmailBrandingProps {
|
|
||||||
showBranding: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* EmailBranding - "Powered by SmoothSchedule" footer
|
|
||||||
*
|
|
||||||
* Displays SmoothSchedule branding at the bottom of emails.
|
|
||||||
* This is shown for free plans and can be hidden on paid plans.
|
|
||||||
*
|
|
||||||
* Note: The actual visibility is controlled by the backend based on
|
|
||||||
* the tenant's billing plan. This component is always rendered in the
|
|
||||||
* editor but the backend will omit it for paid plans.
|
|
||||||
*/
|
|
||||||
export const EmailBranding: ComponentConfig<EmailBrandingProps> = {
|
|
||||||
label: 'Email Branding',
|
|
||||||
fields: {
|
|
||||||
showBranding: {
|
|
||||||
type: 'radio',
|
|
||||||
label: 'Show Branding',
|
|
||||||
options: [
|
|
||||||
{ label: 'Yes', value: true },
|
|
||||||
{ label: 'No (Paid Plans Only)', value: false },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultProps: {
|
|
||||||
showBranding: true,
|
|
||||||
},
|
|
||||||
render: ({ showBranding }) => {
|
|
||||||
if (!showBranding) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
padding: '16px',
|
|
||||||
textAlign: 'center',
|
|
||||||
color: '#9ca3af',
|
|
||||||
fontSize: '12px',
|
|
||||||
fontStyle: 'italic',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
[Branding hidden - available on paid plans]
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
padding: '24px 40px',
|
|
||||||
textAlign: 'center',
|
|
||||||
borderTop: '1px solid #e5e7eb',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="https://smoothschedule.com"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
style={{
|
|
||||||
display: 'inline-flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '8px',
|
|
||||||
textDecoration: 'none',
|
|
||||||
color: '#6b7280',
|
|
||||||
fontSize: '12px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src="/logo-branding.png"
|
|
||||||
alt="SmoothSchedule"
|
|
||||||
width="18"
|
|
||||||
height="18"
|
|
||||||
style={{ verticalAlign: 'middle' }}
|
|
||||||
/>
|
|
||||||
<span>Powered by SmoothSchedule</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EmailBranding;
|
|
||||||
@@ -9,7 +9,6 @@ import type { EmailFooterProps } from './types';
|
|||||||
* Supports template tags for dynamic content.
|
* Supports template tags for dynamic content.
|
||||||
*/
|
*/
|
||||||
const EmailFooterRender: React.FC<EmailFooterProps> = ({ address, phone, email, website }) => {
|
const EmailFooterRender: React.FC<EmailFooterProps> = ({ address, phone, email, website }) => {
|
||||||
console.log('[RENDER] EmailFooterRender called with:', { address, phone, email, website });
|
|
||||||
const contactItems: React.ReactNode[] = [];
|
const contactItems: React.ReactNode[] = [];
|
||||||
|
|
||||||
if (phone) contactItems.push(phone);
|
if (phone) contactItems.push(phone);
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import type { EmailHeaderProps } from './types';
|
|||||||
* Supports optional logo image and preheader text.
|
* Supports optional logo image and preheader text.
|
||||||
*/
|
*/
|
||||||
const EmailHeaderRender: React.FC<EmailHeaderProps> = ({ logoUrl, businessName, preheader }) => {
|
const EmailHeaderRender: React.FC<EmailHeaderProps> = ({ logoUrl, businessName, preheader }) => {
|
||||||
console.log('[RENDER] EmailHeaderRender called with:', { logoUrl, businessName, preheader });
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -19,4 +19,3 @@ export { EmailImage } from './EmailImage';
|
|||||||
export { EmailPanel } from './EmailPanel';
|
export { EmailPanel } from './EmailPanel';
|
||||||
export { EmailTwoColumn } from './EmailTwoColumn';
|
export { EmailTwoColumn } from './EmailTwoColumn';
|
||||||
export { EmailFooter } from './EmailFooter';
|
export { EmailFooter } from './EmailFooter';
|
||||||
export { EmailBranding } from './EmailBranding';
|
|
||||||
|
|||||||
@@ -80,11 +80,6 @@ export interface EmailFooterProps {
|
|||||||
website?: string;
|
website?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Email Branding Props
|
|
||||||
export interface EmailBrandingProps {
|
|
||||||
showBranding: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
// All email component props
|
// All email component props
|
||||||
export type EmailComponentProps = {
|
export type EmailComponentProps = {
|
||||||
EmailLayout: EmailLayoutProps;
|
EmailLayout: EmailLayoutProps;
|
||||||
@@ -98,7 +93,6 @@ export type EmailComponentProps = {
|
|||||||
EmailPanel: EmailPanelProps;
|
EmailPanel: EmailPanelProps;
|
||||||
EmailTwoColumn: EmailTwoColumnProps;
|
EmailTwoColumn: EmailTwoColumnProps;
|
||||||
EmailFooter: EmailFooterProps;
|
EmailFooter: EmailFooterProps;
|
||||||
EmailBranding: EmailBrandingProps;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Email-specific Puck data structure
|
// Email-specific Puck data structure
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
import type { Config } from '@measured/puck';
|
import type { Config } from '@measured/puck';
|
||||||
|
|
||||||
// Import ALL email-specific components
|
// Import email-specific components
|
||||||
import { EmailHeader } from './components/email/EmailHeader';
|
import { EmailHeader } from './components/email/EmailHeader';
|
||||||
import { EmailHeading } from './components/email/EmailHeading';
|
import { EmailHeading } from './components/email/EmailHeading';
|
||||||
import { EmailText } from './components/email/EmailText';
|
import { EmailText } from './components/email/EmailText';
|
||||||
@@ -17,21 +17,11 @@ import { EmailImage } from './components/email/EmailImage';
|
|||||||
import { EmailPanel } from './components/email/EmailPanel';
|
import { EmailPanel } from './components/email/EmailPanel';
|
||||||
import { EmailTwoColumn } from './components/email/EmailTwoColumn';
|
import { EmailTwoColumn } from './components/email/EmailTwoColumn';
|
||||||
import { EmailFooter } from './components/email/EmailFooter';
|
import { EmailFooter } from './components/email/EmailFooter';
|
||||||
import { EmailBranding } from './components/email/EmailBranding';
|
|
||||||
|
|
||||||
// Import the combined type
|
// Import the combined type
|
||||||
import type { EmailComponentProps } from './components/email/types';
|
import type { EmailComponentProps } from './components/email/types';
|
||||||
|
|
||||||
console.log('[emailConfig] Loading ALL email components');
|
// Create the email config with ALL components
|
||||||
console.log('[emailConfig] Verifying render functions are distinct:');
|
|
||||||
console.log(' EmailHeader.render:', EmailHeader.render?.toString().substring(0, 50));
|
|
||||||
console.log(' EmailHeading.render:', EmailHeading.render?.toString().substring(0, 50));
|
|
||||||
console.log(' EmailText.render:', EmailText.render?.toString().substring(0, 50));
|
|
||||||
console.log(' EmailButton.render:', EmailButton.render?.toString().substring(0, 50));
|
|
||||||
console.log(' EmailFooter.render:', EmailFooter.render?.toString().substring(0, 50));
|
|
||||||
console.log(' Are renders same?', EmailHeader.render === EmailFooter.render);
|
|
||||||
|
|
||||||
// Create the email config with ALL components - using direct assignment (not spread)
|
|
||||||
export const emailPuckConfig: Config<EmailComponentProps> = {
|
export const emailPuckConfig: Config<EmailComponentProps> = {
|
||||||
categories: {
|
categories: {
|
||||||
structure: {
|
structure: {
|
||||||
@@ -46,13 +36,8 @@ export const emailPuckConfig: Config<EmailComponentProps> = {
|
|||||||
title: 'Layout',
|
title: 'Layout',
|
||||||
components: ['EmailSpacer', 'EmailDivider', 'EmailPanel', 'EmailTwoColumn'],
|
components: ['EmailSpacer', 'EmailDivider', 'EmailPanel', 'EmailTwoColumn'],
|
||||||
},
|
},
|
||||||
other: {
|
|
||||||
title: 'Other',
|
|
||||||
components: ['EmailBranding'],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
// Direct assignment - no spread to rule out reference issues
|
|
||||||
EmailHeader,
|
EmailHeader,
|
||||||
EmailFooter,
|
EmailFooter,
|
||||||
EmailHeading,
|
EmailHeading,
|
||||||
@@ -63,12 +48,9 @@ export const emailPuckConfig: Config<EmailComponentProps> = {
|
|||||||
EmailDivider,
|
EmailDivider,
|
||||||
EmailPanel,
|
EmailPanel,
|
||||||
EmailTwoColumn,
|
EmailTwoColumn,
|
||||||
EmailBranding,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('[emailConfig] Config ready with components:', Object.keys(emailPuckConfig.components));
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get email editor config - creates a fresh clone each time.
|
* Get email editor config - creates a fresh clone each time.
|
||||||
*/
|
*/
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user