diff --git a/app/contact/page.tsx b/app/contact/page.tsx index 5a2fa89..b77ff07 100644 --- a/app/contact/page.tsx +++ b/app/contact/page.tsx @@ -1,5 +1,4 @@ 'use client'; -import { testMailerSDK } from "@/server/actions/mailer.actions" import { useMemo, useState } from "react"; export default function ContactPage() { @@ -16,7 +15,7 @@ export default function ContactPage() {

Thanks for your interest! I'm looking forward to hearing from you.

-
+
diff --git a/app/projects/[id]/page.tsx b/app/projects/[id]/page.tsx index 27ad7a3..7d44554 100644 --- a/app/projects/[id]/page.tsx +++ b/app/projects/[id]/page.tsx @@ -1,30 +1,25 @@ -import Image from "next/image"; +// 'use client'; import ProjectRepository from "@/server/actions/project.actions"; +import Image from "next/image"; -interface PageProps { - searchParams: any; - params: { - id: any; - } - children: React.ReactNode; -} - -export default async function ProjectById(props: PageProps) { - const { id } = props.params; - +export default async function ProjectById(req: { params: any, searchParams: any }) { const projects = new ProjectRepository(); - const project = await projects.getProjectById(id); + const project = await projects.getProjectById(req.params.id); if (!project) { - return

Project not found!

+ return ( +
+

Project not found!

+
+ ) } return (

{project.name}

- {/*

Started: {project.startDate.toLocaleString()}

-

{project.endDate ? `Finished: ${project.endDate.toLocaleDateString()}` : "(In progress)"}

*/} +

Started: {project.created.toLocaleString()}

+

{project.updated ? `Finished: ${project.updated.toLocaleDateString()}` : "(In progress)"}

diff --git a/components/Projects/ProjectEntry.tsx b/components/Projects/ProjectEntry.tsx deleted file mode 100644 index d6be635..0000000 --- a/components/Projects/ProjectEntry.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import ProjectRepository from "@/server/actions/project.actions" -import Image from "next/image" - -export default async function ProjectEntry({ id }: { id: string }) { - const projects = new ProjectRepository(); - const project = await projects.getProjectById(id); - - if (!project) { - return

Project not found!

- } - - return ( -
-
-

{project.name}

-

Started: {project.startDate.toLocaleString()}

-

{project.endDate ? `Finished: ${project.endDate.toLocaleDateString()}` : "(In progress)"}

-
- -
-

{project.description}

-
- - { project.media && project.media.map((link, idx) => { - return {`Media - })} -
- ) -} diff --git a/env.mjs b/env.mjs new file mode 100644 index 0000000..e2ab82f --- /dev/null +++ b/env.mjs @@ -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 } diff --git a/next.config.js b/next.config.js index 5ccd269..fae793b 100644 --- a/next.config.js +++ b/next.config.js @@ -2,10 +2,6 @@ const nextConfig = { pageExtensions: ['js', 'jsx', 'ts', 'tsx'], reactStrictMode: true, - experimental: { - // mdxRs: true, - serverActions: true, - } } module.exports = nextConfig; diff --git a/package.json b/package.json index 8a04b11..08dc84e 100644 --- a/package.json +++ b/package.json @@ -10,13 +10,13 @@ }, "dependencies": { "@aws-sdk/client-s3": "^3.367.0", - "@sendgrid/mail": "^7.7.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", "pg": "^8.11.3", - "postcss": "8.4.24", + "postcss": "^8.4.31", "react": "18.2.0", "react-dom": "18.2.0", "react-icons": "^4.9.0", @@ -31,6 +31,7 @@ "@types/react-dom": "18.2.4", "@types/uuid": "^9.0.1", "@vercel/style-guide": "^5.0.1", + "ts-node": "^10.9.1", "zod": "^3.22.4" } } diff --git a/server/actions/mailer.actions.ts b/server/actions/mailer.actions.ts deleted file mode 100644 index e728c47..0000000 --- a/server/actions/mailer.actions.ts +++ /dev/null @@ -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: 'and easy to do anywhere, even with Node.js', - } - sgMail - .send(msg) - .then(() => { - console.log('Email sent') - }) - .catch((error: unknown) => { - console.error(error) - }) -} diff --git a/server/actions/project.actions.ts b/server/actions/project.actions.ts index 37273e2..8587e7c 100644 --- a/server/actions/project.actions.ts +++ b/server/actions/project.actions.ts @@ -1,14 +1,35 @@ import { Project, isProject } from "../entities/project"; -import { Client } from "pg"; import createClient from "../services/pg"; export default class ProjectRepository { - async getProjects() { - 'use server'; - - const client = createClient(); + async createProject(data: Project) { + const client = await createClient(); + if (!client) return null; + await client.connect(); - const { rows } = await client.query("SELECT * FROM projects"); + 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))) { @@ -19,10 +40,11 @@ export default class ProjectRepository { } async getProjectById(id: string) { - 'use server'; - - const client = createClient(); - const { rows } = await client.query("SELECT * FROM projects WHERE id = $1", [id]); + 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))) { diff --git a/server/actions/work.actions.ts b/server/actions/work.actions.ts deleted file mode 100644 index bbbf251..0000000 --- a/server/actions/work.actions.ts +++ /dev/null @@ -1,15 +0,0 @@ -export default class WorkActions { - // 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; - // } -} diff --git a/server/entities/project.ts b/server/entities/project.ts index 3f91f37..a414e63 100644 --- a/server/entities/project.ts +++ b/server/entities/project.ts @@ -3,8 +3,8 @@ import { z } from "zod"; export type Project = { name: string; description: string; - startDate: Date; - endDate?: Date; + created: Date; + updated?: Date; tagIDs?: string[]; media?: string[]; // array of URLs } @@ -19,8 +19,8 @@ export class ProjectCreationError extends Error { export const ZProject = z.object({ name: z.string(), description: z.string(), - startDate: z.date(), - endDate: z.date().optional(), + created: z.date(), + updated: z.date().optional(), tagIDs: z.array(z.string()).optional(), media: z.array(z.string()).optional(), }) @@ -34,8 +34,9 @@ export function createProject(data: Partial) { const completeInput = { name: data.name, description: data.description, - startDate: data.startDate || today, - } + created: data.created || today, + updated: today, + } satisfies Partial; const parsedProject = ZProject.safeParse(completeInput); diff --git a/server/services/pg.ts b/server/services/pg.ts index cf2d653..dd2d9fb 100644 --- a/server/services/pg.ts +++ b/server/services/pg.ts @@ -1,4 +1,6 @@ -import { Client } from "pg"; +import { env } from "@/env.mjs"; +import pg from 'pg'; +const { Client } = pg; export class PostgresError extends Error { constructor(message: string) { @@ -7,13 +9,16 @@ export class PostgresError extends Error { } } -export default function createClient() { - if (!process.env.POSTGRES_URL) { - throw new PostgresError("Database connection configured incorrectly") +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; } - - return new Client({ - connectionString: process.env.POSTGRES_URL - }); - } diff --git a/server/services/sendgrid.ts b/server/services/sendgrid.ts deleted file mode 100644 index 5df1818..0000000 --- a/server/services/sendgrid.ts +++ /dev/null @@ -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; - } -} -