improvement: branding; auth; chat-deploy (#1351)

* improvement: branding; auth; chat-deploy

* improvement: docs favicon
This commit is contained in:
Emir Karabeg
2025-09-16 21:16:55 -07:00
committed by GitHub
parent 53792b9a1d
commit 0c30646a2d
33 changed files with 602 additions and 528 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 443 B

After

Width:  |  Height:  |  Size: 521 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 897 B

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 2.1 MiB

View File

@@ -1,16 +1,18 @@
{
"name": "Sim",
"short_name": "Sim",
"name": "MyWebSite",
"short_name": "MySite",
"icons": [
{
"src": "/favicon/android-chrome-192x192.png",
"src": "/web-app-manifest-192x192.png",
"sizes": "192x192",
"type": "image/png"
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/favicon/android-chrome-512x512.png",
"src": "/web-app-manifest-512x512.png",
"sizes": "512x512",
"type": "image/png"
"type": "image/png",
"purpose": "maskable"
}
],
"theme_color": "#ffffff",

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -496,7 +496,7 @@ export default function LoginPage({
className={`${buttonClass} flex w-full items-center justify-center gap-2 rounded-[10px] border font-medium text-[15px] text-white transition-all duration-200`}
disabled={isLoading}
>
{isLoading ? 'Signing in...' : 'Sign In'}
{isLoading ? 'Signing in...' : 'Sign in'}
</Button>
</form>
@@ -529,7 +529,7 @@ export default function LoginPage({
</div>
<div
className={`${inter.className} auth-text-muted fixed right-0 bottom-0 left-0 z-50 pb-8 text-center font-[340] text-[13px] leading-relaxed`}
className={`${inter.className} auth-text-muted absolute right-0 bottom-0 left-0 px-8 pb-8 text-center font-[340] text-[13px] leading-relaxed sm:px-8 md:px-[44px]`}
>
By signing in, you agree to our{' '}
<Link

View File

@@ -406,7 +406,7 @@ function SignupFormContent({
<div className='space-y-6'>
<div className='space-y-2'>
<div className='flex items-center justify-between'>
<Label htmlFor='name'>Full Name</Label>
<Label htmlFor='name'>Full name</Label>
</div>
<Input
id='name'
@@ -511,7 +511,7 @@ function SignupFormContent({
className={`${buttonClass} flex w-full items-center justify-center gap-2 rounded-[10px] border font-medium text-[15px] text-white transition-all duration-200`}
disabled={isLoading}
>
{isLoading ? 'Creating account...' : 'Create Account'}
{isLoading ? 'Creating account...' : 'Create account'}
</Button>
</form>
@@ -544,7 +544,7 @@ function SignupFormContent({
</div>
<div
className={`${inter.className} auth-text-muted fixed right-0 bottom-0 left-0 z-50 pb-8 text-center font-[340] text-[13px] leading-relaxed`}
className={`${inter.className} auth-text-muted absolute right-0 bottom-0 left-0 px-8 pb-8 text-center font-[340] text-[13px] leading-relaxed sm:px-8 md:px-[44px]`}
>
By creating an account, you agree to our{' '}
<Link

View File

@@ -1,82 +1,14 @@
/* Force light mode for chat subdomain by overriding dark mode utilities */
/* This file uses CSS variables from globals.css light mode theme */
/**
* Chat Subdomain Light Mode Overrides
*
* This file overrides dark mode utility classes to force light mode appearance
* in the chat subdomain. It uses CSS variables defined in globals.css.
*
* The layout.tsx already applies the 'light' class which sets all the light
* theme CSS variables from globals.css, so we don't need to redefine them here.
*/
/* When inside the chat layout, force all light mode CSS variables */
.chat-light-wrapper {
/* Core Colors - from globals.css light mode */
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
/* Card Colors */
--card: 0 0% 99.2%;
--card-foreground: 0 0% 3.9%;
/* Popover Colors */
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
/* Primary Colors */
--primary: 0 0% 11.2%;
--primary-foreground: 0 0% 98%;
/* Secondary Colors */
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 11.2%;
/* Muted Colors */
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 46.9%;
/* Accent Colors */
--accent: 0 0% 92.5%;
--accent-foreground: 0 0% 11.2%;
/* Destructive Colors */
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
/* Border & Input Colors */
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 3.9%;
/* Border Radius */
--radius: 0.5rem;
/* Scrollbar Properties */
--scrollbar-track: 0 0% 85%;
--scrollbar-thumb: 0 0% 65%;
--scrollbar-thumb-hover: 0 0% 55%;
--scrollbar-size: 8px;
/* Workflow Properties */
--workflow-background: 0 0% 100%;
--workflow-dots: 0 0% 94.5%;
--card-background: 0 0% 99.2%;
--card-border: 0 0% 89.8%;
--card-text: 0 0% 3.9%;
--card-hover: 0 0% 96.1%;
/* Base Component Properties */
--base-muted-foreground: #737373;
/* Gradient Colors */
--gradient-primary: 263 85% 70%;
--gradient-secondary: 336 95% 65%;
/* Brand Colors */
--brand-primary-hex: #701ffc;
--brand-primary-hover-hex: #802fff;
--brand-secondary-hex: #6518e6;
--brand-accent-hex: #9d54ff;
--brand-accent-hover-hex: #a66fff;
--brand-background-hex: #0c0c0c;
/* UI Surface Colors */
--surface-elevated: #202020;
}
/* Override dark mode utility classes using CSS variables */
/* Background Color Overrides */
.chat-light-wrapper :is(.dark\:bg-black) {
background-color: hsl(var(--secondary));
}
@@ -101,7 +33,7 @@
background-color: hsl(var(--primary));
}
/* Text color overrides using CSS variables */
/* Text Color Overrides */
.chat-light-wrapper :is(.dark\:text-gray-100) {
color: hsl(var(--primary));
}
@@ -126,7 +58,7 @@
color: var(--brand-accent-hex);
}
/* Border color overrides using CSS variables */
/* Border Color Overrides */
.chat-light-wrapper :is(.dark\:border-gray-700) {
border-color: hsl(var(--border));
}
@@ -143,12 +75,12 @@
border-color: hsl(var(--border));
}
/* Hover state overrides */
/* Hover State Overrides */
.chat-light-wrapper :is(.dark\:hover\:bg-gray-800\/60:hover) {
background-color: hsl(var(--card-hover));
}
/* Code blocks specific overrides using CSS variables */
/* Code Block Overrides */
.chat-light-wrapper pre:is(.dark\:bg-black) {
background-color: hsl(var(--workflow-dots));
}
@@ -161,13 +93,14 @@
color: hsl(var(--foreground));
}
/* Tooltip overrides - keep tooltips black with white text for consistency */
/* Special Components */
/* Tooltip overrides - keep tooltips dark with light text for consistency */
.chat-light-wrapper [data-radix-tooltip-content] {
background-color: hsl(0 0% 3.9%) !important;
color: hsl(0 0% 98%) !important;
}
/* Force color scheme */
/* Force light color scheme */
.chat-light-wrapper {
color-scheme: light !important;
}

View File

@@ -1,11 +1,19 @@
'use client'
import { type KeyboardEvent, useState } from 'react'
import { type KeyboardEvent, useEffect, useState } from 'react'
import { Loader2 } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { Input } from '@/components/ui/input'
import { OTPInputForm } from '@/components/ui/input-otp-form'
import { InputOTP, InputOTPGroup, InputOTPSlot } from '@/components/ui/input-otp'
import { Label } from '@/components/ui/label'
import { quickValidateEmail } from '@/lib/email/validation'
import { createLogger } from '@/lib/logs/console/logger'
import { cn } from '@/lib/utils'
import Nav from '@/app/(landing)/components/nav/nav'
import { inter } from '@/app/fonts/inter'
import { soehne } from '@/app/fonts/soehne/soehne'
const logger = createLogger('EmailAuth')
interface EmailAuthProps {
subdomain: string
@@ -14,6 +22,22 @@ interface EmailAuthProps {
primaryColor?: string
}
const validateEmailField = (emailValue: string): string[] => {
const errors: string[] = []
if (!emailValue || !emailValue.trim()) {
errors.push('Email is required.')
return errors
}
const validation = quickValidateEmail(emailValue.trim().toLowerCase())
if (!validation.isValid) {
errors.push(validation.reason || 'Please enter a valid email address.')
}
return errors
}
export default function EmailAuth({
subdomain,
onAuthSuccess,
@@ -25,10 +49,55 @@ export default function EmailAuth({
const [authError, setAuthError] = useState<string | null>(null)
const [isSendingOtp, setIsSendingOtp] = useState(false)
const [isVerifyingOtp, setIsVerifyingOtp] = useState(false)
const [emailErrors, setEmailErrors] = useState<string[]>([])
const [showEmailValidationError, setShowEmailValidationError] = useState(false)
const [buttonClass, setButtonClass] = useState('auth-button-gradient')
// OTP verification state
const [showOtpVerification, setShowOtpVerification] = useState(false)
const [otpValue, setOtpValue] = useState('')
const [countdown, setCountdown] = useState(0)
const [isResendDisabled, setIsResendDisabled] = useState(false)
useEffect(() => {
// Check if CSS variable has been customized
const checkCustomBrand = () => {
const computedStyle = getComputedStyle(document.documentElement)
const brandAccent = computedStyle.getPropertyValue('--brand-accent-hex').trim()
// Check if the CSS variable exists and is different from the default
if (brandAccent && brandAccent !== '#6f3dfa') {
setButtonClass('auth-button-custom')
} else {
setButtonClass('auth-button-gradient')
}
}
checkCustomBrand()
// Also check on window resize or theme changes
window.addEventListener('resize', checkCustomBrand)
const observer = new MutationObserver(checkCustomBrand)
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['style', 'class'],
})
return () => {
window.removeEventListener('resize', checkCustomBrand)
observer.disconnect()
}
}, [])
useEffect(() => {
if (countdown > 0) {
const timer = setTimeout(() => setCountdown(countdown - 1), 1000)
return () => clearTimeout(timer)
}
if (countdown === 0 && isResendDisabled) {
setIsResendDisabled(false)
}
}, [countdown, isResendDisabled])
// Handle email input key down
const handleEmailKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
@@ -38,8 +107,28 @@ export default function EmailAuth({
}
}
const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newEmail = e.target.value
setEmail(newEmail)
// Silently validate but don't show errors until submit
const errors = validateEmailField(newEmail)
setEmailErrors(errors)
setShowEmailValidationError(false)
}
// Handle sending OTP
const handleSendOtp = async () => {
// Validate email on submit
const emailValidationErrors = validateEmailField(email)
setEmailErrors(emailValidationErrors)
setShowEmailValidationError(emailValidationErrors.length > 0)
// If there are validation errors, stop submission
if (emailValidationErrors.length > 0) {
return
}
setAuthError(null)
setIsSendingOtp(true)
@@ -55,14 +144,16 @@ export default function EmailAuth({
if (!response.ok) {
const errorData = await response.json()
setAuthError(errorData.error || 'Failed to send verification code')
setEmailErrors([errorData.error || 'Failed to send verification code'])
setShowEmailValidationError(true)
return
}
setShowOtpVerification(true)
} catch (error) {
console.error('Error sending OTP:', error)
setAuthError('An error occurred while sending the verification code')
logger.error('Error sending OTP:', error)
setEmailErrors(['An error occurred while sending the verification code'])
setShowEmailValidationError(true)
} finally {
setIsSendingOtp(false)
}
@@ -96,7 +187,7 @@ export default function EmailAuth({
onAuthSuccess()
} catch (error) {
console.error('Error verifying OTP:', error)
logger.error('Error verifying OTP:', error)
setAuthError('An error occurred during verification')
} finally {
setIsVerifyingOtp(false)
@@ -106,6 +197,8 @@ export default function EmailAuth({
const handleResendOtp = async () => {
setAuthError(null)
setIsSendingOtp(true)
setIsResendDisabled(true)
setCountdown(30)
try {
const response = await fetch(`/api/chat/${subdomain}/otp`, {
@@ -120,175 +213,195 @@ export default function EmailAuth({
if (!response.ok) {
const errorData = await response.json()
setAuthError(errorData.error || 'Failed to resend verification code')
setIsResendDisabled(false)
setCountdown(0)
return
}
setAuthError('Verification code sent. Please check your email.')
// Don't show success message in error state, just reset OTP
setOtpValue('')
} catch (error) {
console.error('Error resending OTP:', error)
logger.error('Error resending OTP:', error)
setAuthError('An error occurred while resending the verification code')
setIsResendDisabled(false)
setCountdown(0)
} finally {
setIsSendingOtp(false)
}
}
return (
<Dialog open={true} onOpenChange={() => {}}>
<DialogContent
className='flex flex-col gap-0 overflow-hidden p-0 sm:max-w-[450px]'
hideCloseButton
>
<DialogHeader className='border-b px-6 py-4'>
<div className='flex items-center justify-center'>
<a href='https://sim.ai' target='_blank' rel='noopener noreferrer' className='mb-2'>
<svg
width='40'
height='40'
viewBox='0 0 50 50'
fill='none'
xmlns='http://www.w3.org/2000/svg'
className='rounded-[6px]'
<div className='bg-white'>
<Nav variant='auth' />
<div className='flex min-h-[calc(100vh-120px)] items-center justify-center px-4'>
<div className='w-full max-w-[410px]'>
<div className='flex flex-col items-center justify-center'>
{/* Header */}
<div className='space-y-1 text-center'>
<h1
className={`${soehne.className} font-medium text-[32px] text-black tracking-tight`}
>
<rect width='50' height='50' fill='var(--brand-primary-hex)' />
<path
d='M34.1455 20.0728H16.0364C12.7026 20.0728 10 22.7753 10 26.1091V35.1637C10 38.4975 12.7026 41.2 16.0364 41.2H34.1455C37.4792 41.2 40.1818 38.4975 40.1818 35.1637V26.1091C40.1818 22.7753 37.4792 20.0728 34.1455 20.0728Z'
fill='var(--brand-primary-hex)'
stroke='white'
strokeWidth='3.5'
strokeLinecap='round'
strokeLinejoin='round'
/>
<path
d='M25.0919 14.0364C26.7588 14.0364 28.1101 12.6851 28.1101 11.0182C28.1101 9.35129 26.7588 8 25.0919 8C23.425 8 22.0737 9.35129 22.0737 11.0182C22.0737 12.6851 23.425 14.0364 25.0919 14.0364Z'
fill='var(--brand-primary-hex)'
stroke='white'
strokeWidth='4'
strokeLinecap='round'
strokeLinejoin='round'
/>
<path
d='M25.0915 14.856V19.0277V14.856ZM20.5645 32.1398V29.1216V32.1398ZM29.619 29.1216V32.1398V29.1216Z'
fill='var(--brand-primary-hex)'
/>
<path
d='M25.0915 14.856V19.0277M20.5645 32.1398V29.1216M29.619 29.1216V32.1398'
stroke='white'
strokeWidth='4'
strokeLinecap='round'
strokeLinejoin='round'
/>
<circle cx='25' cy='11' r='2' fill='var(--brand-primary-hex)' />
</svg>
</a>
</div>
<DialogTitle className='text-center font-medium text-lg'>{title}</DialogTitle>
</DialogHeader>
<div className='p-6'>
{!showOtpVerification ? (
<>
<div className='mb-4 text-center'>
<p className='text-muted-foreground'>
This chat requires email verification. Please enter your email to continue.
</p>
</div>
{authError && (
<div className='mb-4 rounded-md border border-red-200 bg-red-50 p-3 text-red-600 text-sm'>
{authError}
</div>
)}
<form
onSubmit={(e) => {
e.preventDefault()
handleSendOtp()
}}
className='space-y-4'
>
<div className='space-y-2'>
<Input
id='email'
type='email'
placeholder='Email address'
value={email}
onChange={(e) => setEmail(e.target.value)}
onKeyDown={handleEmailKeyDown}
disabled={isSendingOtp}
className='w-full'
autoFocus
autoComplete='off'
/>
</div>
<Button
type='submit'
onClick={handleSendOtp}
disabled={!email || isSendingOtp}
className='w-full'
style={{ backgroundColor: primaryColor }}
>
{isSendingOtp ? (
<div className='flex items-center justify-center'>
<Loader2 className='mr-2 h-4 w-4 animate-spin' />
Sending Code...
</div>
) : (
'Continue'
)}
</Button>
</form>
</>
) : (
<div className='space-y-4'>
<div className='text-center'>
<p className='mb-1 text-muted-foreground text-sm'>
Enter the verification code sent to
</p>
<p className='break-all font-medium text-sm'>{email}</p>
</div>
{authError && (
<div className='rounded-md border border-red-200 bg-red-50 p-3 text-red-600 text-sm'>
{authError}
</div>
)}
<OTPInputForm
onSubmit={(value) => {
setOtpValue(value)
handleVerifyOtp(value)
}}
isLoading={isVerifyingOtp}
error={null}
/>
<div className='flex items-center justify-center pt-2'>
<button
type='button'
onClick={handleResendOtp}
disabled={isSendingOtp}
className='text-muted-foreground text-sm hover:underline disabled:opacity-50'
>
{isSendingOtp ? 'Sending...' : 'Resend code'}
</button>
<span className='mx-2 text-neutral-300 dark:text-neutral-600'></span>
<button
type='button'
onClick={() => {
setShowOtpVerification(false)
setOtpValue('')
setAuthError(null)
}}
className='text-muted-foreground text-sm hover:underline'
>
Change email
</button>
</div>
{showOtpVerification ? 'Verify Your Email' : 'Email Verification'}
</h1>
<p className={`${inter.className} font-[380] text-[16px] text-muted-foreground`}>
{showOtpVerification
? `A verification code has been sent to ${email}`
: 'This chat requires email verification'}
</p>
</div>
)}
{/* Form */}
<div className={`${inter.className} mt-8 w-full`}>
{!showOtpVerification ? (
<form
onSubmit={(e) => {
e.preventDefault()
handleSendOtp()
}}
className='space-y-8'
>
<div className='space-y-6'>
<div className='space-y-2'>
<div className='flex items-center justify-between'>
<Label htmlFor='email'>Email</Label>
</div>
<Input
id='email'
name='email'
placeholder='Enter your email'
required
autoCapitalize='none'
autoComplete='email'
autoCorrect='off'
value={email}
onChange={handleEmailChange}
onKeyDown={handleEmailKeyDown}
className={cn(
'rounded-[10px] shadow-sm transition-colors focus:border-gray-400 focus:ring-2 focus:ring-gray-100',
showEmailValidationError &&
emailErrors.length > 0 &&
'border-red-500 focus:border-red-500 focus:ring-red-100 focus-visible:ring-red-500'
)}
autoFocus
/>
{showEmailValidationError && emailErrors.length > 0 && (
<div className='mt-1 space-y-1 text-red-400 text-xs'>
{emailErrors.map((error, index) => (
<p key={index}>{error}</p>
))}
</div>
)}
</div>
</div>
<Button
type='submit'
className={`${buttonClass} flex w-full items-center justify-center gap-2 rounded-[10px] border font-medium text-[15px] text-white transition-all duration-200`}
disabled={isSendingOtp}
>
{isSendingOtp ? (
<>
<Loader2 className='mr-2 h-4 w-4 animate-spin' />
Sending Code...
</>
) : (
'Continue'
)}
</Button>
</form>
) : (
<div className='space-y-8'>
<div className='space-y-6'>
<p className='text-center text-muted-foreground text-sm'>
Enter the 6-digit code to verify your account. If you don't see it in your
inbox, check your spam folder.
</p>
<div className='flex justify-center'>
<InputOTP
maxLength={6}
value={otpValue}
onChange={(value) => {
setOtpValue(value)
if (value.length === 6) {
handleVerifyOtp(value)
}
}}
disabled={isVerifyingOtp}
className={cn('gap-2', authError && 'otp-error')}
>
<InputOTPGroup className='[&>div]:!rounded-[10px] gap-2'>
{[0, 1, 2, 3, 4, 5].map((index) => (
<InputOTPSlot
key={index}
index={index}
className={cn(
'!rounded-[10px] h-12 w-12 border bg-white text-center font-medium text-lg shadow-sm transition-all duration-200',
'border-gray-300 hover:border-gray-400',
'focus:border-gray-400 focus:outline-none focus:ring-2 focus:ring-gray-100',
authError &&
'border-red-500 focus:border-red-500 focus:ring-red-100'
)}
/>
))}
</InputOTPGroup>
</InputOTP>
</div>
{/* Error message */}
{authError && (
<div className='mt-1 space-y-1 text-center text-red-400 text-xs'>
<p>{authError}</p>
</div>
)}
</div>
<Button
onClick={() => handleVerifyOtp()}
className={`${buttonClass} flex w-full items-center justify-center gap-2 rounded-[10px] border font-medium text-[15px] text-white transition-all duration-200`}
disabled={otpValue.length !== 6 || isVerifyingOtp}
>
{isVerifyingOtp ? 'Verifying...' : 'Verify Email'}
</Button>
<div className='text-center'>
<p className='text-muted-foreground text-sm'>
Didn't receive a code?{' '}
{countdown > 0 ? (
<span>
Resend in{' '}
<span className='font-medium text-foreground'>{countdown}s</span>
</span>
) : (
<button
className='font-medium text-[var(--brand-accent-hex)] underline-offset-4 transition hover:text-[var(--brand-accent-hover-hex)] hover:underline'
onClick={handleResendOtp}
disabled={isVerifyingOtp || isResendDisabled}
>
Resend
</button>
)}
</p>
</div>
<div className='text-center font-light text-[14px]'>
<button
onClick={() => {
setShowOtpVerification(false)
setOtpValue('')
setAuthError(null)
}}
className='font-medium text-[var(--brand-accent-hex)] underline-offset-4 transition hover:text-[var(--brand-accent-hover-hex)] hover:underline'
>
Change email
</button>
</div>
</div>
)}
</div>
</div>
</div>
</DialogContent>
</Dialog>
</div>
</div>
)
}

View File

@@ -1,10 +1,17 @@
'use client'
import { type KeyboardEvent, useState } from 'react'
import { Loader2 } from 'lucide-react'
import { type KeyboardEvent, useEffect, useState } from 'react'
import { Eye, EyeOff } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { createLogger } from '@/lib/logs/console/logger'
import { cn } from '@/lib/utils'
import Nav from '@/app/(landing)/components/nav/nav'
import { inter } from '@/app/fonts/inter'
import { soehne } from '@/app/fonts/soehne/soehne'
const logger = createLogger('PasswordAuth')
interface PasswordAuthProps {
subdomain: string
@@ -23,6 +30,40 @@ export default function PasswordAuth({
const [password, setPassword] = useState('')
const [authError, setAuthError] = useState<string | null>(null)
const [isAuthenticating, setIsAuthenticating] = useState(false)
const [showPassword, setShowPassword] = useState(false)
const [showValidationError, setShowValidationError] = useState(false)
const [passwordErrors, setPasswordErrors] = useState<string[]>([])
const [buttonClass, setButtonClass] = useState('auth-button-gradient')
useEffect(() => {
// Check if CSS variable has been customized
const checkCustomBrand = () => {
const computedStyle = getComputedStyle(document.documentElement)
const brandAccent = computedStyle.getPropertyValue('--brand-accent-hex').trim()
// Check if the CSS variable exists and is different from the default
if (brandAccent && brandAccent !== '#6f3dfa') {
setButtonClass('auth-button-custom')
} else {
setButtonClass('auth-button-gradient')
}
}
checkCustomBrand()
// Also check on window resize or theme changes
window.addEventListener('resize', checkCustomBrand)
const observer = new MutationObserver(checkCustomBrand)
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['style', 'class'],
})
return () => {
window.removeEventListener('resize', checkCustomBrand)
observer.disconnect()
}
}, [])
// Handle keyboard input for auth forms
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
@@ -32,10 +73,18 @@ export default function PasswordAuth({
}
}
const handlePasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newPassword = e.target.value
setPassword(newPassword)
setShowValidationError(false)
setPasswordErrors([])
}
// Handle authentication
const handleAuthenticate = async () => {
if (!password.trim()) {
setAuthError('Password is required')
setPasswordErrors(['Password is required'])
setShowValidationError(true)
return
}
@@ -57,7 +106,8 @@ export default function PasswordAuth({
if (!response.ok) {
const errorData = await response.json()
setAuthError(errorData.error || 'Authentication failed')
setPasswordErrors([errorData.error || 'Invalid password. Please try again.'])
setShowValidationError(true)
return
}
@@ -67,118 +117,96 @@ export default function PasswordAuth({
// Reset auth state
setPassword('')
} catch (error) {
console.error('Authentication error:', error)
setAuthError('An error occurred during authentication')
logger.error('Authentication error:', error)
setPasswordErrors(['An error occurred during authentication'])
setShowValidationError(true)
} finally {
setIsAuthenticating(false)
}
}
return (
<Dialog open={true} onOpenChange={() => {}}>
<DialogContent
className='flex flex-col gap-0 overflow-hidden p-0 sm:max-w-[450px]'
hideCloseButton
>
<DialogHeader className='border-b px-6 py-4'>
<div className='flex items-center justify-center'>
<a href='https://sim.ai' target='_blank' rel='noopener noreferrer' className='mb-2'>
<svg
width='40'
height='40'
viewBox='0 0 50 50'
fill='none'
xmlns='http://www.w3.org/2000/svg'
className='rounded-[6px]'
<div className='bg-white'>
<Nav variant='auth' />
<div className='flex min-h-[calc(100vh-120px)] items-center justify-center px-4'>
<div className='w-full max-w-[410px]'>
<div className='flex flex-col items-center justify-center'>
{/* Header */}
<div className='space-y-1 text-center'>
<h1
className={`${soehne.className} font-medium text-[32px] text-black tracking-tight`}
>
<rect width='50' height='50' fill='var(--brand-primary-hex)' />
<path
d='M34.1455 20.0728H16.0364C12.7026 20.0728 10 22.7753 10 26.1091V35.1637C10 38.4975 12.7026 41.2 16.0364 41.2H34.1455C37.4792 41.2 40.1818 38.4975 40.1818 35.1637V26.1091C40.1818 22.7753 37.4792 20.0728 34.1455 20.0728Z'
fill='var(--brand-primary-hex)'
stroke='white'
strokeWidth='3.5'
strokeLinecap='round'
strokeLinejoin='round'
/>
<path
d='M25.0919 14.0364C26.7588 14.0364 28.1101 12.6851 28.1101 11.0182C28.1101 9.35129 26.7588 8 25.0919 8C23.425 8 22.0737 9.35129 22.0737 11.0182C22.0737 12.6851 23.425 14.0364 25.0919 14.0364Z'
fill='var(--brand-primary-hex)'
stroke='white'
strokeWidth='4'
strokeLinecap='round'
strokeLinejoin='round'
/>
<path
d='M25.0915 14.856V19.0277V14.856ZM20.5645 32.1398V29.1216V32.1398ZM29.619 29.1216V32.1398V29.1216Z'
fill='var(--brand-primary-hex)'
/>
<path
d='M25.0915 14.856V19.0277M20.5645 32.1398V29.1216M29.619 29.1216V32.1398'
stroke='white'
strokeWidth='4'
strokeLinecap='round'
strokeLinejoin='round'
/>
<circle cx='25' cy='11' r='2' fill='var(--brand-primary-hex)' />
</svg>
</a>
</div>
<DialogTitle className='text-center font-medium text-lg'>{title}</DialogTitle>
</DialogHeader>
<div className='p-6'>
<div className='mb-4 text-center'>
<p className='text-muted-foreground'>
This chat is password-protected. Please enter the password to continue.
</p>
</div>
{authError && (
<div className='mb-4 rounded-md border border-red-200 bg-red-50 p-3 text-red-600 text-sm'>
{authError}
</div>
)}
<form
onSubmit={(e) => {
e.preventDefault()
handleAuthenticate()
}}
className='space-y-4'
>
<div className='space-y-2'>
<Input
id='password'
type='password'
value={password}
onChange={(e) => setPassword(e.target.value)}
onKeyDown={handleKeyDown}
placeholder='Enter password'
disabled={isAuthenticating}
autoComplete='new-password'
className='w-full'
autoFocus
/>
Password Required
</h1>
<p className={`${inter.className} font-[380] text-[16px] text-muted-foreground`}>
This chat is password-protected
</p>
</div>
<Button
type='submit'
disabled={!password || isAuthenticating}
className='w-full'
style={{ backgroundColor: primaryColor }}
{/* Form */}
<form
onSubmit={(e) => {
e.preventDefault()
handleAuthenticate()
}}
className={`${inter.className} mt-8 w-full space-y-8`}
>
{isAuthenticating ? (
<div className='flex items-center justify-center'>
<Loader2 className='mr-2 h-4 w-4 animate-spin' />
Authenticating...
<div className='space-y-6'>
<div className='space-y-2'>
<div className='flex items-center justify-between'>
<Label htmlFor='password'>Password</Label>
</div>
<div className='relative'>
<Input
id='password'
name='password'
required
type={showPassword ? 'text' : 'password'}
autoCapitalize='none'
autoComplete='new-password'
autoCorrect='off'
placeholder='Enter password'
value={password}
onChange={handlePasswordChange}
onKeyDown={handleKeyDown}
className={cn(
'rounded-[10px] pr-10 shadow-sm transition-colors focus:border-gray-400 focus:ring-2 focus:ring-gray-100',
showValidationError &&
passwordErrors.length > 0 &&
'border-red-500 focus:border-red-500 focus:ring-red-100 focus-visible:ring-red-500'
)}
autoFocus
/>
<button
type='button'
onClick={() => setShowPassword(!showPassword)}
className='-translate-y-1/2 absolute top-1/2 right-3 text-gray-500 transition hover:text-gray-700'
aria-label={showPassword ? 'Hide password' : 'Show password'}
>
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
</button>
</div>
{showValidationError && passwordErrors.length > 0 && (
<div className='mt-1 space-y-1 text-red-400 text-xs'>
{passwordErrors.map((error, index) => (
<p key={index}>{error}</p>
))}
</div>
)}
</div>
) : (
'Continue'
)}
</Button>
</form>
</div>
<Button
type='submit'
className={`${buttonClass} flex w-full items-center justify-center gap-2 rounded-[10px] border font-medium text-[15px] text-white transition-all duration-200`}
disabled={isAuthenticating}
>
{isAuthenticating ? 'Authenticating...' : 'Continue'}
</Button>
</form>
</div>
</div>
</DialogContent>
</Dialog>
</div>
</div>
)
}

View File

@@ -1,6 +1,11 @@
'use client'
import { ChatHeader } from '../'
import { useEffect, useState } from 'react'
import { useRouter } from 'next/navigation'
import { Button } from '@/components/ui/button'
import Nav from '@/app/(landing)/components/nav/nav'
import { inter } from '@/app/fonts/inter'
import { soehne } from '@/app/fonts/soehne/soehne'
interface ChatErrorStateProps {
error: string
@@ -8,54 +13,69 @@ interface ChatErrorStateProps {
}
export function ChatErrorState({ error, starCount }: ChatErrorStateProps) {
const router = useRouter()
const [buttonClass, setButtonClass] = useState('auth-button-gradient')
useEffect(() => {
// Check if CSS variable has been customized
const checkCustomBrand = () => {
const computedStyle = getComputedStyle(document.documentElement)
const brandAccent = computedStyle.getPropertyValue('--brand-accent-hex').trim()
// Check if the CSS variable exists and is different from the default
if (brandAccent && brandAccent !== '#6f3dfa') {
setButtonClass('auth-button-custom')
} else {
setButtonClass('auth-button-gradient')
}
}
checkCustomBrand()
// Also check on window resize or theme changes
window.addEventListener('resize', checkCustomBrand)
const observer = new MutationObserver(checkCustomBrand)
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['style', 'class'],
})
return () => {
window.removeEventListener('resize', checkCustomBrand)
observer.disconnect()
}
}, [])
return (
<div className='flex min-h-screen items-center justify-center bg-gray-50'>
<div className='mx-auto max-w-md rounded-xl bg-white p-6 shadow-md'>
<div className='mb-2 flex items-center justify-between'>
<a href='https://sim.ai' target='_blank' rel='noopener noreferrer'>
<svg
width='32'
height='32'
viewBox='0 0 50 50'
fill='none'
xmlns='http://www.w3.org/2000/svg'
className='rounded-[6px]'
>
<rect width='50' height='50' fill='var(--brand-primary-hex)' />
<path
d='M34.1455 20.0728H16.0364C12.7026 20.0728 10 22.7753 10 26.1091V35.1637C10 38.4975 12.7026 41.2 16.0364 41.2H34.1455C37.4792 41.2 40.1818 38.4975 40.1818 35.1637V26.1091C40.1818 22.7753 37.4792 20.0728 34.1455 20.0728Z'
fill='var(--brand-primary-hex)'
stroke='white'
strokeWidth='3.5'
strokeLinecap='round'
strokeLinejoin='round'
/>
<path
d='M25.0919 14.0364C26.7588 14.0364 28.1101 12.6851 28.1101 11.0182C28.1101 9.35129 26.7588 8 25.0919 8C23.425 8 22.0737 9.35129 22.0737 11.0182C22.0737 12.6851 23.425 14.0364 25.0919 14.0364Z'
fill='var(--brand-primary-hex)'
stroke='white'
strokeWidth='4'
strokeLinecap='round'
strokeLinejoin='round'
/>
<path
d='M25.0915 14.856V19.0277V14.856ZM20.5645 32.1398V29.1216V32.1398ZM29.619 29.1216V32.1398V29.1216Z'
fill='var(--brand-primary-hex)'
/>
<path
d='M25.0915 14.856V19.0277M20.5645 32.1398V29.1216M29.619 29.1216V32.1398'
stroke='white'
strokeWidth='4'
strokeLinecap='round'
strokeLinejoin='round'
/>
<circle cx='25' cy='11' r='2' fill='var(--brand-primary-hex)' />
</svg>
</a>
<ChatHeader chatConfig={null} starCount={starCount} />
<div className='bg-white'>
<Nav variant='auth' />
<div className='flex min-h-[calc(100vh-120px)] items-center justify-center px-4'>
<div className='w-full max-w-[410px]'>
<div className='flex flex-col items-center justify-center'>
{/* Error content */}
<div className='space-y-1 text-center'>
<h1
className={`${soehne.className} font-medium text-[32px] text-black tracking-tight`}
>
Chat Unavailable
</h1>
<p className={`${inter.className} font-[380] text-[16px] text-muted-foreground`}>
{error}
</p>
</div>
{/* Action button - matching login form */}
<div className='mt-8 w-full'>
<Button
type='button'
onClick={() => router.push('/workspace')}
className={`${buttonClass} flex w-full items-center justify-center gap-2 rounded-[10px] border font-medium text-[15px] text-white transition-all duration-200`}
>
Return to Workspace
</Button>
</div>
</div>
</div>
<h2 className='mb-2 font-bold text-red-500 text-xl'>Error</h2>
<p className='text-gray-700'>{error}</p>
</div>
</div>
)

View File

@@ -1,6 +1,10 @@
'use client'
import Image from 'next/image'
import Link from 'next/link'
import { GithubIcon } from '@/components/icons'
import { useBrandConfig } from '@/lib/branding/branding'
import { inter } from '@/app/fonts/inter'
interface ChatHeaderProps {
chatConfig: {
@@ -16,79 +20,67 @@ interface ChatHeaderProps {
}
export function ChatHeader({ chatConfig, starCount }: ChatHeaderProps) {
const brand = useBrandConfig()
const primaryColor = chatConfig?.customizations?.primaryColor || 'var(--brand-primary-hex)'
const customImage = chatConfig?.customizations?.imageUrl || chatConfig?.customizations?.logoUrl
return (
<div className='flex items-center justify-between bg-background/95 px-6 py-4 pt-6 backdrop-blur supports-[backdrop-filter]:bg-background/60 md:px-8 md:pt-4'>
<div className='flex items-center gap-4'>
{customImage && (
<img
src={customImage}
alt={`${chatConfig?.title || 'Chat'} logo`}
className='h-8 w-8 rounded-md object-cover'
/>
)}
<h2 className='font-medium text-foreground text-lg'>
{chatConfig?.customizations?.headerText || chatConfig?.title || 'Chat'}
</h2>
<nav
aria-label='Chat navigation'
className={`flex w-full items-center justify-between px-4 pt-[12px] pb-[21px] sm:px-8 sm:pt-[8.5px] md:px-[44px] md:pt-[16px]`}
>
<div className='flex items-center gap-[34px]'>
<div className='flex items-center gap-3'>
{customImage && (
<Image
src={customImage}
alt={`${chatConfig?.title || 'Chat'} logo`}
width={24}
height={24}
className='h-6 w-6 rounded-md object-cover'
/>
)}
<h2 className={`${inter.className} font-medium text-[18px] text-foreground`}>
{chatConfig?.customizations?.headerText || chatConfig?.title || 'Chat'}
</h2>
</div>
</div>
<div className='flex items-center gap-2'>
<a
href='https://github.com/simstudioai/sim'
className='flex items-center gap-1 text-foreground'
aria-label='GitHub'
target='_blank'
rel='noopener noreferrer'
>
<GithubIcon className='h-5 w-5' />
<span className='hidden font-medium text-sm sm:inline-block'>{starCount}</span>
</a>
<a
href='https://sim.ai'
target='_blank'
rel='noopener noreferrer'
className='flex items-center rounded-md p-1 text-foreground/80 transition-colors duration-200 hover:text-foreground/100'
>
<div
className='flex h-7 w-7 items-center justify-center rounded-md'
style={{ backgroundColor: primaryColor }}
{!brand.logoUrl && (
<div className='flex items-center gap-[16px]'>
<a
href='https://github.com/simstudioai/sim'
target='_blank'
rel='noopener noreferrer'
className='flex items-center gap-2 text-[16px] text-muted-foreground transition-colors hover:text-foreground'
aria-label={`GitHub repository - ${starCount} stars`}
>
<svg
width='16'
height='16'
viewBox='0 0 50 50'
fill='none'
xmlns='http://www.w3.org/2000/svg'
>
<path
d='M34.1455 20.0728H16.0364C12.7026 20.0728 10 22.7753 10 26.1091V35.1637C10 38.4975 12.7026 41.2 16.0364 41.2H34.1455C37.4792 41.2 40.1818 38.4975 40.1818 35.1637V26.1091C40.1818 22.7753 37.4792 20.0728 34.1455 20.0728Z'
fill={primaryColor}
stroke='white'
strokeWidth='3.5'
strokeLinecap='round'
strokeLinejoin='round'
/>
<path
d='M25.0919 14.0364C26.7588 14.0364 28.1101 12.6851 28.1101 11.0182C28.1101 9.35129 26.7588 8 25.0919 8C23.425 8 22.0737 9.35129 22.0737 11.0182C22.0737 12.6851 23.425 14.0364 25.0919 14.0364Z'
fill={primaryColor}
stroke='white'
strokeWidth='4'
strokeLinecap='round'
strokeLinejoin='round'
/>
<path
d='M25.0915 14.856V19.0277M20.5645 32.1398V29.1216M29.619 29.1216V32.1398'
stroke='white'
strokeWidth='4'
strokeLinecap='round'
strokeLinejoin='round'
/>
<circle cx='25' cy='11' r='2' fill={primaryColor} />
</svg>
</div>
</a>
</div>
</div>
<GithubIcon className='h-[16px] w-[16px]' aria-hidden='true' />
<span className={`${inter.className}`} aria-live='polite'>
{starCount}
</span>
</a>
{/* Only show Sim logo if no custom branding is set */}
<Link
href='https://sim.ai'
target='_blank'
rel='noopener noreferrer'
aria-label='Sim home'
>
<Image
src='/logo/b&w/text/small.png'
alt='Sim - Workflows for LLMs'
width={29.869884}
height={14.5656}
className='h-[14.5656px] w-auto pb-[1px]'
priority
loading='eager'
quality={100}
/>
</Link>
</div>
)}
</nav>
)
}

View File

@@ -1,11 +1,29 @@
'use client'
import { Skeleton } from '@/components/ui/skeleton'
export function ChatLoadingState() {
return (
<div className='flex min-h-screen items-center justify-center bg-background text-foreground'>
<div className='animate-pulse text-center'>
<div className='mx-auto mb-4 h-8 w-48 rounded bg-muted' />
<div className='mx-auto h-4 w-64 rounded bg-muted' />
<div className='bg-white'>
<div className='flex min-h-[calc(100vh-120px)] items-center justify-center px-4'>
<div className='w-full max-w-[410px]'>
<div className='flex flex-col items-center justify-center'>
{/* Title skeleton */}
<div className='space-y-2 text-center'>
<Skeleton className='mx-auto h-8 w-32' />
<Skeleton className='mx-auto h-4 w-48' />
</div>
{/* Form skeleton */}
<div className='mt-8 w-full space-y-8'>
<div className='space-y-2'>
<Skeleton className='h-4 w-16' />
<Skeleton className='h-10 w-full rounded-[10px]' />
</div>
<Skeleton className='h-10 w-full rounded-[10px]' />
</div>
</div>
</div>
</div>
</div>
)

View File

@@ -120,11 +120,10 @@
--gradient-secondary: 336 95% 65%; /* More vibrant pink */
/* Brand Colors (Default Sim Theme) */
--brand-primary-hex: #8357ff; /* Primary brand purple - matches Get Started gradient start */
--brand-primary-hover-hex: #9266ff; /* Primary brand purple hover - matches Get Started hover */
--brand-secondary-hex: #6f3dfa; /* Secondary brand purple - matches Get Started gradient end */
--brand-primary-hex: #6f3dfa; /* Primary brand purple - matches Get Started gradient start */
--brand-primary-hover-hex: #6338d9; /* Primary brand purple hover - matches Get Started hover */
--brand-accent-hex: #6f3dfa; /* Accent purple for links - matches sign in button */
--brand-accent-hover-hex: #8357ff; /* Accent purple hover - matches sign in gradient start */
--brand-accent-hover-hex: #6f3dfa; /* Accent purple hover - matches sign in gradient start */
--brand-background-hex: #ffffff; /* Primary light background */
/* UI Surface Colors */
@@ -193,7 +192,6 @@
/* Brand Colors (Keep dark background for actual dark mode) */
--brand-primary-hex: #701ffc; /* Primary brand purple */
--brand-primary-hover-hex: #802fff; /* Primary brand purple hover */
--brand-secondary-hex: #6518e6; /* Secondary brand purple */
--brand-accent-hex: #9d54ff; /* Accent purple for links */
--brand-accent-hover-hex: #a66fff; /* Accent purple hover */
--brand-background-hex: #0c0c0c; /* Primary dark background */
@@ -399,10 +397,6 @@ input[type="search"]::-ms-clear {
background-color: var(--brand-primary-hover-hex);
}
.hover\:bg-brand-secondary:hover {
background-color: var(--brand-secondary-hex);
}
.hover\:text-brand-accent-hover:hover {
color: var(--brand-accent-hover-hex);
}

View File

@@ -192,7 +192,7 @@ export function CreateChunkModal({
<Button
onClick={handleCreateChunk}
disabled={!isFormValid || isCreating}
className='bg-[var(--brand-primary-hex)] font-[480] text-primary-foreground shadow-[0_0_0_0_var(--brand-primary-hex)] transition-all duration-200 hover:bg-[var(--brand-secondary-hex)] hover:shadow-[0_0_0_4px_rgba(127,47,255,0.15)]'
className='bg-[var(--brand-primary-hex)] font-[480] text-primary-foreground shadow-[0_0_0_0_var(--brand-primary-hex)] transition-all duration-200 hover:bg-[var(--brand-primary-hover-hex)] hover:shadow-[0_0_0_4px_rgba(127,47,255,0.15)]'
>
{isCreating ? (
<>

View File

@@ -685,7 +685,7 @@ export function Document({
onClick={() => setIsCreateChunkModalOpen(true)}
disabled={documentData?.processingStatus === 'failed' || !userPermissions.canEdit}
size='sm'
className='flex items-center gap-1 bg-[var(--brand-primary-hex)] font-[480] text-white shadow-[0_0_0_0_var(--brand-primary-hex)] transition-all duration-200 hover:bg-[var(--brand-secondary-hex)] hover:shadow-[0_0_0_4px_rgba(127,47,255,0.15)] disabled:cursor-not-allowed disabled:opacity-50'
className='flex items-center gap-1 bg-[var(--brand-primary-hex)] font-[480] text-white shadow-[0_0_0_0_var(--brand-primary-hex)] transition-all duration-200 hover:bg-[var(--brand-primary-hover-hex)] hover:shadow-[0_0_0_4px_rgba(127,47,255,0.15)] disabled:cursor-not-allowed disabled:opacity-50'
>
<Plus className='h-3.5 w-3.5' />
<span>Create Chunk</span>

View File

@@ -65,7 +65,7 @@ export const AgentBlock: BlockConfig<AgentResponse> = {
'The Agent block is a core workflow block that is a wrapper around an LLM. It takes in system/user prompts and calls an LLM provider. It can also make tool calls by directly containing tools inside of its tool input. It can additionally return structured output.',
docsLink: 'https://docs.sim.ai/blocks/agent',
category: 'blocks',
bgColor: 'var(--brand-primary-hover-hex)',
bgColor: 'var(--brand-primary-hex)',
icon: AgentIcon,
subBlocks: [
{

View File

@@ -29,7 +29,7 @@ export function LoadingAgent({ size = 'md' }: LoadingAgentProps) {
>
<path
d='M15.6667 9.25H4.66667C2.64162 9.25 1 10.8916 1 12.9167V18.4167C1 20.4417 2.64162 22.0833 4.66667 22.0833H15.6667C17.6917 22.0833 19.3333 20.4417 19.3333 18.4167V12.9167C19.3333 10.8916 17.6917 9.25 15.6667 9.25Z'
stroke='var(--brand-primary-hover-hex)'
stroke='var(--brand-primary-hex)'
strokeWidth='1.8'
strokeLinecap='round'
strokeLinejoin='round'
@@ -41,7 +41,7 @@ export function LoadingAgent({ size = 'md' }: LoadingAgentProps) {
/>
<path
d='M10.1663 5.58464C11.1789 5.58464 11.9997 4.76382 11.9997 3.7513C11.9997 2.73878 11.1789 1.91797 10.1663 1.91797C9.15382 1.91797 8.33301 2.73878 8.33301 3.7513C8.33301 4.76382 9.15382 5.58464 10.1663 5.58464Z'
stroke='var(--brand-primary-hover-hex)'
stroke='var(--brand-primary-hex)'
strokeWidth='1.8'
strokeLinecap='round'
strokeLinejoin='round'
@@ -54,7 +54,7 @@ export function LoadingAgent({ size = 'md' }: LoadingAgentProps) {
/>
<path
d='M10.167 5.58594V9.2526M7.41699 16.5859V14.7526M12.917 14.7526V16.5859'
stroke='var(--brand-primary-hover-hex)'
stroke='var(--brand-primary-hex)'
strokeWidth='1.8'
strokeLinecap='round'
strokeLinejoin='round'

View File

@@ -3,7 +3,6 @@ import { getEnv } from '@/lib/env'
export interface ThemeColors {
primaryColor?: string
primaryHoverColor?: string
secondaryColor?: string
accentColor?: string
accentHoverColor?: string
backgroundColor?: string
@@ -36,7 +35,6 @@ const defaultConfig: BrandConfig = {
theme: {
primaryColor: '#701ffc',
primaryHoverColor: '#802fff',
secondaryColor: '#6518e6',
accentColor: '#9d54ff',
accentHoverColor: '#a66fff',
backgroundColor: '#0c0c0c',
@@ -48,8 +46,6 @@ const getThemeColors = (): ThemeColors => {
primaryColor: getEnv('NEXT_PUBLIC_BRAND_PRIMARY_COLOR') || defaultConfig.theme?.primaryColor,
primaryHoverColor:
getEnv('NEXT_PUBLIC_BRAND_PRIMARY_HOVER_COLOR') || defaultConfig.theme?.primaryHoverColor,
secondaryColor:
getEnv('NEXT_PUBLIC_BRAND_SECONDARY_COLOR') || defaultConfig.theme?.secondaryColor,
accentColor: getEnv('NEXT_PUBLIC_BRAND_ACCENT_COLOR') || defaultConfig.theme?.accentColor,
accentHoverColor:
getEnv('NEXT_PUBLIC_BRAND_ACCENT_HOVER_COLOR') || defaultConfig.theme?.accentHoverColor,

View File

@@ -19,10 +19,6 @@ export function generateThemeCSS(): string {
cssVars.push(`--brand-primary-hover-hex: ${process.env.NEXT_PUBLIC_BRAND_PRIMARY_HOVER_COLOR};`)
}
if (process.env.NEXT_PUBLIC_BRAND_SECONDARY_COLOR) {
cssVars.push(`--brand-secondary-hex: ${process.env.NEXT_PUBLIC_BRAND_SECONDARY_COLOR};`)
}
if (process.env.NEXT_PUBLIC_BRAND_ACCENT_COLOR) {
cssVars.push(`--brand-accent-hex: ${process.env.NEXT_PUBLIC_BRAND_ACCENT_COLOR};`)
}

View File

@@ -10,7 +10,7 @@ export function generateBrandedMetadata(override: Partial<Metadata> = {}): Metad
const brand = getBrandConfig()
const defaultTitle = brand.name
const summaryFull = `Sim is an open-source AI agent workflow builder. Developers at trail-blazing startups to Fortune 500 companies deploy agentic workflows on the Sim platform. 30,000+ developers are already using Sim to build and deploy AI agent workflows. Sim lets developers integrate with 100+ apps to streamline workflows with AI agents. Sim is SOC2 and HIPAA compliant, ensuring enterprise-level security.`
const summaryFull = `Sim is an open-source AI agent workflow builder. Developers at trail-blazing startups to Fortune 500 companies deploy agentic workflows on the Sim platform. 35,000+ developers are already using Sim to build and deploy AI agent workflows. Sim lets developers integrate with 100+ apps to streamline workflows with AI agents. Sim is SOC2 and HIPAA compliant, ensuring enterprise-level security.`
const summaryShort = `Sim is an open-source AI agent workflow builder.`
return {

View File

@@ -240,7 +240,6 @@ export const env = createEnv({
// Theme Customization
NEXT_PUBLIC_BRAND_PRIMARY_COLOR: z.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(), // Primary brand color (hex format, e.g., "#701ffc")
NEXT_PUBLIC_BRAND_PRIMARY_HOVER_COLOR: z.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(), // Primary brand hover state (hex format)
NEXT_PUBLIC_BRAND_SECONDARY_COLOR: z.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(), // Secondary brand color (hex format)
NEXT_PUBLIC_BRAND_ACCENT_COLOR: z.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(), // Accent brand color (hex format)
NEXT_PUBLIC_BRAND_ACCENT_HOVER_COLOR: z.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(), // Accent brand hover state (hex format)
NEXT_PUBLIC_BRAND_BACKGROUND_COLOR: z.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(), // Brand background color (hex format)
@@ -275,7 +274,6 @@ export const env = createEnv({
NEXT_PUBLIC_PRIVACY_URL: process.env.NEXT_PUBLIC_PRIVACY_URL,
NEXT_PUBLIC_BRAND_PRIMARY_COLOR: process.env.NEXT_PUBLIC_BRAND_PRIMARY_COLOR,
NEXT_PUBLIC_BRAND_PRIMARY_HOVER_COLOR: process.env.NEXT_PUBLIC_BRAND_PRIMARY_HOVER_COLOR,
NEXT_PUBLIC_BRAND_SECONDARY_COLOR: process.env.NEXT_PUBLIC_BRAND_SECONDARY_COLOR,
NEXT_PUBLIC_BRAND_ACCENT_COLOR: process.env.NEXT_PUBLIC_BRAND_ACCENT_COLOR,
NEXT_PUBLIC_BRAND_ACCENT_HOVER_COLOR: process.env.NEXT_PUBLIC_BRAND_ACCENT_HOVER_COLOR,
NEXT_PUBLIC_BRAND_BACKGROUND_COLOR: process.env.NEXT_PUBLIC_BRAND_BACKGROUND_COLOR,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 897 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,19 +0,0 @@
{
"name": "Sim",
"short_name": "Sim",
"icons": [
{
"src": "/favicon/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/favicon/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}