diff --git a/apps/sim/app/(auth)/components/social-login-buttons.tsx b/apps/sim/app/(auth)/components/social-login-buttons.tsx index bfa4b101d2..d160bf9a95 100644 --- a/apps/sim/app/(auth)/components/social-login-buttons.tsx +++ b/apps/sim/app/(auth)/components/social-login-buttons.tsx @@ -99,7 +99,7 @@ export function SocialLoginButtons({ const githubButton = ( - -
- setPassword(e.target.value)} - /> - -
- - - - - - - -

- Don't have an account?{' '} - - Sign up - -

-
- +
+
+

Sign In

+

+ Enter your email below to sign in to your account +

- {/* Forgot Password Dialog */} - - - - Reset Password - - +
+
+
+
+ + +
+
+
+ + +
+
+ setPassword(e.target.value)} + className="bg-neutral-900 border-neutral-700 text-white pr-10" + /> + +
+
+
+ + +
+ +
+
+
+
+
+ or continue with +
+
+ + +
+ +
+ Don't have an account? + + Sign up + +
+
+ + + + + + Reset Password + + +
+
+ Enter your email address and we'll send you a link to reset your password. +
+
+ + setForgotPasswordEmail(e.target.value)} + placeholder="Enter your email" + required + type="email" + className="bg-neutral-900 border-neutral-700/80 text-white focus:border-[#802FFF]/70 focus:ring-[#802FFF]/20" + /> +
+ {resetStatus.type && ( +
+ {resetStatus.message} +
+ )} + +
- + ) } diff --git a/apps/sim/app/(auth)/reset-password/page.tsx b/apps/sim/app/(auth)/reset-password/page.tsx index 353c90219c..7da0409aed 100644 --- a/apps/sim/app/(auth)/reset-password/page.tsx +++ b/apps/sim/app/(auth)/reset-password/page.tsx @@ -12,7 +12,7 @@ import { CardTitle, } from '@/components/ui/card' import { createLogger } from '@/lib/logs/console-logger' -import { SetNewPasswordForm } from '../components/reset-password-form' +import { SetNewPasswordForm } from './reset-password-form' const logger = createLogger('ResetPasswordPage') diff --git a/apps/sim/app/(auth)/components/reset-password-form.tsx b/apps/sim/app/(auth)/reset-password/reset-password-form.tsx similarity index 100% rename from apps/sim/app/(auth)/components/reset-password-form.tsx rename to apps/sim/app/(auth)/reset-password/reset-password-form.tsx diff --git a/apps/sim/app/(auth)/signup/signup-form.tsx b/apps/sim/app/(auth)/signup/signup-form.tsx index 6642665c7f..57d958f2b7 100644 --- a/apps/sim/app/(auth)/signup/signup-form.tsx +++ b/apps/sim/app/(auth)/signup/signup-form.tsx @@ -3,22 +3,13 @@ import { Suspense, useEffect, useState } from 'react' import Link from 'next/link' import { useRouter, useSearchParams } from 'next/navigation' -import { Eye, EyeOff } from 'lucide-react' +import { Command, CornerDownLeft, Eye, EyeOff } from 'lucide-react' import { Button } from '@/components/ui/button' -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from '@/components/ui/card' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { client } from '@/lib/auth-client' import { useNotificationStore } from '@/stores/notifications/store' import { SocialLoginButtons } from '@/app/(auth)/components/social-login-buttons' -import { NotificationList } from '@/app/w/[id]/components/notifications/notifications' const PASSWORD_VALIDATIONS = { minLength: { regex: /.{8,}/, message: 'Password must be at least 8 characters long.' }, @@ -148,7 +139,9 @@ function SignupFormContent({ setPassword(newPassword) // Silently validate but don't show errors - validatePassword(newPassword) + const errors = validatePassword(newPassword) + setPasswordErrors(errors) + setShowValidationError(false) } async function onSubmit(e: React.FormEvent) { @@ -198,20 +191,16 @@ function SignupFormContent({ errorMessage = 'Please enter a valid email address.' } else if (ctx.error.message?.includes('PASSWORD_TOO_SHORT')) { errorMessage = 'Password must be at least 8 characters long.' - } else if (ctx.error.message?.includes('PASSWORD_TOO_LONG')) { - errorMessage = 'Password must be less than 128 characters.' - } else if (ctx.error.message?.includes('USER_ALREADY_EXISTS')) { - errorMessage = 'An account with this email already exists. Please sign in instead.' + } else if (ctx.error.message?.includes('MISSING_CREDENTIALS')) { + errorMessage = 'Please enter all required fields.' + } else if (ctx.error.message?.includes('EMAIL_PASSWORD_DISABLED')) { + errorMessage = 'Email and password signup is disabled.' } else if (ctx.error.message?.includes('FAILED_TO_CREATE_USER')) { errorMessage = 'Failed to create account. Please try again later.' - } else if (ctx.error.message?.includes('FAILED_TO_CREATE_SESSION')) { - errorMessage = 'Failed to create session. Please try again later.' - } else if (ctx.error.message?.includes('rate limit')) { - errorMessage = 'Too many signup attempts. Please try again later.' } else if (ctx.error.message?.includes('network')) { errorMessage = 'Network error. Please check your connection and try again.' - } else if (ctx.error.message?.includes('invalid name')) { - errorMessage = 'Please enter a valid name.' + } else if (ctx.error.message?.includes('rate limit')) { + errorMessage = 'Too many requests. Please wait a moment before trying again.' } addNotification('error', errorMessage, null) @@ -224,126 +213,175 @@ function SignupFormContent({ return } - if (typeof window !== 'undefined') { - sessionStorage.setItem('verificationEmail', emailValue) - - // If this is an invitation flow, store that information for after verification - if (isInviteFlow && redirectUrl) { - sessionStorage.setItem('inviteRedirectUrl', redirectUrl) - sessionStorage.setItem('isInviteFlow', 'true') + // If we have a waitlist token, mark it as used + if (waitlistToken) { + try { + await fetch('/api/waitlist', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + token: waitlistToken, + email: emailValue, + action: 'use', + }), + }) + } catch (error) { + console.error('Error marking waitlist token as used:', error) + // Continue regardless - this is not critical } } - // If verification is required, go to verify page with proper redirect + // Handle invitation flow redirect if (isInviteFlow && redirectUrl) { - router.push( - `/verify?fromSignup=true&redirectAfter=${encodeURIComponent(redirectUrl)}&invite_flow=true` - ) - } else { - router.push(`/verify?fromSignup=true`) + router.push(redirectUrl) + return } - } catch (err: any) { - console.error('Uncaught signup error:', err) - } finally { + + // Send verification OTP in Prod + try { + await client.emailOtp.sendVerificationOtp({ + email: emailValue, + type: 'email-verification', + }) + + if (typeof window !== 'undefined') { + sessionStorage.setItem('verificationEmail', emailValue) + localStorage.setItem('has_logged_in_before', 'true') + document.cookie = 'has_logged_in_before=true; path=/; max-age=31536000; SameSite=Lax' // 1 year expiry + } + + router.push('/verify') + } catch (error) { + console.error('Failed to send verification code:', error) + addNotification('error', 'Account created but failed to send verification code.', null) + router.push('/login') + } + } catch (error) { + console.error('Signup error:', error) setIsLoading(false) } } return ( -
- {/* Ensure NotificationList is always rendered */} - -
-

Sim Studio

- - - Create an account - Enter your details to get started - - -
- -
-
- -
-
- Or continue with -
-
-
-
-
- - -
-
- - setEmail(e.target.value)} - /> -
-
- -
- - -
- {showValidationError && passwordErrors.length > 0 && ( -
-

Password must:

-
    - {passwordErrors.map((error, index) => ( -
  • {error}
  • - ))} -
-
- )} -
- -
-
-
-
- -

- Already have an account?{' '} - - Sign in - -

-
-
+
+
+

Create Account

+

Enter your details to create a new account

-
+ +
+
+
+
+
+ + +
+
+ + setEmail(e.target.value)} + className="bg-neutral-900 border-neutral-700 text-white" + /> +
+
+ +
+ + +
+ {showValidationError && passwordErrors.length > 0 && ( +
+ {passwordErrors.map((error, index) => ( +

{error}

+ ))} +
+ )} +
+
+ + +
+ +
+
+
+
+
+ or continue with +
+
+ + +
+ +
+ Already have an account? + + Sign in + +
+
+ ) } @@ -358,11 +396,7 @@ export default function SignupPage({ }) { return ( -
-
- } + fallback={
Loading...
} > -
-

Sim Studio

- -
- - ) + return } diff --git a/apps/sim/app/(auth)/verify/use-verification.ts b/apps/sim/app/(auth)/verify/use-verification.ts index 548776efa0..ce9beb7df0 100644 --- a/apps/sim/app/(auth)/verify/use-verification.ts +++ b/apps/sim/app/(auth)/verify/use-verification.ts @@ -211,6 +211,17 @@ export function useVerification({ setOtp(value) } + useEffect(() => { + if (!isProduction || !hasResendKey) { + setIsVerified(true) + const timeoutId = setTimeout(() => { + router.push('/w') + }, 1000) + + return () => clearTimeout(timeoutId) + } + }, [isProduction, hasResendKey, router]) + return { otp, email, diff --git a/apps/sim/app/(auth)/verify/verify-content.tsx b/apps/sim/app/(auth)/verify/verify-content.tsx index 79e1b45cb4..cc3a3a036a 100644 --- a/apps/sim/app/(auth)/verify/verify-content.tsx +++ b/apps/sim/app/(auth)/verify/verify-content.tsx @@ -2,14 +2,6 @@ import { Suspense, useEffect, useState } from 'react' import { Button } from '@/components/ui/button' -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from '@/components/ui/card' import { InputOTP, InputOTPGroup, InputOTPSlot } from '@/components/ui/input-otp' import { cn } from '@/lib/utils' import { useVerification } from './use-verification' @@ -59,122 +51,127 @@ function VerificationForm({ } return ( - <> - - {isVerified ? 'Email Verified!' : 'Verify your email'} - - {isVerified ? ( - 'Your email has been verified. Redirecting to dashboard...' - ) : hasResendKey ? ( -

A verification code has been sent to {email || 'your email'}

- ) : !isProduction ? ( -
-

Development mode: No Resend API key configured

-

- Check your console logs for the verification code -

-
- ) : ( -

Error: Invalid API key configuration

- )} -
-
- - {/* Add debug output for error state */} -
- Debug - isInvalidOtp: {String(isInvalidOtp)}, errorMessage: {errorMessage || 'none'} +
+
+

+ {isVerified ? 'Email Verified!' : 'Verify Your Email'} +

+

+ {isVerified + ? 'Your email has been verified. Redirecting to dashboard...' + : hasResendKey + ? `A verification code has been sent to ${email || 'your email'}` + : !isProduction + ? 'Development mode: Check your console logs for the verification code' + : 'Error: Invalid API key configuration'} +

{!isVerified && ( - -

- Enter the 6-digit code to verify your account. - {hasResendKey ? " If you don't see it in your email, check your spam folder." : ''} -

-
+
+
+

+ Enter the 6-digit code to verify your account. + {hasResendKey ? " If you don't see it in your inbox, check your spam folder." : ''} +

+
- - - - - - + + + + + +
-
- {/* Error message - moved above the button for better visibility */} - {errorMessage && ( -
-

{errorMessage}

-
- )} - - - - )} - - {!isVerified && hasResendKey && ( - -

- Didn't receive a code?{' '} - {countdown > 0 ? ( - - Resend in {countdown}s - - ) : ( - + {/* Error message */} + {errorMessage && ( +

+

{errorMessage}

+
)} -

-
+ + + + {hasResendKey && ( +
+

+ Didn't receive a code?{' '} + {countdown > 0 ? ( + + Resend in {countdown}s + + ) : ( + + )} +

+
+ )} +
+
)} - +
) } // Fallback component while the verification form is loading function VerificationFormFallback() { return ( - - Loading verification... - Please wait while we load your verification details... - +
+
+
+
+
+
) } export function VerifyContent({ hasResendKey, baseUrl, isProduction }: VerifyContentProps) { return ( - - }> - - - - {/* Login link for already verified users */} - -

- Already have an account? Go to{' '} - - Login - -

-
-
+ }> + + ) } diff --git a/apps/sim/app/(landing)/components/sections/hero.tsx b/apps/sim/app/(landing)/components/sections/hero.tsx index cdb48acdc1..f52c976e9d 100644 --- a/apps/sim/app/(landing)/components/sections/hero.tsx +++ b/apps/sim/app/(landing)/components/sections/hero.tsx @@ -4,164 +4,73 @@ import React, { useEffect, useState } from 'react' import { useRouter } from 'next/navigation' import { Command, CornerDownLeft } from 'lucide-react' import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' import { useSession } from '@/lib/auth-client' import { GridPattern } from '../grid-pattern' import HeroWorkflowProvider from '../hero-workflow' function Hero() { const router = useRouter() - const [email, setEmail] = useState('') - const [isSubmitting, setIsSubmitting] = useState(false) - const [status, setStatus] = useState<'idle' | 'success' | 'error'>('idle') - const [hasLoggedInBefore, setHasLoggedInBefore] = useState(false) + const [isTransitioning, setIsTransitioning] = useState(true) const { data: session, isPending } = useSession() const isAuthenticated = !isPending && !!session?.user - const [isTransitioning, setIsTransitioning] = useState(true) - const handleStartNowClick = () => { - // Ensure the cookie is set for middleware + const handleNavigate = () => { if (typeof window !== 'undefined') { - document.cookie = 'has_logged_in_before=true; path=/; max-age=31536000; SameSite=Lax' // 1 year expiry - } + // Check if user has an active session + if (isAuthenticated) { + router.push('/w') + } else { + // Check if user has logged in before + const hasLoggedInBefore = + localStorage.getItem('has_logged_in_before') === 'true' || + document.cookie.includes('has_logged_in_before=true') - // If user has an active session, go directly to workflows - if (isAuthenticated) { - router.push('/w') - } else { - // User has logged in before but doesn't have an active session - router.push('/login') - } - } - - useEffect(() => { - // Check if user has previously logged in - if (typeof window !== 'undefined') { - const loggedInBefore = localStorage.getItem('has_logged_in_before') === 'true' - setHasLoggedInBefore(loggedInBefore) - } - - const handler = (e: KeyboardEvent) => { - if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') { - if (isAuthenticated) { - router.push('/w') - } else { + if (hasLoggedInBefore) { + // User has logged in before but doesn't have an active session router.push('/login') + } else { + // User has never logged in before + router.push('/signup') } } } - window.addEventListener('keydown', handler) - return () => window.removeEventListener('keydown', handler) - }, [router, isAuthenticated]) + } + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') { + handleNavigate() + } + } + + window.addEventListener('keydown', handleKeyDown) + return () => window.removeEventListener('keydown', handleKeyDown) + }, [isAuthenticated]) - // Handle transition state useEffect(() => { const timer = setTimeout(() => { setIsTransitioning(false) - }, 800) // Slightly longer than animation-delay to ensure smooth appearance - + }, 300) // Reduced delay for faster button appearance return () => clearTimeout(timer) }, []) - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault() - setStatus('idle') - - // Simple email validation - if (!email.includes('@') || email.trim().length < 5) { - setStatus('error') - return - } - - try { - setIsSubmitting(true) - const response = await fetch('/api/waitlist', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ email }), - }) - - // Always show success for valid emails, regardless of API response - setStatus('success') - setEmail('') - } catch (error) { - // Don't show error to user, just log it - console.error('Error submitting email:', error) - setStatus('success') - } finally { - setIsSubmitting(false) - } - } - - const getButtonText = () => { - if (isSubmitting) return 'Joining...' - if (status === 'success') return 'Joined!' - if (status === 'error') return 'Try again' - return 'Join waitlist' - } - - const getButtonStyle = () => { - switch (status) { - case 'success': - return 'bg-green-500 hover:bg-green-600 text-white' - case 'error': - return 'bg-red-500 hover:bg-red-600 text-white' - default: - return 'bg-[#701ffc] hover:bg-[#802FFF] text-white' - } - } - - // Render the appropriate action UI based on auth status const renderActionUI = () => { - // If we're still in the initial animation phase or auth is pending, - // return an empty div with height to maintain layout consistency if (isTransitioning || isPending) { return
} - - if (isAuthenticated || hasLoggedInBefore) { - return ( - - ) - } - return ( -
-
- setEmail(e.target.value)} - disabled={isSubmitting} - /> - +
Start now
+ - + ) } diff --git a/apps/sim/app/w/components/sidebar/components/settings-modal/components/account/account.tsx b/apps/sim/app/w/components/sidebar/components/settings-modal/components/account/account.tsx index 90806c2710..a12386f90b 100644 --- a/apps/sim/app/w/components/sidebar/components/settings-modal/components/account/account.tsx +++ b/apps/sim/app/w/components/sidebar/components/settings-modal/components/account/account.tsx @@ -15,7 +15,7 @@ import { import { signOut, useSession } from '@/lib/auth-client' import { createLogger } from '@/lib/logs/console-logger' import { cn } from '@/lib/utils' -import { RequestResetForm } from '@/app/(auth)/components/reset-password-form' +import { RequestResetForm } from '@/app/(auth)/reset-password/reset-password-form' import { clearUserData } from '@/stores' const logger = createLogger('Account') diff --git a/apps/sim/app/w/components/sidebar/sidebar.tsx b/apps/sim/app/w/components/sidebar/sidebar.tsx index db7dd0f765..ed42e222a4 100644 --- a/apps/sim/app/w/components/sidebar/sidebar.tsx +++ b/apps/sim/app/w/components/sidebar/sidebar.tsx @@ -1,28 +1,13 @@ 'use client' import { useEffect, useMemo, useState } from 'react' -import Link from 'next/link' import { usePathname, useRouter } from 'next/navigation' import clsx from 'clsx' -import { - ChevronLeft, - ChevronRight, - HelpCircle, - Home, - PanelRight, - PenLine, - ScrollText, - Send, - Settings, - Shapes, - Store, - Users, -} from 'lucide-react' -import { AgentIcon } from '@/components/icons' -import { Button } from '@/components/ui/button' +import { HelpCircle, ScrollText, Send, Settings } from 'lucide-react' import { Skeleton } from '@/components/ui/skeleton' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { useSession } from '@/lib/auth-client' +import { isProd } from '@/lib/environment' import { useSidebarStore } from '@/stores/sidebar/store' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' import { WorkflowMetadata } from '@/stores/workflows/registry/types' @@ -172,6 +157,9 @@ export function Sidebar() { mode === 'hover' && ((isHovered && !isAnyModalOpen && explicitMouseEnter) || workspaceDropdownOpen) + // Invite members is only shown in production + const shouldShowInviteMembers = isProd + return (