Skip to content

Commit fd83d5f

Browse files
committed
feat: badges, usage limit; fix(note): iframe security; improvement(s-modal): sizing
1 parent 45d2cab commit fd83d5f

File tree

11 files changed

+505
-439
lines changed

11 files changed

+505
-439
lines changed

apps/sim/app/api/billing/route.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export async function GET(request: NextRequest) {
9393
const { searchParams } = new URL(request.url)
9494
const context = searchParams.get('context') || 'user'
9595
const contextId = searchParams.get('id')
96+
const includeOrg = searchParams.get('includeOrg') === 'true'
9697

9798
// Validate context parameter
9899
if (!['user', 'organization'].includes(context)) {
@@ -115,14 +116,38 @@ export async function GET(request: NextRequest) {
115116
if (context === 'user') {
116117
// Get user billing (may include organization if they're part of one)
117118
billingData = await getSimplifiedBillingSummary(session.user.id, contextId || undefined)
119+
118120
// Attach effective billing blocked status (includes org owner check)
119121
const billingStatus = await getEffectiveBillingStatus(session.user.id)
122+
120123
billingData = {
121124
...billingData,
122125
billingBlocked: billingStatus.billingBlocked,
123126
billingBlockedReason: billingStatus.billingBlockedReason,
124127
blockedByOrgOwner: billingStatus.blockedByOrgOwner,
125128
}
129+
130+
// Optionally include organization membership and role
131+
if (includeOrg) {
132+
const userMembership = await db
133+
.select({
134+
organizationId: member.organizationId,
135+
role: member.role,
136+
})
137+
.from(member)
138+
.where(eq(member.userId, session.user.id))
139+
.limit(1)
140+
141+
if (userMembership.length > 0) {
142+
billingData = {
143+
...billingData,
144+
organization: {
145+
id: userMembership[0].organizationId,
146+
role: userMembership[0].role as 'owner' | 'admin' | 'member',
147+
},
148+
}
149+
}
150+
}
126151
} else {
127152
// Get user role in organization for permission checks first
128153
const memberRecord = await db

apps/sim/app/workspace/[workspaceId]/logs/utils.ts

Lines changed: 43 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,15 @@ import { getIntegrationMetadata } from '@/lib/logs/get-trigger-options'
55
import { getBlock } from '@/blocks/registry'
66

77
const CORE_TRIGGER_TYPES = ['manual', 'api', 'schedule', 'chat', 'webhook'] as const
8-
const RUNNING_COLOR = '#22c55e' as const
9-
const PENDING_COLOR = '#f59e0b' as const
8+
9+
/** Possible execution status values for workflow logs */
1010
export type LogStatus = 'error' | 'pending' | 'running' | 'info' | 'cancelled'
1111

12+
/**
13+
* Maps raw status string to LogStatus for display.
14+
* @param status - Raw status from API
15+
* @returns Normalized LogStatus value
16+
*/
1217
export function getDisplayStatus(status: string | null | undefined): LogStatus {
1318
switch (status) {
1419
case 'running':
@@ -24,146 +29,75 @@ export function getDisplayStatus(status: string | null | undefined): LogStatus {
2429
}
2530
}
2631

27-
/**
28-
* Checks if a hex color is gray/neutral (low saturation) or too light/dark
29-
*/
30-
export function isGrayOrNeutral(hex: string): boolean {
31-
const r = Number.parseInt(hex.slice(1, 3), 16)
32-
const g = Number.parseInt(hex.slice(3, 5), 16)
33-
const b = Number.parseInt(hex.slice(5, 7), 16)
34-
35-
const max = Math.max(r, g, b)
36-
const min = Math.min(r, g, b)
37-
const lightness = (max + min) / 2 / 255
38-
39-
const delta = max - min
40-
const saturation = delta === 0 ? 0 : delta / (1 - Math.abs(2 * lightness - 1)) / 255
41-
42-
return saturation < 0.2 || lightness > 0.8 || lightness < 0.25
32+
/** Configuration mapping log status to Badge variant and display label */
33+
const STATUS_VARIANT_MAP: Record<
34+
LogStatus,
35+
{ variant: React.ComponentProps<typeof Badge>['variant']; label: string }
36+
> = {
37+
error: { variant: 'red', label: 'Error' },
38+
pending: { variant: 'amber', label: 'Pending' },
39+
running: { variant: 'green', label: 'Running' },
40+
cancelled: { variant: 'gray', label: 'Cancelled' },
41+
info: { variant: 'gray', label: 'Info' },
4342
}
4443

45-
/**
46-
* Converts a hex color to a background variant with appropriate opacity
47-
*/
48-
export function hexToBackground(hex: string): string {
49-
const r = Number.parseInt(hex.slice(1, 3), 16)
50-
const g = Number.parseInt(hex.slice(3, 5), 16)
51-
const b = Number.parseInt(hex.slice(5, 7), 16)
52-
return `rgba(${r}, ${g}, ${b}, 0.2)`
53-
}
54-
55-
/**
56-
* Lightens a hex color to make it more vibrant for text
57-
*/
58-
export function lightenColor(hex: string, percent = 30): string {
59-
const r = Number.parseInt(hex.slice(1, 3), 16)
60-
const g = Number.parseInt(hex.slice(3, 5), 16)
61-
const b = Number.parseInt(hex.slice(5, 7), 16)
62-
63-
const newR = Math.min(255, Math.round(r + (255 - r) * (percent / 100)))
64-
const newG = Math.min(255, Math.round(g + (255 - g) * (percent / 100)))
65-
const newB = Math.min(255, Math.round(b + (255 - b) * (percent / 100)))
66-
67-
return `#${newR.toString(16).padStart(2, '0')}${newG.toString(16).padStart(2, '0')}${newB.toString(16).padStart(2, '0')}`
44+
/** Configuration mapping core trigger types to Badge color variants */
45+
const TRIGGER_VARIANT_MAP: Record<string, React.ComponentProps<typeof Badge>['variant']> = {
46+
manual: 'gray-secondary',
47+
api: 'blue',
48+
schedule: 'teal',
49+
chat: 'purple',
50+
webhook: 'orange',
6851
}
6952

7053
interface StatusBadgeProps {
54+
/** The execution status to display */
7155
status: LogStatus
7256
}
7357

7458
/**
75-
* Displays a styled badge for a log execution status
59+
* Renders a colored badge indicating log execution status.
60+
* @param props - Component props containing the status
61+
* @returns A Badge with dot indicator and status label
7662
*/
7763
export const StatusBadge = React.memo(({ status }: StatusBadgeProps) => {
78-
const config = {
79-
error: {
80-
bg: 'var(--terminal-status-error-bg)',
81-
color: 'var(--text-error)',
82-
label: 'Error',
83-
},
84-
pending: {
85-
bg: hexToBackground(PENDING_COLOR),
86-
color: lightenColor(PENDING_COLOR, 65),
87-
label: 'Pending',
88-
},
89-
running: {
90-
bg: hexToBackground(RUNNING_COLOR),
91-
color: lightenColor(RUNNING_COLOR, 65),
92-
label: 'Running',
93-
},
94-
cancelled: {
95-
bg: 'var(--terminal-status-info-bg)',
96-
color: 'var(--terminal-status-info-color)',
97-
label: 'Cancelled',
98-
},
99-
info: {
100-
bg: 'var(--terminal-status-info-bg)',
101-
color: 'var(--terminal-status-info-color)',
102-
label: 'Info',
103-
},
104-
}[status]
105-
106-
return React.createElement(
107-
'div',
108-
{
109-
className:
110-
'inline-flex items-center gap-[6px] rounded-[6px] px-[9px] py-[2px] font-medium text-[12px]',
111-
style: { backgroundColor: config.bg, color: config.color },
112-
},
113-
React.createElement('div', {
114-
className: 'h-[6px] w-[6px] rounded-[2px]',
115-
style: { backgroundColor: config.color },
116-
}),
117-
config.label
118-
)
64+
const config = STATUS_VARIANT_MAP[status]
65+
return React.createElement(Badge, { variant: config.variant, dot: true }, config.label)
11966
})
12067

12168
StatusBadge.displayName = 'StatusBadge'
12269

12370
interface TriggerBadgeProps {
71+
/** The trigger type identifier (e.g., 'manual', 'api', or integration block type) */
12472
trigger: string
12573
}
12674

12775
/**
128-
* Displays a styled badge for a workflow trigger type
76+
* Renders a colored badge indicating the workflow trigger type.
77+
* Core triggers display with their designated colors; integrations show with icons.
78+
* @param props - Component props containing the trigger type
79+
* @returns A Badge with appropriate styling for the trigger type
12980
*/
13081
export const TriggerBadge = React.memo(({ trigger }: TriggerBadgeProps) => {
13182
const metadata = getIntegrationMetadata(trigger)
13283
const isIntegration = !(CORE_TRIGGER_TYPES as readonly string[]).includes(trigger)
13384
const block = isIntegration ? getBlock(trigger) : null
13485
const IconComponent = block?.icon
13586

136-
const isUnknownIntegration = isIntegration && trigger !== 'generic' && !block
137-
if (
138-
trigger === 'manual' ||
139-
trigger === 'generic' ||
140-
isUnknownIntegration ||
141-
isGrayOrNeutral(metadata.color)
142-
) {
87+
const coreVariant = TRIGGER_VARIANT_MAP[trigger]
88+
if (coreVariant) {
89+
return React.createElement(Badge, { variant: coreVariant }, metadata.label)
90+
}
91+
92+
if (IconComponent) {
14393
return React.createElement(
14494
Badge,
145-
{
146-
variant: 'default',
147-
className:
148-
'inline-flex items-center gap-[6px] rounded-[6px] px-[9px] py-[2px] font-medium text-[12px]',
149-
},
150-
IconComponent && React.createElement(IconComponent, { className: 'h-[12px] w-[12px]' }),
95+
{ variant: 'gray-secondary', icon: IconComponent },
15196
metadata.label
15297
)
15398
}
15499

155-
const textColor = lightenColor(metadata.color, 65)
156-
157-
return React.createElement(
158-
'div',
159-
{
160-
className:
161-
'inline-flex items-center gap-[6px] rounded-[6px] px-[9px] py-[2px] font-medium text-[12px]',
162-
style: { backgroundColor: hexToBackground(metadata.color), color: textColor },
163-
},
164-
IconComponent && React.createElement(IconComponent, { className: 'h-[12px] w-[12px]' }),
165-
metadata.label
166-
)
100+
return React.createElement(Badge, { variant: 'gray-secondary' }, metadata.label)
167101
})
168102

169103
TriggerBadge.displayName = 'TriggerBadge'

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/note-block/note-block.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,11 @@ const NoteMarkdown = memo(function NoteMarkdown({ content }: { content: string }
126126
<iframe
127127
src={`https://www.youtube.com/embed/${videoId}`}
128128
title='YouTube video'
129-
allow='accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture'
129+
allow='accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture; web-share'
130130
allowFullScreen
131+
loading='lazy'
132+
referrerPolicy='strict-origin-when-cross-origin'
133+
sandbox='allow-scripts allow-same-origin allow-presentation allow-popups'
131134
className='aspect-video w-full'
132135
/>
133136
</span>

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/deploy-modal.tsx

Lines changed: 8 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
'use client'
22

33
import { useCallback, useEffect, useState } from 'react'
4-
import clsx from 'clsx'
54
import {
5+
Badge,
66
Button,
77
Modal,
88
ModalBody,
@@ -729,29 +729,10 @@ interface StatusBadgeProps {
729729

730730
function StatusBadge({ isWarning }: StatusBadgeProps) {
731731
const label = isWarning ? 'Update deployment' : 'Live'
732-
733732
return (
734-
<div
735-
className={clsx(
736-
'flex h-[24px] items-center justify-start gap-[8px] rounded-[6px] border px-[9px]',
737-
isWarning ? 'border-[#A16207] bg-[#452C0F]' : 'border-[#22703D] bg-[#14291B]'
738-
)}
739-
>
740-
<div
741-
className='h-[6px] w-[6px] rounded-[2px]'
742-
style={{
743-
backgroundColor: isWarning ? '#EAB308' : '#4ADE80',
744-
}}
745-
/>
746-
<span
747-
className='font-medium text-[11.5px]'
748-
style={{
749-
color: isWarning ? '#EAB308' : '#86EFAC',
750-
}}
751-
>
752-
{label}
753-
</span>
754-
</div>
733+
<Badge variant={isWarning ? 'amber' : 'green'} size='lg' dot>
734+
{label}
735+
</Badge>
755736
)
756737
}
757738

@@ -771,35 +752,10 @@ function TemplateStatusBadge({ status, views, stars }: TemplateStatusBadgeProps)
771752
: null
772753

773754
return (
774-
<div
775-
className={clsx(
776-
'flex h-[24px] items-center justify-start gap-[8px] rounded-[6px] border px-[9px]',
777-
isPending ? 'border-[#A16207] bg-[#452C0F]' : 'border-[#22703D] bg-[#14291B]'
778-
)}
779-
>
780-
<div
781-
className='h-[6px] w-[6px] rounded-[2px]'
782-
style={{
783-
backgroundColor: isPending ? '#EAB308' : '#4ADE80',
784-
}}
785-
/>
786-
<span
787-
className='font-medium text-[11.5px]'
788-
style={{
789-
color: isPending ? '#EAB308' : '#86EFAC',
790-
}}
791-
>
792-
{label}
793-
</span>
794-
{statsText && (
795-
<span
796-
className='font-medium text-[11.5px]'
797-
style={{ color: isPending ? '#EAB308' : '#86EFAC' }}
798-
>
799-
{statsText}
800-
</span>
801-
)}
802-
</div>
755+
<Badge variant={isPending ? 'amber' : 'green'} size='lg' dot>
756+
{label}
757+
{statsText && <span>{statsText}</span>}
758+
</Badge>
803759
)
804760
}
805761

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/terminal.tsx

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
import Link from 'next/link'
2525
import { useShallow } from 'zustand/react/shallow'
2626
import {
27+
Badge,
2728
Button,
2829
Code,
2930
Input,
@@ -1327,33 +1328,9 @@ export function Terminal() {
13271328
{/* Status */}
13281329
<div className={clsx(COLUMN_WIDTHS.STATUS, COLUMN_BASE_CLASS)}>
13291330
{statusInfo ? (
1330-
<div
1331-
className={clsx(
1332-
'flex h-[24px] w-[56px] items-center justify-start rounded-[6px] border pl-[9px]',
1333-
statusInfo.isError
1334-
? 'gap-[5px] border-[var(--terminal-status-error-border)] bg-[var(--terminal-status-error-bg)]'
1335-
: 'gap-[8px] border-[var(--terminal-status-info-border)] bg-[var(--terminal-status-info-bg)]'
1336-
)}
1337-
>
1338-
<div
1339-
className='h-[6px] w-[6px] rounded-[2px]'
1340-
style={{
1341-
backgroundColor: statusInfo.isError
1342-
? 'var(--text-error)'
1343-
: 'var(--terminal-status-info-color)',
1344-
}}
1345-
/>
1346-
<span
1347-
className='font-medium text-[11.5px]'
1348-
style={{
1349-
color: statusInfo.isError
1350-
? 'var(--text-error)'
1351-
: 'var(--terminal-status-info-color)',
1352-
}}
1353-
>
1354-
{statusInfo.label}
1355-
</span>
1356-
</div>
1331+
<Badge variant={statusInfo.isError ? 'red' : 'gray'} dot>
1332+
{statusInfo.label}
1333+
</Badge>
13571334
) : (
13581335
<span className={ROW_TEXT_CLASS}>-</span>
13591336
)}

0 commit comments

Comments
 (0)