mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-05-05 22:03:16 -04:00
Merge pull request #225 from spacedriveapp/revert-224-revert-223-eng-125-implement-waitlist-on-landing-page
Revert "Revert "Eng 125 implement waitlist on landing page""
This commit is contained in:
@@ -25,9 +25,11 @@
|
||||
"phosphor-react": "^1.4.1",
|
||||
"prismjs": "^1.28.0",
|
||||
"react": "^18.1.0",
|
||||
"react-canvas-confetti": "^1.3.0",
|
||||
"react-device-detect": "^2.2.2",
|
||||
"react-dom": "^18.1.0",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-hook-form": "^7.31.3",
|
||||
"react-router-dom": "6.3.0",
|
||||
"react-tsparticles": "^2.0.6",
|
||||
"simple-icons": "^7.0.0",
|
||||
@@ -36,6 +38,7 @@
|
||||
"devDependencies": {
|
||||
"@babel/preset-react": "^7.17.12",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@types/node": "^17.0.36",
|
||||
"@types/prismjs": "^1.26.0",
|
||||
"@types/react": "^18.0.9",
|
||||
"@types/react-dom": "^18.0.5",
|
||||
|
||||
@@ -4,22 +4,15 @@ import { loadFull } from 'tsparticles';
|
||||
|
||||
export const Bubbles = () => {
|
||||
const particlesInit = async (main: any) => {
|
||||
console.log(main);
|
||||
await loadFull(main);
|
||||
};
|
||||
|
||||
const particlesLoaded = (container: any) => {
|
||||
console.log(container);
|
||||
};
|
||||
|
||||
return (
|
||||
//@ts-ignore
|
||||
<Particles
|
||||
id="tsparticles"
|
||||
className="absolute z-0"
|
||||
init={particlesInit}
|
||||
//@ts-ignore
|
||||
loaded={particlesLoaded}
|
||||
options={{
|
||||
fpsLimit: 120,
|
||||
interactivity: {
|
||||
|
||||
@@ -1,86 +1,197 @@
|
||||
import { Apple, Github, Linux, Windows } from '@icons-pack/react-simple-icons';
|
||||
import { Github } from '@icons-pack/react-simple-icons';
|
||||
import { Button, Input } from '@sd/ui';
|
||||
// import clsx from 'clsx';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useState } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import React, { FormEvent, useState } from 'react';
|
||||
import ReactCanvasConfetti from 'react-canvas-confetti';
|
||||
import { SubmitHandler, useForm } from 'react-hook-form';
|
||||
|
||||
import { ReactComponent as Alert } from '../../../../packages/interface/src/assets/svg/alert.svg';
|
||||
import { ReactComponent as Info } from '../../../../packages/interface/src/assets/svg/info.svg';
|
||||
import { ReactComponent as Spinner } from '../../../../packages/interface/src/assets/svg/spinner.svg';
|
||||
|
||||
interface WaitlistInputs {
|
||||
email: string;
|
||||
}
|
||||
|
||||
export function HomeCTA() {
|
||||
// const [showWaitlistInput, setShowWaitlistInput] = useState(false);
|
||||
// const [waitlistEmail, setWaitlistEmail] = useState('');
|
||||
const { register, handleSubmit } = useForm<WaitlistInputs>();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [showWaitlistInput, setShowWaitlistInput] = useState(false);
|
||||
const [waitlistError, setWaitlistError] = useState('');
|
||||
const [waitlistSubmitted, setWaitlistSubmitted] = useState(false);
|
||||
const [fire, setFire] = useState<boolean | number>(false);
|
||||
|
||||
const prod = import.meta.env.NODE_ENV === 'production';
|
||||
const url = prod ? 'https://waitlist-api.spacedrive.com' : 'http://localhost:3000';
|
||||
|
||||
async function handleWaitlistSubmit<SubmitHandler>({ email }: WaitlistInputs) {
|
||||
if (!email.trim().length) return;
|
||||
|
||||
console.log('email', email);
|
||||
|
||||
setLoading(true);
|
||||
|
||||
const req = await fetch(`${url}/api/waitlist`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email
|
||||
})
|
||||
});
|
||||
|
||||
if (req.status === 200) {
|
||||
setWaitlistError('');
|
||||
setFire(Math.random());
|
||||
setWaitlistSubmitted(true);
|
||||
setLoading(false);
|
||||
} else if (req.status >= 400 && req.status < 500) {
|
||||
const res = await req.json();
|
||||
setWaitlistError(res.message);
|
||||
|
||||
// Remove error after a few seconds
|
||||
setTimeout(() => {
|
||||
setWaitlistError('');
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ReactCanvasConfetti
|
||||
fire={fire}
|
||||
angle={44}
|
||||
className="absolute top-48"
|
||||
colors={['#26ccff', '#a25afd']}
|
||||
decay={0.8}
|
||||
drift={1}
|
||||
gravity={1}
|
||||
origin={{
|
||||
x: 0.5,
|
||||
y: 0.5
|
||||
}}
|
||||
particleCount={55}
|
||||
resize
|
||||
scalar={1}
|
||||
shapes={['circle', 'square']}
|
||||
spread={360}
|
||||
startVelocity={45}
|
||||
ticks={600}
|
||||
useWorker
|
||||
zIndex={-1}
|
||||
/>
|
||||
<div className="z-30 flex flex-row items-center h-10 space-x-4 animation-delay-2 fade-in">
|
||||
{/* {showWaitlistInput ? ( */}
|
||||
{/* <>
|
||||
{!showWaitlistInput ? (
|
||||
<>
|
||||
<Button
|
||||
onClick={() => setShowWaitlistInput(true)}
|
||||
className="z-30 border-0 cursor-pointer"
|
||||
variant="primary"
|
||||
>
|
||||
Join Waitlist
|
||||
</Button> */}
|
||||
<Button
|
||||
href="https://github.com/spacedriveapp/spacedrive"
|
||||
target="_blank"
|
||||
className="z-30 cursor-pointer"
|
||||
variant="gray"
|
||||
>
|
||||
<Github className="inline w-5 h-5 -mt-[4px] -ml-1 mr-2" fill="white" />
|
||||
Star on GitHub
|
||||
</Button>
|
||||
{/* </>
|
||||
</Button>
|
||||
<Button
|
||||
href="https://github.com/spacedriveapp/spacedrive"
|
||||
target="_blank"
|
||||
className="z-30 cursor-pointer"
|
||||
variant="gray"
|
||||
>
|
||||
<Github className="inline w-5 h-5 -mt-[4px] -ml-1 mr-2" fill="white" />
|
||||
Star on GitHub
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
fetch('https://waitlist-api.spacedrive.com/api/expression-of-interest', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: waitlistEmail
|
||||
})
|
||||
});
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<Input
|
||||
type="email"
|
||||
name="email"
|
||||
autoFocus
|
||||
value={waitlistEmail}
|
||||
onChange={(e) => setWaitlistEmail(e.target.value)}
|
||||
placeholder="Enter your email"
|
||||
className="rounded-r-none"
|
||||
/>
|
||||
<Button
|
||||
onClick={() => setShowWaitlistInput(true)}
|
||||
className="z-30 border-0 rounded-l-none cursor-pointer"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
<form onSubmit={handleSubmit(handleWaitlistSubmit)}>
|
||||
<div className="flex flex-col justify-center">
|
||||
{(waitlistError || waitlistSubmitted) && (
|
||||
<div
|
||||
className={clsx({
|
||||
'flex flex-row items-center bg-opacity-20 border-2 my-2 px-2 rounded-md': true,
|
||||
'bg-red-800 border-red-900': waitlistError,
|
||||
'bg-green-800 border-green-900': !waitlistError,
|
||||
'-mt-2': waitlistSubmitted
|
||||
})}
|
||||
>
|
||||
{waitlistError ? (
|
||||
<Alert className="fill-red-500 w-5 mr-1" />
|
||||
) : (
|
||||
<Info className="fill-green-500 w-5 mr-1" />
|
||||
)}
|
||||
<p
|
||||
className={clsx({
|
||||
'text-sm': true,
|
||||
'text-red-500': waitlistError,
|
||||
'text-green-500': !waitlistError
|
||||
})}
|
||||
>
|
||||
{waitlistError || 'You have been added to the waitlist'}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<div className={'flex flex-row'}>
|
||||
<Input
|
||||
{...register('email')}
|
||||
type="email"
|
||||
autoFocus
|
||||
autoComplete="off"
|
||||
placeholder="Enter your email"
|
||||
className={clsx({
|
||||
'hidden': waitlistSubmitted,
|
||||
'rounded-r-none': !waitlistSubmitted
|
||||
})}
|
||||
disabled={waitlistSubmitted}
|
||||
/>
|
||||
{!waitlistSubmitted && (
|
||||
<Button
|
||||
onClick={() => setShowWaitlistInput(true)}
|
||||
className={clsx('z-30 border-0 rounded-l-none cursor-pointer', {
|
||||
'opacity-50 cursor-default': loading
|
||||
})}
|
||||
disabled={loading}
|
||||
variant="primary"
|
||||
type="submit"
|
||||
>
|
||||
{loading ? (
|
||||
<Spinner className="w-6 h-6 text-white text-opacity-40 animate-spin fill-white" />
|
||||
) : (
|
||||
'Submit'
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
*/}
|
||||
</div>
|
||||
<p className="z-30 px-6 mt-3 text-sm text-center text-gray-450 animation-delay-3 fade-in">
|
||||
{/* {showWaitlistInput ? (
|
||||
<p
|
||||
className={clsx('z-30 px-6 text-sm text-center text-gray-450 animation-delay-3 fade-in', {
|
||||
'mt-10': waitlistError,
|
||||
'mt-3': !waitlistError
|
||||
})}
|
||||
>
|
||||
{showWaitlistInput ? (
|
||||
<>
|
||||
We'll keep your place in the queue for early access.
|
||||
<br />
|
||||
<br />
|
||||
</>
|
||||
) : waitlistSubmitted ? (
|
||||
<>
|
||||
You have been added to the waitlist.
|
||||
<br />
|
||||
<br />
|
||||
</>
|
||||
) : (
|
||||
<>*/}
|
||||
Coming soon on macOS, Windows and Linux.
|
||||
<br />
|
||||
Shortly after to iOS & Android.
|
||||
{/* )}
|
||||
</p>*/}
|
||||
<>
|
||||
Coming soon on macOS, Windows and Linux.
|
||||
<br />
|
||||
Shortly after to iOS & Android.
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import { Apple, Github, Linux, Windows } from '@icons-pack/react-simple-icons';
|
||||
import { Button, Input } from '@sd/ui';
|
||||
import clsx from 'clsx';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
||||
import { ReactComponent as Info } from '../../../../packages/interface/src/assets/svg/info.svg';
|
||||
import AppEmbed from '../components/AppEmbed';
|
||||
import { Bubbles } from '../components/Bubbles';
|
||||
import { Footer } from '../components/Footer';
|
||||
import HomeCTA from '../components/HomeCTA';
|
||||
import NavBar from '../components/NavBar';
|
||||
import NewBanner from '../components/NewBanner';
|
||||
|
||||
interface SectionProps {
|
||||
orientation: 'left' | 'right';
|
||||
@@ -44,6 +40,40 @@ function Section(props: SectionProps = { orientation: 'left' }) {
|
||||
}
|
||||
|
||||
function Page() {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [unsubscribedFromWaitlist, setUnsubscribedFromWaitlist] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!window) return;
|
||||
|
||||
const cuid = searchParams.get('wunsub');
|
||||
if (!cuid) return;
|
||||
|
||||
(async () => {
|
||||
const prod = import.meta.env.NODE_ENV === 'production';
|
||||
const url = prod ? 'https://waitlist-api.spacedrive.com' : 'http://localhost:3000';
|
||||
|
||||
const req = await fetch(`${url}/api/waitlist?i=${cuid}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
if (req.status === 200) {
|
||||
setUnsubscribedFromWaitlist(true);
|
||||
window.history.replaceState(
|
||||
{},
|
||||
'',
|
||||
prod ? 'https://spacedrive.com' : 'http://localhost:8003'
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
setUnsubscribedFromWaitlist(false);
|
||||
}, 5000);
|
||||
} else if (req.status >= 400 && req.status < 500) {
|
||||
alert('An error occurred while unsubscribing from waitlist');
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mt-22 lg:mt-28" id="content" aria-hidden="true" />
|
||||
@@ -53,6 +83,17 @@ function Page() {
|
||||
href="https://spacedrive.hashnode.dev/spacedrive-funding-announcement"
|
||||
link="Read post"
|
||||
/> */}
|
||||
{unsubscribedFromWaitlist && (
|
||||
<div
|
||||
className={
|
||||
'-mt-8 flex flex-row items-center bg-opacity-20 border-2 my-2 px-2 rounded-md bg-green-800 border-green-900'
|
||||
}
|
||||
>
|
||||
<Info className="fill-green-500 w-5 mr-1" />
|
||||
<p className={'text-sm text-green-500'}>You have been unsubscribed from the waitlist</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<h1 className="z-30 px-2 mb-3 text-4xl font-black leading-tight text-center fade-in-heading md:text-6xl">
|
||||
A file explorer from the future.
|
||||
</h1>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"extends": "../../packages/config/interface.tsconfig.json",
|
||||
"compilerOptions": {},
|
||||
"include": ["src"]
|
||||
"extends": "../../packages/config/interface.tsconfig.json",
|
||||
"compilerOptions": {},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
||||
|
||||
12
packages/interface/src/assets/svg/alert.svg
Normal file
12
packages/interface/src/assets/svg/alert.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M16 3C13.4288 3 10.9154 3.76244 8.77759 5.1909C6.63975 6.61935 4.97351 8.64968 3.98957 11.0251C3.00563 13.4006 2.74819 16.0144 3.2498 18.5362C3.75141 21.0579 4.98953 23.3743 6.80762 25.1924C8.6257 27.0105 10.9421 28.2486 13.4638 28.7502C15.9856 29.2518 18.5995 28.9944 20.9749 28.0104C23.3503 27.0265 25.3807 25.3603 26.8091 23.2224C28.2376 21.0846 29 18.5712 29 16C28.9934 12.5542 27.6216 9.25145 25.1851 6.81491C22.7486 4.37837 19.4458 3.00661 16 3ZM15 10C15 9.73478 15.1054 9.48043 15.2929 9.29289C15.4804 9.10536 15.7348 9 16 9C16.2652 9 16.5196 9.10536 16.7071 9.29289C16.8946 9.48043 17 9.73478 17 10V17C17 17.2652 16.8946 17.5196 16.7071 17.7071C16.5196 17.8946 16.2652 18 16 18C15.7348 18 15.4804 17.8946 15.2929 17.7071C15.1054 17.5196 15 17.2652 15 17V10ZM16 23C15.7033 23 15.4133 22.912 15.1667 22.7472C14.92 22.5824 14.7277 22.3481 14.6142 22.074C14.5007 21.7999 14.471 21.4983 14.5288 21.2074C14.5867 20.9164 14.7296 20.6491 14.9393 20.4393C15.1491 20.2296 15.4164 20.0867 15.7074 20.0288C15.9983 19.9709 16.2999 20.0007 16.574 20.1142C16.8481 20.2277 17.0824 20.42 17.2472 20.6666C17.412 20.9133 17.5 21.2033 17.5 21.5C17.5 21.8978 17.342 22.2794 17.0607 22.5607C16.7794 22.842 16.3978 23 16 23Z"
|
||||
fill="fill-current"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
12
packages/interface/src/assets/svg/info.svg
Normal file
12
packages/interface/src/assets/svg/info.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M16 3C13.4288 3 10.9154 3.76244 8.77759 5.1909C6.63975 6.61935 4.97351 8.64968 3.98957 11.0251C3.00563 13.4006 2.74819 16.0144 3.2498 18.5362C3.75141 21.0579 4.98953 23.3743 6.80762 25.1924C8.6257 27.0105 10.9421 28.2486 13.4638 28.7502C15.9856 29.2518 18.5995 28.9944 20.9749 28.0104C23.3503 27.0265 25.3807 25.3603 26.8091 23.2224C28.2376 21.0846 29 18.5712 29 16C28.9934 12.5542 27.6216 9.25145 25.1851 6.81491C22.7486 4.37837 19.4458 3.00661 16 3ZM15.75 9C16.0467 9 16.3367 9.08797 16.5834 9.2528C16.83 9.41762 17.0223 9.65189 17.1358 9.92597C17.2494 10.2001 17.2791 10.5017 17.2212 10.7926C17.1633 11.0836 17.0204 11.3509 16.8107 11.5607C16.6009 11.7704 16.3336 11.9133 16.0426 11.9712C15.7517 12.0291 15.4501 11.9994 15.176 11.8858C14.9019 11.7723 14.6676 11.58 14.5028 11.3334C14.338 11.0867 14.25 10.7967 14.25 10.5C14.25 10.1022 14.408 9.72064 14.6893 9.43934C14.9706 9.15804 15.3522 9 15.75 9ZM17 23H16C15.7348 23 15.4804 22.8946 15.2929 22.7071C15.1054 22.5196 15 22.2652 15 22V16C14.7348 16 14.4804 15.8946 14.2929 15.7071C14.1054 15.5196 14 15.2652 14 15C14 14.7348 14.1054 14.4804 14.2929 14.2929C14.4804 14.1054 14.7348 14 15 14H16C16.2652 14 16.5196 14.1054 16.7071 14.2929C16.8946 14.4804 17 14.7348 17 15V21C17.2652 21 17.5196 21.1054 17.7071 21.2929C17.8946 21.4804 18 21.7348 18 22C18 22.2652 17.8946 22.5196 17.7071 22.7071C17.5196 22.8946 17.2652 23 17 23Z"
|
||||
fill="fill-current"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
15
packages/interface/src/assets/svg/spinner.svg
Normal file
15
packages/interface/src/assets/svg/spinner.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg
|
||||
role="status"
|
||||
viewBox="0 0 100 101"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
<path
|
||||
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
|
||||
fill="currentFill"
|
||||
></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -33,9 +33,10 @@ interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
variant?: keyof typeof variants;
|
||||
}
|
||||
|
||||
export const Input = ({ ...props }: InputProps) => {
|
||||
export const Input = React.forwardRef<HTMLInputElement, InputProps>(({ ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={clsx(
|
||||
`px-3 py-1 rounded-md border leading-7 outline-none shadow-xs focus:ring-2 transition-all`,
|
||||
@@ -44,7 +45,7 @@ export const Input = ({ ...props }: InputProps) => {
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
interface TextAreaProps extends React.InputHTMLAttributes<HTMLTextAreaElement> {
|
||||
variant?: keyof typeof variants;
|
||||
|
||||
BIN
pnpm-lock.yaml
generated
BIN
pnpm-lock.yaml
generated
Binary file not shown.
Reference in New Issue
Block a user