Skip to content

Commit f938ed1

Browse files
committed
chore(website): wip landing page
1 parent b57e0ba commit f938ed1

File tree

7 files changed

+75
-149
lines changed

7 files changed

+75
-149
lines changed

website/src/components/landing/github-button.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export function GitHubButton() {
2727
href="https://github.com/TheEdoRan/next-safe-action"
2828
target="_blank"
2929
rel="noopener noreferrer"
30-
className="!no-underline hover:translate-y-[-2px] transition-transform border border-zinc-700 dark:border-zinc-300 text-zinc-200 dark:text-zinc-800 cursor-pointer rounded-lg bg-zinc-800 dark:bg-zinc-200 px-4 py-2 md:px-5 md:py-3 font-bold inline-flex items-center justify-center gap-x-1 text-sm md:text-base"
30+
className="inline-flex cursor-pointer items-center justify-center gap-x-1 rounded-lg bg-zinc-100 px-4 py-2 text-sm font-bold !text-zinc-800 !no-underline shadow-lg transition-transform hover:translate-y-[-2px] md:text-base"
3131
>
3232
<StarIcon className="size-4 md:size-5" />
3333
<span>{starsCount ? Intl.NumberFormat("en", { notation: "compact" }).format(starsCount) : "..."} GitHub</span>

website/src/components/landing/hero-example.tsx

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -57,33 +57,33 @@ export function Greet() {
5757
};
5858

5959
return (
60-
<div className="h-[30rem] relative hidden lg:block rounded-xl shadow-xl">
61-
<div className="absolute inset-0 pointer-events-none bg-gradient-to-r from-sky-700 to-purple-700 blur-lg rounded-xl" />
62-
<div className="absolute inset-0 bg-zinc-900 text-white border-zinc-800 p-4 rounded-xl">
63-
<div className="flex items-center mb-3 border-b border-zinc-800">
64-
<div className="flex space-x-2 absolute left-4">
65-
<div className="w-3 h-3 rounded-full bg-red-500"></div>
66-
<div className="w-3 h-3 rounded-full bg-yellow-500"></div>
67-
<div className="w-3 h-3 rounded-full bg-green-500"></div>
60+
<div className="group relative h-[30rem] rounded-xl shadow-xl">
61+
<div className="pointer-events-none absolute inset-0 rounded-xl bg-gradient-to-r from-blue-700 to-purple-700 blur-lg transition-all group-hover:brightness-75 dark:group-hover:brightness-125" />
62+
<div className="absolute inset-0 rounded-xl border-zinc-800 bg-zinc-900 p-4 text-white">
63+
<div className="mb-3 flex items-center border-b border-zinc-800">
64+
<div className="xs:flex absolute left-4 hidden space-x-2">
65+
<div className="h-3 w-3 rounded-full bg-red-500"></div>
66+
<div className="h-3 w-3 rounded-full bg-yellow-500"></div>
67+
<div className="h-3 w-3 rounded-full bg-green-500"></div>
6868
</div>
69-
<div className="flex mx-auto space-x-4">
69+
<div className="mx-auto flex space-x-4">
7070
<div className="flex items-center">
7171
{tabs.map((tab) => (
7272
<button
7373
key={tab.id}
7474
onClick={() => setActiveTab(tab.id)}
75-
className={`cursor-pointer bg-transparent border-none rounded py-1 px-2 text-sm font-medium transition-colors relative ${
75+
className={`relative cursor-pointer rounded border-none bg-transparent px-2 py-1 text-sm font-medium transition-colors ${
7676
activeTab === tab.id ? "text-zinc-100" : "text-zinc-400 hover:text-zinc-300"
7777
}`}
7878
>
7979
{tab.label}
80-
{activeTab === tab.id && <span className="absolute bottom-0 left-0 w-full h-0.5 bg-zinc-200"></span>}
80+
{activeTab === tab.id && <span className="absolute bottom-0 left-0 h-0.5 w-full bg-zinc-200"></span>}
8181
</button>
8282
))}
8383
</div>
8484
</div>
8585
</div>
86-
<div className="overflow-auto h-[calc(100%-2.5rem)]">
86+
<div className="h-[calc(100%-2.5rem)] overflow-auto">
8787
<SyntaxHighlighter
8888
language="typescript"
8989
style={vscDarkPlus}

website/src/components/landing/hero.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,31 @@ export function Hero() {
77
const { siteConfig } = useDocusaurusContext();
88

99
return (
10-
<header className="relative py-16 md:py-24 lg:py-32 bg-gradient-to-br from-zinc-50 to-zinc-100 dark:from-zinc-900 dark:to-zinc-950 overflow-hidden">
10+
<header className="relative overflow-hidden bg-gradient-to-br from-zinc-50 to-zinc-100 py-16 md:py-24 lg:py-32 dark:from-zinc-900 dark:to-zinc-950">
1111
<div className="px-5 md:px-10">
1212
<div className="mx-auto w-full max-w-7xl">
13-
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
14-
<div className="flex flex-col z-10">
13+
<div className="grid grid-cols-1 items-center gap-12 lg:grid-cols-2">
14+
<div className="z-10 flex flex-col">
1515
<div>
16-
<h1 className="py-1 text-3xl md:text-4xl lg:text-5xl font-bold tracking-tight bg-clip-text text-transparent bg-gradient-to-r from-zinc-800 to-zinc-600 dark:from-zinc-100 dark:to-zinc-400 mb-4">
16+
<h1 className="mb-4 bg-gradient-to-r from-zinc-800 to-zinc-600 bg-clip-text py-1 text-3xl font-bold tracking-tight text-transparent md:text-4xl lg:text-5xl dark:from-zinc-100 dark:to-zinc-400">
1717
{siteConfig.tagline}
1818
</h1>
19-
<h2 className="text-zinc-700 dark:text-zinc-300 text-base md:text-lg max-w-xl leading-relaxed font-medium">
19+
<h2 className="max-w-xl text-base font-medium leading-relaxed text-zinc-700 md:text-lg dark:text-zinc-300">
2020
next-safe-action handles your Next.js app mutations type safety, input/output validation, server
2121
errors and even more!
2222
</h2>
2323
</div>
2424

2525
<InstallBox className="mt-6" />
2626

27-
<div className="flex gap-4 mt-8">
27+
<div className="mt-6 flex gap-4">
28+
<GitHubButton />
2829
<a
2930
href="/docs/getting-started"
30-
className="!no-underline hover:translate-y-[-2px] transition-transform border border-zinc-300 dark:border-zinc-700 !text-zinc-800 dark:!text-zinc-200 cursor-pointer rounded-lg bg-white dark:bg-zinc-800 px-4 py-2 md:px-5 md:py-3 font-semibold inline-flex items-center justify-center space-x-2 text-sm md:text-base"
31+
className="inline-flex cursor-pointer items-center justify-center space-x-2 rounded-lg bg-gradient-to-r from-blue-500 to-purple-500 px-4 py-2 text-sm font-semibold !text-zinc-100 !no-underline shadow-lg transition-transform hover:translate-y-[-2px] md:text-base"
3132
>
32-
Getting started
33+
Get started
3334
</a>
34-
<GitHubButton />
3535
</div>
3636
</div>
3737

website/src/components/landing/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ export function Landing() {
88
return (
99
<main className="bg-white dark:bg-zinc-950">
1010
<Hero />
11+
<Testimonials />
1112
<Playground />
1213
<Features />
13-
<Testimonials />
1414
<GettingStarted />
1515
</main>
1616
);

website/src/components/landing/install-box.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,28 +34,28 @@ export function InstallBox({ className }: Props) {
3434

3535
return (
3636
<div className={`flex flex-col ${className ?? ""}`}>
37-
<div className="flex items-center gap-2">
37+
<div className="flex items-center">
3838
{packageManagers.map((pm) => (
3939
<button
4040
key={pm}
4141
onClick={() => setPackageManager(pm)}
42-
className={`cursor-pointer bg-zinc-100 dark:bg-zinc-800 border-none rounded py-2 px-6 text-sm font-medium transition-colors relative ${
42+
className={`relative cursor-pointer border-none bg-transparent px-6 py-2 text-sm font-medium transition-colors ${
4343
packageManager === pm
4444
? "text-zinc-900 dark:text-zinc-100"
45-
: "text-zinc-500 dark:text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-300"
45+
: "text-zinc-500 hover:text-zinc-700 dark:text-zinc-400 dark:hover:text-zinc-300"
4646
}`}
4747
>
4848
{pm}
4949
{packageManager === pm && (
50-
<span className="absolute bottom-0 left-0 w-full h-0.5 bg-zinc-800 dark:bg-zinc-200"></span>
50+
<span className="absolute bottom-0 left-0 h-0.5 w-full bg-zinc-800 dark:bg-zinc-200"></span>
5151
)}
5252
</button>
5353
))}
5454
</div>
5555

56-
<div className="mt-2 relative bg-zinc-100 dark:bg-zinc-900 p-4 rounded-md">
57-
<div className="font-mono text-sm flex items-center">
58-
<span className="text-zinc-500 mr-3 select-none">$</span>
56+
<div className="relative mt-2 rounded-md bg-zinc-100 p-4 dark:bg-zinc-900">
57+
<div className="flex items-center font-mono text-sm">
58+
<span className="mr-3 select-none text-zinc-500">$</span>
5959
<div>
6060
<span className="text-cyan-800 dark:text-cyan-200">{getPmInstall(packageManager)}</span>
6161
<span> </span>
@@ -64,7 +64,7 @@ export function InstallBox({ className }: Props) {
6464
</div>
6565
<button
6666
onClick={copyToClipboard}
67-
className="cursor-pointer border-none absolute right-3 top-3 p-1.5 rounded-md bg-zinc-200 dark:bg-zinc-800 hover:bg-zinc-300 dark:hover:bg-zinc-700 transition-colors"
67+
className="absolute right-3 top-3 cursor-pointer rounded-md border-none bg-zinc-200 p-1.5 transition-colors hover:bg-zinc-300 dark:bg-zinc-800 dark:hover:bg-zinc-700"
6868
title="Copy to clipboard"
6969
>
7070
{copied ? (
Lines changed: 39 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,125 +1,47 @@
1-
import { useState } from "react";
1+
"use client";
22

3-
export function Playground() {
4-
const [name, setName] = useState("");
5-
const [result, setResult] = useState(null);
6-
const [loading, setLoading] = useState(false);
7-
8-
const handleSubmit = (e) => {
9-
e.preventDefault();
10-
setLoading(true);
3+
import { useEffect, useRef, useState } from "react";
114

12-
// Simulate API call
13-
setTimeout(() => {
14-
setResult({ success: true, data: { message: `Hello, ${name || "World"}!` } });
15-
setLoading(false);
16-
}, 500);
17-
};
5+
export function Playground() {
6+
const [isVisible, setIsVisible] = useState(false);
7+
const containerRef = useRef<HTMLDivElement>(null);
8+
9+
useEffect(() => {
10+
const observer = new IntersectionObserver(
11+
(entries) => {
12+
if (entries[0].isIntersecting) {
13+
setIsVisible(true);
14+
observer.disconnect();
15+
}
16+
},
17+
{ threshold: 0.1 }
18+
);
19+
20+
if (containerRef.current) {
21+
observer.observe(containerRef.current);
22+
}
23+
24+
return () => {
25+
observer.disconnect();
26+
};
27+
}, []);
1828

1929
return (
20-
<div className="px-5 md:px-10 py-20 bg-white dark:bg-zinc-950">
21-
<div className="mx-auto w-full max-w-5xl">
22-
<div className="text-center mb-12">
23-
<h2 className="text-2xl md:text-3xl font-bold mb-4">Try it out</h2>
24-
<p className="text-zinc-600 dark:text-zinc-400 max-w-2xl mx-auto">
25-
See how next-safe-action handles your server action in real time
26-
</p>
27-
</div>
28-
29-
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 bg-zinc-50 dark:bg-zinc-900 rounded-xl p-6 md:p-8 shadow-lg">
30-
<div className="space-y-6">
31-
<div className="bg-white dark:bg-zinc-800 rounded-lg p-4 border border-zinc-200 dark:border-zinc-700">
32-
<div className="text-sm font-semibold mb-2 text-zinc-500 dark:text-zinc-400">Client Component</div>
33-
<pre className="text-xs md:text-sm font-mono text-zinc-800 dark:text-zinc-200 overflow-auto">
34-
<code>{`'use client';
35-
36-
import { useAction } from 'next-safe-action/hooks';
37-
import { greetAction } from '@/actions';
38-
39-
export function GreetingForm() {
40-
const { execute, result, status } =
41-
useAction(greetAction);
42-
43-
return (
44-
<form onSubmit={(e) => {
45-
e.preventDefault();
46-
const formData = new FormData(e.target);
47-
execute({
48-
name: formData.get('name')
49-
});
50-
}}>
51-
<input name="name" />
52-
<button type="submit">
53-
{status === 'executing' ? 'Loading...' : 'Submit'}
54-
</button>
55-
56-
{result?.data && (
57-
<div>{result.data.message}</div>
58-
)}
59-
</form>
60-
);
61-
}`}</code>
62-
</pre>
63-
</div>
64-
65-
<div className="bg-white dark:bg-zinc-800 rounded-lg p-4 border border-zinc-200 dark:border-zinc-700">
66-
<div className="text-sm font-semibold mb-2 text-zinc-500 dark:text-zinc-400">Server Action</div>
67-
<pre className="text-xs md:text-sm font-mono text-zinc-800 dark:text-zinc-200 overflow-auto">
68-
<code>{`import { z } from 'zod';
69-
import { createSafeAction } from 'next-safe-action';
70-
71-
export const greetAction = createSafeAction({
72-
input: z.object({
73-
name: z.string().min(1)
74-
}),
75-
76-
handler: async ({ name }) => {
77-
return { message: \`Hello, \${name}!\` };
78-
}
79-
});`}</code>
80-
</pre>
81-
</div>
82-
</div>
83-
84-
<div className="flex flex-col justify-center space-y-8">
85-
<div className="bg-white dark:bg-zinc-800 rounded-lg p-6 border border-zinc-200 dark:border-zinc-700">
86-
<h3 className="font-medium text-lg mb-4">Live Demo</h3>
87-
88-
<form onSubmit={handleSubmit} className="space-y-4">
89-
<div>
90-
<label htmlFor="name" className="block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1">
91-
Your name
92-
</label>
93-
<input
94-
type="text"
95-
id="name"
96-
value={name}
97-
onChange={(e) => setName(e.target.value)}
98-
placeholder="Enter your name"
99-
className="w-full px-3 py-2 bg-zinc-50 dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-700 rounded-md focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-transparent outline-none"
100-
/>
101-
</div>
102-
103-
<button
104-
type="submit"
105-
disabled={loading}
106-
className={`w-full py-2 px-4 rounded-md font-medium text-white ${loading ? "bg-blue-400 dark:bg-blue-600" : "bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700"} transition-colors`}
107-
>
108-
{loading ? "Processing..." : "Submit"}
109-
</button>
110-
</form>
111-
112-
{result && (
113-
<div className="mt-6 p-4 bg-zinc-100 dark:bg-zinc-900 rounded-md">
114-
<pre className="text-sm font-mono text-zinc-800 dark:text-zinc-200 whitespace-pre-wrap">
115-
{JSON.stringify(result, null, 2)}
116-
</pre>
117-
</div>
118-
)}
119-
</div>
120-
</div>
121-
</div>
30+
<div className="mx-auto w-full max-w-7xl py-6" ref={containerRef}>
31+
<div className="mb-12 text-center">
32+
<h2 className="mb-4 text-2xl font-bold md:text-3xl">Try it out</h2>
33+
<p className="mx-auto max-w-2xl text-zinc-600 dark:text-zinc-400">
34+
See how next-safe-action lets you handle Server Actions in a type safe way.
35+
</p>
12236
</div>
37+
{isVisible && (
38+
<iframe
39+
loading="lazy"
40+
onError={() => {}}
41+
className="h-[40rem] w-full rounded-lg"
42+
src="https://stackblitz.com/edit/next-safe-action-playground?embed=1&file=src%2Flib%2Fsafe-action.ts&ctl=1"
43+
/>
44+
)}
12345
</div>
12446
);
12547
}

website/tailwind.config.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ module.exports = {
66
preflight: false,
77
},
88
theme: {
9-
extend: {},
9+
extend: {
10+
screens: {
11+
xs: "490px",
12+
},
13+
},
1014
},
1115
plugins: [require("tailwindcss-bg-patterns")],
1216
};

0 commit comments

Comments
 (0)