mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-02 11:13:29 +00:00
move releases and expression of interest into landing (#848)
* move releases and expression of interest into landing * fix eslint on landing * fs routing kinda sucks * fix waitlist link * cringe
This commit is contained in:
parent
a470844f85
commit
9fb72a1dc8
|
@ -49,7 +49,7 @@
|
|||
"active": false,
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEZBMURCMkU5NEU3NDAyOEMKUldTTUFuUk82YklkK296dlkxUGkrTXhCT3ZMNFFVOWROcXNaS0RqWU1kMUdRV2tDdFdIS0Y3YUsK",
|
||||
"endpoints": [
|
||||
"https://releases-6oxwxxryr-spacedrive.vercel.app/{{target}}/{{arch}}/{{current_version}}"
|
||||
"https://spacedrive.com/api/releases/{{target}}/{{arch}}/{{current_version}}"
|
||||
]
|
||||
},
|
||||
"allowlist": {
|
||||
|
|
8
apps/landing/drizzle.config.ts
Normal file
8
apps/landing/drizzle.config.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import 'dotenv/config';
|
||||
import { Config } from 'drizzle-kit';
|
||||
import { env } from './src/env';
|
||||
|
||||
export default {
|
||||
schema: ['./src/server/db.ts'],
|
||||
connectionString: env.DATABASE_URL
|
||||
} satisfies Config;
|
1
apps/landing/next-env.d.ts
vendored
1
apps/landing/next-env.d.ts
vendored
|
@ -1,5 +1,6 @@
|
|||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
/// <reference types="next/navigation-types/compat/navigation" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
const { withContentlayer } = require('next-contentlayer');
|
||||
import { withContentlayer } from 'next-contentlayer';
|
||||
// Validate env on build
|
||||
import './src/env.js';
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
|
@ -37,4 +39,4 @@ const nextConfig = {
|
|||
}
|
||||
};
|
||||
|
||||
module.exports = withContentlayer(nextConfig);
|
||||
export default withContentlayer(nextConfig);
|
|
@ -1,20 +1,27 @@
|
|||
{
|
||||
"name": "@sd/landing",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "contentlayer build && next build",
|
||||
"start": "next start",
|
||||
"prod": "pnpm build && pnpm start",
|
||||
"lint": "next lint",
|
||||
"typecheck": "contentlayer build && tsc -b"
|
||||
"typecheck": "contentlayer build && tsc -b",
|
||||
"push": "drizzle-kit push:mysql"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-ses": "^3.337.0",
|
||||
"@icons-pack/react-simple-icons": "^7.2.0",
|
||||
"@planetscale/database": "^1.7.0",
|
||||
"@sd/assets": "workspace:*",
|
||||
"@sd/ui": "workspace:*",
|
||||
"@t3-oss/env-nextjs": "^0.3.1",
|
||||
"@vercel/edge-config": "^0.1.11",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"clsx": "^1.2.1",
|
||||
"contentlayer": "^0.3.2",
|
||||
"drizzle-orm": "^0.26.0",
|
||||
"markdown-to-jsx": "^7.2.0",
|
||||
"next": "13.4.3",
|
||||
"next-contentlayer": "^0.3.2",
|
||||
|
@ -33,7 +40,8 @@
|
|||
"remark-gfm": "^3.0.1",
|
||||
"remark-math": "^5.1.1",
|
||||
"sharp": "^0.32.1",
|
||||
"tsparticles": "^2.9.3"
|
||||
"tsparticles": "^2.9.3",
|
||||
"zod": "^3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sd/config": "workspace:*",
|
||||
|
@ -43,6 +51,7 @@
|
|||
"@types/react-burger-menu": "^2.8.3",
|
||||
"@types/react-dom": "18.2.4",
|
||||
"@types/react-helmet": "^6.1.6",
|
||||
"drizzle-kit": "db-push",
|
||||
"postcss": "^8.4.23",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"typescript": "5.0.4"
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import { get } from '@vercel/edge-config';
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export const config = {
|
||||
runtime: 'edge'
|
||||
};
|
||||
export const runtime = 'edge';
|
||||
|
||||
export async function GET(
|
||||
_: Request,
|
112
apps/landing/src/app/api/waitlist/route.ts
Normal file
112
apps/landing/src/app/api/waitlist/route.ts
Normal file
|
@ -0,0 +1,112 @@
|
|||
import type { NextRequest } from 'next/server';
|
||||
import { z } from 'zod';
|
||||
import { sendEmail } from '~/server/aws';
|
||||
import { db, eq, waitlistTable } from '~/server/db';
|
||||
import { welcomeTemplate } from './welcomeEmail';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
const emailSchema = z.object({
|
||||
email: z
|
||||
.string({
|
||||
required_error: 'Email is required',
|
||||
invalid_type_error: 'Email must be a string'
|
||||
})
|
||||
.email({
|
||||
message: 'Invalid email address'
|
||||
})
|
||||
.transform((value) => value.toLowerCase())
|
||||
});
|
||||
|
||||
function randomId(len = 10) {
|
||||
if (len % 2 !== 0) throw new Error('len must be a multiple of 2');
|
||||
const array = new Uint8Array(len / 2); // 1 char int to 2 chars hex
|
||||
self.crypto.getRandomValues(array);
|
||||
return [...array].map((c) => c.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
const result = emailSchema.safeParse(await req.json());
|
||||
if (!result.success) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
message: result.error.toString()
|
||||
}),
|
||||
{
|
||||
status: 400
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const { email } = result.data;
|
||||
|
||||
try {
|
||||
const emailExist = await db.select({ email: waitlistTable.email }).from(waitlistTable);
|
||||
if (emailExist.length > 0) {
|
||||
return new Response(undefined, {
|
||||
status: 204
|
||||
});
|
||||
}
|
||||
|
||||
const unsubId = randomId(26);
|
||||
await db.insert(waitlistTable).values({
|
||||
cuid: unsubId,
|
||||
email,
|
||||
created_at: new Date()
|
||||
});
|
||||
|
||||
await sendEmail(
|
||||
email,
|
||||
'Welcome to Spacedrive',
|
||||
welcomeTemplate(`https://spacedrive.com/?wunsub=${unsubId}`)
|
||||
);
|
||||
|
||||
return new Response(null, {
|
||||
status: 204
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
message: 'Something went wrong while trying to create invite'
|
||||
}),
|
||||
{
|
||||
status: 500,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(req: NextRequest) {
|
||||
const url = new URL(req.url);
|
||||
|
||||
const id = url.searchParams.get('i');
|
||||
if (!id)
|
||||
return new Response(JSON.stringify(undefined), {
|
||||
status: 400
|
||||
});
|
||||
|
||||
try {
|
||||
await db.delete(waitlistTable).where(eq(waitlistTable.cuid, id));
|
||||
|
||||
return new Response(null, {
|
||||
status: 204
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
message: 'Something went wrong while trying to unsubscribe from waitlist'
|
||||
}),
|
||||
{
|
||||
status: 500,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
25
apps/landing/src/app/api/waitlist/welcomeEmail.ts
Normal file
25
apps/landing/src/app/api/waitlist/welcomeEmail.ts
Normal file
File diff suppressed because one or more lines are too long
|
@ -20,17 +20,12 @@ export function HomeCTA() {
|
|||
const [waitlistSubmitted, setWaitlistSubmitted] = useState(false);
|
||||
const [fire, setFire] = useState<boolean | number>(false);
|
||||
|
||||
const url =
|
||||
process.env.NODE_ENV === 'production'
|
||||
? 'https://waitlist-api.spacedrive.com'
|
||||
: 'http://localhost:3000';
|
||||
|
||||
async function handleWaitlistSubmit<SubmitHandler>({ email }: WaitlistInputs) {
|
||||
if (!email.trim().length) return;
|
||||
|
||||
setLoading(true);
|
||||
|
||||
const req = await fetch(`${url}/api/waitlist`, {
|
||||
const req = await fetch(`/api/waitlist`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
|
|
31
apps/landing/src/env.js
Normal file
31
apps/landing/src/env.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
// @ts-check
|
||||
//
|
||||
// Has to be `.mjs` so it can be imported in `next.config.mjs`.
|
||||
// Next.js are so cringe for not having support for Typescript config files.
|
||||
//
|
||||
// Using `.mjs` with Drizzle Kit is seemingly impossible without `.ts` so we resort to `.js`.
|
||||
// Why does JS make this shit so hard, I just wanna import the file.
|
||||
//
|
||||
import { createEnv } from '@t3-oss/env-nextjs';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const env = createEnv({
|
||||
server: {
|
||||
DATABASE_URL: z.string().url(),
|
||||
AWS_SES_ACCESS_KEY: z.string(),
|
||||
AWS_SES_SECRET_KEY: z.string(),
|
||||
AWS_SES_REGION: z.string(),
|
||||
MAILER_FROM: z.string().default('Spacedrive <no-reply@spacedrive.com>')
|
||||
},
|
||||
client: {},
|
||||
runtimeEnv: {
|
||||
DATABASE_URL: process.env.DATABASE_URL,
|
||||
AWS_SES_ACCESS_KEY: process.env.AWS_SES_ACCESS_KEY,
|
||||
AWS_SES_SECRET_KEY: process.env.AWS_SES_SECRET_KEY,
|
||||
AWS_SES_REGION: process.env.AWS_SES_REGION,
|
||||
MAILER_FROM: process.env.MAILER_FROM
|
||||
},
|
||||
// In dev or in eslint disable checking.
|
||||
// Kinda sucks for in dev but you don't need the whole setup to change the docs.
|
||||
skipValidation: process.env.VERCEL !== '1'
|
||||
});
|
|
@ -1,13 +0,0 @@
|
|||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
|
||||
type Data = {
|
||||
name: string
|
||||
}
|
||||
|
||||
export default function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<Data>
|
||||
) {
|
||||
res.status(200).json({ name: 'John Doe' })
|
||||
}
|
|
@ -57,9 +57,8 @@ export default function HomePage() {
|
|||
(async () => {
|
||||
console.log('Unsubscribing from waitlist', process.env.NODE_ENV);
|
||||
const prod = process.env.NODE_ENV === 'production';
|
||||
const url = prod ? 'https://waitlist-api.spacedrive.com' : 'http://localhost:3000';
|
||||
|
||||
const req = await fetch(`${url}/api/waitlist?i=${cuid}`, {
|
||||
const req = await fetch(`/api/waitlist?i=${cuid}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
|
|
33
apps/landing/src/server/aws.ts
Normal file
33
apps/landing/src/server/aws.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { SESClient, SendEmailCommand } from '@aws-sdk/client-ses';
|
||||
import { env } from '~/env';
|
||||
|
||||
export const ses = new SESClient({
|
||||
region: env.AWS_SES_REGION,
|
||||
credentials: {
|
||||
accessKeyId: env.AWS_SES_ACCESS_KEY,
|
||||
secretAccessKey: env.AWS_SES_SECRET_KEY
|
||||
}
|
||||
});
|
||||
|
||||
export async function sendEmail(email: string, subject: string, body: string) {
|
||||
await ses.send(
|
||||
new SendEmailCommand({
|
||||
Destination: {
|
||||
ToAddresses: [email]
|
||||
},
|
||||
Message: {
|
||||
Body: {
|
||||
Html: {
|
||||
Charset: 'UTF-8',
|
||||
Data: body
|
||||
}
|
||||
},
|
||||
Subject: {
|
||||
Charset: 'UTF-8',
|
||||
Data: subject
|
||||
}
|
||||
},
|
||||
Source: env.MAILER_FROM
|
||||
})
|
||||
);
|
||||
}
|
23
apps/landing/src/server/db.ts
Normal file
23
apps/landing/src/server/db.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { connect } from '@planetscale/database';
|
||||
import { mysqlTable, serial, timestamp, varchar } from 'drizzle-orm/mysql-core';
|
||||
import { drizzle } from 'drizzle-orm/planetscale-serverless';
|
||||
import { env } from '~/env';
|
||||
|
||||
export { eq, and, or, type InferModel } from 'drizzle-orm';
|
||||
|
||||
const dbConnection = connect({
|
||||
url: env.DATABASE_URL
|
||||
});
|
||||
|
||||
export const db = drizzle(dbConnection);
|
||||
|
||||
export const waitlistTable = mysqlTable('waitlist', {
|
||||
id: serial('id').primaryKey(),
|
||||
cuid: varchar('cuid', {
|
||||
length: 26
|
||||
}).notNull(),
|
||||
email: varchar('email', {
|
||||
length: 255
|
||||
}).notNull(),
|
||||
created_at: timestamp('created_at').notNull()
|
||||
});
|
|
@ -21,8 +21,20 @@
|
|||
"paths": {
|
||||
"~/*": ["./src/*"],
|
||||
"@contentlayer/generated": ["./.contentlayer/generated"]
|
||||
}
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
]
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"src/env.js",
|
||||
"src/drizzle.config.ts",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
|
35
apps/releases/.gitignore
vendored
35
apps/releases/.gitignore
vendored
|
@ -1,35 +0,0 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
|
@ -1,8 +0,0 @@
|
|||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
appDir: true
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"name": "releases",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint --strict"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": "18.15.11",
|
||||
"@types/react": "18.0.35",
|
||||
"@types/react-dom": "18.0.11",
|
||||
"@vercel/edge-config": "^0.1.7",
|
||||
"eslint-config-next": "13.3.0",
|
||||
"next": "13.3.0",
|
||||
"octokit": "^2.0.14",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"typescript": "5.0.4"
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"~/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
1762
pnpm-lock.yaml
1762
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue