chore(careers): remove careers page, redirect to Ashby jobs portal (#3401)

* chore(careers): remove careers page, redirect to Ashby jobs portal

* lint
This commit is contained in:
Waleed
2026-03-02 14:12:03 -08:00
committed by GitHub
parent ebb9a2bdd3
commit a8e0203a92
14 changed files with 16 additions and 1197 deletions

View File

@@ -1,534 +0,0 @@
'use client'
import { useRef, useState } from 'react'
import { createLogger } from '@sim/logger'
import { X } from 'lucide-react'
import { Textarea } from '@/components/emcn'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { isHosted } from '@/lib/core/config/feature-flags'
import { cn } from '@/lib/core/utils/cn'
import { quickValidateEmail } from '@/lib/messaging/email/validation'
import { soehne } from '@/app/_styles/fonts/soehne/soehne'
import { BrandedButton } from '@/app/(auth)/components/branded-button'
import Footer from '@/app/(landing)/components/footer/footer'
import Nav from '@/app/(landing)/components/nav/nav'
const logger = createLogger('CareersPage')
const validateName = (name: string): string[] => {
const errors: string[] = []
if (!name || name.trim().length < 2) {
errors.push('Name must be at least 2 characters')
}
return errors
}
const validateEmail = (email: string): string[] => {
const errors: string[] = []
if (!email || !email.trim()) {
errors.push('Email is required')
return errors
}
const validation = quickValidateEmail(email.trim().toLowerCase())
if (!validation.isValid) {
errors.push(validation.reason || 'Please enter a valid email address')
}
return errors
}
const validatePosition = (position: string): string[] => {
const errors: string[] = []
if (!position || position.trim().length < 2) {
errors.push('Please specify the position you are interested in')
}
return errors
}
const validateLinkedIn = (url: string): string[] => {
if (!url || url.trim() === '') return []
const errors: string[] = []
try {
new URL(url)
} catch {
errors.push('Please enter a valid LinkedIn URL')
}
return errors
}
const validatePortfolio = (url: string): string[] => {
if (!url || url.trim() === '') return []
const errors: string[] = []
try {
new URL(url)
} catch {
errors.push('Please enter a valid portfolio URL')
}
return errors
}
const validateLocation = (location: string): string[] => {
const errors: string[] = []
if (!location || location.trim().length < 2) {
errors.push('Please enter your location')
}
return errors
}
const validateMessage = (message: string): string[] => {
const errors: string[] = []
if (!message || message.trim().length < 50) {
errors.push('Please tell us more about yourself (at least 50 characters)')
}
return errors
}
export default function CareersPage() {
const [isSubmitting, setIsSubmitting] = useState(false)
const [submitStatus, setSubmitStatus] = useState<'idle' | 'success' | 'error'>('idle')
const [showErrors, setShowErrors] = useState(false)
// Form fields
const [name, setName] = useState('')
const [email, setEmail] = useState('')
const [phone, setPhone] = useState('')
const [position, setPosition] = useState('')
const [linkedin, setLinkedin] = useState('')
const [portfolio, setPortfolio] = useState('')
const [experience, setExperience] = useState('')
const [location, setLocation] = useState('')
const [message, setMessage] = useState('')
const [resume, setResume] = useState<File | null>(null)
const fileInputRef = useRef<HTMLInputElement>(null)
// Field errors
const [nameErrors, setNameErrors] = useState<string[]>([])
const [emailErrors, setEmailErrors] = useState<string[]>([])
const [positionErrors, setPositionErrors] = useState<string[]>([])
const [linkedinErrors, setLinkedinErrors] = useState<string[]>([])
const [portfolioErrors, setPortfolioErrors] = useState<string[]>([])
const [experienceErrors, setExperienceErrors] = useState<string[]>([])
const [locationErrors, setLocationErrors] = useState<string[]>([])
const [messageErrors, setMessageErrors] = useState<string[]>([])
const [resumeErrors, setResumeErrors] = useState<string[]>([])
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0] || null
setResume(file)
if (file) {
setResumeErrors([])
}
}
async function onSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault()
setShowErrors(true)
// Validate all fields
const nameErrs = validateName(name)
const emailErrs = validateEmail(email)
const positionErrs = validatePosition(position)
const linkedinErrs = validateLinkedIn(linkedin)
const portfolioErrs = validatePortfolio(portfolio)
const experienceErrs = experience ? [] : ['Please select your years of experience']
const locationErrs = validateLocation(location)
const messageErrs = validateMessage(message)
const resumeErrs = resume ? [] : ['Resume is required']
setNameErrors(nameErrs)
setEmailErrors(emailErrs)
setPositionErrors(positionErrs)
setLinkedinErrors(linkedinErrs)
setPortfolioErrors(portfolioErrs)
setExperienceErrors(experienceErrs)
setLocationErrors(locationErrs)
setMessageErrors(messageErrs)
setResumeErrors(resumeErrs)
if (
nameErrs.length > 0 ||
emailErrs.length > 0 ||
positionErrs.length > 0 ||
linkedinErrs.length > 0 ||
portfolioErrs.length > 0 ||
experienceErrs.length > 0 ||
locationErrs.length > 0 ||
messageErrs.length > 0 ||
resumeErrs.length > 0
) {
return
}
setIsSubmitting(true)
setSubmitStatus('idle')
try {
const formData = new FormData()
formData.append('name', name)
formData.append('email', email)
formData.append('phone', phone || '')
formData.append('position', position)
formData.append('linkedin', linkedin || '')
formData.append('portfolio', portfolio || '')
formData.append('experience', experience)
formData.append('location', location)
formData.append('message', message)
if (resume) formData.append('resume', resume)
const response = await fetch('/api/careers/submit', {
method: 'POST',
body: formData,
})
if (!response.ok) {
throw new Error('Failed to submit application')
}
setSubmitStatus('success')
} catch (error) {
logger.error('Error submitting application:', error)
setSubmitStatus('error')
} finally {
setIsSubmitting(false)
}
}
return (
<main className={`${soehne.className} min-h-screen bg-white text-gray-900`}>
<Nav variant='landing' />
{/* Content */}
<div className='px-4 pt-[60px] pb-[80px] sm:px-8 md:px-[44px]'>
<h1 className='mb-10 text-center font-bold text-4xl text-gray-900 md:text-5xl'>
Join Our Team
</h1>
<div className='mx-auto max-w-4xl'>
{/* Form Section */}
<section className='rounded-2xl border border-gray-200 bg-white p-6 shadow-sm sm:p-10'>
<form onSubmit={onSubmit} className='space-y-5'>
{/* Name and Email */}
<div className='grid grid-cols-1 gap-4 sm:gap-6 md:grid-cols-2'>
<div className='space-y-2'>
<Label htmlFor='name' className='font-medium text-sm'>
Full Name *
</Label>
<Input
id='name'
placeholder='John Doe'
value={name}
onChange={(e) => setName(e.target.value)}
className={cn(
showErrors &&
nameErrors.length > 0 &&
'border-red-500 focus:border-red-500 focus:ring-red-100 focus-visible:ring-red-500'
)}
/>
{showErrors && nameErrors.length > 0 && (
<div className='mt-1 space-y-1 text-red-400 text-xs'>
{nameErrors.map((error, index) => (
<p key={index}>{error}</p>
))}
</div>
)}
</div>
<div className='space-y-2'>
<Label htmlFor='email' className='font-medium text-sm'>
Email *
</Label>
<Input
id='email'
type='email'
placeholder='john@example.com'
value={email}
onChange={(e) => setEmail(e.target.value)}
className={cn(
showErrors &&
emailErrors.length > 0 &&
'border-red-500 focus:border-red-500 focus:ring-red-100 focus-visible:ring-red-500'
)}
/>
{showErrors && emailErrors.length > 0 && (
<div className='mt-1 space-y-1 text-red-400 text-xs'>
{emailErrors.map((error, index) => (
<p key={index}>{error}</p>
))}
</div>
)}
</div>
</div>
{/* Phone and Position */}
<div className='grid grid-cols-1 gap-4 sm:gap-6 md:grid-cols-2'>
<div className='space-y-2'>
<Label htmlFor='phone' className='font-medium text-sm'>
Phone Number
</Label>
<Input
id='phone'
type='tel'
placeholder='+1 (555) 123-4567'
value={phone}
onChange={(e) => setPhone(e.target.value)}
/>
</div>
<div className='space-y-2'>
<Label htmlFor='position' className='font-medium text-sm'>
Position of Interest *
</Label>
<Input
id='position'
placeholder='e.g. Full Stack Engineer, Product Designer'
value={position}
onChange={(e) => setPosition(e.target.value)}
className={cn(
showErrors &&
positionErrors.length > 0 &&
'border-red-500 focus:border-red-500 focus:ring-red-100 focus-visible:ring-red-500'
)}
/>
{showErrors && positionErrors.length > 0 && (
<div className='mt-1 space-y-1 text-red-400 text-xs'>
{positionErrors.map((error, index) => (
<p key={index}>{error}</p>
))}
</div>
)}
</div>
</div>
{/* LinkedIn and Portfolio */}
<div className='grid grid-cols-1 gap-4 sm:gap-6 md:grid-cols-2'>
<div className='space-y-2'>
<Label htmlFor='linkedin' className='font-medium text-sm'>
LinkedIn Profile
</Label>
<Input
id='linkedin'
placeholder='https://linkedin.com/in/yourprofile'
value={linkedin}
onChange={(e) => setLinkedin(e.target.value)}
className={cn(
showErrors &&
linkedinErrors.length > 0 &&
'border-red-500 focus:border-red-500 focus:ring-red-100 focus-visible:ring-red-500'
)}
/>
{showErrors && linkedinErrors.length > 0 && (
<div className='mt-1 space-y-1 text-red-400 text-xs'>
{linkedinErrors.map((error, index) => (
<p key={index}>{error}</p>
))}
</div>
)}
</div>
<div className='space-y-2'>
<Label htmlFor='portfolio' className='font-medium text-sm'>
Portfolio / Website
</Label>
<Input
id='portfolio'
placeholder='https://yourportfolio.com'
value={portfolio}
onChange={(e) => setPortfolio(e.target.value)}
className={cn(
showErrors &&
portfolioErrors.length > 0 &&
'border-red-500 focus:border-red-500 focus:ring-red-100 focus-visible:ring-red-500'
)}
/>
{showErrors && portfolioErrors.length > 0 && (
<div className='mt-1 space-y-1 text-red-400 text-xs'>
{portfolioErrors.map((error, index) => (
<p key={index}>{error}</p>
))}
</div>
)}
</div>
</div>
{/* Experience and Location */}
<div className='grid grid-cols-1 gap-4 sm:gap-6 md:grid-cols-2'>
<div className='space-y-2'>
<Label htmlFor='experience' className='font-medium text-sm'>
Years of Experience *
</Label>
<Select value={experience} onValueChange={setExperience}>
<SelectTrigger
className={cn(
showErrors &&
experienceErrors.length > 0 &&
'border-red-500 focus:border-red-500 focus:ring-red-100 focus-visible:ring-red-500'
)}
>
<SelectValue placeholder='Select experience level' />
</SelectTrigger>
<SelectContent>
<SelectItem value='0-1'>0-1 years</SelectItem>
<SelectItem value='1-3'>1-3 years</SelectItem>
<SelectItem value='3-5'>3-5 years</SelectItem>
<SelectItem value='5-10'>5-10 years</SelectItem>
<SelectItem value='10+'>10+ years</SelectItem>
</SelectContent>
</Select>
{showErrors && experienceErrors.length > 0 && (
<div className='mt-1 space-y-1 text-red-400 text-xs'>
{experienceErrors.map((error, index) => (
<p key={index}>{error}</p>
))}
</div>
)}
</div>
<div className='space-y-2'>
<Label htmlFor='location' className='font-medium text-sm'>
Location *
</Label>
<Input
id='location'
placeholder='e.g. San Francisco, CA'
value={location}
onChange={(e) => setLocation(e.target.value)}
className={cn(
showErrors &&
locationErrors.length > 0 &&
'border-red-500 focus:border-red-500 focus:ring-red-100 focus-visible:ring-red-500'
)}
/>
{showErrors && locationErrors.length > 0 && (
<div className='mt-1 space-y-1 text-red-400 text-xs'>
{locationErrors.map((error, index) => (
<p key={index}>{error}</p>
))}
</div>
)}
</div>
</div>
{/* Message */}
<div className='space-y-2'>
<Label htmlFor='message' className='font-medium text-sm'>
Tell us about yourself *
</Label>
<Textarea
id='message'
placeholder='Tell us about your experience, what excites you about Sim, and why you would be a great fit for this role...'
className={cn(
'min-h-[140px]',
showErrors &&
messageErrors.length > 0 &&
'border-red-500 focus:border-red-500 focus:ring-red-100 focus-visible:ring-red-500'
)}
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
<p className='mt-1.5 text-gray-500 text-xs'>Minimum 50 characters</p>
{showErrors && messageErrors.length > 0 && (
<div className='mt-1 space-y-1 text-red-400 text-xs'>
{messageErrors.map((error, index) => (
<p key={index}>{error}</p>
))}
</div>
)}
</div>
{/* Resume Upload */}
<div className='space-y-2'>
<Label htmlFor='resume' className='font-medium text-sm'>
Resume *
</Label>
<div className='relative'>
{resume ? (
<div className='flex items-center gap-2 rounded-md border border-input bg-background px-3 py-2'>
<span className='flex-1 truncate text-sm'>{resume.name}</span>
<button
type='button'
onClick={(e) => {
e.preventDefault()
setResume(null)
if (fileInputRef.current) {
fileInputRef.current.value = ''
}
}}
className='flex-shrink-0 text-muted-foreground transition-colors hover:text-foreground'
aria-label='Remove file'
>
<X className='h-4 w-4' />
</button>
</div>
) : (
<Input
id='resume'
type='file'
accept='.pdf,.doc,.docx'
onChange={handleFileChange}
ref={fileInputRef}
className={cn(
showErrors &&
resumeErrors.length > 0 &&
'border-red-500 focus:border-red-500 focus:ring-red-100 focus-visible:ring-red-500'
)}
/>
)}
</div>
<p className='mt-1.5 text-gray-500 text-xs'>PDF or Word document, max 10MB</p>
{showErrors && resumeErrors.length > 0 && (
<div className='mt-1 space-y-1 text-red-400 text-xs'>
{resumeErrors.map((error, index) => (
<p key={index}>{error}</p>
))}
</div>
)}
</div>
{/* Submit Button */}
<div className='flex justify-end pt-2'>
<BrandedButton
type='submit'
disabled={isSubmitting || submitStatus === 'success'}
loading={isSubmitting}
loadingText='Submitting'
showArrow={false}
fullWidth={false}
className='min-w-[200px]'
>
{submitStatus === 'success' ? 'Submitted' : 'Submit Application'}
</BrandedButton>
</div>
</form>
</section>
{/* Additional Info */}
<section className='mt-6 text-center text-gray-600 text-sm'>
<p>
Questions? Email us at{' '}
<a
href='mailto:careers@sim.ai'
className='font-medium text-gray-900 underline transition-colors hover:text-gray-700'
>
careers@sim.ai
</a>
</p>
</section>
</div>
</div>
{/* Footer - Only for hosted instances */}
{isHosted && (
<div className='relative z-20'>
<Footer fullWidth={true} />
</div>
)}
</main>
)
}

View File

@@ -77,12 +77,14 @@ export default function Footer({ fullWidth = false }: FooterProps) {
>
Status
</Link>
<Link
href='/careers'
<a
href='https://jobs.ashbyhq.com/sim'
target='_blank'
rel='noopener noreferrer'
className='text-[14px] text-muted-foreground transition-colors hover:text-foreground'
>
Careers
</Link>
</a>
<Link
href='/privacy'
target='_blank'

View File

@@ -91,12 +91,14 @@ export default function Nav({ hideAuthButtons = false, variant = 'landing' }: Na
</button>
</li>
<li>
<Link
href='/careers'
<a
href='https://jobs.ashbyhq.com/sim'
target='_blank'
rel='noopener noreferrer'
className='text-[16px] text-muted-foreground transition-colors hover:text-foreground'
>
Careers
</Link>
</a>
</li>
<li>
<a

View File

@@ -18,7 +18,6 @@ export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
pathname.startsWith('/privacy') ||
pathname.startsWith('/invite') ||
pathname.startsWith('/verify') ||
pathname.startsWith('/careers') ||
pathname.startsWith('/changelog') ||
pathname.startsWith('/chat') ||
pathname.startsWith('/studio') ||

View File

@@ -1,192 +0,0 @@
import { render } from '@react-email/components'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { CareersConfirmationEmail, CareersSubmissionEmail } from '@/components/emails'
import { generateRequestId } from '@/lib/core/utils/request'
import { sendEmail } from '@/lib/messaging/email/mailer'
export const dynamic = 'force-dynamic'
const logger = createLogger('CareersAPI')
const MAX_FILE_SIZE = 10 * 1024 * 1024
const ALLOWED_FILE_TYPES = [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
]
const CareersSubmissionSchema = z.object({
name: z.string().min(2, 'Name must be at least 2 characters'),
email: z.string().email('Please enter a valid email address'),
phone: z.string().optional(),
position: z.string().min(2, 'Please specify the position you are interested in'),
linkedin: z.string().url('Please enter a valid LinkedIn URL').optional().or(z.literal('')),
portfolio: z.string().url('Please enter a valid portfolio URL').optional().or(z.literal('')),
experience: z.enum(['0-1', '1-3', '3-5', '5-10', '10+']),
location: z.string().min(2, 'Please enter your location'),
message: z.string().min(50, 'Please tell us more about yourself (at least 50 characters)'),
})
export async function POST(request: NextRequest) {
const requestId = generateRequestId()
try {
const formData = await request.formData()
const data = {
name: formData.get('name') as string,
email: formData.get('email') as string,
phone: formData.get('phone') as string,
position: formData.get('position') as string,
linkedin: formData.get('linkedin') as string,
portfolio: formData.get('portfolio') as string,
experience: formData.get('experience') as string,
location: formData.get('location') as string,
message: formData.get('message') as string,
}
const resumeFile = formData.get('resume') as File | null
if (!resumeFile) {
return NextResponse.json(
{
success: false,
message: 'Resume is required',
errors: [{ path: ['resume'], message: 'Resume is required' }],
},
{ status: 400 }
)
}
if (resumeFile.size > MAX_FILE_SIZE) {
return NextResponse.json(
{
success: false,
message: 'Resume file size must be less than 10MB',
errors: [{ path: ['resume'], message: 'File size must be less than 10MB' }],
},
{ status: 400 }
)
}
if (!ALLOWED_FILE_TYPES.includes(resumeFile.type)) {
return NextResponse.json(
{
success: false,
message: 'Resume must be a PDF or Word document',
errors: [{ path: ['resume'], message: 'File must be PDF or Word document' }],
},
{ status: 400 }
)
}
const resumeBuffer = await resumeFile.arrayBuffer()
const resumeBase64 = Buffer.from(resumeBuffer).toString('base64')
const validatedData = CareersSubmissionSchema.parse(data)
logger.info(`[${requestId}] Processing career application`, {
name: validatedData.name,
email: validatedData.email,
position: validatedData.position,
resumeSize: resumeFile.size,
resumeType: resumeFile.type,
})
const submittedDate = new Date()
const careersEmailHtml = await render(
CareersSubmissionEmail({
name: validatedData.name,
email: validatedData.email,
phone: validatedData.phone,
position: validatedData.position,
linkedin: validatedData.linkedin,
portfolio: validatedData.portfolio,
experience: validatedData.experience,
location: validatedData.location,
message: validatedData.message,
submittedDate,
})
)
const confirmationEmailHtml = await render(
CareersConfirmationEmail({
name: validatedData.name,
position: validatedData.position,
submittedDate,
})
)
const careersEmailResult = await sendEmail({
to: 'careers@sim.ai',
subject: `New Career Application: ${validatedData.name} - ${validatedData.position}`,
html: careersEmailHtml,
emailType: 'transactional',
replyTo: validatedData.email,
attachments: [
{
filename: resumeFile.name,
content: resumeBase64,
contentType: resumeFile.type,
},
],
})
if (!careersEmailResult.success) {
logger.error(`[${requestId}] Failed to send email to careers@sim.ai`, {
error: careersEmailResult.message,
})
throw new Error('Failed to submit application')
}
const confirmationResult = await sendEmail({
to: validatedData.email,
subject: `Your Application to Sim - ${validatedData.position}`,
html: confirmationEmailHtml,
emailType: 'transactional',
replyTo: validatedData.email,
})
if (!confirmationResult.success) {
logger.warn(`[${requestId}] Failed to send confirmation email to applicant`, {
email: validatedData.email,
error: confirmationResult.message,
})
}
logger.info(`[${requestId}] Career application submitted successfully`, {
careersEmailSent: careersEmailResult.success,
confirmationEmailSent: confirmationResult.success,
})
return NextResponse.json({
success: true,
message: 'Application submitted successfully',
})
} catch (error) {
if (error instanceof z.ZodError) {
logger.warn(`[${requestId}] Invalid application data`, { errors: error.errors })
return NextResponse.json(
{
success: false,
message: 'Invalid application data',
errors: error.errors,
},
{ status: 400 }
)
}
logger.error(`[${requestId}] Error processing career application:`, error)
return NextResponse.json(
{
success: false,
message:
'Failed to submit application. Please try again or email us directly at careers@sim.ai',
},
{ status: 500 }
)
}
}

View File

@@ -2,8 +2,6 @@ import type { NextRequest } from 'next/server'
import { NextResponse } from 'next/server'
import {
renderBatchInvitationEmail,
renderCareersConfirmationEmail,
renderCareersSubmissionEmail,
renderCreditPurchaseEmail,
renderEnterpriseSubscriptionEmail,
renderFreeTierUpgradeEmail,
@@ -94,22 +92,6 @@ const emailTemplates = {
failureReason: 'Card declined',
}),
// Careers emails
'careers-confirmation': () => renderCareersConfirmationEmail('John Doe', 'Senior Engineer'),
'careers-submission': () =>
renderCareersSubmissionEmail({
name: 'John Doe',
email: 'john@example.com',
phone: '+1 (555) 123-4567',
position: 'Senior Engineer',
linkedin: 'https://linkedin.com/in/johndoe',
portfolio: 'https://johndoe.dev',
experience: '5-10',
location: 'San Francisco, CA',
message:
'I have 10 years of experience building scalable distributed systems. Most recently, I led a team at a Series B startup where we scaled from 100K to 10M users.',
}),
// Notification emails
'workflow-notification-success': () =>
renderWorkflowNotificationEmail({
@@ -176,7 +158,6 @@ export async function GET(request: NextRequest) {
'credit-purchase',
'payment-failed',
],
Careers: ['careers-confirmation', 'careers-submission'],
Notifications: [
'workflow-notification-success',
'workflow-notification-error',

View File

@@ -55,7 +55,7 @@ Sim provides a visual drag-and-drop interface for building and deploying AI agen
## Optional
- [Careers](${baseUrl}/careers): Join the Sim team
- [Careers](https://jobs.ashbyhq.com/sim): Join the Sim team
- [Terms of Service](${baseUrl}/terms): Legal terms
- [Privacy Policy](${baseUrl}/privacy): Data handling practices
`

View File

@@ -28,10 +28,6 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
url: `${baseUrl}/changelog`,
lastModified: now,
},
{
url: `${baseUrl}/careers`,
lastModified: new Date('2024-10-06'),
},
{
url: `${baseUrl}/terms`,
lastModified: new Date('2024-10-14'),

View File

@@ -1,60 +0,0 @@
import { Text } from '@react-email/components'
import { format } from 'date-fns'
import { baseStyles } from '@/components/emails/_styles'
import { EmailLayout } from '@/components/emails/components'
import { getBaseUrl } from '@/lib/core/utils/urls'
import { getBrandConfig } from '@/ee/whitelabeling'
interface CareersConfirmationEmailProps {
name: string
position: string
submittedDate?: Date
}
export function CareersConfirmationEmail({
name,
position,
submittedDate = new Date(),
}: CareersConfirmationEmailProps) {
const brand = getBrandConfig()
const baseUrl = getBaseUrl()
return (
<EmailLayout
preview={`Your application to ${brand.name} has been received`}
showUnsubscribe={false}
>
<Text style={baseStyles.paragraph}>Hello {name},</Text>
<Text style={baseStyles.paragraph}>
We've received your application for <strong>{position}</strong>. Our team reviews every
application and will reach out if there's a match.
</Text>
<Text style={baseStyles.paragraph}>
In the meantime, explore our{' '}
<a
href='https://docs.sim.ai'
target='_blank'
rel='noopener noreferrer'
style={baseStyles.link}
>
docs
</a>{' '}
or{' '}
<a href={`${baseUrl}/studio`} style={baseStyles.link}>
blog
</a>{' '}
to learn more about what we're building.
</Text>
{/* Divider */}
<div style={baseStyles.divider} />
<Text style={{ ...baseStyles.footerText, textAlign: 'left' }}>
Submitted on {format(submittedDate, 'MMMM do, yyyy')}.
</Text>
</EmailLayout>
)
}
export default CareersConfirmationEmail

View File

@@ -1,337 +0,0 @@
import { Section, Text } from '@react-email/components'
import { format } from 'date-fns'
import { baseStyles, colors } from '@/components/emails/_styles'
import { EmailLayout } from '@/components/emails/components'
interface CareersSubmissionEmailProps {
name: string
email: string
phone?: string
position: string
linkedin?: string
portfolio?: string
experience: string
location: string
message: string
submittedDate?: Date
}
const getExperienceLabel = (experience: string) => {
const labels: Record<string, string> = {
'0-1': '0-1 years',
'1-3': '1-3 years',
'3-5': '3-5 years',
'5-10': '5-10 years',
'10+': '10+ years',
}
return labels[experience] || experience
}
export function CareersSubmissionEmail({
name,
email,
phone,
position,
linkedin,
portfolio,
experience,
location,
message,
submittedDate = new Date(),
}: CareersSubmissionEmailProps) {
return (
<EmailLayout preview={`New Career Application from ${name}`} hideFooter showUnsubscribe={false}>
<Text
style={{
...baseStyles.paragraph,
fontSize: '18px',
fontWeight: 'bold',
color: colors.textPrimary,
}}
>
New Career Application
</Text>
<Text style={baseStyles.paragraph}>
A new career application has been submitted on {format(submittedDate, 'MMMM do, yyyy')} at{' '}
{format(submittedDate, 'h:mm a')}.
</Text>
{/* Applicant Information */}
<Section
style={{
marginTop: '24px',
marginBottom: '24px',
padding: '20px',
backgroundColor: colors.bgOuter,
borderRadius: '8px',
border: `1px solid ${colors.divider}`,
}}
>
<Text
style={{
margin: '0 0 16px 0',
fontSize: '16px',
fontWeight: 'bold',
color: colors.textPrimary,
fontFamily: baseStyles.fontFamily,
}}
>
Applicant Information
</Text>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<tbody>
<tr>
<td
style={{
padding: '8px 0',
fontSize: '14px',
fontWeight: 'bold',
color: colors.textMuted,
width: '40%',
fontFamily: baseStyles.fontFamily,
}}
>
Name:
</td>
<td
style={{
padding: '8px 0',
fontSize: '14px',
color: colors.textPrimary,
fontFamily: baseStyles.fontFamily,
}}
>
{name}
</td>
</tr>
<tr>
<td
style={{
padding: '8px 0',
fontSize: '14px',
fontWeight: 'bold',
color: colors.textMuted,
fontFamily: baseStyles.fontFamily,
}}
>
Email:
</td>
<td
style={{
padding: '8px 0',
fontSize: '14px',
color: colors.textPrimary,
fontFamily: baseStyles.fontFamily,
}}
>
<a href={`mailto:${email}`} style={baseStyles.link}>
{email}
</a>
</td>
</tr>
{phone && (
<tr>
<td
style={{
padding: '8px 0',
fontSize: '14px',
fontWeight: 'bold',
color: colors.textMuted,
fontFamily: baseStyles.fontFamily,
}}
>
Phone:
</td>
<td
style={{
padding: '8px 0',
fontSize: '14px',
color: colors.textPrimary,
fontFamily: baseStyles.fontFamily,
}}
>
<a href={`tel:${phone}`} style={baseStyles.link}>
{phone}
</a>
</td>
</tr>
)}
<tr>
<td
style={{
padding: '8px 0',
fontSize: '14px',
fontWeight: 'bold',
color: colors.textMuted,
fontFamily: baseStyles.fontFamily,
}}
>
Position:
</td>
<td
style={{
padding: '8px 0',
fontSize: '14px',
color: colors.textPrimary,
fontFamily: baseStyles.fontFamily,
}}
>
{position}
</td>
</tr>
<tr>
<td
style={{
padding: '8px 0',
fontSize: '14px',
fontWeight: 'bold',
color: colors.textMuted,
fontFamily: baseStyles.fontFamily,
}}
>
Experience:
</td>
<td
style={{
padding: '8px 0',
fontSize: '14px',
color: colors.textPrimary,
fontFamily: baseStyles.fontFamily,
}}
>
{getExperienceLabel(experience)}
</td>
</tr>
<tr>
<td
style={{
padding: '8px 0',
fontSize: '14px',
fontWeight: 'bold',
color: colors.textMuted,
fontFamily: baseStyles.fontFamily,
}}
>
Location:
</td>
<td
style={{
padding: '8px 0',
fontSize: '14px',
color: colors.textPrimary,
fontFamily: baseStyles.fontFamily,
}}
>
{location}
</td>
</tr>
{linkedin && (
<tr>
<td
style={{
padding: '8px 0',
fontSize: '14px',
fontWeight: 'bold',
color: colors.textMuted,
fontFamily: baseStyles.fontFamily,
}}
>
LinkedIn:
</td>
<td
style={{
padding: '8px 0',
fontSize: '14px',
color: colors.textPrimary,
fontFamily: baseStyles.fontFamily,
}}
>
<a
href={linkedin}
target='_blank'
rel='noopener noreferrer'
style={baseStyles.link}
>
View Profile
</a>
</td>
</tr>
)}
{portfolio && (
<tr>
<td
style={{
padding: '8px 0',
fontSize: '14px',
fontWeight: 'bold',
color: colors.textMuted,
fontFamily: baseStyles.fontFamily,
}}
>
Portfolio:
</td>
<td
style={{
padding: '8px 0',
fontSize: '14px',
color: colors.textPrimary,
fontFamily: baseStyles.fontFamily,
}}
>
<a
href={portfolio}
target='_blank'
rel='noopener noreferrer'
style={baseStyles.link}
>
View Portfolio
</a>
</td>
</tr>
)}
</tbody>
</table>
</Section>
{/* Message */}
<Section
style={{
marginTop: '24px',
marginBottom: '24px',
padding: '20px',
backgroundColor: colors.bgOuter,
borderRadius: '8px',
border: `1px solid ${colors.divider}`,
}}
>
<Text
style={{
margin: '0 0 12px 0',
fontSize: '16px',
fontWeight: 'bold',
color: colors.textPrimary,
fontFamily: baseStyles.fontFamily,
}}
>
About Themselves
</Text>
<Text
style={{
margin: '0',
fontSize: '14px',
color: colors.textPrimary,
lineHeight: '1.6',
whiteSpace: 'pre-wrap',
fontFamily: baseStyles.fontFamily,
}}
>
{message}
</Text>
</Section>
</EmailLayout>
)
}
export default CareersSubmissionEmail

View File

@@ -1,2 +0,0 @@
export { CareersConfirmationEmail } from './careers-confirmation-email'
export { CareersSubmissionEmail } from './careers-submission-email'

View File

@@ -4,8 +4,6 @@ export * from './_styles'
export * from './auth'
// Billing emails
export * from './billing'
// Careers emails
export * from './careers'
// Shared components
export * from './components'
// Invitation emails

View File

@@ -8,7 +8,6 @@ import {
PlanWelcomeEmail,
UsageThresholdEmail,
} from '@/components/emails/billing'
import { CareersConfirmationEmail, CareersSubmissionEmail } from '@/components/emails/careers'
import {
BatchInvitationEmail,
InvitationEmail,
@@ -225,44 +224,6 @@ export async function renderPaymentFailedEmail(params: {
)
}
export async function renderCareersConfirmationEmail(
name: string,
position: string
): Promise<string> {
return await render(
CareersConfirmationEmail({
name,
position,
})
)
}
export async function renderCareersSubmissionEmail(params: {
name: string
email: string
phone?: string
position: string
linkedin?: string
portfolio?: string
experience: string
location: string
message: string
}): Promise<string> {
return await render(
CareersSubmissionEmail({
name: params.name,
email: params.email,
phone: params.phone,
position: params.position,
linkedin: params.linkedin,
portfolio: params.portfolio,
experience: params.experience,
location: params.location,
message: params.message,
})
)
}
export async function renderWorkflowNotificationEmail(
params: WorkflowNotificationEmailProps
): Promise<string> {

View File

@@ -328,6 +328,11 @@ const nextConfig: NextConfig = {
source: '/team',
destination: 'https://cal.com/emirkarabeg/sim-team',
permanent: false,
},
{
source: '/careers',
destination: 'https://jobs.ashbyhq.com/sim',
permanent: true,
}
)