Merge pull request #3 from innocuous-symmetry/staging

Staging
This commit is contained in:
Mikayla Dobson
2023-10-08 14:17:40 -05:00
committed by GitHub
50 changed files with 418 additions and 364 deletions

View File

@@ -1,5 +1,10 @@
name: build check (mikayla dot dev)
env:
POSTGRES_URL: ${{ secrets.POSTGRES_URL }}
POSTGRES_USER: ${{ secrets.POSTGRES_USER }}
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
on:
push:
branches:

View File

@@ -1,5 +1,10 @@
name: build check (mikayla dot dev)
env:
POSTGRES_URL: ${{ secrets.POSTGRES_URL }}
POSTGRES_USER: ${{ secrets.POSTGRES_USER }}
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
on:
push:
branches:

View File

@@ -1,3 +0,0 @@
export default function EducationPage() {
return <>Education Page</>
}

View File

@@ -1,19 +0,0 @@
import Link from 'next/link';
export default function MusicPage() {
return (
<div>
<Link href="/about/music/projects">
<p>Projects</p>
</Link>
<Link href="/about/music/works">
<p>Works</p>
</Link>
<Link href="/about/music/stream">
<p>Stream</p>
</Link>
</div>
)
}

View File

@@ -1,9 +0,0 @@
export default function MusicProjectPage() {
return (
<div>
<h1>Music Project Page</h1>
<p>This is where I&apos;ll keep a running list of my projects</p>
</div>
)
}

View File

@@ -1,7 +0,0 @@
export default function MusicStreamingPage() {
return (
<div>
<h1>Music Streaming Page</h1>
</div>
)
}

View File

@@ -1,16 +0,0 @@
export default function MusicalWorkPage({ params }: { params: { id: string }}) {
if (Number.isNaN(parseInt(params.id))) {
return <div>Fail</div>
}
return (
<div>
<h1>Music Works Page</h1>
<p>Work No. {params.id}</p>
<div>
<p>This page is coming soon!</p>
</div>
</div>
)
}

View File

@@ -1,21 +0,0 @@
import Link from 'next/link';
export default function MusicWorksPage() {
return (
<div>
<h1>Music Works Page</h1>
<Link href="/about/music/works/1">
<p>First</p>
</Link>
<Link href="/about/music/works/2">
<p>Second</p>
</Link>
<Link href="/about/music/works/3">
<p>Third</p>
</Link>
</div>
)
}

View File

@@ -1,18 +0,0 @@
'use client';
import { usePathname } from "next/navigation";
export default function ExperiencePage() {
const path = usePathname();
return (
<div>
<div id="spacer" className='h-[6rem] w-full' />
<h1>Work Page</h1>
<p>Employer: {path.split('/').at(-1)}</p>
<div>
<p>This section is coming soon!</p>
</div>
</div>
)
}

View File

@@ -1,9 +0,0 @@
const WorkHistory = () => {
return (
<div>
<h1>Work History</h1>
</div>
);
}
export default WorkHistory;

View File

@@ -1,5 +1,4 @@
'use client';
import { contactFormSubmit, testMailerSDK } from "@/server/actions/mailer.actions"
import { useMemo, useState } from "react";
export default function ContactPage() {
@@ -16,7 +15,7 @@ export default function ContactPage() {
<div className="flex flex-col mx-24 items-center dark:text-white ">
<h1 className="text-3xl my-8 place-self-start">Thanks for your interest! I&apos;m looking forward to hearing from you.</h1>
<form action={testMailerSDK} className="w-full">
<form className="w-full">
<div className="flex w-full">
<div className="flex flex-col w-1/2 mr-2">
<label htmlFor="name">Name</label>

View File

@@ -1,56 +1,14 @@
'use client'
import './globals.css'
import Head from 'next/head'
import Navbar from '@/components/Navbar'
import SiteTree from '@/components/SiteTree'
import { Inter, Besley, Cabin } from 'next/font/google'
import { usePathname } from 'next/navigation'
import { IconContext } from 'react-icons'
import { useEffect, useState } from 'react'
export const inter = Inter({ subsets: ['latin'] })
export const besley = Besley({ subsets: ['latin'] })
export const cabin = Cabin({ subsets: ['latin'] })
export default function RootLayout({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
const [bg, setBg] = useState('bg-slate-400 dark:bg-slate-900');
const [overlay, setOverlay] = useState(false);
const [pageIsScrolled, setPageIsScrolled] = useState(false);
// useEffect(() => {
// if (pathname === '/contact') setOverlay(true);
// switch (pathname) {
// case '/contact':
// setBg('bg-purple-300 dark:bg-darkPlum');
// break;
// case '/about':
// setBg("bg-blue-100 dark:bg-slate-900");
// break;
// case "/links":
// setBg("bg-sky-100 dark:bg-slate-900");
// break;
// case '/':
// default:
// setBg('bg-slate-400 dark:bg-slate-900');
// break;
// }
// }, [pathname])
useEffect(() => {
document.addEventListener('scroll', () => {
if (window.scrollY > 0) {
setPageIsScrolled(true);
} else {
setPageIsScrolled(false);
}
})
}, [])
return (
<html lang="en">
<Head>
@@ -60,14 +18,11 @@ export default function RootLayout({ children }: { children: React.ReactNode })
<link rel="icon" href="/favicon.ico" />
</Head>
<body className={inter.className}>
<Navbar pageIsScrolled={pageIsScrolled} />
<SiteTree />
<IconContext.Provider value={{}}>
<div>
<div id="navbar-spacer" className="h-[6rem] w-full bg-slate-300 dark:bg-black " />
{children}
</div>
</IconContext.Provider>
<Navbar />
<div>
<div id="navbar-spacer" className="h-[6rem] w-full bg-slate-300 dark:bg-black " />
{children}
</div>
</body>
</html>
)

View File

@@ -1,10 +1,7 @@
import SocialMedia from "@/components/Links/SocialMedia";
import Panel from "@/components/ui/Panel";
import Image from "next/image";
import { FaBandcamp, FaGithub, FaLinkedin, FaPatreon, FaSoundcloud, FaYoutube } from "react-icons/fa";
import { RxArrowRight } from "react-icons/rx";
import { FaBandcamp, FaGithub, FaLinkedin, FaSoundcloud } from "react-icons/fa";
export default function LinksPage() {
export default async function LinksPage() {
return (
<div className="flex flex-col min-h-screen min-w-screen items-center bg-gradient-to-b from-slate-300 to-sky-100 dark:from-black dark:to-slate-900 bg-fixed">
{/* <section className="flex flex-col flex-wrap w-11/12 m-12">

View File

@@ -1,3 +1,5 @@
export default function ListenByCollectionID() {
return <div>ListenByCollectionID</div>;
import InProgress from "@/components/InProgress";
export default async function ListenByCollectionID() {
return <InProgress />
}

7
app/listen/layout.tsx Normal file
View File

@@ -0,0 +1,7 @@
export default function ListenLayout({ children }: { children: React.ReactNode}) {
return (
<main className="min-h-screen bg-gradient-to-b from-slate-300 to-cyan-800 dark:from-black dark:to-cyan-900 text-black dark:text-white">
{ children }
</main>
)
}

View File

@@ -1,3 +1,11 @@
export default function ListenIndex() {
return <div>ListenIndex</div>;
import InProgress from "@/components/InProgress";
export default async function ListenIndex() {
return (
<div>
<h1>Listen</h1>
{/* @ts-ignore server component */}
<InProgress />
</div>
)
}

View File

@@ -2,7 +2,7 @@ import { ColorChangeName } from "@/components/Home";
import Image from "next/image";
import Link from "next/link";
export default function Home() {
export default async function Home() {
return (
<main className="min-h-screen bg-fixed bg-gradient-to-b from-slate-300 to-slate-400 dark:from-black dark:to-slate-900">
<div className="flex flex-col w-full">

View File

@@ -1,12 +1,34 @@
import { usePathname } from 'next/navigation'
// 'use client';
import ProjectRepository from "@/server/actions/project.actions";
import Image from "next/image";
export default function ProjectById() {
const pathname = usePathname();
export default async function ProjectById(req: { params: any, searchParams: any }) {
const projects = new ProjectRepository();
const project = await projects.getProjectById(req.params.id);
if (!project) {
return (
<div>
<h1>Project not found!</h1>
</div>
)
}
return (
<div>
<h1>ProjectById Page</h1>
<p>Project ID: {pathname}</p>
</div>
<article id="project-entry-body">
<header>
<h1 className="text-4xl font-bold">{project.name}</h1>
<p>Started: {project.created.toLocaleString()}</p>
<p>{project.updated ? `Finished: ${project.updated.toLocaleDateString()}` : "(In progress)"}</p>
</header>
<div id="project-entry-content" className="flex flex-col">
<p>{project.description}</p>
</div>
{ project.media && project.media.map((link, idx) => {
return <Image src={link} key={idx} alt={`Media for ${project.name}`} width={80} height={80} />
})}
</article>
)
}

7
app/projects/layout.tsx Normal file
View File

@@ -0,0 +1,7 @@
export default function ProjectsLayout({ children }: { children: React.ReactNode }) {
return (
<div className="min-h-screen bg-gradient-to-b from-slate-300 to-sky-700 dark:from-black dark:to-green-950 text-black dark:text-white">
{ children }
</div>
)
}

View File

@@ -1,9 +1,10 @@
export default function ProjectsPage() {
import InProgress from "@/components/InProgress";
export default async function ProjectsPage() {
return (
<div>
<h1>Learn more about my work</h1>
<p>Contents of this page coming soon!</p>
<InProgress />
</div>
)
}

7
app/read/layout.tsx Normal file
View File

@@ -0,0 +1,7 @@
export default function ReadSectionLayout({ children }: { children: React.ReactNode }) {
return (
<div className="min-h-screen bg-gradient-to-b from-slate-300 to-fuchsia-100 dark:from-black dark:to-fuchsia-900 text-black dark:text-white">
{ children }
</div>
)
}

View File

@@ -1,3 +1,5 @@
export default function BlogIndex() {
return <div>BlogIndex</div>;
import InProgress from "@/components/InProgress";
export default async function BlogIndex() {
return <InProgress />;
}

View File

@@ -1,6 +1,6 @@
'use client';
import { useColorShift } from "../logo";
import { useColorShift } from "../Navbar/logo";
export const ColorChangeName = () => {
const { firstColor, secondColor, thirdColor } = useColorShift(14000);

View File

@@ -0,0 +1,3 @@
export default function InProgress() {
return <div>Under construction! Come back soon.</div>;
}

View File

@@ -1,14 +1,26 @@
'use client';
import Link from 'next/link'
import { InlineLogo, useColorShift } from '../logo'
import { useState } from 'react';
import { InlineLogo, useColorShift } from './logo'
import { useEffect, useState } from 'react';
import { RxActivityLog } from "react-icons/rx";
import { NavbarButton } from '../ui/Button';
const SHIFT_INTERVAL = 3000;
export default function Navbar({ pageIsScrolled = false }) {
export default function Navbar() {
const navbarColorShift = useColorShift(SHIFT_INTERVAL);
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [pageIsScrolled, setPageIsScrolled] = useState(false);
useEffect(() => {
document.addEventListener('scroll', () => {
if (window.scrollY > 0) {
setPageIsScrolled(true);
} else {
setPageIsScrolled(false);
}
})
}, [])
return (
<>
@@ -16,12 +28,8 @@ export default function Navbar({ pageIsScrolled = false }) {
id="navbar"
className={`
w-full z-50 fixed flex flex-nowrap items-baseline justify-apart px-8 py-4
${mobileMenuOpen
? "bg-slate-300 dark:bg-[#131313] "
: pageIsScrolled
? "bg-slate-300 dark:bg-black "
: "bg-inherit "
}text-white transition-all duration-200`
${pageIsScrolled ? "bg-slate-300 dark:bg-black " : "bg-inherit "}
text-white transition-all duration-200`
}>
<Link passHref href="/" className="w-1/4">
<InlineLogo customHookInstance={navbarColorShift} />

View File

@@ -1,8 +1,8 @@
'use client'
import { FC } from "react";
import useColorShift, { UseColorShiftReturnType, type ColorListType } from "./useColorShift";
import useColorShift, { UseColorShiftReturnType, type ColorListType } from "../../hooks/useColorShift";
import { useRouter } from "next/navigation";
export { default as useColorShift } from "./useColorShift";
export { default as useColorShift } from "../../hooks/useColorShift";
const DEFAULT_SHIFT_INTERVAL = 3000;

View File

@@ -1,45 +0,0 @@
import { v4 } from "uuid"
import { cabin } from "@/app/layout";
type ElementType<Key extends keyof JSX.IntrinsicElements> = React.FC<JSX.IntrinsicElements[Key]>
type FormattedTags = {
[Key in keyof JSX.IntrinsicElements]: ElementType<Key>
}
const H1TAG: ElementType<"h1"> = ({ children }) => { return (
<h1 key={v4()} className={`text-4xl text-red-500 ${cabin.className} tracking-wide`}>{children}</h1>
)}
const H2Tag: ElementType<"h2"> = ({ children }) => (
<h2 key={v4()}>{children}</h2>
)
const H3Tag: ElementType<"h3"> = ({ children }) => (
<h3 key={v4()}>{children}</h3>
)
const H4Tag: ElementType<"h4"> = ({ children }) => (
<h4 key={v4()}>{children}</h4>
)
const PTag: ElementType<"p"> = ({ children }) => (
<p key={v4()}>{children}</p>
)
const LiTag: ElementType<"li"> = ({ children }) => (
<li key={v4()}>{children}</li>
)
const BrTag: ElementType<"br"> = () => (
<br key={v4()} />
)
export default {
"h1": H1TAG,
"h2": H2Tag,
"h3": H3Tag,
"h4": H4Tag,
"p": PTag,
"li": LiTag,
"br": BrTag
} satisfies Partial<FormattedTags>

17
env.mjs Normal file
View File

@@ -0,0 +1,17 @@
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from 'zod';
const env = createEnv({
server: {
POSTGRES_URL: z.string().url(),
POSTGRES_USER: z.string(),
POSTGRES_PASSWORD: z.string(),
},
runtimeEnv: {
POSTGRES_URL: process.env.POSTGRES_URL,
POSTGRES_USER: process.env.POSTGRES_USER,
POSTGRES_PASSWORD: process.env.POSTGRES_PASSWORD,
}
})
export { env }

View File

@@ -2,10 +2,6 @@
const nextConfig = {
pageExtensions: ['js', 'jsx', 'ts', 'tsx'],
reactStrictMode: true,
experimental: {
// mdxRs: true,
serverActions: true,
}
}
module.exports = nextConfig;

View File

@@ -10,13 +10,13 @@
},
"dependencies": {
"@aws-sdk/client-s3": "^3.367.0",
"@sendgrid/mail": "^7.7.0",
"@supabase/supabase-js": "^2.26.0",
"@t3-oss/env-nextjs": "^0.7.0",
"autoprefixer": "10.4.14",
"eslint": "^8.46.0",
"eslint-config-next": "^13.4.12",
"next": "^13.4.12",
"postcss": "8.4.24",
"pg": "^8.11.3",
"postcss": "^8.4.31",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-icons": "^4.9.0",
@@ -26,9 +26,12 @@
},
"devDependencies": {
"@types/node": "20.2.5",
"@types/pg": "^8.10.3",
"@types/react": "18.2.7",
"@types/react-dom": "18.2.4",
"@types/uuid": "^9.0.1",
"@vercel/style-guide": "^5.0.1"
"@vercel/style-guide": "^5.0.1",
"ts-node": "^10.9.1",
"zod": "^3.22.4"
}
}

20
pkg/.eslintrc.json Normal file
View File

@@ -0,0 +1,20 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
],
"rules": {
}
}

1
pkg/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
lib

2
pkg/.npmignore Normal file
View File

@@ -0,0 +1,2 @@
src
eslintrc.json

2
pkg/README.md Normal file
View File

@@ -0,0 +1,2 @@
# Helpers for mikayla.dev

29
pkg/package.json Normal file
View File

@@ -0,0 +1,29 @@
{
"name": "@mkladotdev/utils",
"version": "0.0.1",
"description": "Utilities for mikayla.dev",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"files": [
"./lib/**/*"
],
"scripts": {
"build": "tsc",
"lint": "eslint . --ext .ts",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "Mikayla Dobson",
"license": "ISC",
"dependencies": {},
"devDependencies": {
"@types/node": "^20.5.9",
"@typescript-eslint/eslint-plugin": "^6.6.0",
"@typescript-eslint/parser": "^6.6.0",
"eslint": "^8.49.0",
"typescript": "^5.2.2"
},
"publishConfig": {
"registry": "http://localhost:4873"
}
}

View File

@@ -0,0 +1,12 @@
export type Track = {
name: string;
date: Date;
description: string;
}
export type AudioCollection = {
name: string;
date: Date;
tracklist: Track[];
directory: string;
}

9
pkg/src/entities/post.ts Normal file
View File

@@ -0,0 +1,9 @@
export type Post = {
name: string;
date: Date;
author: string;
description: string;
body: string;
tagIDs: string[];
media?: string[]; // array of URLs
}

View File

@@ -0,0 +1,49 @@
import { z } from "zod";
export type Project = {
name: string;
description: string;
created: Date;
updated?: Date;
tagIDs?: string[];
media?: string[]; // array of URLs
}
export class ProjectCreationError extends Error {
constructor(message: string) {
super(message);
this.name = "ProjectCreationError";
}
}
export const ZProject = z.object({
name: z.string(),
description: z.string(),
created: z.date(),
updated: z.date().optional(),
tagIDs: z.array(z.string()).optional(),
media: z.array(z.string()).optional(),
})
export function createProject(data: Partial<Project>) {
if (!data.name) throw new ProjectCreationError("Project name is required");
if (!data.description) throw new ProjectCreationError("Project description is required");
const today = new Date();
const completeInput = {
name: data.name,
description: data.description,
created: data.created || today,
updated: today,
} satisfies Partial<Project>;
const parsedProject = ZProject.safeParse(completeInput);
if (!parsedProject.success) throw new ProjectCreationError("Invalid project data");
return parsedProject.data satisfies Project;
}
export function isProject(data: unknown): data is Project {
return ZProject.safeParse(data).success;
}

28
pkg/tsconfig.json Normal file
View File

@@ -0,0 +1,28 @@
{
"compilerOptions": {
/* Projects */
"incremental": true,
/* Language and Environment */
"target": "es2016",
/* Modules */
"module": "commonjs",
"rootDir": "./src",
/* Emit */
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./lib",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
/* Type Checking */
"strict": true,
"noImplicitAny": true,
"skipLibCheck": true
},
"include": ["src"],
"exclude": ["lib", "node_modules"]
}

View File

@@ -1,34 +0,0 @@
'use server';
import { Mailer } from "../services/sendgrid";
export async function contactFormSubmit(e: FormData) {
const data = [e.get('email'), e.get('message'), e.get('name')].map(each => each?.valueOf());
const mailer = new Mailer();
data.forEach(item => {
if (typeof item !== 'string') throw new Error('Invalid form data')
})
const result = await mailer.send(...data as [string, string, string]);
return result;
}
export async function testMailerSDK(e: FormData) {
const sgMail = require('@sendgrid/mail')
sgMail.setApiKey(process.env.SENDGRID_API_KEY)
const msg = {
to: 'mikaylaherself@gmail.com', // Change to your recipient
from: 'me@mikayla.dev', // Change to your verified sender
subject: 'Sending with SendGrid is Fun',
text: 'and easy to do anywhere, even with Node.js',
html: '<strong>and easy to do anywhere, even with Node.js</strong>',
}
sgMail
.send(msg)
.then(() => {
console.log('Email sent')
})
.catch((error: unknown) => {
console.error(error)
})
}

View File

@@ -0,0 +1,56 @@
import { Project, isProject } from "../entities/project";
import createClient from "../services/pg";
export default class ProjectRepository {
async createProject(data: Project) {
const client = await createClient();
if (!client) return null;
await client.connect();
const { rows } = await client.query(
"INSERT INTO project (name, description, created, updated) VALUES ($1, $2, $3, $4) RETURNING *"
, [
data.name,
data.description,
data.created,
data.updated,
]);
await client.end();
if (rows.every(row => isProject(row))) {
return rows[0] as Project;
}
return null;
}
async getProjects() {
const client = await createClient();
if (!client) return null;
const { rows } = await client.query("SELECT * FROM project");
await client.end();
if (rows.every(row => isProject(row))) {
return rows as Project[];
}
return null;
}
async getProjectById(id: string) {
const client = await createClient();
if (!client) return null;
await client.connect();
const { rows } = await client.query("SELECT * FROM project WHERE id = $1", [id]);
await client.end();
if (rows.every(row => isProject(row))) {
return rows[0] as Project;
}
return null;
}
}

View File

@@ -1,19 +0,0 @@
import supabaseClient from "../services/supabase";
export default class ProjectsActions {
static api = supabaseClient();
static async getProjects() {
const { data, error } = await this.api.from("projects").select("*");
if (error) throw error;
return data;
}
static async getProjectsById(id: string) {
const { data, error } = await this.api.from("projects").select("*").eq("id", id);
if (error) throw error;
return data;
}
}

View File

@@ -1,21 +0,0 @@
'use server';
import supabaseClient from "../services/supabase";
export default class WorkActions {
static api = supabaseClient();
static async getWork() {
const { data, error } = await this.api.from("work").select("*");
if (error) throw error;
return data;
}
static async getWorkById(id: string) {
const { data, error } = await this.api.from("work").select("*").eq("id", id);
if (error) throw error;
return data;
}
}

View File

@@ -1,9 +1,49 @@
type Project = {
name: string;
startDate: Date;
endDate: Date;
current: boolean;
description: string;
tagIDs: string[];
media?: string[]; // array of URLs
import { z } from "zod";
export type Project = {
name: string;
description: string;
created: Date;
updated?: Date;
tagIDs?: string[];
media?: string[]; // array of URLs
}
export class ProjectCreationError extends Error {
constructor(message: string) {
super(message);
this.name = "ProjectCreationError";
}
}
export const ZProject = z.object({
name: z.string(),
description: z.string(),
created: z.date(),
updated: z.date().optional(),
tagIDs: z.array(z.string()).optional(),
media: z.array(z.string()).optional(),
})
export function createProject(data: Partial<Project>) {
if (!data.name) throw new ProjectCreationError("Project name is required");
if (!data.description) throw new ProjectCreationError("Project description is required");
const today = new Date();
const completeInput = {
name: data.name,
description: data.description,
created: data.created || today,
updated: today,
} satisfies Partial<Project>;
const parsedProject = ZProject.safeParse(completeInput);
if (!parsedProject.success) throw new ProjectCreationError("Invalid project data");
return parsedProject.data satisfies Project;
}
export function isProject(data: unknown): data is Project {
return ZProject.safeParse(data).success;
}

24
server/services/pg.ts Normal file
View File

@@ -0,0 +1,24 @@
import { env } from "@/env.mjs";
import pg from 'pg';
const { Client } = pg;
export class PostgresError extends Error {
constructor(message: string) {
super(message);
this.name = "PostgresError";
}
}
export default async function createClient() {
try {
return new Client({
connectionString: env.POSTGRES_URL,
user: env.POSTGRES_USER,
password: env.POSTGRES_PASSWORD,
ssl: { rejectUnauthorized: false }
});
} catch(e) {
console.log('error creating client', e);
return null;
}
}

View File

@@ -1,28 +0,0 @@
import mailer, { ClientResponse, MailDataRequired, MailService } from "@sendgrid/mail";
export class Mailer {
private mailer: MailService;
constructor() {
const service = mailer;
const key = process.env.SENDGRID_API_KEY;
if (!key) throw new Error("No SendGrid API key provided");
service.setApiKey(key);
this.mailer = service;
}
public async send(from: string, text: string, name: string) {
const data: MailDataRequired = {
text, from,
cc: from,
to: 'hello@mikayla.dev',
subject: `Contact form submission from ${name}`
}
const result = await this.mailer.send(data);
return result[0] as ClientResponse;
}
}

View File

@@ -1,13 +0,0 @@
import { createClient } from "@supabase/supabase-js";
export default function supabaseClient() {
if (typeof process.env.SUPABASE_URL !== "string") {
throw new Error("SUPABASE_URL is not defined");
}
if (typeof process.env.SUPABASE_KEY !== "string") {
throw new Error("SUPABASE_KEY is not defined");
}
return createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY);
}