1
+ import React , { useEffect , useState } from 'react' ;
2
+ import Head from '@docusaurus/Head' ;
3
+ import LayoutProvider from '@theme/Layout/Provider' ;
4
+ import LandingHeader from '../landing/LandingHeader' ;
5
+ import Footer from '../landing/Footer' ;
6
+ import RadialBlur from '../landing/RadialBlur' ;
7
+ import LandingSection from '../landing/LandingSection' ;
8
+
9
+ // Cache expiration time in milliseconds (24 hours)
10
+ const CACHE_EXPIRATION = 24 * 60 * 60 * 1000 ;
11
+
12
+ export default function Careers ( ) {
13
+ const [ jobs , setJobs ] = useState ( [ ] ) ;
14
+ const [ loading , setLoading ] = useState ( true ) ;
15
+
16
+ useEffect ( ( ) => {
17
+ const fetchJobs = async ( ) => {
18
+ try {
19
+ // Check if we have cached data
20
+ const cachedData = localStorage . getItem ( 'windmill_jobs_cache' ) ;
21
+
22
+ if ( cachedData ) {
23
+ const { data, timestamp } = JSON . parse ( cachedData ) ;
24
+ const currentTime = new Date ( ) . getTime ( ) ;
25
+
26
+ // Use cached data if it's still fresh (less than 24 hours old)
27
+ if ( currentTime - timestamp < CACHE_EXPIRATION ) {
28
+ setJobs ( data ) ;
29
+ setLoading ( false ) ;
30
+ return ;
31
+ }
32
+ }
33
+
34
+ // If no valid cache, fetch from API
35
+ const response = await fetch ( 'https://app.windmill.dev/api/w/windmill-labs/jobs/run_wait_result/p/f/all/job_offers' , {
36
+ method : 'POST' ,
37
+ headers : {
38
+ 'Content-Type' : 'application/json' ,
39
+ 'Authorization' : 'Bearer arDPgCvSsZlp3oPKdaGXtKFYxEuNOgyq' // intentionally, harmless token
40
+ } ,
41
+ body : JSON . stringify ( { } )
42
+ } ) ;
43
+
44
+ if ( ! response . ok ) {
45
+ throw new Error ( `HTTP error! status: ${ response . status } ` ) ;
46
+ }
47
+
48
+ const data = await response . json ( ) ;
49
+
50
+ // Store in cache with current timestamp
51
+ localStorage . setItem ( 'windmill_jobs_cache' , JSON . stringify ( {
52
+ data,
53
+ timestamp : new Date ( ) . getTime ( )
54
+ } ) ) ;
55
+
56
+ setJobs ( data ) ;
57
+ } catch ( error ) {
58
+ console . error ( 'Error fetching jobs:' , error ) ;
59
+
60
+ // Try to use cached data even if expired in case of error
61
+ try {
62
+ const cachedData = localStorage . getItem ( 'windmill_jobs_cache' ) ;
63
+ if ( cachedData ) {
64
+ const { data } = JSON . parse ( cachedData ) ;
65
+ setJobs ( data ) ;
66
+ }
67
+ } catch ( cacheError ) {
68
+ console . error ( 'Error retrieving cache:' , cacheError ) ;
69
+ }
70
+ } finally {
71
+ setLoading ( false ) ;
72
+ }
73
+ } ;
74
+
75
+ fetchJobs ( ) ;
76
+
77
+ // Clean up function to handle potential memory leaks
78
+ return ( ) => {
79
+ // Any cleanup code if needed
80
+ } ;
81
+ } , [ ] ) ;
82
+
83
+ return (
84
+ < LayoutProvider >
85
+ < main >
86
+ < Head >
87
+ < title > Careers | Windmill</ title >
88
+ < meta name = "title" content = "Join the Windmill Team." />
89
+ < meta name = "description" content = "Explore career opportunities at Windmill and join our team of experts." />
90
+ </ Head >
91
+ < LandingHeader />
92
+ < LandingSection bgClass = "relative" >
93
+ < >
94
+ < RadialBlur />
95
+ < div className = "space-y-12 text-center pt-32 pb-16" >
96
+ < div className = "space-y-5 sm:mx-auto sm:max-w-xl sm:space-y-4 lg:max-w-5xl" >
97
+ < h1 className = "!text-4xl font-bold tracking-tight sm:!text-5xl mb-8 text-blue-600" > We are hiring</ h1 >
98
+ < p className = "text-lg" > Help us build the next generation infra software.</ p >
99
+ < img src = "/images/careers/team_sun.jpeg" alt = "Team Sun" className = "mx-auto mt-8 rounded-lg shadow-lg" />
100
+ </ div >
101
+ </ div >
102
+ </ >
103
+ </ LandingSection >
104
+ < LandingSection bgClass = "relative" >
105
+ < div className = "space-y-8 pt-32 pb-16" >
106
+ < h2 className = "!text-3xl font-bold tracking-tight sm:!text-4xl mb-8 text-center" > Back to the essence of code</ h2 >
107
+ < div className = "grid grid-cols-1 md:grid-cols-2 gap-8 text-left max-w-4xl mx-auto px-6 sm:px-10 lg:px-12" >
108
+ < div className = "space-y-5" >
109
+ < p className = "text-md" >
110
+ At Windmill, we believe code falls into two categories: code that matters (your business logic) and boilerplate (everything else). Most platforms attempt to solve this by abstracting away code entirely, sacrificing flexibility and power.
111
+ </ p >
112
+ < p className = "text-md" >
113
+ Our mission is to eliminate the boilerplate while preserving the full power of code. We've built an open-source developer platform that turns scripts in multiple languages into endpoints, workflows, and UIs without the traditional overhead.
114
+ </ p >
115
+ </ div >
116
+ < div className = "space-y-5" >
117
+ < p className = "text-md" >
118
+ We're creating infrastructure that delivers the best of both worlds: accessible to semi-technical users without compromising on the flexibility and control senior engineers demand. Our platform handles the undifferentiated heavy lifting—permissions, scheduling, secret management, deployment—so you can focus on what matters.
119
+ </ p >
120
+ < p className = "text-md" >
121
+ If you believe infrastructure should get out of your way, that good tooling should amplify rather than constrain engineers, and that powerful systems can also be accessible, we want you on our team.
122
+ </ p >
123
+ </ div >
124
+ </ div >
125
+ </ div >
126
+ </ LandingSection >
127
+ < section className = "max-w-6xl mx-auto px-4 sm:px-6 py-12 md:py-20" >
128
+ < div className = "space-y-10" >
129
+ { jobs . some ( job => job . title ) && (
130
+ < >
131
+ < div className = "text-center" >
132
+ < h2 className = "!text-3xl font-bold tracking-tight sm:!text-4xl mb-8" > Open roles</ h2 >
133
+ </ div >
134
+ < div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mt-12" >
135
+ { jobs . map ( ( job , index ) => (
136
+ job . title && (
137
+ < a
138
+ key = { index }
139
+ href = { job . url }
140
+ target = "_blank"
141
+ rel = "noopener noreferrer"
142
+ className = "bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow block flex flex-col h-full"
143
+ style = { { textDecoration : 'none' , color : 'inherit' } }
144
+ >
145
+ < div className = "flex-grow" >
146
+ < h3 className = "text-xl font-semibold text-blue-600 mb-2" > { job . title } </ h3 >
147
+ < div className = "space-y-3 mb-4" >
148
+ < p > < span className = "font-medium" > Location:</ span > { job . location } </ p >
149
+ < p > < span className = "font-medium" > Compensation:</ span > { job . wage } </ p >
150
+ < p > < span className = "font-medium" > Equity:</ span > { job . equity } </ p >
151
+ </ div >
152
+ </ div >
153
+ < div className = "mt-auto pt-4" >
154
+ < span
155
+ className = "inline-block bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-md transition-colors"
156
+ onClick = { ( e ) => {
157
+ // Prevent duplicate navigation
158
+ e . preventDefault ( ) ;
159
+ window . open ( job . url , '_blank' , 'noopener,noreferrer' ) ;
160
+ } }
161
+ >
162
+ Apply
163
+ </ span >
164
+ </ div >
165
+ </ a >
166
+ )
167
+ ) ) }
168
+ </ div >
169
+ < div className = "text-center mt-12" >
170
+ < p className = "text-lg" > Don't see a perfect fit? We're always looking for talented individuals.</ p >
171
+ </ div >
172
+ </ >
173
+ ) }
174
+ { ! jobs . some ( job => job . title ) && (
175
+ < div className = "text-center mt-12" >
176
+ < p className = "text-lg" > We're always looking for talented individuals.</ p >
177
+ </ div >
178
+ ) }
179
+ < div className = "text-center mt-4" >
180
+ < a
181
+
182
+ className = "inline-block bg-gray-50 dark:bg-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 font-medium py-2 px-6 rounded-md transition-colors"
183
+ >
184
+ Send general application
185
+ </ a >
186
+ </ div >
187
+ </ div >
188
+ </ section >
189
+ < Footer />
190
+ </ main >
191
+ </ LayoutProvider >
192
+ ) ;
193
+ }
0 commit comments