mirror of
https://github.com/simstudioai/sim.git
synced 2026-03-15 03:00:33 -04:00
Compare commits
2 Commits
improvemen
...
cursor/her
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f56602c98 | ||
|
|
dc98812bd7 |
@@ -274,7 +274,7 @@ export default function Collaboration() {
|
||||
|
||||
<Link
|
||||
href='/signup'
|
||||
className='group/cta mt-[12px] inline-flex h-[32px] cursor-none items-center gap-[6px] rounded-[5px] border border-[#33C482] bg-[#33C482] px-[10px] font-[430] font-season text-[14px] text-black transition-[filter] hover:brightness-110'
|
||||
className='group/cta mt-[12px] inline-flex h-[32px] cursor-none items-center gap-[6px] rounded-[5px] border border-[#FFFFFF] bg-[#FFFFFF] px-[10px] font-[430] font-season text-[14px] text-black transition-colors hover:border-[#E0E0E0] hover:bg-[#E0E0E0]'
|
||||
>
|
||||
Build together
|
||||
<span className='relative h-[10px] w-[10px] shrink-0'>
|
||||
|
||||
@@ -79,7 +79,7 @@ export default function Hero() {
|
||||
</Link>
|
||||
<Link
|
||||
href='/signup'
|
||||
className={`${CTA_BASE} gap-[8px] border-[#33C482] bg-[#33C482] text-black transition-[filter] hover:brightness-110`}
|
||||
className={`${CTA_BASE} gap-[8px] border-[#FFFFFF] bg-[#FFFFFF] text-black transition-colors hover:border-[#E0E0E0] hover:bg-[#E0E0E0]`}
|
||||
aria-label='Get started with Sim'
|
||||
>
|
||||
Get started
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
'use client'
|
||||
|
||||
import { memo, useCallback, useRef, useState } from 'react'
|
||||
import { ArrowUp } from 'lucide-react'
|
||||
import { useLandingSubmit } from '@/app/(home)/components/landing-preview/components/landing-preview-panel/landing-preview-panel'
|
||||
import { useAnimatedPlaceholder } from '@/app/workspace/[workspaceId]/home/hooks/use-animated-placeholder'
|
||||
|
||||
const C = {
|
||||
SURFACE: '#292929',
|
||||
BORDER: '#3d3d3d',
|
||||
TEXT_PRIMARY: '#e6e6e6',
|
||||
} as const
|
||||
|
||||
/**
|
||||
* Landing preview replica of the workspace Home initial view.
|
||||
* Shows a greeting heading and a minimal chat input (no + or mic).
|
||||
* On submit, stores the prompt and redirects to /signup.
|
||||
*/
|
||||
export const LandingPreviewHome = memo(function LandingPreviewHome() {
|
||||
const landingSubmit = useLandingSubmit()
|
||||
const [inputValue, setInputValue] = useState('')
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
||||
const animatedPlaceholder = useAnimatedPlaceholder()
|
||||
|
||||
const isEmpty = inputValue.trim().length === 0
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
if (isEmpty) return
|
||||
landingSubmit(inputValue)
|
||||
}, [isEmpty, inputValue, landingSubmit])
|
||||
|
||||
const MAX_HEIGHT = 200
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault()
|
||||
handleSubmit()
|
||||
}
|
||||
},
|
||||
[handleSubmit]
|
||||
)
|
||||
|
||||
const handleInput = useCallback((e: React.FormEvent<HTMLTextAreaElement>) => {
|
||||
const target = e.target as HTMLTextAreaElement
|
||||
target.style.height = 'auto'
|
||||
target.style.height = `${Math.min(target.scrollHeight, MAX_HEIGHT)}px`
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className='flex min-w-0 flex-1 flex-col items-center justify-center px-[24px] pb-[2vh]'>
|
||||
<h1
|
||||
className='mb-[24px] max-w-[42rem] font-[430] font-season text-[32px] tracking-[-0.02em]'
|
||||
style={{ color: C.TEXT_PRIMARY }}
|
||||
>
|
||||
What should we get done?
|
||||
</h1>
|
||||
|
||||
<div className='w-full max-w-[32rem]'>
|
||||
<div
|
||||
className='cursor-text rounded-[20px] border px-[10px] py-[8px]'
|
||||
style={{ borderColor: C.BORDER, backgroundColor: C.SURFACE }}
|
||||
onClick={() => textareaRef.current?.focus()}
|
||||
>
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
onInput={handleInput}
|
||||
placeholder={animatedPlaceholder}
|
||||
rows={1}
|
||||
className='m-0 box-border min-h-[24px] w-full resize-none overflow-y-auto border-0 bg-transparent px-[4px] py-[4px] font-body text-[15px] leading-[24px] tracking-[-0.015em] outline-none placeholder:font-[380] placeholder:text-[#787878] focus-visible:ring-0'
|
||||
style={{
|
||||
color: C.TEXT_PRIMARY,
|
||||
caretColor: C.TEXT_PRIMARY,
|
||||
maxHeight: `${MAX_HEIGHT}px`,
|
||||
}}
|
||||
/>
|
||||
<div className='flex items-center justify-end'>
|
||||
<button
|
||||
type='button'
|
||||
onClick={handleSubmit}
|
||||
disabled={isEmpty}
|
||||
className='flex h-[28px] w-[28px] items-center justify-center rounded-full border-0 p-0 transition-colors'
|
||||
style={{
|
||||
background: isEmpty ? '#808080' : '#e0e0e0',
|
||||
cursor: isEmpty ? 'not-allowed' : 'pointer',
|
||||
}}
|
||||
>
|
||||
<ArrowUp size={16} strokeWidth={2.25} color='#1b1b1b' />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
@@ -8,6 +8,23 @@ import { createPortal } from 'react-dom'
|
||||
import { BubbleChatPreview, ChevronDown, MoreHorizontal, Play } from '@/components/emcn'
|
||||
import { LandingPromptStorage } from '@/lib/core/utils/browser-storage'
|
||||
|
||||
/**
|
||||
* Stores the prompt in browser storage and redirects to /signup.
|
||||
* Shared by both the copilot panel and the landing home view.
|
||||
*/
|
||||
export function useLandingSubmit() {
|
||||
const router = useRouter()
|
||||
return useCallback(
|
||||
(text: string) => {
|
||||
const trimmed = text.trim()
|
||||
if (!trimmed) return
|
||||
LandingPromptStorage.store(trimmed)
|
||||
router.push('/signup')
|
||||
},
|
||||
[router]
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Lightweight static panel replicating the real workspace panel styling.
|
||||
* The copilot tab is active with a functional user input.
|
||||
@@ -18,7 +35,7 @@ import { LandingPromptStorage } from '@/lib/core/utils/browser-storage'
|
||||
* inside Content > Copilot > header-bar(mx-[-1px]) > UserInput(p-8)
|
||||
*/
|
||||
export const LandingPreviewPanel = memo(function LandingPreviewPanel() {
|
||||
const router = useRouter()
|
||||
const landingSubmit = useLandingSubmit()
|
||||
const [inputValue, setInputValue] = useState('')
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
||||
const [cursorPos, setCursorPos] = useState<{ x: number; y: number } | null>(null)
|
||||
@@ -27,9 +44,8 @@ export const LandingPreviewPanel = memo(function LandingPreviewPanel() {
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
if (isEmpty) return
|
||||
LandingPromptStorage.store(inputValue)
|
||||
router.push('/signup')
|
||||
}, [isEmpty, inputValue, router])
|
||||
landingSubmit(inputValue)
|
||||
}, [isEmpty, inputValue, landingSubmit])
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
@@ -60,10 +76,10 @@ export const LandingPreviewPanel = memo(function LandingPreviewPanel() {
|
||||
onMouseMove={(e) => setCursorPos({ x: e.clientX, y: e.clientY })}
|
||||
onMouseLeave={() => setCursorPos(null)}
|
||||
>
|
||||
<div className='flex h-[30px] items-center rounded-[5px] bg-[#32bd7e] px-[10px] transition-[filter] hover:brightness-110'>
|
||||
<div className='flex h-[30px] items-center rounded-[5px] bg-[#33C482] px-[10px] transition-colors hover:bg-[#2DAC72]'>
|
||||
<span className='font-medium text-[#1b1b1b] text-[12px]'>Deploy</span>
|
||||
</div>
|
||||
<div className='flex h-[30px] items-center gap-[8px] rounded-[5px] bg-[#32bd7e] px-[10px] transition-[filter] hover:brightness-110'>
|
||||
<div className='flex h-[30px] items-center gap-[8px] rounded-[5px] bg-[#33C482] px-[10px] transition-colors hover:bg-[#2DAC72]'>
|
||||
<Play className='h-[11.5px] w-[11.5px] text-[#1b1b1b]' />
|
||||
<span className='font-medium text-[#1b1b1b] text-[12px]'>Run</span>
|
||||
</div>
|
||||
|
||||
@@ -1,141 +1,204 @@
|
||||
'use client'
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { Database, Layout, Search, Settings } from 'lucide-react'
|
||||
import { ChevronDown, Library } from '@/components/emcn'
|
||||
import { ChevronDown, Home, Library } from '@/components/emcn'
|
||||
import {
|
||||
Calendar,
|
||||
Database,
|
||||
File,
|
||||
HelpCircle,
|
||||
Search,
|
||||
Settings,
|
||||
Table,
|
||||
} from '@/components/emcn/icons'
|
||||
import type { PreviewWorkflow } from '@/app/(home)/components/landing-preview/components/landing-preview-workflow/workflow-data'
|
||||
|
||||
/**
|
||||
* Props for the LandingPreviewSidebar component
|
||||
*/
|
||||
interface LandingPreviewSidebarProps {
|
||||
workflows: PreviewWorkflow[]
|
||||
activeWorkflowId: string
|
||||
activeView: 'home' | 'workflow'
|
||||
onSelectWorkflow: (id: string) => void
|
||||
onSelectHome: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Static footer navigation items matching the real sidebar
|
||||
* Hardcoded dark-theme equivalents of the real sidebar CSS variables.
|
||||
* The preview lives inside a `dark` wrapper but CSS variable cascade
|
||||
* isn't guaranteed, so we pin the hex values directly.
|
||||
*/
|
||||
const FOOTER_NAV_ITEMS = [
|
||||
{ id: 'logs', label: 'Logs', icon: Library },
|
||||
{ id: 'templates', label: 'Templates', icon: Layout },
|
||||
const C = {
|
||||
SURFACE_1: '#1e1e1e',
|
||||
SURFACE_2: '#252525',
|
||||
SURFACE_ACTIVE: '#363636',
|
||||
BORDER: '#2c2c2c',
|
||||
TEXT_PRIMARY: '#e6e6e6',
|
||||
TEXT_BODY: '#cdcdcd',
|
||||
TEXT_ICON: '#939393',
|
||||
BRAND: '#33C482',
|
||||
} as const
|
||||
|
||||
const WORKSPACE_NAV = [
|
||||
{ id: 'tables', label: 'Tables', icon: Table },
|
||||
{ id: 'files', label: 'Files', icon: File },
|
||||
{ id: 'knowledge-base', label: 'Knowledge Base', icon: Database },
|
||||
{ id: 'scheduled-tasks', label: 'Scheduled Tasks', icon: Calendar },
|
||||
{ id: 'logs', label: 'Logs', icon: Library },
|
||||
] as const
|
||||
|
||||
const FOOTER_NAV = [
|
||||
{ id: 'help', label: 'Help', icon: HelpCircle },
|
||||
{ id: 'settings', label: 'Settings', icon: Settings },
|
||||
] as const
|
||||
|
||||
function StaticNavItem({
|
||||
icon: Icon,
|
||||
label,
|
||||
}: {
|
||||
icon: React.ComponentType<{ className?: string; style?: React.CSSProperties }>
|
||||
label: string
|
||||
}) {
|
||||
return (
|
||||
<div className='pointer-events-none mx-[2px] flex h-[28px] items-center gap-[8px] rounded-[8px] px-[8px]'>
|
||||
<Icon className='h-[14px] w-[14px] flex-shrink-0' style={{ color: C.TEXT_ICON }} />
|
||||
<span className='truncate text-[13px]' style={{ color: C.TEXT_BODY, fontWeight: 450 }}>
|
||||
{label}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Lightweight static sidebar replicating the real workspace sidebar styling.
|
||||
* Lightweight sidebar replicating the real workspace sidebar layout and sizing.
|
||||
* Starts from the workspace header (no logo/collapse row).
|
||||
* Only workflow items are interactive — everything else is pointer-events-none.
|
||||
*
|
||||
* Colors sourced from the dark theme CSS variables:
|
||||
* --surface-1: #1e1e1e, --surface-5: #363636, --border: #2c2c2c, --border-1: #3d3d3d
|
||||
* --text-primary: #e6e6e6, --text-tertiary: #b3b3b3, --text-muted: #787878
|
||||
*/
|
||||
export function LandingPreviewSidebar({
|
||||
workflows,
|
||||
activeWorkflowId,
|
||||
activeView,
|
||||
onSelectWorkflow,
|
||||
onSelectHome,
|
||||
}: LandingPreviewSidebarProps) {
|
||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false)
|
||||
const dropdownRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const handleToggle = useCallback(() => {
|
||||
setIsDropdownOpen((prev) => !prev)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDropdownOpen) return
|
||||
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
|
||||
setIsDropdownOpen(false)
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside)
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside)
|
||||
}, [isDropdownOpen])
|
||||
const isHomeActive = activeView === 'home'
|
||||
|
||||
return (
|
||||
<div className='flex h-full w-[220px] flex-shrink-0 flex-col border-[#2c2c2c] border-r bg-[#1e1e1e]'>
|
||||
{/* Header */}
|
||||
<div className='relative flex-shrink-0 px-[14px] pt-[12px]' ref={dropdownRef}>
|
||||
<div className='flex items-center justify-between'>
|
||||
<button
|
||||
type='button'
|
||||
onClick={handleToggle}
|
||||
className='group -mx-[6px] flex cursor-pointer items-center gap-[8px] rounded-[6px] bg-transparent px-[6px] py-[4px] transition-colors hover:bg-[#363636]'
|
||||
<div
|
||||
className='flex h-full w-[248px] flex-shrink-0 flex-col pt-[12px] tracking-[0.02em]'
|
||||
style={{ backgroundColor: C.SURFACE_1 }}
|
||||
>
|
||||
{/* Workspace Header */}
|
||||
<div className='flex-shrink-0 px-[10px]'>
|
||||
<div
|
||||
className='pointer-events-none flex h-[32px] w-full items-center gap-[8px] rounded-[8px] border pr-[8px] pl-[5px]'
|
||||
style={{ borderColor: C.BORDER, backgroundColor: C.SURFACE_2 }}
|
||||
>
|
||||
<div className='flex h-[20px] w-[20px] flex-shrink-0 items-center justify-center rounded-[4px] bg-white'>
|
||||
<svg width='10' height='10' viewBox='0 0 10 10' fill='none'>
|
||||
<path
|
||||
d='M1 9C1 4.58 4.58 1 9 1'
|
||||
stroke='#1e1e1e'
|
||||
strokeWidth='1.8'
|
||||
strokeLinecap='round'
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<span
|
||||
className='min-w-0 flex-1 truncate text-left font-medium text-[13px]'
|
||||
style={{ color: C.TEXT_PRIMARY }}
|
||||
>
|
||||
<span className='truncate font-base text-[#e6e6e6] text-[14px]'>My Workspace</span>
|
||||
<ChevronDown
|
||||
className={`h-[8px] w-[10px] flex-shrink-0 text-[#787878] transition-all duration-100 group-hover:text-[#cccccc] ${isDropdownOpen ? 'rotate-180' : ''}`}
|
||||
/>
|
||||
</button>
|
||||
<div className='pointer-events-none flex flex-shrink-0 items-center'>
|
||||
<Search className='h-[14px] w-[14px] text-[#787878]' />
|
||||
Superark
|
||||
</span>
|
||||
<ChevronDown className='h-[8px] w-[10px] flex-shrink-0' style={{ color: C.TEXT_ICON }} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Top Navigation: Home (interactive), Search (static) */}
|
||||
<div className='mt-[10px] flex flex-shrink-0 flex-col gap-[2px] px-[8px]'>
|
||||
<button
|
||||
type='button'
|
||||
onClick={onSelectHome}
|
||||
className='mx-[2px] flex h-[28px] items-center gap-[8px] rounded-[8px] px-[8px] transition-colors'
|
||||
style={{ backgroundColor: isHomeActive ? C.SURFACE_ACTIVE : 'transparent' }}
|
||||
onMouseEnter={(e) => {
|
||||
if (!isHomeActive) e.currentTarget.style.backgroundColor = C.SURFACE_ACTIVE
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (!isHomeActive) e.currentTarget.style.backgroundColor = 'transparent'
|
||||
}}
|
||||
>
|
||||
<Home className='h-[14px] w-[14px] flex-shrink-0' style={{ color: C.TEXT_ICON }} />
|
||||
<span className='truncate text-[13px]' style={{ color: C.TEXT_BODY, fontWeight: 450 }}>
|
||||
Home
|
||||
</span>
|
||||
</button>
|
||||
<StaticNavItem icon={Search} label='Search' />
|
||||
</div>
|
||||
|
||||
{/* Workspace */}
|
||||
<div className='mt-[14px] flex flex-shrink-0 flex-col'>
|
||||
<div className='px-[16px] pb-[6px]'>
|
||||
<div className='font-base text-[12px]' style={{ color: C.TEXT_ICON }}>
|
||||
Workspace
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-col gap-[2px] px-[8px]'>
|
||||
{WORKSPACE_NAV.map((item) => (
|
||||
<StaticNavItem key={item.id} icon={item.icon} label={item.label} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Workspace switcher dropdown */}
|
||||
{isDropdownOpen && (
|
||||
<div className='absolute top-[42px] left-[8px] z-50 min-w-[160px] max-w-[160px] rounded-[6px] bg-[#242424] px-[6px] py-[6px] shadow-lg'>
|
||||
<div
|
||||
className='flex h-[26px] cursor-pointer items-center gap-[8px] rounded-[6px] bg-[#3d3d3d] px-[6px] font-base text-[#e6e6e6] text-[13px]'
|
||||
role='menuitem'
|
||||
onClick={() => setIsDropdownOpen(false)}
|
||||
>
|
||||
<span className='min-w-0 flex-1 truncate'>My Workspace</span>
|
||||
{/* Scrollable Tasks + Workflows */}
|
||||
<div className='flex flex-1 flex-col overflow-y-auto overflow-x-hidden pt-[14px]'>
|
||||
{/* Workflows */}
|
||||
<div className='flex flex-col'>
|
||||
<div className='px-[16px]'>
|
||||
<div className='font-base text-[12px]' style={{ color: C.TEXT_ICON }}>
|
||||
Workflows
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Workflow items */}
|
||||
<div className='mt-[8px] space-y-[2px] overflow-x-hidden px-[8px]'>
|
||||
{workflows.map((workflow) => {
|
||||
const isActive = workflow.id === activeWorkflowId
|
||||
return (
|
||||
<button
|
||||
key={workflow.id}
|
||||
type='button'
|
||||
onClick={() => onSelectWorkflow(workflow.id)}
|
||||
className={`group flex h-[26px] w-full items-center gap-[8px] rounded-[8px] px-[6px] text-[14px] transition-colors ${
|
||||
isActive ? 'bg-[#363636]' : 'bg-transparent hover:bg-[#363636]'
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className='h-[14px] w-[14px] flex-shrink-0 rounded-[4px]'
|
||||
style={{ backgroundColor: workflow.color }}
|
||||
/>
|
||||
<div className='min-w-0 flex-1'>
|
||||
<div
|
||||
className={`min-w-0 truncate text-left font-medium ${
|
||||
isActive ? 'text-[#e6e6e6]' : 'text-[#b3b3b3] group-hover:text-[#e6e6e6]'
|
||||
}`}
|
||||
<div className='mt-[6px] flex flex-col gap-[2px] px-[8px]'>
|
||||
{workflows.map((workflow) => {
|
||||
const isActive = activeView === 'workflow' && workflow.id === activeWorkflowId
|
||||
return (
|
||||
<button
|
||||
key={workflow.id}
|
||||
type='button'
|
||||
onClick={() => onSelectWorkflow(workflow.id)}
|
||||
className='group mx-[2px] flex h-[28px] w-full items-center gap-[8px] rounded-[8px] px-[8px] transition-colors'
|
||||
style={{ backgroundColor: isActive ? C.SURFACE_ACTIVE : 'transparent' }}
|
||||
onMouseEnter={(e) => {
|
||||
if (!isActive) e.currentTarget.style.backgroundColor = C.SURFACE_ACTIVE
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (!isActive) e.currentTarget.style.backgroundColor = 'transparent'
|
||||
}}
|
||||
>
|
||||
{workflow.name}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
<div
|
||||
className='h-[14px] w-[14px] flex-shrink-0 rounded-[4px] border-[2.5px]'
|
||||
style={{
|
||||
backgroundColor: workflow.color,
|
||||
borderColor: `${workflow.color}60`,
|
||||
backgroundClip: 'padding-box',
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className='min-w-0 flex-1 truncate text-left text-[13px]'
|
||||
style={{ color: C.TEXT_BODY, fontWeight: 450 }}
|
||||
>
|
||||
{workflow.name}
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer navigation — static */}
|
||||
<div className='pointer-events-none mt-auto flex flex-shrink-0 flex-col gap-[2px] border-[#2c2c2c] border-t px-[7.75px] pt-[8px] pb-[8px]'>
|
||||
{FOOTER_NAV_ITEMS.map((item) => {
|
||||
const Icon = item.icon
|
||||
return (
|
||||
<div
|
||||
key={item.id}
|
||||
className='flex h-[26px] items-center gap-[8px] rounded-[8px] px-[6px] text-[14px]'
|
||||
>
|
||||
<Icon className='h-[14px] w-[14px] flex-shrink-0 text-[#b3b3b3]' />
|
||||
<span className='truncate font-medium text-[#b3b3b3] text-[13px]'>{item.label}</span>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
{/* Footer */}
|
||||
<div className='flex flex-shrink-0 flex-col gap-[2px] px-[8px] pt-[9px] pb-[8px]'>
|
||||
{FOOTER_NAV.map((item) => (
|
||||
<StaticNavItem key={item.id} icon={item.icon} label={item.label} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ import { memo } from 'react'
|
||||
import { motion } from 'framer-motion'
|
||||
import { Database } from 'lucide-react'
|
||||
import { Handle, type NodeProps, Position } from 'reactflow'
|
||||
import { Blimp } from '@/components/emcn'
|
||||
import {
|
||||
AgentIcon,
|
||||
AnthropicIcon,
|
||||
@@ -63,6 +64,7 @@ const BLOCK_ICONS: Record<string, React.ComponentType<{ className?: string }>> =
|
||||
reducto: ReductoIcon,
|
||||
textract: TextractIcon,
|
||||
linkedin: LinkedInIcon,
|
||||
mothership: Blimp,
|
||||
}
|
||||
|
||||
/** Model prefix → provider icon for the "Model" row in agent blocks. */
|
||||
|
||||
@@ -91,11 +91,11 @@ const IT_SERVICE_WORKFLOW: PreviewWorkflow = {
|
||||
}
|
||||
|
||||
/**
|
||||
* Content pipeline workflow — Schedule -> Agent (X + YouTube tools)
|
||||
* Self-healing CRM workflow — Schedule -> Mothership
|
||||
*/
|
||||
const CONTENT_PIPELINE_WORKFLOW: PreviewWorkflow = {
|
||||
id: 'wf-content-pipeline',
|
||||
name: 'Content Pipeline',
|
||||
const SELF_HEALING_CRM_WORKFLOW: PreviewWorkflow = {
|
||||
id: 'wf-self-healing-crm',
|
||||
name: 'Self-healing CRM',
|
||||
color: '#33C482',
|
||||
blocks: [
|
||||
{
|
||||
@@ -111,23 +111,16 @@ const CONTENT_PIPELINE_WORKFLOW: PreviewWorkflow = {
|
||||
hideTargetHandle: true,
|
||||
},
|
||||
{
|
||||
id: 'agent-2',
|
||||
name: 'Agent',
|
||||
type: 'agent',
|
||||
bgColor: '#701ffc',
|
||||
rows: [
|
||||
{ title: 'Model', value: 'grok-4' },
|
||||
{ title: 'System Prompt', value: 'Repurpose trending...' },
|
||||
],
|
||||
tools: [
|
||||
{ name: 'X', type: 'x', bgColor: '#000000' },
|
||||
{ name: 'YouTube', type: 'youtube', bgColor: '#FF0000' },
|
||||
],
|
||||
id: 'mothership-1',
|
||||
name: 'Update Agent',
|
||||
type: 'mothership',
|
||||
bgColor: '#33C482',
|
||||
rows: [{ title: 'Prompt', value: 'Audit CRM records, fix...' }],
|
||||
position: { x: 420, y: 180 },
|
||||
hideSourceHandle: true,
|
||||
},
|
||||
],
|
||||
edges: [{ id: 'e-3', source: 'schedule-1', target: 'agent-2' }],
|
||||
edges: [{ id: 'e-3', source: 'schedule-1', target: 'mothership-1' }],
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,7 +147,7 @@ const NEW_AGENT_WORKFLOW: PreviewWorkflow = {
|
||||
}
|
||||
|
||||
export const PREVIEW_WORKFLOWS: PreviewWorkflow[] = [
|
||||
CONTENT_PIPELINE_WORKFLOW,
|
||||
SELF_HEALING_CRM_WORKFLOW,
|
||||
IT_SERVICE_WORKFLOW,
|
||||
NEW_AGENT_WORKFLOW,
|
||||
]
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { motion, type Variants } from 'framer-motion'
|
||||
import { LandingPreviewHome } from '@/app/(home)/components/landing-preview/components/landing-preview-home/landing-preview-home'
|
||||
import { LandingPreviewPanel } from '@/app/(home)/components/landing-preview/components/landing-preview-panel/landing-preview-panel'
|
||||
import { LandingPreviewSidebar } from '@/app/(home)/components/landing-preview/components/landing-preview-sidebar/landing-preview-sidebar'
|
||||
import { LandingPreviewWorkflow } from '@/app/(home)/components/landing-preview/components/landing-preview-workflow/landing-preview-workflow'
|
||||
@@ -56,6 +57,7 @@ const panelVariants: Variants = {
|
||||
* load — workflow switches render instantly.
|
||||
*/
|
||||
export function LandingPreview() {
|
||||
const [activeView, setActiveView] = useState<'home' | 'workflow'>('workflow')
|
||||
const [activeWorkflowId, setActiveWorkflowId] = useState(PREVIEW_WORKFLOWS[0].id)
|
||||
const isInitialMount = useRef(true)
|
||||
|
||||
@@ -63,12 +65,23 @@ export function LandingPreview() {
|
||||
isInitialMount.current = false
|
||||
}, [])
|
||||
|
||||
const handleSelectWorkflow = useCallback((id: string) => {
|
||||
setActiveWorkflowId(id)
|
||||
setActiveView('workflow')
|
||||
}, [])
|
||||
|
||||
const handleSelectHome = useCallback(() => {
|
||||
setActiveView('home')
|
||||
}, [])
|
||||
|
||||
const activeWorkflow =
|
||||
PREVIEW_WORKFLOWS.find((w) => w.id === activeWorkflowId) ?? PREVIEW_WORKFLOWS[0]
|
||||
|
||||
const isWorkflowView = activeView === 'workflow'
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className='dark flex aspect-[1116/549] w-full overflow-hidden rounded bg-[#1b1b1b] antialiased'
|
||||
className='dark flex aspect-[1116/549] w-full overflow-hidden rounded bg-[#1e1e1e] antialiased'
|
||||
initial='hidden'
|
||||
animate='visible'
|
||||
variants={containerVariants}
|
||||
@@ -77,15 +90,34 @@ export function LandingPreview() {
|
||||
<LandingPreviewSidebar
|
||||
workflows={PREVIEW_WORKFLOWS}
|
||||
activeWorkflowId={activeWorkflowId}
|
||||
onSelectWorkflow={setActiveWorkflowId}
|
||||
activeView={activeView}
|
||||
onSelectWorkflow={handleSelectWorkflow}
|
||||
onSelectHome={handleSelectHome}
|
||||
/>
|
||||
</motion.div>
|
||||
<div className='relative flex-1 overflow-hidden'>
|
||||
<LandingPreviewWorkflow workflow={activeWorkflow} animate={isInitialMount.current} />
|
||||
<div className='flex min-w-0 flex-1 flex-col p-[8px] pl-0'>
|
||||
<div className='flex flex-1 overflow-hidden rounded-[8px] border border-[#2c2c2c] bg-[#1b1b1b]'>
|
||||
<div
|
||||
className={
|
||||
isWorkflowView
|
||||
? 'relative min-w-0 flex-1 overflow-hidden'
|
||||
: 'relative flex min-w-0 flex-1 flex-col overflow-hidden'
|
||||
}
|
||||
>
|
||||
{isWorkflowView ? (
|
||||
<LandingPreviewWorkflow workflow={activeWorkflow} animate={isInitialMount.current} />
|
||||
) : (
|
||||
<LandingPreviewHome />
|
||||
)}
|
||||
</div>
|
||||
<motion.div
|
||||
className={isWorkflowView ? 'hidden lg:flex' : 'hidden'}
|
||||
variants={panelVariants}
|
||||
>
|
||||
<LandingPreviewPanel />
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
<motion.div className='hidden lg:flex' variants={panelVariants}>
|
||||
<LandingPreviewPanel />
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ export default function Navbar() {
|
||||
</Link>
|
||||
<Link
|
||||
href='/signup'
|
||||
className='inline-flex h-[30px] items-center gap-[7px] rounded-[5px] border border-[#33C482] bg-[#33C482] px-[9px] text-[13.5px] text-black transition-[filter] hover:brightness-110'
|
||||
className='inline-flex h-[30px] items-center gap-[7px] rounded-[5px] border border-[#FFFFFF] bg-[#FFFFFF] px-[9px] text-[13.5px] text-black transition-colors hover:border-[#E0E0E0] hover:bg-[#E0E0E0]'
|
||||
aria-label='Get started with Sim'
|
||||
>
|
||||
Get started
|
||||
|
||||
@@ -123,7 +123,7 @@ function PricingCard({ tier }: PricingCardProps) {
|
||||
) : isPro ? (
|
||||
<Link
|
||||
href={tier.cta.href}
|
||||
className='flex h-[32px] w-full items-center justify-center rounded-[5px] border border-[#33C482] bg-[#33C482] px-[10px] font-[430] font-season text-[14px] text-white transition-[filter] hover:brightness-110'
|
||||
className='flex h-[32px] w-full items-center justify-center rounded-[5px] border border-[#1D1D1D] bg-[#1D1D1D] px-[10px] font-[430] font-season text-[14px] text-white transition-colors hover:border-[#2A2A2A] hover:bg-[#2A2A2A]'
|
||||
>
|
||||
{tier.cta.label}
|
||||
</Link>
|
||||
|
||||
@@ -557,7 +557,7 @@ export default function Templates() {
|
||||
type='button'
|
||||
onClick={handleUseTemplate}
|
||||
disabled={isPreparingTemplate}
|
||||
className='group/cta absolute top-[16px] right-[16px] z-10 inline-flex h-[32px] cursor-pointer items-center gap-[6px] rounded-[5px] border border-[#33C482] bg-[#33C482] px-[10px] font-[430] font-season text-[14px] text-black transition-[filter] hover:brightness-110'
|
||||
className='group/cta absolute top-[16px] right-[16px] z-10 inline-flex h-[32px] cursor-pointer items-center gap-[6px] rounded-[5px] border border-[#FFFFFF] bg-[#FFFFFF] px-[10px] font-[430] font-season text-[14px] text-black transition-colors hover:border-[#E0E0E0] hover:bg-[#E0E0E0]'
|
||||
>
|
||||
{isPreparingTemplate ? 'Preparing...' : 'Use template'}
|
||||
<span className='relative h-[10px] w-[10px] shrink-0'>
|
||||
|
||||
@@ -41,6 +41,19 @@
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@keyframes sidebar-collapse-guard {
|
||||
from {
|
||||
pointer-events: none;
|
||||
}
|
||||
to {
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-container[data-collapsed] {
|
||||
animation: sidebar-collapse-guard 250ms step-end;
|
||||
}
|
||||
|
||||
.sidebar-container.is-resizing {
|
||||
transition: none;
|
||||
}
|
||||
@@ -145,7 +158,7 @@
|
||||
--brand-400: #8e4cfb;
|
||||
--brand-secondary: #33b4ff;
|
||||
--brand-tertiary: #22c55e;
|
||||
--brand-tertiary-2: #32bd7e;
|
||||
--brand-tertiary-2: #33c482;
|
||||
--selection: #1a5cf6;
|
||||
--warning: #ea580c;
|
||||
|
||||
@@ -267,7 +280,7 @@
|
||||
--brand-400: #8e4cfb;
|
||||
--brand-secondary: #33b4ff;
|
||||
--brand-tertiary: #22c55e;
|
||||
--brand-tertiary-2: #32bd7e;
|
||||
--brand-tertiary-2: #33c482;
|
||||
--selection: #4b83f7;
|
||||
--warning: #ff6600;
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ export async function GET(request: NextRequest) {
|
||||
([category, templates]) => `
|
||||
<h2 style="margin-top: 24px; margin-bottom: 12px; font-size: 14px; color: #666; text-transform: uppercase; letter-spacing: 0.5px;">${category}</h2>
|
||||
<ul style="list-style: none; padding: 0; margin: 0;">
|
||||
${templates.map((t) => `<li style="margin: 8px 0;"><a href="?template=${t}" style="color: #32bd7e; text-decoration: none; font-size: 16px;">${t}</a></li>`).join('')}
|
||||
${templates.map((t) => `<li style="margin: 8px 0;"><a href="?template=${t}" style="color: #33C482; text-decoration: none; font-size: 16px;">${t}</a></li>`).join('')}
|
||||
</ul>
|
||||
`
|
||||
)
|
||||
|
||||
@@ -11,7 +11,7 @@ interface ThankYouScreenProps {
|
||||
}
|
||||
|
||||
/** Default green color matching --brand-tertiary-2 */
|
||||
const DEFAULT_THANK_YOU_COLOR = '#32bd7e'
|
||||
const DEFAULT_THANK_YOU_COLOR = '#33C482'
|
||||
|
||||
/** Legacy blue default that should be treated as "no custom color" */
|
||||
const LEGACY_BLUE_DEFAULT = '#3972F6'
|
||||
|
||||
@@ -1249,7 +1249,7 @@ export default function ResumeExecutionPage({
|
||||
{message && <Badge variant='green'>{message}</Badge>}
|
||||
|
||||
{/* Action */}
|
||||
<Button variant='tertiary' onClick={handleResume} disabled={resumeDisabled}>
|
||||
<Button variant='primary' onClick={handleResume} disabled={resumeDisabled}>
|
||||
{loadingAction ? 'Resuming...' : 'Resume Execution'}
|
||||
</Button>
|
||||
</>
|
||||
|
||||
@@ -733,7 +733,7 @@ export default function TemplateDetails({ isWorkspaceContext = false }: Template
|
||||
<>
|
||||
{!currentUserId ? (
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={() => {
|
||||
const callbackUrl =
|
||||
isWorkspaceContext && workspaceId
|
||||
@@ -749,7 +749,7 @@ export default function TemplateDetails({ isWorkspaceContext = false }: Template
|
||||
</Button>
|
||||
) : isWorkspaceContext ? (
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={handleUseTemplate}
|
||||
disabled={isUsing}
|
||||
className='!text-[#FFFFFF] h-[32px] rounded-[6px] px-[12px] text-[14px]'
|
||||
|
||||
@@ -343,7 +343,7 @@ export function Home({ chatId }: HomeProps = {}) {
|
||||
|
||||
if (!hasMessages) {
|
||||
return (
|
||||
<div className='h-full overflow-y-auto bg-[var(--bg)]'>
|
||||
<div className='h-full overflow-y-auto bg-[var(--bg)] [scrollbar-gutter:stable]'>
|
||||
<div className='flex min-h-full flex-col items-center justify-center px-[24px] pb-[2vh]'>
|
||||
<h1 className='mb-[24px] max-w-[42rem] font-[430] font-season text-[32px] text-[var(--text-primary)] tracking-[-0.02em]'>
|
||||
What should we get done
|
||||
@@ -372,7 +372,7 @@ export function Home({ chatId }: HomeProps = {}) {
|
||||
<div className='flex h-full min-w-0 flex-1 flex-col'>
|
||||
<div
|
||||
ref={scrollContainerRef}
|
||||
className='min-h-0 flex-1 overflow-y-auto overflow-x-hidden px-6 pt-4 pb-8'
|
||||
className='min-h-0 flex-1 overflow-y-auto overflow-x-hidden px-6 pt-4 pb-8 [scrollbar-gutter:stable]'
|
||||
>
|
||||
<div className='mx-auto max-w-[42rem] space-y-6'>
|
||||
{messages.map((msg, index) => {
|
||||
|
||||
@@ -554,7 +554,7 @@ export function DocumentTagsModal({
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={saveDocumentTag}
|
||||
className='flex-1'
|
||||
disabled={!canSaveTag}
|
||||
@@ -718,7 +718,7 @@ export function DocumentTagsModal({
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={saveDocumentTag}
|
||||
className='flex-1'
|
||||
disabled={
|
||||
|
||||
@@ -558,7 +558,7 @@ export function AddConnectorModal({ open, onOpenChange, knowledgeBaseId }: AddCo
|
||||
<Button variant='default' onClick={() => onOpenChange(false)} disabled={isCreating}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant='tertiary' onClick={handleSubmit} disabled={!canSubmit || isCreating}>
|
||||
<Button variant='primary' onClick={handleSubmit} disabled={!canSubmit || isCreating}>
|
||||
{isCreating ? (
|
||||
<>
|
||||
<Loader2 className='mr-1.5 h-3.5 w-3.5 animate-spin' />
|
||||
|
||||
@@ -347,7 +347,7 @@ export function AddDocumentsModal({
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
type='button'
|
||||
onClick={handleUpload}
|
||||
disabled={files.length === 0 || isUploading}
|
||||
|
||||
@@ -387,7 +387,7 @@ export function BaseTagsModal({ open, onOpenChange, knowledgeBaseId }: BaseTagsM
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={saveTagDefinition}
|
||||
className='flex-1'
|
||||
disabled={
|
||||
|
||||
@@ -161,7 +161,7 @@ export function EditConnectorModal({
|
||||
<Button variant='default' onClick={() => onOpenChange(false)} disabled={isSaving}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant='tertiary' onClick={handleSave} disabled={!hasChanges || isSaving}>
|
||||
<Button variant='primary' onClick={handleSave} disabled={!hasChanges || isSaving}>
|
||||
{isSaving ? (
|
||||
<>
|
||||
<Loader2 className='mr-1.5 h-3.5 w-3.5 animate-spin' />
|
||||
|
||||
@@ -124,7 +124,7 @@ export function RenameDocumentModal({
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
type='submit'
|
||||
disabled={isSubmitting || !name?.trim() || name.trim() === initialName}
|
||||
>
|
||||
|
||||
@@ -522,7 +522,7 @@ export function CreateBaseModal({ open, onOpenChange }: CreateBaseModalProps) {
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
type='submit'
|
||||
disabled={isSubmitting || !nameValue?.trim()}
|
||||
>
|
||||
|
||||
@@ -159,7 +159,7 @@ export function EditKnowledgeBaseModal({
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
type='submit'
|
||||
disabled={isSubmitting || !nameValue?.trim() || !isDirty}
|
||||
>
|
||||
|
||||
@@ -580,7 +580,7 @@ export const NotificationSettings = memo(function NotificationSettings({
|
||||
|
||||
<div className='flex flex-shrink-0 items-center gap-[8px]'>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={() => handleTest(subscription.id)}
|
||||
disabled={testNotification.isPending && testStatus?.id !== subscription.id}
|
||||
>
|
||||
@@ -1235,7 +1235,7 @@ export const NotificationSettings = memo(function NotificationSettings({
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={handleSave}
|
||||
disabled={createNotification.isPending || updateNotification.isPending}
|
||||
>
|
||||
@@ -1254,7 +1254,7 @@ export const NotificationSettings = memo(function NotificationSettings({
|
||||
resetForm()
|
||||
setShowForm(true)
|
||||
}}
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
disabled={isLoading}
|
||||
>
|
||||
<Plus className='mr-[6px] h-[13px] w-[13px]' />
|
||||
|
||||
@@ -541,7 +541,7 @@ export function ScheduleModal({ open, onOpenChange, workspaceId, schedule }: Sch
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={handleSubmit}
|
||||
disabled={
|
||||
!isFormValid || createScheduleMutation.isPending || updateScheduleMutation.isPending
|
||||
|
||||
@@ -149,7 +149,7 @@ export function ApiKeys() {
|
||||
e.currentTarget.blur()
|
||||
setIsCreateDialogOpen(true)
|
||||
}}
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
disabled={createButtonDisabled}
|
||||
>
|
||||
<Plus className='mr-[6px] h-[13px] w-[13px]' />
|
||||
|
||||
@@ -190,7 +190,7 @@ export function CreateApiKeyModal({
|
||||
</Button>
|
||||
<Button
|
||||
type='button'
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={handleCreateKey}
|
||||
disabled={
|
||||
!keyName.trim() ||
|
||||
|
||||
@@ -277,11 +277,7 @@ export function BYOK() {
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
variant='primary'
|
||||
className='!bg-[var(--brand-tertiary-2)] !text-[var(--text-inverse)] hover:!bg-[var(--brand-tertiary-2)]/90'
|
||||
onClick={() => openEditModal(provider.id)}
|
||||
>
|
||||
<Button variant='primary' onClick={() => openEditModal(provider.id)}>
|
||||
Add Key
|
||||
</Button>
|
||||
)}
|
||||
@@ -391,7 +387,7 @@ export function BYOK() {
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={handleSave}
|
||||
disabled={!apiKeyInput.trim() || upsertKey.isPending}
|
||||
>
|
||||
|
||||
@@ -200,7 +200,7 @@ export function Copilot() {
|
||||
setIsCreateDialogOpen(true)
|
||||
setCreateError(null)
|
||||
}}
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
disabled={isLoading}
|
||||
>
|
||||
<Plus className='mr-[6px] h-[13px] w-[13px]' />
|
||||
@@ -302,7 +302,7 @@ export function Copilot() {
|
||||
</Button>
|
||||
<Button
|
||||
type='button'
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={handleCreateKey}
|
||||
disabled={!newKeyName.trim() || generateKey.isPending}
|
||||
>
|
||||
|
||||
@@ -628,7 +628,7 @@ export function CredentialSets() {
|
||||
/>
|
||||
</div>
|
||||
{canManageCredentialSets && (
|
||||
<Button variant='tertiary' onClick={() => setShowCreateModal(true)}>
|
||||
<Button variant='primary' onClick={() => setShowCreateModal(true)}>
|
||||
<Plus className='mr-[6px] h-[13px] w-[13px]' />
|
||||
Create
|
||||
</Button>
|
||||
@@ -671,7 +671,7 @@ export function CredentialSets() {
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={() => handleAcceptInvitation(invitation.token)}
|
||||
disabled={acceptInvitation.isPending}
|
||||
>
|
||||
@@ -843,7 +843,7 @@ export function CredentialSets() {
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={handleCreateCredentialSet}
|
||||
disabled={!newSetName.trim() || createCredentialSet.isPending}
|
||||
>
|
||||
|
||||
@@ -1311,7 +1311,7 @@ export function CredentialsManager() {
|
||||
</Button>
|
||||
{isSelectedAdmin && (
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={handleSaveDetails}
|
||||
disabled={!isDetailsDirty || isSavingDetails}
|
||||
>
|
||||
@@ -1414,7 +1414,7 @@ export function CredentialsManager() {
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
disabled={isLoading || !hasChanges || hasConflicts || hasInvalidKeys}
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
className={`${hasConflicts || hasInvalidKeys ? 'cursor-not-allowed opacity-50' : ''}`}
|
||||
>
|
||||
Save
|
||||
|
||||
@@ -101,7 +101,7 @@ export function CustomTools() {
|
||||
className='h-auto flex-1 border-0 bg-transparent p-0 font-base leading-none placeholder:text-[var(--text-tertiary)] focus-visible:ring-0 focus-visible:ring-offset-0'
|
||||
/>
|
||||
</div>
|
||||
<Button onClick={() => setShowAddForm(true)} disabled={isLoading} variant='tertiary'>
|
||||
<Button onClick={() => setShowAddForm(true)} disabled={isLoading} variant='primary'>
|
||||
<Plus className='mr-[6px] h-[13px] w-[13px]' />
|
||||
Add
|
||||
</Button>
|
||||
|
||||
@@ -50,7 +50,7 @@ export function Debug() {
|
||||
disabled={importWorkflow.isPending}
|
||||
/>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={handleImport}
|
||||
disabled={importWorkflow.isPending || !workflowId.trim()}
|
||||
>
|
||||
|
||||
@@ -515,7 +515,7 @@ export function General() {
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={handleResetPasswordConfirm}
|
||||
disabled={resetPassword.isPending || resetPassword.isSuccess}
|
||||
>
|
||||
|
||||
@@ -108,7 +108,7 @@ export function InboxEnableToggle() {
|
||||
<Button variant='default' onClick={() => setIsEnableOpen(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant='tertiary' onClick={handleEnable} disabled={toggleInbox.isPending}>
|
||||
<Button variant='primary' onClick={handleEnable} disabled={toggleInbox.isPending}>
|
||||
{toggleInbox.isPending ? 'Enabling...' : 'Enable'}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
|
||||
@@ -282,7 +282,7 @@ export function InboxSettingsTab() {
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={handleAddSender}
|
||||
disabled={!newSenderEmail.trim() || addSender.isPending}
|
||||
>
|
||||
|
||||
@@ -41,7 +41,7 @@ export function Inbox() {
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={() => router.push(`/workspace/${workspaceId}/settings/subscription`)}
|
||||
>
|
||||
Upgrade to Max
|
||||
|
||||
@@ -678,7 +678,7 @@ export function IntegrationsManager() {
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={handleCreateCredential}
|
||||
disabled={
|
||||
!createOAuthProviderId ||
|
||||
@@ -1027,7 +1027,7 @@ export function IntegrationsManager() {
|
||||
</Button>
|
||||
{isSelectedAdmin && (
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={handleSaveDetails}
|
||||
disabled={!isDetailsDirty || isSavingDetails}
|
||||
>
|
||||
@@ -1066,7 +1066,7 @@ export function IntegrationsManager() {
|
||||
<Button
|
||||
onClick={() => setShowCreateModal(true)}
|
||||
disabled={credentialsLoading}
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
>
|
||||
<Plus className='mr-[6px] h-[13px] w-[13px]' />
|
||||
Connect
|
||||
|
||||
@@ -719,12 +719,12 @@ export function McpServerFormModal({
|
||||
<Button
|
||||
onClick={handleSubmitJson}
|
||||
disabled={isSubmitting || !jsonInput.trim()}
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
>
|
||||
{isSubmitting ? 'Adding...' : submitLabel}
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={handleSubmitForm} disabled={isSubmitDisabled} variant='tertiary'>
|
||||
<Button onClick={handleSubmitForm} disabled={isSubmitDisabled} variant='primary'>
|
||||
{isSubmitting ? (mode === 'add' ? 'Adding...' : 'Saving...') : submitLabel}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -637,11 +637,7 @@ export function MCP({ initialServerId }: MCPProps) {
|
||||
className='h-auto flex-1 border-0 bg-transparent p-0 font-base leading-none placeholder:text-[var(--text-tertiary)] focus-visible:ring-0 focus-visible:ring-offset-0'
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => setShowAddModal(true)}
|
||||
variant='tertiary'
|
||||
disabled={serversLoading}
|
||||
>
|
||||
<Button onClick={() => setShowAddModal(true)} variant='primary' disabled={serversLoading}>
|
||||
<Plus className='mr-[6px] h-[13px] w-[13px]' />
|
||||
Add
|
||||
</Button>
|
||||
|
||||
@@ -303,7 +303,7 @@ export function RecentlyDeleted() {
|
||||
<div className='flex shrink-0 items-center gap-[8px]'>
|
||||
<span className='text-[13px] text-[var(--text-tertiary)]'>Restored</span>
|
||||
<Button
|
||||
variant='default'
|
||||
variant='primary'
|
||||
size='sm'
|
||||
onClick={() =>
|
||||
router.push(
|
||||
@@ -316,7 +316,7 @@ export function RecentlyDeleted() {
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
variant='default'
|
||||
variant='primary'
|
||||
size='sm'
|
||||
disabled={isRestoring}
|
||||
onClick={() => handleRestore(resource)}
|
||||
|
||||
@@ -210,7 +210,7 @@ export function SkillModal({
|
||||
<Button variant='default' onClick={() => onOpenChange(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant='tertiary' onClick={handleSave} disabled={saving || !hasChanges}>
|
||||
<Button variant='primary' onClick={handleSave} disabled={saving || !hasChanges}>
|
||||
{saving ? 'Saving...' : initialValues ? 'Update' : 'Create'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -95,7 +95,7 @@ export function Skills() {
|
||||
className='h-auto flex-1 border-0 bg-transparent p-0 font-base leading-none placeholder:text-[var(--text-tertiary)] focus-visible:ring-0 focus-visible:ring-offset-0'
|
||||
/>
|
||||
</div>
|
||||
<Button onClick={() => setShowAddForm(true)} disabled={isLoading} variant='tertiary'>
|
||||
<Button onClick={() => setShowAddForm(true)} disabled={isLoading} variant='primary'>
|
||||
<Plus className='mr-[6px] h-[13px] w-[13px]' />
|
||||
Add
|
||||
</Button>
|
||||
|
||||
@@ -160,7 +160,7 @@ export function CreditBalance({
|
||||
</Button>
|
||||
</ModalClose>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={handlePurchase}
|
||||
disabled={purchaseCredits.isPending || !amount}
|
||||
>
|
||||
|
||||
@@ -245,7 +245,7 @@ function CreditPlanCard({
|
||||
{isCancelledAtPeriodEnd ? 'Restore Subscription' : 'Manage plan'}
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={onButtonClick} className='w-full' variant='tertiary'>
|
||||
<Button onClick={onButtonClick} className='w-full' variant='primary'>
|
||||
{buttonText}
|
||||
</Button>
|
||||
)}
|
||||
@@ -1134,7 +1134,7 @@ function TeamPlanModal({ open, onOpenChange, isAnnual, onConfirm }: TeamPlanModa
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={() => onConfirm(selectedTier, selectedSeats)}
|
||||
disabled={selectedSeats < 1}
|
||||
>
|
||||
@@ -1291,7 +1291,7 @@ function ManagePlanModal({
|
||||
</span>
|
||||
</div>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
className='ml-[12px] shrink-0'
|
||||
onClick={action.onClick}
|
||||
disabled={action.disabled}
|
||||
@@ -1312,7 +1312,7 @@ function ManagePlanModal({
|
||||
<Button variant='default' onClick={() => onOpenChange(false)}>
|
||||
Close
|
||||
</Button>
|
||||
<Button variant='tertiary' onClick={onRestore}>
|
||||
<Button variant='primary' onClick={onRestore}>
|
||||
Restore Subscription
|
||||
</Button>
|
||||
</>
|
||||
|
||||
@@ -239,7 +239,7 @@ export function MemberInvitationCard({
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={() => onInviteMember()}
|
||||
disabled={!hasValidEmails || isInviting || !hasAvailableSeats}
|
||||
>
|
||||
|
||||
@@ -111,7 +111,7 @@ export function NoOrganizationView({
|
||||
)}
|
||||
<div className='flex justify-end'>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={onCreateOrganization}
|
||||
disabled={!orgName || !orgSlug || isCreatingOrg}
|
||||
>
|
||||
@@ -193,7 +193,7 @@ export function NoOrganizationView({
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={onCreateOrganization}
|
||||
disabled={isCreatingOrg || !orgName.trim()}
|
||||
>
|
||||
@@ -217,7 +217,7 @@ export function NoOrganizationView({
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Button variant='tertiary' onClick={() => navigateToSettings({ section: 'subscription' })}>
|
||||
<Button variant='primary' onClick={() => navigateToSettings({ section: 'subscription' })}>
|
||||
Upgrade to Team Plan
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -72,7 +72,7 @@ export function TeamSeatsOverview({
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={() => {
|
||||
onConfirmTeamUpgrade(2)
|
||||
}}
|
||||
|
||||
@@ -135,7 +135,7 @@ export function TeamSeats({
|
||||
<Tooltip.Trigger asChild>
|
||||
<span>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={() => onConfirm(selectedSeats)}
|
||||
disabled={
|
||||
isLoading ||
|
||||
|
||||
@@ -505,7 +505,7 @@ export function TemplateProfile() {
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
disabled={saveStatus === 'saving' || !isFormValid}
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
>
|
||||
{saveStatus === 'saving' ? 'Saving...' : saveStatus === 'saved' ? 'Saved' : 'Save'}
|
||||
</Button>
|
||||
|
||||
@@ -349,7 +349,7 @@ function ServerDetailView({ workspaceId, serverId, onBack }: ServerDetailViewPro
|
||||
<Tooltip.Trigger asChild>
|
||||
<div className='inline-flex'>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={() => setShowAddWorkflow(true)}
|
||||
disabled
|
||||
>
|
||||
@@ -364,7 +364,7 @@ function ServerDetailView({ workspaceId, serverId, onBack }: ServerDetailViewPro
|
||||
</Tooltip.Root>
|
||||
) : (
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={() => setShowAddWorkflow(true)}
|
||||
disabled={!canAddWorkflow}
|
||||
>
|
||||
@@ -480,7 +480,7 @@ function ServerDetailView({ workspaceId, serverId, onBack }: ServerDetailViewPro
|
||||
workflows via the MCP block.
|
||||
</p>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
className='self-start'
|
||||
disabled={addToWorkspaceMutation.isPending || addedToWorkspace}
|
||||
onClick={async () => {
|
||||
@@ -739,7 +739,7 @@ function ServerDetailView({ workspaceId, serverId, onBack }: ServerDetailViewPro
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={async () => {
|
||||
if (!toolToView) return
|
||||
try {
|
||||
@@ -859,7 +859,7 @@ function ServerDetailView({ workspaceId, serverId, onBack }: ServerDetailViewPro
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={handleAddWorkflow}
|
||||
disabled={!selectedWorkflowId || addToolMutation.isPending}
|
||||
>
|
||||
@@ -920,7 +920,7 @@ function ServerDetailView({ workspaceId, serverId, onBack }: ServerDetailViewPro
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={handleSaveServerEdit}
|
||||
disabled={
|
||||
!editServerName.trim() ||
|
||||
@@ -1059,7 +1059,7 @@ export function WorkflowMcpServers() {
|
||||
className='h-auto flex-1 border-0 bg-transparent p-0 font-base leading-none placeholder:text-[var(--text-tertiary)] focus-visible:ring-0 focus-visible:ring-offset-0'
|
||||
/>
|
||||
</div>
|
||||
<Button onClick={() => setShowAddModal(true)} disabled={isLoading} variant='tertiary'>
|
||||
<Button onClick={() => setShowAddModal(true)} disabled={isLoading} variant='primary'>
|
||||
<Plus className='mr-[6px] h-[13px] w-[13px]' />
|
||||
Add
|
||||
</Button>
|
||||
@@ -1203,7 +1203,7 @@ export function WorkflowMcpServers() {
|
||||
<Button
|
||||
onClick={handleCreateServer}
|
||||
disabled={!isFormValid || createServerMutation.isPending}
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
>
|
||||
{createServerMutation.isPending ? 'Adding...' : 'Add Server'}
|
||||
</Button>
|
||||
|
||||
@@ -221,7 +221,7 @@ export function RowModal({ mode, isOpen, onClose, table, row, rowIds, onSuccess
|
||||
</Button>
|
||||
<Button
|
||||
type='button'
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={handleFormSubmit}
|
||||
disabled={isSubmitting}
|
||||
className='min-w-[120px]'
|
||||
|
||||
@@ -65,7 +65,7 @@ export function CheckpointConfirmation({
|
||||
{!isRestoreVariant && onContinue && (
|
||||
<Button
|
||||
onClick={onContinue}
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
size='sm'
|
||||
className='flex-1'
|
||||
disabled={isProcessing}
|
||||
|
||||
@@ -1390,7 +1390,7 @@ function RunSkipButtons({
|
||||
// Standardized buttons for all interrupt tools: Allow, Always Allow, Skip
|
||||
return (
|
||||
<div className='mt-[10px] flex gap-[6px]'>
|
||||
<Button onClick={onRun} disabled={isProcessing} variant='tertiary'>
|
||||
<Button onClick={onRun} disabled={isProcessing} variant='primary'>
|
||||
{isProcessing ? 'Allowing...' : 'Allow'}
|
||||
</Button>
|
||||
{showAlwaysAllow && (
|
||||
@@ -2130,7 +2130,7 @@ export function ToolCall({
|
||||
onStateChange?.('background')
|
||||
await sendToolDecision(toolCall.id, 'background')
|
||||
}}
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
title='Move to Background'
|
||||
>
|
||||
Move to Background
|
||||
@@ -2144,7 +2144,7 @@ export function ToolCall({
|
||||
onStateChange?.('background')
|
||||
await sendToolDecision(toolCall.id, 'background')
|
||||
}}
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
title='Wake'
|
||||
>
|
||||
Wake
|
||||
|
||||
@@ -1006,12 +1006,7 @@ function GeneralFooter({
|
||||
<ModalFooter className='items-center justify-between'>
|
||||
<StatusBadge isWarning={needsRedeployment} />
|
||||
<div className='flex items-center gap-2'>
|
||||
<Button
|
||||
variant='default'
|
||||
onClick={onUndeploy}
|
||||
disabled={isUndeploying || isSubmitting}
|
||||
className='px-[7px] py-[5px]'
|
||||
>
|
||||
<Button variant='default' onClick={onUndeploy} disabled={isUndeploying || isSubmitting}>
|
||||
{isUndeploying ? 'Undeploying...' : 'Undeploy'}
|
||||
</Button>
|
||||
{needsRedeployment && (
|
||||
|
||||
@@ -180,7 +180,7 @@ export function OAuthRequiredModal({
|
||||
<Button variant='default' onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant='tertiary' type='button' onClick={handleConnectDirectly}>
|
||||
<Button variant='primary' type='button' onClick={handleConnectDirectly}>
|
||||
Connect
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
|
||||
@@ -863,7 +863,7 @@ try {
|
||||
placeholder='Generate...'
|
||||
/>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
disabled={!schemaPromptInput.trim() || schemaGeneration.isStreaming}
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault()
|
||||
@@ -955,7 +955,7 @@ try {
|
||||
placeholder='Generate...'
|
||||
/>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
disabled={!codePromptInput.trim() || codeGeneration.isStreaming}
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault()
|
||||
@@ -1135,7 +1135,7 @@ try {
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={() => setActiveSection('code')}
|
||||
disabled={!isSchemaValid || !!schemaError}
|
||||
>
|
||||
@@ -1161,7 +1161,7 @@ try {
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={handleSave}
|
||||
disabled={!isSchemaValid || !!schemaError || !hasChanges}
|
||||
>
|
||||
|
||||
@@ -129,7 +129,7 @@ export function ParameterWithLabel({
|
||||
placeholder='Generate with AI...'
|
||||
/>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
disabled={!searchQuery.trim() || isStreaming}
|
||||
onMouseDown={(e: React.MouseEvent) => {
|
||||
e.preventDefault()
|
||||
|
||||
@@ -331,7 +331,7 @@ const renderLabel = (
|
||||
placeholder='Generate with AI...'
|
||||
/>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
disabled={!wandState.searchQuery.trim() || wandState.isStreaming}
|
||||
onMouseDown={(e: React.MouseEvent) => {
|
||||
e.preventDefault()
|
||||
|
||||
@@ -372,7 +372,7 @@ export function TrainingModal() {
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={() => {
|
||||
useCopilotTrainingStore.getState().stopTraining()
|
||||
setLocalPrompt('')
|
||||
@@ -439,7 +439,7 @@ export function TrainingModal() {
|
||||
<Button
|
||||
onClick={handleStart}
|
||||
disabled={!localTitle.trim() || !localPrompt.trim()}
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
className='w-full'
|
||||
>
|
||||
Start Training Session
|
||||
@@ -470,7 +470,7 @@ export function TrainingModal() {
|
||||
<div className='flex gap-[8px]'>
|
||||
{selectedDatasets.size > 0 && (
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={handleSendSelected}
|
||||
disabled={sendingSelected}
|
||||
>
|
||||
@@ -755,7 +755,7 @@ export function TrainingModal() {
|
||||
sendingLiveWorkflow ||
|
||||
currentWorkflow.getBlockCount() === 0
|
||||
}
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
className={cn(
|
||||
'w-full',
|
||||
liveWorkflowSent && '!bg-green-600 !text-white hover:!bg-green-700',
|
||||
|
||||
@@ -539,7 +539,7 @@ export function HelpModal({ open, onOpenChange, workflowId, workspaceId }: HelpM
|
||||
<Button variant='default' onClick={handleClose} type='button' disabled={isSubmitting}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type='submit' variant='tertiary' disabled={isSubmitting || isProcessing}>
|
||||
<Button type='submit' variant='primary' disabled={isSubmitting || isProcessing}>
|
||||
{isSubmitting
|
||||
? 'Submitting...'
|
||||
: submitStatus === 'error'
|
||||
|
||||
@@ -209,7 +209,7 @@ function ColorPickerSubmenu({
|
||||
className='h-[20px] min-w-0 flex-1 rounded-[4px] border border-[var(--border-1)] bg-[var(--surface-5)] px-[6px] font-medium text-[11px] text-[var(--text-primary)] uppercase transition-colors focus:outline-none focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0'
|
||||
/>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
disabled={!canSubmitHex}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
|
||||
@@ -77,7 +77,7 @@ export function CreateWorkspaceModal({
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={() => void handleSubmit()}
|
||||
disabled={!name.trim() || isCreating}
|
||||
>
|
||||
|
||||
@@ -575,7 +575,7 @@ export function InviteModal({ open, onOpenChange, workspaceName }: InviteModalPr
|
||||
</Button>
|
||||
<Button
|
||||
type='button'
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
disabled={isSaving || isSubmitting}
|
||||
onClick={handleSaveChanges}
|
||||
tabIndex={hasPendingChanges && userPerms.canAdmin ? 0 : -1}
|
||||
@@ -586,7 +586,7 @@ export function InviteModal({ open, onOpenChange, workspaceName }: InviteModalPr
|
||||
|
||||
<Button
|
||||
type='button'
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={() => formRef.current?.requestSubmit()}
|
||||
disabled={
|
||||
!userPerms.canAdmin || isSubmitting || isSaving || !workspaceId || !hasNewInvites
|
||||
|
||||
@@ -365,6 +365,7 @@ export function WorkspaceHeader({
|
||||
align='start'
|
||||
side={isCollapsed ? 'right' : 'bottom'}
|
||||
sideOffset={isCollapsed ? 16 : 8}
|
||||
className='flex max-h-none flex-col overflow-hidden'
|
||||
style={
|
||||
isCollapsed
|
||||
? {
|
||||
@@ -385,7 +386,7 @@ export function WorkspaceHeader({
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className='flex items-center gap-[8px] px-[2px] py-[4px]'>
|
||||
<div className='flex items-center gap-[8px] px-[2px] py-[2px]'>
|
||||
<div
|
||||
className='flex h-[32px] w-[32px] flex-shrink-0 items-center justify-center rounded-[6px] font-medium text-[12px] text-white'
|
||||
style={{
|
||||
@@ -404,7 +405,7 @@ export function WorkspaceHeader({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DropdownMenuGroup className='mt-[4px]'>
|
||||
<DropdownMenuGroup className='mt-[4px] min-h-0 flex-1'>
|
||||
<div className='flex max-h-[130px] flex-col gap-[2px] overflow-y-auto'>
|
||||
{workspaces.map((workspace) => (
|
||||
<div key={workspace.id}>
|
||||
@@ -492,7 +493,9 @@ export function WorkspaceHeader({
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</DropdownMenuGroup>
|
||||
|
||||
<div className='flex flex-col gap-[2px]'>
|
||||
<button
|
||||
type='button'
|
||||
className='flex w-full cursor-pointer select-none items-center gap-[8px] rounded-[5px] px-[8px] py-[5px] font-medium text-[12px] text-[var(--text-body)] outline-none transition-colors hover:bg-[var(--surface-active)] disabled:pointer-events-none disabled:opacity-50'
|
||||
@@ -506,7 +509,7 @@ export function WorkspaceHeader({
|
||||
<Plus className='h-[14px] w-[14px] shrink-0 text-[var(--text-icon)]' />
|
||||
Create new workspace
|
||||
</button>
|
||||
</DropdownMenuGroup>
|
||||
</div>
|
||||
|
||||
{!isInvitationsDisabled && (
|
||||
<>
|
||||
|
||||
@@ -1056,10 +1056,7 @@ export const Sidebar = memo(function Sidebar() {
|
||||
<div className='flex flex-shrink-0 flex-col space-y-[4px] px-[16px]'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<div
|
||||
className={cn(
|
||||
'font-base text-[var(--text-icon)] text-small',
|
||||
isCollapsed && 'opacity-0'
|
||||
)}
|
||||
className={`font-base text-[var(--text-icon)] text-small${isCollapsed ? ' opacity-0' : ''}`}
|
||||
>
|
||||
All tasks
|
||||
</div>
|
||||
|
||||
@@ -18,5 +18,9 @@ export default function WorkspaceRootLayout({ children }: WorkspaceRootLayoutPro
|
||||
}
|
||||
: undefined
|
||||
|
||||
return <SocketProvider user={user}>{children}</SocketProvider>
|
||||
return (
|
||||
<SocketProvider user={user}>
|
||||
<div className='tracking-[0.02em]'>{children}</div>
|
||||
</SocketProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export const colors = {
|
||||
/** Brand primary - purple */
|
||||
brandPrimary: '#6f3dfa',
|
||||
/** Brand tertiary - green (matches Run/Deploy buttons) */
|
||||
brandTertiary: '#32bd7e',
|
||||
brandTertiary: '#33C482',
|
||||
/** Border/divider color */
|
||||
divider: '#ededed',
|
||||
/** Footer background */
|
||||
|
||||
@@ -104,7 +104,7 @@ const buttonGroupItemVariants = cva(
|
||||
{
|
||||
variants: {
|
||||
active: {
|
||||
true: 'bg-[var(--brand-tertiary-2)] text-[var(--text-inverse)] border-[var(--brand-tertiary-2)] hover:brightness-106',
|
||||
true: 'bg-[#1D1D1D] text-[var(--text-inverse)] border-[#1D1D1D] hover:bg-[#2A2A2A] hover:border-[#2A2A2A] dark:bg-white dark:border-white dark:hover:bg-[#E0E0E0] dark:hover:border-[#E0E0E0]',
|
||||
false:
|
||||
'bg-[var(--surface-4)] text-[var(--text-secondary)] border-[var(--border)] hover:text-[var(--text-primary)] hover:bg-[var(--surface-6)] hover:border-[var(--border-1)]',
|
||||
},
|
||||
|
||||
@@ -14,11 +14,12 @@ const buttonVariants = cva(
|
||||
'3d': 'text-[var(--text-tertiary)] border-t border-l border-r border-[var(--border-1)] shadow-[0_2px_0_0_var(--border-1)] hover:shadow-[0_4px_0_0_var(--border-1)] transition-all hover:-translate-y-0.5 hover:text-[var(--text-primary)]',
|
||||
outline:
|
||||
'border border-[var(--text-muted)] bg-transparent hover:border-[var(--text-secondary)]',
|
||||
primary: 'bg-[var(--brand-400)] text-[var(--text-primary)] hover:brightness-106',
|
||||
primary:
|
||||
'bg-[#1D1D1D] text-[var(--text-inverse)] hover:text-[var(--text-inverse)] hover:bg-[#2A2A2A] dark:bg-white dark:hover:bg-[#E0E0E0]',
|
||||
destructive: 'bg-[var(--text-error)] text-white hover:text-white hover:brightness-106',
|
||||
secondary: 'bg-[var(--brand-secondary)] text-[var(--text-primary)]',
|
||||
tertiary:
|
||||
'!bg-[var(--brand-tertiary-2)] !text-[var(--text-inverse)] hover:brightness-106 hover:!text-[var(--text-inverse)] ![transition-property:background-color,border-color,fill,stroke]',
|
||||
'!bg-[var(--brand-tertiary-2)] !text-[var(--text-inverse)] hover:!text-[var(--text-inverse)] hover:!bg-[#2DAC72] dark:!bg-[var(--brand-tertiary-2)] dark:hover:!bg-[#2DAC72] dark:!text-[var(--text-inverse)] dark:hover:!text-[var(--text-inverse)]',
|
||||
ghost: '',
|
||||
subtle: 'text-[var(--text-body)] hover:text-[var(--text-body)] hover:bg-[var(--surface-4)]',
|
||||
'ghost-secondary': 'text-[var(--text-muted)]',
|
||||
|
||||
@@ -70,7 +70,7 @@ export function DiffControlsDemo() {
|
||||
height: '100%',
|
||||
alignItems: 'center',
|
||||
border: '1px solid rgba(0, 0, 0, 0.15)',
|
||||
backgroundColor: '#32bd7e',
|
||||
backgroundColor: '#33C482',
|
||||
paddingRight: '12px',
|
||||
paddingLeft: '20px',
|
||||
fontWeight: 500,
|
||||
|
||||
@@ -138,7 +138,7 @@ function AddMembersModal({
|
||||
className='h-auto flex-1 border-0 bg-transparent p-0 font-base text-[14px] leading-none placeholder:text-[var(--text-tertiary)] focus-visible:ring-0 focus-visible:ring-offset-0'
|
||||
/>
|
||||
</div>
|
||||
<Button variant='tertiary' onClick={handleToggleAll}>
|
||||
<Button variant='primary' onClick={handleToggleAll}>
|
||||
{allFilteredSelected ? 'Deselect All' : 'Select All'}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -203,7 +203,7 @@ function AddMembersModal({
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={onAddMembers}
|
||||
disabled={selectedMemberIds.size === 0 || isAdding}
|
||||
>
|
||||
@@ -724,7 +724,7 @@ export function AccessControl() {
|
||||
<span className='font-medium text-[14px] text-[var(--text-secondary)]'>
|
||||
Members
|
||||
</span>
|
||||
<Button variant='tertiary' onClick={handleOpenAddMembersModal}>
|
||||
<Button variant='primary' onClick={handleOpenAddMembersModal}>
|
||||
<Plus className='mr-[6px] h-[13px] w-[13px]' />
|
||||
Add
|
||||
</Button>
|
||||
@@ -842,7 +842,7 @@ export function AccessControl() {
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={() => {
|
||||
const allAllowed =
|
||||
editingConfig?.allowedModelProviders === null ||
|
||||
@@ -896,7 +896,7 @@ export function AccessControl() {
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={() => {
|
||||
const allAllowed =
|
||||
editingConfig?.allowedIntegrations === null ||
|
||||
@@ -957,7 +957,7 @@ export function AccessControl() {
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={() => {
|
||||
const allVisible =
|
||||
!editingConfig?.hideKnowledgeBaseTab &&
|
||||
@@ -1080,7 +1080,7 @@ export function AccessControl() {
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={handleSaveConfig}
|
||||
disabled={updatePermissionGroup.isPending || !hasConfigChanges}
|
||||
>
|
||||
@@ -1113,7 +1113,7 @@ export function AccessControl() {
|
||||
Discard Changes
|
||||
</Button>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={() => {
|
||||
setShowUnsavedChanges(false)
|
||||
handleSaveConfig()
|
||||
@@ -1155,7 +1155,7 @@ export function AccessControl() {
|
||||
className='h-auto flex-1 border-0 bg-transparent p-0 font-base leading-none placeholder:text-[var(--text-tertiary)] focus-visible:ring-0 focus-visible:ring-offset-0'
|
||||
/>
|
||||
</div>
|
||||
<Button variant='tertiary' onClick={() => setShowCreateModal(true)}>
|
||||
<Button variant='primary' onClick={() => setShowCreateModal(true)}>
|
||||
<Plus className='mr-[6px] h-[13px] w-[13px]' />
|
||||
Create
|
||||
</Button>
|
||||
@@ -1248,7 +1248,7 @@ export function AccessControl() {
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
onClick={handleCreatePermissionGroup}
|
||||
disabled={!newGroupName.trim() || createPermissionGroup.isPending}
|
||||
>
|
||||
|
||||
@@ -446,7 +446,7 @@ export function SSO() {
|
||||
|
||||
{/* Footer */}
|
||||
<div className='mt-auto flex items-center justify-end'>
|
||||
<Button onClick={handleEdit} variant='tertiary'>
|
||||
<Button onClick={handleEdit} variant='primary'>
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
@@ -879,7 +879,7 @@ export function SSO() {
|
||||
{error && <p className='mr-auto text-[13px] text-[var(--text-error)]'>{error}</p>}
|
||||
<Button
|
||||
type='submit'
|
||||
variant='tertiary'
|
||||
variant='primary'
|
||||
disabled={configureSSOMutation.isPending || hasAnyErrors(errors) || !isFormValid()}
|
||||
>
|
||||
{configureSSOMutation.isPending
|
||||
|
||||
@@ -1047,7 +1047,7 @@ export const invitation = pgTable(
|
||||
export const workspace = pgTable('workspace', {
|
||||
id: text('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
color: text('color').notNull().default('#32bd7e'),
|
||||
color: text('color').notNull().default('#33C482'),
|
||||
ownerId: text('owner_id')
|
||||
.notNull()
|
||||
.references(() => user.id, { onDelete: 'cascade' }),
|
||||
|
||||
Reference in New Issue
Block a user