Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/sim/app/api/careers/submit/route.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { render } from '@react-email/components'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import CareersConfirmationEmail from '@/components/emails/careers-confirmation-email'
import CareersSubmissionEmail from '@/components/emails/careers-submission-email'
import CareersConfirmationEmail from '@/components/emails/careers/careers-confirmation-email'
import CareersSubmissionEmail from '@/components/emails/careers/careers-submission-email'
import { sendEmail } from '@/lib/email/mailer'
import { createLogger } from '@/lib/logs/console/logger'
import { generateRequestId } from '@/lib/utils'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1642,19 +1642,19 @@ export function ToolInput({
<p className='text-xs'>
{tool.usageControl === 'auto' && (
<span>
<span className='font-medium'>Auto:</span> The model decides when to
use the tool
<span className='font-medium' /> The model decides when to use the
tool
</span>
)}
{tool.usageControl === 'force' && (
<span>
<span className='font-medium'>Force:</span> Always use this tool in
the response
<span className='font-medium' /> Always use this tool in the
response
</span>
)}
{tool.usageControl === 'none' && (
<span>
<span className='font-medium'>Deny:</span> Never use this tool
<span className='font-medium' /> Never use this tool
</span>
)}
</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function Account(_props: AccountProps) {
const router = useRouter()
const brandConfig = useBrandConfig()

// React Query hooks - with placeholderData to show cached data immediately (no skeleton loading!)
// React Query hooks - with placeholderData to show cached data immediately
const { data: profile } = useUserProfile()
const updateProfile = useUpdateUserProfile()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function CreatorProfile() {
const { data: session } = useSession()
const userId = session?.user?.id || ''

// React Query hooks - with placeholderData to show cached data immediately (no skeleton loading!)
// React Query hooks - with placeholderData to show cached data immediately
const { data: organizations = [] } = useOrganizations()
const { data: existingProfile } = useCreatorProfile(userId)
const saveProfile = useSaveCreatorProfile()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { Check, ChevronDown, ExternalLink, Search } from 'lucide-react'
import { useRouter, useSearchParams } from 'next/navigation'
import { Button } from '@/components/emcn'
import { Input, Label } from '@/components/ui'
import { useSession } from '@/lib/auth-client'
import { createLogger } from '@/lib/logs/console/logger'
import { OAUTH_PROVIDERS } from '@/lib/oauth/oauth'
import { cn } from '@/lib/utils'
Expand All @@ -26,63 +25,38 @@ interface CredentialsProps {
export function Credentials({ onOpenChange, registerCloseHandler }: CredentialsProps) {
const router = useRouter()
const searchParams = useSearchParams()
const { data: session } = useSession()
const userId = session?.user?.id
const pendingServiceRef = useRef<HTMLDivElement>(null)

// React Query hooks - with placeholderData to show cached data immediately (no skeleton loading!)
// React Query hooks - with placeholderData to show cached data immediately
const { data: services = [] } = useOAuthConnections()
const connectService = useConnectOAuthService()
const disconnectService = useDisconnectOAuthService()

// Local UI state
const [searchTerm, setSearchTerm] = useState('')
const [pendingService, setPendingService] = useState<string | null>(null)
const [_pendingScopes, setPendingScopes] = useState<string[]>([])
const [authSuccess, setAuthSuccess] = useState(false)
const [showActionRequired, setShowActionRequired] = useState(false)
const prevConnectedIdsRef = useRef<Set<string>>(new Set())
const connectionAddedRef = useRef<boolean>(false)

// Check for OAuth callback
// Check for OAuth callback - just show success message
useEffect(() => {
const code = searchParams.get('code')
const state = searchParams.get('state')
const error = searchParams.get('error')

// Handle OAuth callback
if (code && state) {
// This is an OAuth callback - try to restore state from localStorage
try {
const stored = localStorage.getItem('pending_oauth_state')
if (stored) {
const oauthState = JSON.parse(stored)
logger.info('OAuth callback with restored state:', oauthState)

// Mark as pending if we have context about what service was being connected
if (oauthState.serviceId) {
setPendingService(oauthState.serviceId)
setShowActionRequired(true)
}

// Clean up the state (one-time use)
localStorage.removeItem('pending_oauth_state')
} else {
logger.warn('OAuth callback but no state found in localStorage')
}
} catch (error) {
logger.error('Error loading OAuth state from localStorage:', error)
localStorage.removeItem('pending_oauth_state') // Clean up corrupted state
}

// Set success flag
logger.info('OAuth callback successful')
setAuthSuccess(true)

// Clear the URL parameters
router.replace('/workspace')
// Clear URL parameters without changing the page
const url = new URL(window.location.href)
url.searchParams.delete('code')
url.searchParams.delete('state')
router.replace(url.pathname + url.search)
} else if (error) {
logger.error('OAuth error:', { error })
router.replace('/workspace')
}
}, [searchParams, router])

Expand Down Expand Up @@ -132,6 +106,7 @@ export function Credentials({ onOpenChange, registerCloseHandler }: CredentialsP
scopes: service.scopes,
})

// better-auth will automatically redirect back to this URL after OAuth
await connectService.mutateAsync({
providerId: service.providerId,
callbackURL: window.location.href,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export function Files() {
const params = useParams()
const workspaceId = params?.workspaceId as string

// React Query hooks - with placeholderData to show cached data immediately (no skeleton loading!)
// React Query hooks - with placeholderData to show cached data immediately
const { data: files = [] } = useWorkspaceFiles(workspaceId)
const { data: storageInfo } = useStorageInfo(isBillingEnabled)
const uploadFile = useUploadWorkspaceFile()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function General() {
const [isSuperUser, setIsSuperUser] = useState(false)
const [loadingSuperUser, setLoadingSuperUser] = useState(true)

// React Query hooks - with placeholderData to show cached data immediately (no skeleton loading!)
// React Query hooks - with placeholderData to show cached data immediately
const { data: settings, isLoading } = useGeneralSettings()
const updateSetting = useUpdateGeneralSetting()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const TOOLTIPS = {
}

export function Privacy() {
// React Query hooks - with placeholderData to show cached data immediately (no skeleton loading!)
// React Query hooks - with placeholderData to show cached data immediately
const { data: settings } = useGeneralSettings()
const updateSetting = useUpdateGeneralSetting()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,15 @@ export function Subscription({ onOpenChange }: SubscriptionProps) {
/>
</div>

{/* Enterprise Usage Limit Notice */}
{subscription.isEnterprise && (
<div className='text-center'>
<p className='text-muted-foreground text-xs'>
Contact enterprise for support usage limit changes
</p>
</div>
)}

{/* Cost Breakdown */}
{/* TODO: Re-enable CostBreakdown component in the next billing period
once sufficient copilot cost data has been collected for accurate display.
Expand Down Expand Up @@ -554,14 +563,6 @@ export function Subscription({ onOpenChange }: SubscriptionProps) {
{/* Billing usage notifications toggle */}
{subscription.isPaid && <BillingUsageNotificationsToggle />}

{subscription.isEnterprise && (
<div className='text-center'>
<p className='text-muted-foreground text-xs'>
Contact enterprise for support usage limit changes
</p>
</div>
)}

{/* Cancel Subscription */}
{permissions.canCancelSubscription && (
<div className='mt-2'>
Expand Down Expand Up @@ -631,9 +632,6 @@ function BillingUsageNotificationsToggle() {
const updateSetting = useUpdateGeneralSetting()
const isLoading = updateSetting.isPending

// Settings are automatically loaded by SettingsLoader provider
// No need to load here - Zustand is synced from React Query

return (
<div className='mt-4 flex items-center justify-between'>
<div className='flex flex-col'>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ export function UsageIndicator({ onClick }: UsageIndicatorProps) {
)
}

const handleClick = () => {
const handleClick = async () => {
try {
if (onClick) {
onClick()
Expand All @@ -194,7 +194,35 @@ export function UsageIndicator({ onClick }: UsageIndicatorProps) {
const blocked = getBillingStatus(subscriptionData?.data) === 'blocked'
const canUpg = canUpgrade(subscriptionData?.data)

// Open Settings modal to the subscription tab (upgrade UI lives there)
// If blocked, try to open billing portal directly for faster recovery
if (blocked) {
try {
const context = subscription.isTeam || subscription.isEnterprise ? 'organization' : 'user'
const organizationId =
subscription.isTeam || subscription.isEnterprise
? subscriptionData?.data?.organization?.id
: undefined

const response = await fetch('/api/billing/portal', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ context, organizationId }),
})

if (response.ok) {
const { url } = await response.json()
window.open(url, '_blank')
logger.info('Opened billing portal for blocked account', { context, organizationId })
return
}
} catch (portalError) {
logger.warn('Failed to open billing portal, falling back to settings', {
error: portalError,
})
}
}

// Fallback: Open Settings modal to the subscription tab
if (typeof window !== 'undefined') {
window.dispatchEvent(new CustomEvent('open-settings', { detail: { tab: 'subscription' } }))
logger.info('Opened settings to subscription tab', { blocked, canUpgrade: canUpg })
Expand All @@ -206,7 +234,9 @@ export function UsageIndicator({ onClick }: UsageIndicatorProps) {

return (
<div
className='group flex flex-shrink-0 cursor-pointer flex-col gap-[8px] border-t px-[13.5px] pt-[8px] pb-[10px]'
className={`group flex flex-shrink-0 cursor-pointer flex-col gap-[8px] border-t px-[13.5px] pt-[8px] pb-[10px] ${
isBlocked ? 'border-red-500/50 bg-red-950/20' : ''
}`}
onClick={handleClick}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
Expand All @@ -219,8 +249,8 @@ export function UsageIndicator({ onClick }: UsageIndicatorProps) {
<div className='flex items-center gap-[4px]'>
{isBlocked ? (
<>
<span className='font-medium text-[12px] text-[var(--text-tertiary)]'>Over</span>
<span className='font-medium text-[12px] text-[var(--text-tertiary)]'>limit</span>
<span className='font-medium text-[12px] text-red-400'>Payment</span>
<span className='font-medium text-[12px] text-red-400'>Required</span>
</>
) : (
<>
Expand All @@ -238,10 +268,14 @@ export function UsageIndicator({ onClick }: UsageIndicatorProps) {
{showUpgradeButton && (
<Button
variant='ghost'
className='-mx-1 !h-auto !px-1 !py-0 !text-[#F473B7] group-hover:!text-[#F789C4] mt-[-2px] transition-colors duration-100'
className={`-mx-1 !h-auto !px-1 !py-0 mt-[-2px] transition-colors duration-100 ${
isBlocked
? '!text-red-400 group-hover:!text-red-300'
: '!text-[#F473B7] group-hover:!text-[#F789C4]'
}`}
onClick={handleClick}
>
<span className='font-medium text-[12px]'>Upgrade</span>
<span className='font-medium text-[12px]'>{isBlocked ? 'Fix Now' : 'Upgrade'}</span>
</Button>
)}
</div>
Expand All @@ -251,7 +285,11 @@ export function UsageIndicator({ onClick }: UsageIndicatorProps) {
{Array.from({ length: pillCount }).map((_, i) => {
const isFilled = i < filledPillsCount

const baseColor = isFilled ? (isAlmostOut ? '#ef4444' : '#34B5FF') : '#414141'
const baseColor = isFilled
? isBlocked || isAlmostOut
? '#ef4444'
: '#34B5FF'
: '#414141'

let backgroundColor = baseColor
let backgroundImage: string | undefined
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import {
Text,
} from '@react-email/components'
import { format } from 'date-fns'
import { baseStyles } from '@/components/emails/base-styles'
import EmailFooter from '@/components/emails/footer'
import { getBrandConfig } from '@/lib/branding/branding'
import { getBaseUrl } from '@/lib/urls/utils'
import { baseStyles } from './base-styles'
import EmailFooter from './footer'

interface EnterpriseSubscriptionEmailProps {
userName?: string
Expand Down
Loading