diff --git a/apps/sim/app/(auth)/components/auth-button-classes.ts b/apps/sim/app/(auth)/components/auth-button-classes.ts new file mode 100644 index 00000000000..02d1d5e47ed --- /dev/null +++ b/apps/sim/app/(auth)/components/auth-button-classes.ts @@ -0,0 +1,3 @@ +/** Shared className for primary auth form submit buttons across all auth pages. */ +export const AUTH_SUBMIT_BTN = + 'inline-flex h-[32px] w-full items-center justify-center gap-2 rounded-[5px] border border-white bg-white px-2.5 font-[430] font-season text-black text-sm transition-colors hover:border-[var(--border-1)] hover:bg-[var(--border-1)] disabled:cursor-not-allowed disabled:opacity-50' as const diff --git a/apps/sim/app/(auth)/components/branded-button.tsx b/apps/sim/app/(auth)/components/branded-button.tsx deleted file mode 100644 index dab6a6ce3a8..00000000000 --- a/apps/sim/app/(auth)/components/branded-button.tsx +++ /dev/null @@ -1,102 +0,0 @@ -'use client' - -import { forwardRef, useState } from 'react' -import { ArrowRight, ChevronRight, Loader2 } from 'lucide-react' -import { cn } from '@/lib/core/utils/cn' -import { useBrandConfig } from '@/ee/whitelabeling' - -export interface BrandedButtonProps extends React.ButtonHTMLAttributes { - loading?: boolean - loadingText?: string - showArrow?: boolean - fullWidth?: boolean -} - -/** - * Branded button for auth and status pages. - * Default: white button matching the landing page "Get started" style. - * Whitelabel: uses the brand's primary color as background with white text. - */ -export const BrandedButton = forwardRef( - ( - { - children, - loading = false, - loadingText, - showArrow = true, - fullWidth = true, - className, - disabled, - onMouseEnter, - onMouseLeave, - ...props - }, - ref - ) => { - const brand = useBrandConfig() - const hasCustomColor = brand.isWhitelabeled && Boolean(brand.theme?.primaryColor) - const [isHovered, setIsHovered] = useState(false) - - const handleMouseEnter = (e: React.MouseEvent) => { - setIsHovered(true) - onMouseEnter?.(e) - } - - const handleMouseLeave = (e: React.MouseEvent) => { - setIsHovered(false) - onMouseLeave?.(e) - } - - return ( - - ) - } -) - -BrandedButton.displayName = 'BrandedButton' diff --git a/apps/sim/app/(auth)/components/sso-login-button.tsx b/apps/sim/app/(auth)/components/sso-login-button.tsx index e8e25955efc..2800c90ef5d 100644 --- a/apps/sim/app/(auth)/components/sso-login-button.tsx +++ b/apps/sim/app/(auth)/components/sso-login-button.tsx @@ -4,23 +4,18 @@ import { useRouter } from 'next/navigation' import { Button } from '@/components/emcn' import { getEnv, isTruthy } from '@/lib/core/config/env' import { cn } from '@/lib/core/utils/cn' +import { AUTH_SUBMIT_BTN } from '@/app/(auth)/components/auth-button-classes' interface SSOLoginButtonProps { callbackURL?: string className?: string - // Visual variant for button styling and placement contexts - // - 'primary' matches the main auth action button style - // - 'outline' matches social provider buttons variant?: 'primary' | 'outline' - // Optional class used when variant is primary to match brand/gradient - primaryClassName?: string } export function SSOLoginButton({ callbackURL, className, variant = 'outline', - primaryClassName, }: SSOLoginButtonProps) { const router = useRouter() @@ -33,11 +28,6 @@ export function SSOLoginButton({ router.push(ssoUrl) } - const primaryBtnClasses = cn( - primaryClassName || 'branded-button-gradient', - 'flex w-full items-center justify-center gap-2 rounded-[10px] border font-medium text-base text-white transition-all duration-200' - ) - const outlineBtnClasses = cn('w-full rounded-[10px]') return ( @@ -45,7 +35,7 @@ export function SSOLoginButton({ type='button' onClick={handleSSOClick} variant={variant === 'outline' ? 'outline' : undefined} - className={cn(variant === 'outline' ? outlineBtnClasses : primaryBtnClasses, className)} + className={cn(variant === 'outline' ? outlineBtnClasses : AUTH_SUBMIT_BTN, className)} > Sign in with SSO diff --git a/apps/sim/app/(auth)/login/login-form.tsx b/apps/sim/app/(auth)/login/login-form.tsx index c922830fd2d..8a43548acb4 100644 --- a/apps/sim/app/(auth)/login/login-form.tsx +++ b/apps/sim/app/(auth)/login/login-form.tsx @@ -2,7 +2,7 @@ import { useRef, useState } from 'react' import { createLogger } from '@sim/logger' -import { Eye, EyeOff } from 'lucide-react' +import { Eye, EyeOff, Loader2 } from 'lucide-react' import Link from 'next/link' import { useRouter, useSearchParams } from 'next/navigation' import { @@ -20,10 +20,9 @@ import { validateCallbackUrl } from '@/lib/core/security/input-validation' import { cn } from '@/lib/core/utils/cn' import { getBaseUrl } from '@/lib/core/utils/urls' import { quickValidateEmail } from '@/lib/messaging/email/validation' -import { BrandedButton } from '@/app/(auth)/components/branded-button' +import { AUTH_SUBMIT_BTN } from '@/app/(auth)/components/auth-button-classes' import { SocialLoginButtons } from '@/app/(auth)/components/social-login-buttons' import { SSOLoginButton } from '@/app/(auth)/components/sso-login-button' -import { useBrandedButtonClass } from '@/hooks/use-branded-button-class' const logger = createLogger('LoginForm') @@ -87,8 +86,6 @@ export default function LoginPage({ const [passwordErrors, setPasswordErrors] = useState([]) const [showValidationError, setShowValidationError] = useState(false) const [formError, setFormError] = useState(null) - const buttonClass = useBrandedButtonClass() - const callbackUrlParam = searchParams?.get('callbackUrl') const isValidCallbackUrl = callbackUrlParam ? validateCallbackUrl(callbackUrlParam) : false const invalidCallbackRef = useRef(false) @@ -353,11 +350,7 @@ export default function LoginPage({ {/* SSO Login Button (primary top-only when it is the only method) */} {showTopSSO && (
- +
)} @@ -454,14 +447,16 @@ export default function LoginPage({ )} - - Sign in - + )} @@ -488,11 +483,7 @@ export default function LoginPage({ callbackURL={callbackUrl} > {ssoEnabled && !hasOnlySSO && ( - + )} @@ -571,14 +562,16 @@ export default function LoginPage({

{resetStatus.message}

)} - - Send Reset Link - + diff --git a/apps/sim/app/(auth)/oauth/consent/page.tsx b/apps/sim/app/(auth)/oauth/consent/page.tsx index e105b24641d..82127d22f6b 100644 --- a/apps/sim/app/(auth)/oauth/consent/page.tsx +++ b/apps/sim/app/(auth)/oauth/consent/page.tsx @@ -1,12 +1,12 @@ 'use client' import { useCallback, useEffect, useState } from 'react' -import { ArrowLeftRight } from 'lucide-react' +import { ArrowLeftRight, Loader2 } from 'lucide-react' import Image from 'next/image' import { useRouter, useSearchParams } from 'next/navigation' import { Button } from '@/components/emcn' import { signOut, useSession } from '@/lib/auth/auth-client' -import { BrandedButton } from '@/app/(auth)/components/branded-button' +import { AUTH_SUBMIT_BTN } from '@/app/(auth)/components/auth-button-classes' const SCOPE_DESCRIPTIONS: Record = { openid: 'Verify your identity', @@ -150,7 +150,9 @@ export default function OAuthConsentPage() {

- router.push('/')}>Return to Home +
) @@ -230,7 +232,7 @@ export default function OAuthConsentPage() { {scopes.length > 0 && (
-
+

This will allow the application to:

    {scopes.map((s) => ( @@ -257,15 +259,20 @@ export default function OAuthConsentPage() { > Deny - handleConsent(true)} + disabled={submitting} + className={AUTH_SUBMIT_BTN} > - Allow - + {submitting ? ( + + + Authorizing... + + ) : ( + 'Allow' + )} +
) diff --git a/apps/sim/app/(auth)/reset-password/reset-password-form.tsx b/apps/sim/app/(auth)/reset-password/reset-password-form.tsx index f159314377a..0ff43b0561a 100644 --- a/apps/sim/app/(auth)/reset-password/reset-password-form.tsx +++ b/apps/sim/app/(auth)/reset-password/reset-password-form.tsx @@ -1,10 +1,10 @@ 'use client' import { useState } from 'react' -import { Eye, EyeOff } from 'lucide-react' +import { Eye, EyeOff, Loader2 } from 'lucide-react' import { Input, Label } from '@/components/emcn' import { cn } from '@/lib/core/utils/cn' -import { BrandedButton } from '@/app/(auth)/components/branded-button' +import { AUTH_SUBMIT_BTN } from '@/app/(auth)/components/auth-button-classes' interface RequestResetFormProps { email: string @@ -64,14 +64,16 @@ export function RequestResetForm({ )}
- - Send Reset Link - + ) } @@ -219,14 +221,16 @@ export function SetNewPasswordForm({ )} - - Reset Password - + ) } diff --git a/apps/sim/app/(auth)/signup/signup-form.tsx b/apps/sim/app/(auth)/signup/signup-form.tsx index 75467feade5..c3db07b511e 100644 --- a/apps/sim/app/(auth)/signup/signup-form.tsx +++ b/apps/sim/app/(auth)/signup/signup-form.tsx @@ -3,7 +3,7 @@ import { Suspense, useMemo, useRef, useState } from 'react' import { Turnstile, type TurnstileInstance } from '@marsidev/react-turnstile' import { createLogger } from '@sim/logger' -import { Eye, EyeOff } from 'lucide-react' +import { Eye, EyeOff, Loader2 } from 'lucide-react' import Link from 'next/link' import { useRouter, useSearchParams } from 'next/navigation' import { Input, Label } from '@/components/emcn' @@ -11,10 +11,9 @@ import { client, useSession } from '@/lib/auth/auth-client' import { getEnv, isFalsy, isTruthy } from '@/lib/core/config/env' import { cn } from '@/lib/core/utils/cn' import { quickValidateEmail } from '@/lib/messaging/email/validation' -import { BrandedButton } from '@/app/(auth)/components/branded-button' +import { AUTH_SUBMIT_BTN } from '@/app/(auth)/components/auth-button-classes' import { SocialLoginButtons } from '@/app/(auth)/components/social-login-buttons' import { SSOLoginButton } from '@/app/(auth)/components/sso-login-button' -import { useBrandedButtonClass } from '@/hooks/use-branded-button-class' const logger = createLogger('SignupForm') @@ -96,8 +95,6 @@ function SignupFormContent({ const captchaResolveRef = useRef<((token: string) => void) | null>(null) const captchaRejectRef = useRef<((reason: Error) => void) | null>(null) const turnstileSiteKey = useMemo(() => getEnv('NEXT_PUBLIC_TURNSTILE_SITE_KEY'), []) - const buttonClass = useBrandedButtonClass() - const redirectUrl = useMemo( () => searchParams.get('redirect') || searchParams.get('callbackUrl') || '', [searchParams] @@ -380,11 +377,7 @@ function SignupFormContent({ return hasOnlySSO })() && (
- +
)} @@ -548,15 +541,16 @@ function SignupFormContent({ )} - - Create account - + )} @@ -602,11 +596,7 @@ function SignupFormContent({ isProduction={isProduction} > {isTruthy(getEnv('NEXT_PUBLIC_SSO_ENABLED')) && ( - + )} diff --git a/apps/sim/app/(auth)/verify/verify-content.tsx b/apps/sim/app/(auth)/verify/verify-content.tsx index cf613ba3e30..9f5b7e47590 100644 --- a/apps/sim/app/(auth)/verify/verify-content.tsx +++ b/apps/sim/app/(auth)/verify/verify-content.tsx @@ -1,10 +1,11 @@ 'use client' import { Suspense, useEffect, useState } from 'react' +import { Loader2 } from 'lucide-react' import { useRouter } from 'next/navigation' import { InputOTP, InputOTPGroup, InputOTPSlot } from '@/components/emcn' import { cn } from '@/lib/core/utils/cn' -import { BrandedButton } from '@/app/(auth)/components/branded-button' +import { AUTH_SUBMIT_BTN } from '@/app/(auth)/components/auth-button-classes' import { useVerification } from '@/app/(auth)/verify/use-verification' interface VerifyContentProps { @@ -110,15 +111,20 @@ function VerificationForm({ )} - - Verify Email - + {isLoading ? ( + + + Verifying... + + ) : ( + 'Verify Email' + )} + {hasEmailService && (
diff --git a/apps/sim/app/(landing)/actions/github.ts b/apps/sim/app/(home)/actions/github.ts similarity index 100% rename from apps/sim/app/(landing)/actions/github.ts rename to apps/sim/app/(home)/actions/github.ts diff --git a/apps/sim/app/(home)/components/footer/footer-cta.tsx b/apps/sim/app/(home)/components/footer/footer-cta.tsx index b4df3534957..b67ae3b3f84 100644 --- a/apps/sim/app/(home)/components/footer/footer-cta.tsx +++ b/apps/sim/app/(home)/components/footer/footer-cta.tsx @@ -4,7 +4,7 @@ import { useCallback, useRef, useState } from 'react' import { ArrowUp } from 'lucide-react' import Link from 'next/link' 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' +import { useAnimatedPlaceholder } from '@/hooks/use-animated-placeholder' const MAX_HEIGHT = 120 diff --git a/apps/sim/app/(home)/components/landing-preview/components/landing-preview-home/landing-preview-home.tsx b/apps/sim/app/(home)/components/landing-preview/components/landing-preview-home/landing-preview-home.tsx index 5413f9b4d59..3a7b88d2ff6 100644 --- a/apps/sim/app/(home)/components/landing-preview/components/landing-preview-home/landing-preview-home.tsx +++ b/apps/sim/app/(home)/components/landing-preview/components/landing-preview-home/landing-preview-home.tsx @@ -3,7 +3,7 @@ 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' +import { useAnimatedPlaceholder } from '@/hooks/use-animated-placeholder' const C = { SURFACE: '#292929', diff --git a/apps/sim/app/(home)/components/navbar/components/github-stars.tsx b/apps/sim/app/(home)/components/navbar/components/github-stars.tsx index f33f7619c18..7fd496d8181 100644 --- a/apps/sim/app/(home)/components/navbar/components/github-stars.tsx +++ b/apps/sim/app/(home)/components/navbar/components/github-stars.tsx @@ -3,7 +3,7 @@ import { useEffect, useState } from 'react' import { createLogger } from '@sim/logger' import { GithubOutlineIcon } from '@/components/icons' -import { getFormattedGitHubStars } from '@/app/(landing)/actions/github' +import { getFormattedGitHubStars } from '@/app/(home)/actions/github' const logger = createLogger('github-stars') diff --git a/apps/sim/app/(landing)/components/background/background-svg.tsx b/apps/sim/app/(landing)/components/background/background-svg.tsx deleted file mode 100644 index 98bea428c54..00000000000 --- a/apps/sim/app/(landing)/components/background/background-svg.tsx +++ /dev/null @@ -1,136 +0,0 @@ -export default function BackgroundSVG() { - return ( - - ) -} diff --git a/apps/sim/app/(landing)/components/background/background.tsx b/apps/sim/app/(landing)/components/background/background.tsx deleted file mode 100644 index 46f7d8d0781..00000000000 --- a/apps/sim/app/(landing)/components/background/background.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import dynamic from 'next/dynamic' -import { cn } from '@/lib/core/utils/cn' - -// Lazy load the SVG to reduce initial bundle size -const BackgroundSVG = dynamic(() => import('./background-svg'), { - ssr: true, // Enable SSR for SEO - loading: () => null, // Don't show loading state -}) - -type BackgroundProps = { - className?: string - children?: React.ReactNode -} - -export default function Background({ className, children }: BackgroundProps) { - return ( -
-
- -
{children}
-
- ) -} diff --git a/apps/sim/app/(landing)/components/footer/components/compliance-badges.tsx b/apps/sim/app/(landing)/components/footer/components/compliance-badges.tsx deleted file mode 100644 index 82b484d1792..00000000000 --- a/apps/sim/app/(landing)/components/footer/components/compliance-badges.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import Image from 'next/image' -import Link from 'next/link' -import { HIPAABadgeIcon } from '@/components/icons' - -export default function ComplianceBadges() { - return ( -
- {/* SOC2 badge */} - - SOC2 Compliant - - {/* HIPAA badge */} - - - -
- ) -} diff --git a/apps/sim/app/(landing)/components/footer/components/index.ts b/apps/sim/app/(landing)/components/footer/components/index.ts deleted file mode 100644 index 2f9863a65e1..00000000000 --- a/apps/sim/app/(landing)/components/footer/components/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import ComplianceBadges from './compliance-badges' -import Logo from './logo' -import SocialLinks from './social-links' -import StatusIndicator from './status-indicator' - -export { ComplianceBadges, Logo, SocialLinks, StatusIndicator } diff --git a/apps/sim/app/(landing)/components/footer/components/logo.tsx b/apps/sim/app/(landing)/components/footer/components/logo.tsx deleted file mode 100644 index f9c2ec12cde..00000000000 --- a/apps/sim/app/(landing)/components/footer/components/logo.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import Image from 'next/image' -import Link from 'next/link' - -export default function Logo() { - return ( - - Sim - Workflows for LLMs - - ) -} diff --git a/apps/sim/app/(landing)/components/footer/components/social-links.tsx b/apps/sim/app/(landing)/components/footer/components/social-links.tsx deleted file mode 100644 index 2205c2d390d..00000000000 --- a/apps/sim/app/(landing)/components/footer/components/social-links.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { DiscordIcon, GithubIcon, LinkedInIcon, xIcon as XIcon } from '@/components/icons' - -export default function SocialLinks() { - return ( - - ) -} diff --git a/apps/sim/app/(landing)/components/footer/components/status-indicator.tsx b/apps/sim/app/(landing)/components/footer/components/status-indicator.tsx deleted file mode 100644 index 046b652b9a5..00000000000 --- a/apps/sim/app/(landing)/components/footer/components/status-indicator.tsx +++ /dev/null @@ -1,69 +0,0 @@ -'use client' - -import type { SVGProps } from 'react' -import Link from 'next/link' -import type { StatusType } from '@/app/api/status/types' -import { useStatus } from '@/hooks/queries/status' - -interface StatusDotIconProps extends SVGProps { - status: 'operational' | 'degraded' | 'outage' | 'maintenance' | 'loading' | 'error' -} - -export function StatusDotIcon({ status, className, ...props }: StatusDotIconProps) { - const colors = { - operational: '#10B981', - degraded: '#F59E0B', - outage: '#EF4444', - maintenance: '#3B82F6', - loading: '#9CA3AF', - error: '#9CA3AF', - } - - return ( - - - - ) -} - -const STATUS_COLORS: Record = { - operational: 'text-[#10B981] hover:text-[#059669]', - degraded: 'text-[var(--caution)] hover:text-[#D97706]', - outage: 'text-[var(--text-error)] hover:text-[var(--error)]', - maintenance: 'text-[#3B82F6] hover:text-[#2563EB]', - loading: 'text-muted-foreground hover:text-foreground', - error: 'text-muted-foreground hover:text-foreground', -} - -export default function StatusIndicator() { - const { data, isLoading, isError } = useStatus() - - const status = isLoading ? 'loading' : isError ? 'error' : data?.status || 'error' - const message = isLoading - ? 'Checking Status...' - : isError - ? 'Status Unknown' - : data?.message || 'Status Unknown' - const statusUrl = data?.url || 'https://status.sim.ai' - - return ( - -