Skip to content

Commit c032827

Browse files
committed
improvement(platform): added more email validation utils, added integrations page, improved enterprise section, update docs generation script
1 parent 507954c commit c032827

28 files changed

Lines changed: 13506 additions & 143 deletions

File tree

apps/sim/app/(home)/components/collaboration/collaboration.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ export default function Collaboration() {
303303
</div>
304304

305305
<Link
306-
href='/studio/multiplayer'
306+
href='/blog/multiplayer'
307307
target='_blank'
308308
rel='noopener noreferrer'
309309
className='relative mx-4 mb-6 flex cursor-none items-center gap-[14px] rounded-[5px] border border-[#2A2A2A] bg-[#1C1C1C] px-[12px] py-[10px] transition-colors hover:border-[#3d3d3d] hover:bg-[#232323] sm:mx-8 md:absolute md:bottom-10 md:left-[80px] md:z-20 md:mx-0 md:mb-0'

apps/sim/app/(home)/components/enterprise/enterprise.tsx

Lines changed: 225 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@
1515
'use client'
1616

1717
import { useEffect, useRef, useState } from 'react'
18-
import { AnimatePresence, motion } from 'framer-motion'
18+
import { AnimatePresence, motion, useInView } from 'framer-motion'
1919
import Image from 'next/image'
2020
import Link from 'next/link'
2121
import { Badge, ChevronDown } from '@/components/emcn'
2222
import { Lock } from '@/components/emcn/icons'
2323
import { GithubIcon } from '@/components/icons'
24+
import { PROVIDER_DEFINITIONS } from '@/providers/models'
2425

2526
/** Consistent color per actor — same pattern as Collaboration section cursors. */
2627
const ACTOR_COLORS: Record<string, string> = {
@@ -216,21 +217,13 @@ function AuditRow({ entry, index }: AuditRowProps) {
216217
{timeAgo}
217218
</span>
218219

219-
{/* Description — description hidden on mobile to avoid truncation */}
220220
<span className='min-w-0 truncate font-[430] font-season text-[12px] leading-none tracking-[0.02em]'>
221221
<span className='text-[#F6F6F6]/80'>{entry.actor}</span>
222222
<span className='hidden sm:inline'>
223223
<span className='text-[#F6F6F6]/40'> · </span>
224224
<span className='text-[#F6F6F6]/55'>{entry.description}</span>
225225
</span>
226226
</span>
227-
228-
{/* Resource type label — formatted name, neutral so it doesn't compete with actor colors */}
229-
{resourceLabel && (
230-
<span className='ml-auto shrink-0 rounded border border-[#2A2A2A] px-[7px] py-[3px] font-[430] font-season text-[#F6F6F6]/25 text-[10px] leading-none tracking-[0.04em]'>
231-
{resourceLabel}
232-
</span>
233-
)}
234227
</div>
235228
</div>
236229
)
@@ -271,60 +264,202 @@ function AuditLogPreview() {
271264
}, [])
272265

273266
return (
274-
<div className='mx-6 mt-6 overflow-hidden rounded-[8px] border border-[#2A2A2A] md:mx-8 md:mt-8'>
275-
{/* Header */}
276-
<div className='flex items-center justify-between border-[#2A2A2A] border-b bg-[#161616] px-4 py-[10px]'>
277-
<div className='flex items-center gap-2'>
278-
{/* Pulsing live indicator */}
279-
<span className='relative flex h-[8px] w-[8px]'>
280-
<span
281-
className='absolute inline-flex h-full w-full animate-ping rounded-full opacity-50'
282-
style={{ backgroundColor: '#33C482' }}
283-
/>
284-
<span
285-
className='relative inline-flex h-[8px] w-[8px] rounded-full'
286-
style={{ backgroundColor: '#33C482' }}
287-
/>
288-
</span>
289-
<span className='font-[430] font-season text-[#F6F6F6]/40 text-[11px] uppercase tracking-[0.08em]'>
290-
Audit Log
291-
</span>
292-
</div>
293-
<div className='flex items-center gap-2'>
294-
<span className='rounded border border-[#2A2A2A] px-[8px] py-[3px] font-[430] font-season text-[#F6F6F6]/20 text-[11px] tracking-[0.02em]'>
295-
Export
296-
</span>
297-
<span className='rounded border border-[#2A2A2A] px-[8px] py-[3px] font-[430] font-season text-[#F6F6F6]/20 text-[11px] tracking-[0.02em]'>
298-
Filter
299-
</span>
300-
</div>
301-
</div>
267+
<div className='mt-5 overflow-hidden px-6 md:mt-6 md:px-8'>
268+
<AnimatePresence mode='popLayout' initial={false}>
269+
{entries.map((entry, index) => (
270+
<motion.div
271+
key={entry.id}
272+
layout
273+
initial={{ y: -48, opacity: 0 }}
274+
animate={{ y: 0, opacity: 1 }}
275+
exit={{ opacity: 0 }}
276+
transition={{
277+
layout: {
278+
type: 'spring',
279+
stiffness: 380,
280+
damping: 38,
281+
mass: 0.8,
282+
},
283+
y: { duration: 0.32, ease: [0.25, 0.46, 0.45, 0.94] },
284+
opacity: { duration: 0.25 },
285+
}}
286+
>
287+
<AuditRow entry={entry} index={index} />
288+
</motion.div>
289+
))}
290+
</AnimatePresence>
291+
</div>
292+
)
293+
}
294+
295+
const CHECK_PATH =
296+
'M10.97 4.97a.75.75 0 0 1 1.07 1.05l-3.99 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425z'
297+
298+
interface PermissionFeature {
299+
name: string
300+
key: string
301+
defaultEnabled: boolean
302+
providerId?: string
303+
}
304+
305+
interface PermissionCategory {
306+
label: string
307+
color: string
308+
features: PermissionFeature[]
309+
}
310+
311+
const PERMISSION_CATEGORIES: PermissionCategory[] = [
312+
{
313+
label: 'Providers',
314+
color: '#FA4EDF',
315+
features: [
316+
{ key: 'openai', name: 'OpenAI', defaultEnabled: true, providerId: 'openai' },
317+
{ key: 'anthropic', name: 'Anthropic', defaultEnabled: true, providerId: 'anthropic' },
318+
{ key: 'google', name: 'Google', defaultEnabled: false, providerId: 'google' },
319+
{ key: 'xai', name: 'xAI', defaultEnabled: true, providerId: 'xai' },
320+
],
321+
},
322+
{
323+
label: 'Workspace',
324+
color: '#2ABBF8',
325+
features: [
326+
{ key: 'knowledge-base', name: 'Knowledge Base', defaultEnabled: true },
327+
{ key: 'tables', name: 'Tables', defaultEnabled: true },
328+
{ key: 'copilot', name: 'Copilot', defaultEnabled: false },
329+
{ key: 'environment', name: 'Environment', defaultEnabled: false },
330+
],
331+
},
332+
{
333+
label: 'Tools',
334+
color: '#33C482',
335+
features: [
336+
{ key: 'mcp-tools', name: 'MCP Tools', defaultEnabled: true },
337+
{ key: 'custom-tools', name: 'Custom Tools', defaultEnabled: false },
338+
{ key: 'skills', name: 'Skills', defaultEnabled: true },
339+
{ key: 'invitations', name: 'Invitations', defaultEnabled: true },
340+
],
341+
},
342+
]
302343

303-
{/* Log entries — new items push existing ones down */}
304-
<div className='overflow-hidden'>
305-
<AnimatePresence mode='popLayout' initial={false}>
306-
{entries.map((entry, index) => (
344+
const INITIAL_ACCESS_STATE = Object.fromEntries(
345+
PERMISSION_CATEGORIES.flatMap((category) =>
346+
category.features.map((feature) => [feature.key, feature.defaultEnabled])
347+
)
348+
)
349+
350+
function CheckboxIcon({ checked, color }: { checked: boolean; color: string }) {
351+
return (
352+
<div
353+
className='h-[6px] w-[6px] shrink-0 rounded-full transition-colors duration-200'
354+
style={{
355+
backgroundColor: checked ? color : 'transparent',
356+
border: checked ? 'none' : '1.5px solid #3A3A3A',
357+
}}
358+
/>
359+
)
360+
}
361+
362+
function ProviderPreviewIcon({ providerId }: { providerId?: string }) {
363+
if (!providerId) return null
364+
365+
const ProviderIcon = PROVIDER_DEFINITIONS[providerId]?.icon
366+
if (!ProviderIcon) return null
367+
368+
return (
369+
<div className='relative flex h-[14px] w-[14px] shrink-0 items-center justify-center opacity-50 brightness-0 invert'>
370+
<ProviderIcon className='!h-[14px] !w-[14px]' />
371+
</div>
372+
)
373+
}
374+
375+
function AccessControlPanel() {
376+
const ref = useRef(null)
377+
const isInView = useInView(ref, { once: true, margin: '-40px' })
378+
const [accessState, setAccessState] = useState<Record<string, boolean>>(INITIAL_ACCESS_STATE)
379+
380+
const allFeatures = PERMISSION_CATEGORIES.flatMap((c) => c.features)
381+
382+
return (
383+
<div ref={ref}>
384+
{/* Mobile — single row, subset of features */}
385+
<div className='flex flex-wrap gap-x-5 gap-y-3 lg:hidden'>
386+
{allFeatures.slice(0, 8).map((feature, i) => {
387+
const enabled = accessState[feature.key]
388+
const category = PERMISSION_CATEGORIES.find((c) =>
389+
c.features.some((f) => f.key === feature.key)
390+
)!
391+
392+
return (
307393
<motion.div
308-
key={entry.id}
309-
layout
310-
initial={{ y: -48, opacity: 0 }}
311-
animate={{ y: 0, opacity: 1 }}
312-
exit={{ opacity: 0 }}
313-
transition={{
314-
layout: {
315-
type: 'spring',
316-
stiffness: 380,
317-
damping: 38,
318-
mass: 0.8,
319-
},
320-
y: { duration: 0.32, ease: [0.25, 0.46, 0.45, 0.94] },
321-
opacity: { duration: 0.25 },
322-
}}
394+
key={feature.key}
395+
className='flex cursor-pointer items-center gap-[8px]'
396+
initial={{ opacity: 0, x: -6 }}
397+
animate={isInView ? { opacity: 1, x: 0 } : {}}
398+
transition={{ delay: 0.05 + i * 0.04, duration: 0.3 }}
399+
onClick={() =>
400+
setAccessState((prev) => ({ ...prev, [feature.key]: !prev[feature.key] }))
401+
}
402+
whileTap={{ scale: 0.98 }}
323403
>
324-
<AuditRow entry={entry} index={index} />
404+
<CheckboxIcon checked={enabled} color={category.color} />
405+
<ProviderPreviewIcon providerId={feature.providerId} />
406+
<span
407+
className='font-[430] font-season text-[13px] leading-none tracking-[0.02em]'
408+
style={{ color: enabled ? '#F6F6F6AA' : '#F6F6F640' }}
409+
>
410+
{feature.name}
411+
</span>
325412
</motion.div>
326-
))}
327-
</AnimatePresence>
413+
)
414+
})}
415+
</div>
416+
417+
{/* Desktop — categorized grid */}
418+
<div className='hidden lg:block'>
419+
{PERMISSION_CATEGORIES.map((category, catIdx) => (
420+
<div key={category.label} className={catIdx > 0 ? 'mt-4' : ''}>
421+
<span className='font-[430] font-season text-[#F6F6F6]/30 text-[10px] uppercase leading-none tracking-[0.08em]'>
422+
{category.label}
423+
</span>
424+
<div className='mt-[8px] grid grid-cols-2 gap-x-4 gap-y-[8px]'>
425+
{category.features.map((feature, featIdx) => {
426+
const enabled = accessState[feature.key]
427+
const currentIndex =
428+
PERMISSION_CATEGORIES.slice(0, catIdx).reduce(
429+
(sum, c) => sum + c.features.length,
430+
0
431+
) + featIdx
432+
433+
return (
434+
<motion.div
435+
key={feature.key}
436+
className='flex cursor-pointer items-center gap-[8px] rounded-[4px] py-[2px]'
437+
initial={{ opacity: 0, x: -6 }}
438+
animate={isInView ? { opacity: 1, x: 0 } : {}}
439+
transition={{
440+
delay: 0.1 + currentIndex * 0.04,
441+
duration: 0.3,
442+
ease: [0.25, 0.46, 0.45, 0.94],
443+
}}
444+
onClick={() =>
445+
setAccessState((prev) => ({ ...prev, [feature.key]: !prev[feature.key] }))
446+
}
447+
whileTap={{ scale: 0.98 }}
448+
>
449+
<CheckboxIcon checked={enabled} color={category.color} />
450+
<ProviderPreviewIcon providerId={feature.providerId} />
451+
<span
452+
className='truncate font-[430] font-season text-[11px] leading-none tracking-[0.02em] transition-opacity duration-200'
453+
style={{ color: enabled ? '#F6F6F6AA' : '#F6F6F640' }}
454+
>
455+
{feature.name}
456+
</span>
457+
</motion.div>
458+
)
459+
})}
460+
</div>
461+
</div>
462+
))}
328463
</div>
329464
</div>
330465
)
@@ -420,7 +555,37 @@ export default function Enterprise() {
420555
</div>
421556

422557
<div className='mt-8 overflow-hidden rounded-[12px] bg-[#1C1C1C] sm:mt-10 md:mt-12'>
423-
<AuditLogPreview />
558+
<div className='grid grid-cols-1 border-[#2A2A2A] border-b lg:grid-cols-[1fr_420px]'>
559+
{/* Audit Trail */}
560+
<div className='border-[#2A2A2A] lg:border-r'>
561+
<div className='px-6 pt-6 md:px-8 md:pt-8'>
562+
<h3 className='font-[430] font-season text-[16px] text-white leading-[120%] tracking-[-0.01em]'>
563+
Audit Trail
564+
</h3>
565+
<p className='mt-2 max-w-[480px] font-[430] font-season text-[#F6F6F6]/50 text-[14px] leading-[150%] tracking-[0.02em]'>
566+
Every action is captured with full actor attribution.
567+
</p>
568+
</div>
569+
<AuditLogPreview />
570+
<div className='h-6 md:h-8' />
571+
</div>
572+
573+
{/* Access Control */}
574+
<div className='border-[#2A2A2A] border-t lg:border-t-0'>
575+
<div className='px-6 pt-6 md:px-8 md:pt-8'>
576+
<h3 className='font-[430] font-season text-[16px] text-white leading-[120%] tracking-[-0.01em]'>
577+
Access Control
578+
</h3>
579+
<p className='mt-[6px] font-[430] font-season text-[#F6F6F6]/50 text-[14px] leading-[150%] tracking-[0.02em]'>
580+
Restrict providers, surfaces, and tools per group.
581+
</p>
582+
</div>
583+
<div className='mt-5 px-6 pb-6 md:mt-6 md:px-8 md:pb-8'>
584+
<AccessControlPanel />
585+
</div>
586+
</div>
587+
</div>
588+
424589
<TrustStrip />
425590

426591
{/* Scrolling feature ticker */}

apps/sim/app/(home)/components/footer/footer.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,15 @@ const PRODUCT_LINKS: FooterItem[] = [
1515
{ label: 'Enterprise', href: 'https://form.typeform.com/to/jqCO12pF', external: true },
1616
{ label: 'Self Hosting', href: 'https://docs.sim.ai/self-hosting', external: true },
1717
{ label: 'MCP', href: 'https://docs.sim.ai/mcp', external: true },
18+
{ label: 'Knowledge Base', href: 'https://docs.sim.ai/knowledgebase', external: true },
19+
{ label: 'Tables', href: 'https://docs.sim.ai/tables', external: true },
20+
{ label: 'API', href: 'https://docs.sim.ai/api-reference/getting-started', external: true },
1821
{ label: 'Status', href: 'https://status.sim.ai', external: true },
1922
]
2023

2124
const RESOURCES_LINKS: FooterItem[] = [
2225
{ label: 'Blog', href: '/blog' },
26+
{ label: 'Templates', href: '/templates' },
2327
{ label: 'Docs', href: 'https://docs.sim.ai', external: true },
2428
{ label: 'Careers', href: 'https://jobs.ashbyhq.com/sim', external: true },
2529
{ label: 'Changelog', href: '/changelog' },
@@ -39,6 +43,7 @@ const BLOCK_LINKS: FooterItem[] = [
3943
]
4044

4145
const INTEGRATION_LINKS: FooterItem[] = [
46+
{ label: 'All Integrations →', href: '/integrations' },
4247
{ label: 'Confluence', href: 'https://docs.sim.ai/tools/confluence', external: true },
4348
{ label: 'Slack', href: 'https://docs.sim.ai/tools/slack', external: true },
4449
{ label: 'GitHub', href: 'https://docs.sim.ai/tools/github', external: true },

0 commit comments

Comments
 (0)