Skip to content

Commit 2211363

Browse files
committed
fix(enterprise): address PR review — h-full for recently-deleted, shared SettingRow, toast UX, stale form fix, emcn tokens
1 parent 007e49f commit 2211363

File tree

4 files changed

+125
-107
lines changed

4 files changed

+125
-107
lines changed

apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,12 @@ export function SettingsPage({ section }: SettingsPageProps) {
216216
}, [effectiveSection, sessionLoading, posthog])
217217

218218
return (
219-
<div className={cn(effectiveSection === 'access-control' && 'flex h-full flex-col')}>
219+
<div
220+
className={cn(
221+
(effectiveSection === 'access-control' || effectiveSection === 'recently-deleted') &&
222+
'flex h-full flex-col'
223+
)}
224+
>
220225
<h2 className='mb-7 font-medium text-[22px] text-[var(--text-primary)]'>{label}</h2>
221226
{effectiveSection === 'general' && <General />}
222227
{effectiveSection === 'integrations' && <Integrations />}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Label } from '@/components/emcn'
2+
3+
interface SettingRowProps {
4+
label: string
5+
description?: string
6+
children: React.ReactNode
7+
}
8+
9+
export function SettingRow({ label, description, children }: SettingRowProps) {
10+
return (
11+
<div className='flex flex-col gap-1.5'>
12+
<Label className='text-[13px] text-[var(--text-primary)]'>{label}</Label>
13+
{description && <p className='text-[12px] text-[var(--text-muted)]'>{description}</p>}
14+
{children}
15+
</div>
16+
)
17+
}

apps/sim/ee/data-retention/components/data-retention-settings.tsx

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import { useEffect, useState } from 'react'
44
import { createLogger } from '@sim/logger'
55
import { toError } from '@sim/utils/errors'
66
import { useParams } from 'next/navigation'
7-
import { Button, Combobox, Label, toast } from '@/components/emcn'
7+
import { Button, Combobox, toast } from '@/components/emcn'
88
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
9+
import { SettingRow } from '@/ee/components/setting-row'
910
import { DataRetentionSkeleton } from '@/ee/data-retention/components/data-retention-skeleton'
1011
import {
1112
useUpdateWorkspaceRetention,
@@ -38,22 +39,6 @@ function daysToHours(days: string): number | null {
3839
return Number(days) * 24
3940
}
4041

41-
interface SettingRowProps {
42-
label: string
43-
description?: string
44-
children: React.ReactNode
45-
}
46-
47-
function SettingRow({ label, description, children }: SettingRowProps) {
48-
return (
49-
<div className='flex flex-col gap-1.5'>
50-
<Label className='text-[13px] text-[var(--text-primary)]'>{label}</Label>
51-
{description && <p className='text-[12px] text-[var(--text-muted)]'>{description}</p>}
52-
{children}
53-
</div>
54-
)
55-
}
56-
5742
interface RetentionSelectProps {
5843
value: string
5944
onChange: (value: string) => void

apps/sim/ee/whitelabeling/components/whitelabeling-settings.tsx

Lines changed: 100 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
'use client'
22

3-
import { useCallback, useEffect, useState } from 'react'
3+
import { useEffect, useState } from 'react'
44
import { createLogger } from '@sim/logger'
55
import { toError } from '@sim/utils/errors'
66
import { Loader2, X } from 'lucide-react'
77
import Image from 'next/image'
88
import { useParams } from 'next/navigation'
9-
import { Button, Input, Label, toast } from '@/components/emcn'
9+
import { Button, Input, Label, Skeleton, toast } from '@/components/emcn'
1010
import { useSession } from '@/lib/auth/auth-client'
1111
import { getSubscriptionAccessState } from '@/lib/billing/client/utils'
1212
import { HEX_COLOR_REGEX } from '@/lib/branding'
1313
import { isBillingEnabled } from '@/lib/core/config/feature-flags'
1414
import { cn } from '@/lib/core/utils/cn'
1515
import { getUserRole } from '@/lib/workspaces/organization/utils'
1616
import { useProfilePictureUpload } from '@/app/workspace/[workspaceId]/settings/hooks/use-profile-picture-upload'
17+
import { SettingRow } from '@/ee/components/setting-row'
1718
import {
1819
useUpdateWhitelabelSettings,
1920
useWhitelabelSettings,
@@ -33,33 +34,24 @@ interface DropZoneProps {
3334
function DropZone({ onDrop, children, className }: DropZoneProps) {
3435
const [isDragging, setIsDragging] = useState(false)
3536

36-
const handleDragOver = useCallback((e: React.DragEvent) => {
37-
if (e.dataTransfer.types.includes('Files')) {
38-
e.preventDefault()
39-
setIsDragging(true)
40-
}
41-
}, [])
42-
43-
const handleDragLeave = useCallback((e: React.DragEvent) => {
44-
if (!e.currentTarget.contains(e.relatedTarget as Node)) {
45-
setIsDragging(false)
46-
}
47-
}, [])
48-
49-
const handleDrop = useCallback(
50-
(e: React.DragEvent) => {
51-
setIsDragging(false)
52-
onDrop(e)
53-
},
54-
[onDrop]
55-
)
56-
5737
return (
5838
<div
5939
className={cn('relative', className)}
60-
onDragOver={handleDragOver}
61-
onDragLeave={handleDragLeave}
62-
onDrop={handleDrop}
40+
onDragOver={(e) => {
41+
if (e.dataTransfer.types.includes('Files')) {
42+
e.preventDefault()
43+
setIsDragging(true)
44+
}
45+
}}
46+
onDragLeave={(e) => {
47+
if (!e.currentTarget.contains(e.relatedTarget as Node)) {
48+
setIsDragging(false)
49+
}
50+
}}
51+
onDrop={(e) => {
52+
setIsDragging(false)
53+
onDrop(e)
54+
}}
6355
>
6456
{children}
6557
{isDragging && (
@@ -81,22 +73,6 @@ interface ColorInputProps {
8173
function ColorInput({ label, value, onChange, placeholder = '#000000' }: ColorInputProps) {
8274
const isValidHex = !value || HEX_COLOR_REGEX.test(value)
8375

84-
const handleChange = useCallback(
85-
(e: React.ChangeEvent<HTMLInputElement>) => {
86-
let v = e.target.value.trim()
87-
if (v && !v.startsWith('#')) {
88-
v = `#${v}`
89-
}
90-
v = v.slice(0, 1) + v.slice(1).replace(/[^0-9a-fA-F]/g, '')
91-
onChange(v.slice(0, 7))
92-
},
93-
[onChange]
94-
)
95-
96-
const handleFocus = useCallback((e: React.FocusEvent<HTMLInputElement>) => {
97-
e.target.select()
98-
}, [])
99-
10076
return (
10177
<div className='flex flex-col gap-1.5'>
10278
<Label className='text-[13px] text-[var(--text-primary)]'>{label}</Label>
@@ -110,39 +86,32 @@ function ColorInput({ label, value, onChange, placeholder = '#000000' }: ColorIn
11086
</div>
11187
<Input
11288
value={value}
113-
onChange={handleChange}
114-
onFocus={handleFocus}
89+
onChange={(e) => {
90+
let v = e.target.value.trim()
91+
if (v && !v.startsWith('#')) {
92+
v = `#${v}`
93+
}
94+
v = v.slice(0, 1) + v.slice(1).replace(/[^0-9a-fA-F]/g, '')
95+
onChange(v.slice(0, 7))
96+
}}
97+
onFocus={(e) => e.target.select()}
11598
placeholder={placeholder}
11699
className={cn(
117100
'h-[36px] font-mono text-[13px]',
118-
!isValidHex && 'border-red-500 focus-visible:ring-red-500'
101+
!isValidHex && 'border-[var(--text-error)] focus-visible:ring-[var(--text-error)]'
119102
)}
120103
maxLength={7}
121104
/>
122105
</div>
123106
{!isValidHex && (
124-
<p className='text-[12px] text-red-500'>Must be a valid hex color (e.g. #701ffc)</p>
107+
<p className='text-[12px] text-[var(--text-error)]'>
108+
Must be a valid hex color (e.g. #701ffc)
109+
</p>
125110
)}
126111
</div>
127112
)
128113
}
129114

130-
interface SettingRowProps {
131-
label: string
132-
description?: string
133-
children: React.ReactNode
134-
}
135-
136-
function SettingRow({ label, description, children }: SettingRowProps) {
137-
return (
138-
<div className='flex flex-col gap-1.5'>
139-
<Label className='text-[13px] text-[var(--text-primary)]'>{label}</Label>
140-
{description && <p className='text-[12px] text-[var(--text-muted)]'>{description}</p>}
141-
{children}
142-
</div>
143-
)
144-
}
145-
146115
function SectionTitle({ children }: { children: React.ReactNode }) {
147116
return <h3 className='mb-4 font-medium text-[15px] text-[var(--text-primary)]'>{children}</h3>
148117
}
@@ -177,20 +146,53 @@ export function WhitelabelingSettings() {
177146
const [logoUrl, setLogoUrl] = useState<string | null>(null)
178147
const [wordmarkUrl, setWordmarkUrl] = useState<string | null>(null)
179148
const [formInitialized, setFormInitialized] = useState(false)
149+
const [savedBrandName, setSavedBrandName] = useState('')
150+
const [savedPrimaryColor, setSavedPrimaryColor] = useState('')
151+
const [savedPrimaryHoverColor, setSavedPrimaryHoverColor] = useState('')
152+
const [savedAccentColor, setSavedAccentColor] = useState('')
153+
const [savedAccentHoverColor, setSavedAccentHoverColor] = useState('')
154+
const [savedSupportEmail, setSavedSupportEmail] = useState('')
155+
const [savedDocumentationUrl, setSavedDocumentationUrl] = useState('')
156+
const [savedTermsUrl, setSavedTermsUrl] = useState('')
157+
const [savedPrivacyUrl, setSavedPrivacyUrl] = useState('')
158+
const [savedLogoUrl, setSavedLogoUrl] = useState<string | null>(null)
159+
const [savedWordmarkUrl, setSavedWordmarkUrl] = useState<string | null>(null)
180160

181161
useEffect(() => {
182162
if (!savedSettings || formInitialized) return
183-
setBrandName(savedSettings.brandName ?? '')
184-
setPrimaryColor(savedSettings.primaryColor ?? '')
185-
setPrimaryHoverColor(savedSettings.primaryHoverColor ?? '')
186-
setAccentColor(savedSettings.accentColor ?? '')
187-
setAccentHoverColor(savedSettings.accentHoverColor ?? '')
188-
setSupportEmail(savedSettings.supportEmail ?? '')
189-
setDocumentationUrl(savedSettings.documentationUrl ?? '')
190-
setTermsUrl(savedSettings.termsUrl ?? '')
191-
setPrivacyUrl(savedSettings.privacyUrl ?? '')
192-
setLogoUrl(savedSettings.logoUrl ?? null)
193-
setWordmarkUrl(savedSettings.wordmarkUrl ?? null)
163+
const brand = savedSettings.brandName ?? ''
164+
const primary = savedSettings.primaryColor ?? ''
165+
const primaryHover = savedSettings.primaryHoverColor ?? ''
166+
const accent = savedSettings.accentColor ?? ''
167+
const accentHover = savedSettings.accentHoverColor ?? ''
168+
const support = savedSettings.supportEmail ?? ''
169+
const docs = savedSettings.documentationUrl ?? ''
170+
const terms = savedSettings.termsUrl ?? ''
171+
const privacy = savedSettings.privacyUrl ?? ''
172+
const logo = savedSettings.logoUrl ?? null
173+
const wordmark = savedSettings.wordmarkUrl ?? null
174+
setBrandName(brand)
175+
setPrimaryColor(primary)
176+
setPrimaryHoverColor(primaryHover)
177+
setAccentColor(accent)
178+
setAccentHoverColor(accentHover)
179+
setSupportEmail(support)
180+
setDocumentationUrl(docs)
181+
setTermsUrl(terms)
182+
setPrivacyUrl(privacy)
183+
setLogoUrl(logo)
184+
setWordmarkUrl(wordmark)
185+
setSavedBrandName(brand)
186+
setSavedPrimaryColor(primary)
187+
setSavedPrimaryHoverColor(primaryHover)
188+
setSavedAccentColor(accent)
189+
setSavedAccentHoverColor(accentHover)
190+
setSavedSupportEmail(support)
191+
setSavedDocumentationUrl(docs)
192+
setSavedTermsUrl(terms)
193+
setSavedPrivacyUrl(privacy)
194+
setSavedLogoUrl(logo)
195+
setSavedWordmarkUrl(wordmark)
194196
setFormInitialized(true)
195197
}, [savedSettings, formInitialized])
196198

@@ -212,18 +214,17 @@ export function WhitelabelingSettings() {
212214

213215
const hasChanges =
214216
formInitialized &&
215-
!!savedSettings &&
216-
(brandName !== (savedSettings.brandName ?? '') ||
217-
primaryColor !== (savedSettings.primaryColor ?? '') ||
218-
primaryHoverColor !== (savedSettings.primaryHoverColor ?? '') ||
219-
accentColor !== (savedSettings.accentColor ?? '') ||
220-
accentHoverColor !== (savedSettings.accentHoverColor ?? '') ||
221-
supportEmail !== (savedSettings.supportEmail ?? '') ||
222-
documentationUrl !== (savedSettings.documentationUrl ?? '') ||
223-
termsUrl !== (savedSettings.termsUrl ?? '') ||
224-
privacyUrl !== (savedSettings.privacyUrl ?? '') ||
225-
(logoUpload.previewUrl || null) !== savedSettings.logoUrl ||
226-
(wordmarkUpload.previewUrl || null) !== savedSettings.wordmarkUrl)
217+
(brandName !== savedBrandName ||
218+
primaryColor !== savedPrimaryColor ||
219+
primaryHoverColor !== savedPrimaryHoverColor ||
220+
accentColor !== savedAccentColor ||
221+
accentHoverColor !== savedAccentHoverColor ||
222+
supportEmail !== savedSupportEmail ||
223+
documentationUrl !== savedDocumentationUrl ||
224+
termsUrl !== savedTermsUrl ||
225+
privacyUrl !== savedPrivacyUrl ||
226+
(logoUpload.previewUrl || null) !== savedLogoUrl ||
227+
(wordmarkUpload.previewUrl || null) !== savedWordmarkUrl)
227228

228229
async function handleSave() {
229230
if (!orgId) return
@@ -258,7 +259,17 @@ export function WhitelabelingSettings() {
258259

259260
try {
260261
await updateSettings.mutateAsync({ orgId, settings })
261-
setFormInitialized(false)
262+
setSavedBrandName(brandName)
263+
setSavedPrimaryColor(primaryColor)
264+
setSavedPrimaryHoverColor(primaryHoverColor)
265+
setSavedAccentColor(accentColor)
266+
setSavedAccentHoverColor(accentHoverColor)
267+
setSavedSupportEmail(supportEmail)
268+
setSavedDocumentationUrl(documentationUrl)
269+
setSavedTermsUrl(termsUrl)
270+
setSavedPrivacyUrl(privacyUrl)
271+
setSavedLogoUrl(logoUpload.previewUrl || null)
272+
setSavedWordmarkUrl(wordmarkUpload.previewUrl || null)
262273
toast.success('Whitelabeling settings saved.')
263274
} catch (error) {
264275
logger.error('Failed to save whitelabel settings', { error })
@@ -295,10 +306,10 @@ export function WhitelabelingSettings() {
295306
if (isLoading) {
296307
return (
297308
<div className='flex flex-col gap-8'>
298-
{[...Array(3)].map((_, i) => (
309+
{Array.from({ length: 3 }).map((_, i) => (
299310
<div key={i} className='flex flex-col gap-3'>
300-
<div className='h-4 w-32 animate-pulse rounded bg-[var(--surface-3)]' />
301-
<div className='h-9 w-full animate-pulse rounded-lg bg-[var(--surface-3)]' />
311+
<Skeleton className='h-[16px] w-[128px]' />
312+
<Skeleton className='h-[36px] w-full rounded-lg' />
302313
</div>
303314
))}
304315
</div>

0 commit comments

Comments
 (0)