-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpage.tsx
360 lines (352 loc) · 12.7 KB
/
page.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
import { Suspense } from 'react';
import { unstable_noStore as noStore } from 'next/cache';
import Link from 'next/link';
import Image from 'next/image';
import smashing from 'public/images/home/smashing.jpg';
import summit from 'public/images/home/summit.jpg';
import reactathon from 'public/images/home/reactathon.jpg';
import ship from 'public/images/home/ship.jpg';
import filming from 'public/images/home/filming.jpg';
import meetups from 'public/images/home/meetups.jpg';
import vercel from 'public/images/home/vercel.jpg';
import avatar from 'app/avatar.jpg';
import ViewCounter from 'app/blog/view-counter';
import { PreloadResources } from 'app/preload';
import {
getLeeYouTubeSubs,
getVercelYouTubeSubs,
getViewsCount,
} from 'app/db/queries';
function Badge(props) {
return (
<a
{...props}
target="_blank"
className="inline-flex items-center rounded border border-neutral-200 bg-neutral-50 p-1 text-sm leading-4 text-neutral-900 no-underline dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-100"
/>
);
}
function ArrowIcon() {
return (
<svg
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M2.07102 11.3494L0.963068 10.2415L9.2017 1.98864H2.83807L2.85227 0.454545H11.8438V9.46023H10.2955L10.3097 3.09659L2.07102 11.3494Z"
fill="currentColor"
/>
</svg>
);
}
function ChannelLink({ img, link, name }) {
return (
<div className="group flex w-full">
<a
href={link}
target="_blank"
className="flex w-full items-center justify-between rounded border border-neutral-200 bg-neutral-50 px-3 py-4 dark:border-neutral-700 dark:bg-neutral-800"
>
<div className="flex items-center space-x-3">
<div className="relative h-16">
<Image
alt={name}
src={img}
height={64}
width={64}
sizes="33vw"
className="h-16 w-16 rounded-full border border-neutral-200 dark:border-neutral-700"
priority
/>
<div className="relative -right-10 -top-6 inline-flex h-6 w-6 items-center rounded-full border border-neutral-200 bg-white p-1 dark:border-neutral-700">
<svg width="15" height="11" role="img" aria-label="YouTube logo">
<use href="/sprite.svg#youtube" />
</svg>
</div>
</div>
<div className="flex flex-col">
<p className="font-medium text-neutral-900 dark:text-neutral-100">
{name}
</p>
<Suspense fallback={<p className="h-6" />}>
<Subs name={name} />
</Suspense>
</div>
</div>
<div className="transform text-neutral-700 transition-transform duration-300 group-hover:-rotate-12 dark:text-neutral-300">
<ArrowIcon />
</div>
</a>
</div>
);
}
async function Subs({ name }: { name: string }) {
noStore();
let subscribers;
if (name === '@leerob') {
subscribers = await getLeeYouTubeSubs();
} else {
subscribers = await getVercelYouTubeSubs();
}
return (
<p className="text-neutral-600 dark:text-neutral-400">
{subscribers} subscribers
</p>
);
}
function BlogLink({ slug, name }) {
return (
<div className="group">
<a
href={`/blog/${slug}`}
className="flex w-full items-center justify-between rounded border border-neutral-200 bg-neutral-50 px-3 py-4 dark:border-neutral-700 dark:bg-neutral-800"
>
<div className="flex flex-col">
<p className="font-medium text-neutral-900 dark:text-neutral-100">
{name}
</p>
<Suspense fallback={<p className="h-6" />}>
<Views slug={slug} />
</Suspense>
</div>
<div className="transform text-neutral-700 transition-transform duration-300 group-hover:-rotate-12 dark:text-neutral-300">
<ArrowIcon />
</div>
</a>
</div>
);
}
async function Views({ slug }: { slug: string }) {
let views = await getViewsCount();
return <ViewCounter allViews={views} slug={slug} />;
}
export default function Page() {
return (
<section>
<PreloadResources />
<h1 className="mb-8 text-2xl font-medium tracking-tighter">
hey, I'm leerob 👋
</h1>
<p className="prose prose-neutral dark:prose-invert">
{`I'm a frontend developer, optimist, and community builder. I currently `}
<Link href="/work">work</Link>
{` as the VP of Product at `}
<span className="not-prose">
<Badge href="https://vercel.com/home">
<svg
width="13"
height="11"
role="img"
aria-label="Vercel logo"
className="mr-1 inline-flex"
>
<use href="/sprite.svg#vercel" />
</svg>
Vercel
</Badge>
</span>
{`, where I help teach the `}
<Badge href="https://nextjs.org">
<img
alt="Next.js logomark"
src="/next-logo.svg"
className="!mr-1"
width="14"
height="14"
/>
Next.js
</Badge>
{` community, an open-source web framework built with `}
<Badge href="https://react.dev">
<svg
width="14"
height="14"
role="img"
aria-label="React logo"
className="!mr-1"
>
<use href="/sprite.svg#react" />
</svg>
React
</Badge>
.
</p>
<div className="grid grid-cols-2 grid-rows-4 sm:grid-rows-3 sm:grid-cols-3 gap-4 my-8">
<div className="relative h-40">
<Image
alt="Me speaking on stage at React Summit about the future of Next.js"
src={summit}
fill
sizes="(max-width: 768px) 213px, 33vw"
priority
className="rounded-lg object-cover"
/>
</div>
<div className="relative sm:row-span-2 row-span-1">
<Image
alt="Me standing on stage at Reactathon delivering the keynote"
src={reactathon}
fill
sizes="(max-width: 768px) 213px, 33vw"
priority
className="rounded-lg object-cover object-top sm:object-center"
/>
</div>
<div className="relative">
<Image
alt="Me and Guillermo Rauch on stage for Vercel Ship, answering questions from the Next.js community"
src={ship}
fill
sizes="(max-width: 768px) 213px, 33vw"
priority
className="rounded-lg object-cover"
/>
</div>
<div className="relative row-span-2">
<Image
alt="Me, Lydia, and Delba filming the Next.js Conf keynote"
src={filming}
fill
sizes="(max-width: 768px) 213px, 33vw"
priority
className="rounded-lg object-cover sm:object-center"
/>
</div>
<div className="relative row-span-2">
<Image
alt="My badge on top of a pile of badges from a Vercel meetup we held"
src={meetups}
fill
sizes="(max-width: 768px) 213px, 33vw"
priority
className="rounded-lg object-cover"
/>
</div>
<div className="relative h-40">
<Image
alt="Me standing on stage at SmashingConf giving a talk about my optimism for the web"
src={smashing}
fill
sizes="(max-width: 768px) 213px, 33vw"
priority
className="rounded-lg object-cover"
/>
</div>
</div>
<div className="prose prose-neutral dark:prose-invert">
<p>
I create educational content for developers, teaching them about web
development, JavaScript and TypeScript, React and Next.js, and more.
This comes in all forms: blog posts, videos, tweets, conference talks,
and workshops. You can watch some of my favorites below.
</p>
</div>
<div className="my-8 flex w-full flex-col space-x-0 space-y-4 sm:flex-row sm:space-x-4 sm:space-y-0">
<ChannelLink
img={avatar}
name="@leerob"
link="https://www.youtube.com/@leerob"
/>
<ChannelLink
img={vercel}
name="@vercel"
link="https://www.youtube.com/@vercelhq"
/>
</div>
<div className="prose prose-neutral dark:prose-invert">
<p>
Over the past decade, I've written content on my blog and newsletter.
I try to keep things simple. You'll find writing about technologies
I'm interested in at the time, or how I'm learning and growing in my
career, sharing knowledge along the way.
</p>
</div>
<div className="my-8 flex w-full flex-col space-y-4">
<BlogLink
name="What Makes A Great Developer Experience?"
slug="developer-experience"
/>
<BlogLink name="What is Developer Relations?" slug="devrel" />
<BlogLink name="The Story of Heroku" slug="heroku" />
</div>
<div className="prose prose-neutral dark:prose-invert">
<p>
I invest small angel checks into early stage startups building tools
for developers.
</p>
</div>
<div className="my-8 flex h-14 w-full flex-row space-x-2 overflow-x-auto">
<div className="flex items-center justify-between rounded border border-neutral-200 bg-neutral-50 px-3 py-4 dark:border-neutral-700 dark:bg-neutral-800">
<a href="https://linear.app">
<svg width="78" height="20" role="img" aria-label="Linear logo">
<use href="/sprite.svg#linear" />
</svg>
</a>
</div>
<div className="flex items-center justify-between rounded border border-neutral-200 bg-neutral-50 px-3 py-4 dark:border-neutral-700 dark:bg-neutral-800">
<a href="https://supabase.com">
<svg width="100" height="19" role="img" aria-label="Supabase logo">
<use href="/sprite.svg#supabase" />
</svg>
</a>
</div>
<div className="flex items-center justify-between rounded border border-neutral-200 bg-neutral-50 px-3 py-4 dark:border-neutral-700 dark:bg-neutral-800">
<a href="https://www.makeswift.com/blog/makeswift-is-joining-bigcommerce">
<svg width="96" height="19" role="img" aria-label="Makeswift logo">
<use href="/sprite.svg#makeswift" />
</svg>
</a>
</div>
<div className="flex items-center justify-between rounded border border-neutral-200 bg-neutral-50 px-3 py-4 dark:border-neutral-700 dark:bg-neutral-800">
<a href="https://resend.com">
<svg width="70" height="17" role="img" aria-label="Resend logo">
<use href="/sprite.svg#resend" />
</svg>
</a>
</div>
<div className="flex items-center justify-between rounded border border-neutral-200 bg-neutral-50 px-3 py-4 dark:border-neutral-700 dark:bg-neutral-800">
<a href="https://bun.sh">
<svg width="35" height="27" role="img" aria-label="Bun logo">
<use href="/sprite.svg#bun" />
</svg>
</a>
</div>
</div>
<div className="prose prose-neutral dark:prose-invert">
<p>
I've worked with and advised companies on{' '}
<Link href="/blog/developer-marketing">developer marketing</Link>,{' '}
<Link href="/blog/devrel">developer relations</Link>, building
open-source communities, product-led growth, and more.
</p>
</div>
<ul className="font-sm mt-8 flex flex-col space-x-0 space-y-2 text-neutral-600 md:flex-row md:space-x-4 md:space-y-0 dark:text-neutral-300">
<li>
<a
className="flex items-center transition-all hover:text-neutral-800 dark:hover:text-neutral-100"
rel="noopener noreferrer"
target="_blank"
href="https://twitter.com/leeerob"
>
<ArrowIcon />
<p className="ml-2 h-7">follow me</p>
</a>
</li>
<li>
<a
className="flex items-center transition-all hover:text-neutral-800 dark:hover:text-neutral-100"
rel="noopener noreferrer"
target="_blank"
href="https://leerob.substack.com"
>
<ArrowIcon />
<p className="ml-2 h-7">get email updates</p>
</a>
</li>
</ul>
</section>
);
}