in progress: build out server side

This commit is contained in:
2023-10-06 15:13:57 -05:00
parent 111e211fa7
commit a3ff7598ae
18 changed files with 186 additions and 122 deletions

View File

@@ -1,5 +1,5 @@
'use client';
import { contactFormSubmit, testMailerSDK } from "@/server/actions/mailer.actions"
import { testMailerSDK } from "@/server/actions/mailer.actions"
import { useMemo, useState } from "react";
export default function ContactPage() {

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

@@ -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,39 @@
import { usePathname } from 'next/navigation'
import Image from "next/image";
import ProjectRepository from "@/server/actions/project.actions";
export default function ProjectById() {
const pathname = usePathname();
interface PageProps {
searchParams: any;
params: {
id: any;
}
children: React.ReactNode;
}
export default async function ProjectById(props: PageProps) {
const { id } = props.params;
const projects = new ProjectRepository();
const project = await projects.getProjectById(id);
if (!project) {
return <p>Project not found!</p>
}
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.startDate.toLocaleString()}</p>
<p>{project.endDate ? `Finished: ${project.endDate.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>
)
}

View File

@@ -1,4 +1,4 @@
export default function ProjectsPage() {
export default async function ProjectsPage() {
return (
<div>
<h1>Learn more about my work</h1>

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

@@ -1,5 +1,5 @@
import Link from 'next/link'
import { InlineLogo, useColorShift } from '../logo'
import { InlineLogo, useColorShift } from './logo'
import { useState } from 'react';
import { RxActivityLog } from "react-icons/rx";
import { NavbarButton } from '../ui/Button';

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

@@ -0,0 +1,29 @@
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 <p>Project not found!</p>
}
return (
<article id="project-entry-body">
<header>
<h1 className="text-4xl font-bold">{project.name}</h1>
<p>Started: {project.startDate.toLocaleString()}</p>
<p>{project.endDate ? `Finished: ${project.endDate.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>
)
}

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>

View File

@@ -11,11 +11,11 @@
"dependencies": {
"@aws-sdk/client-s3": "^3.367.0",
"@sendgrid/mail": "^7.7.0",
"@supabase/supabase-js": "^2.26.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",
"react": "18.2.0",
"react-dom": "18.2.0",
@@ -26,9 +26,11 @@
},
"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",
"zod": "^3.22.4"
}
}

View File

@@ -0,0 +1,34 @@
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();
const { rows } = await client.query("SELECT * FROM projects");
await client.end();
if (rows.every(row => isProject(row))) {
return rows as Project[];
}
return null;
}
async getProjectById(id: string) {
'use server';
const client = createClient();
const { rows } = await client.query("SELECT * FROM projects 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 +1,15 @@
'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("*");
static async getWork() {
const { data, error } = await this.api.from("work").select("*");
// if (error) throw error;
// return data;
// }
if (error) throw error;
return data;
}
// static async getWorkById(id: string) {
// const { data, error } = await this.api.from("work").select("*").eq("id", id);
static async getWorkById(id: string) {
const { data, error } = await this.api.from("work").select("*").eq("id", id);
if (error) throw error;
return data;
}
// if (error) throw error;
// return data;
// }
}

View File

@@ -1,9 +1,48 @@
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;
startDate: Date;
endDate?: 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(),
startDate: z.date(),
endDate: 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,
startDate: data.startDate || today,
}
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;
}

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

@@ -0,0 +1,19 @@
import { Client } from "pg";
export class PostgresError extends Error {
constructor(message: string) {
super(message);
this.name = "PostgresError";
}
}
export default function createClient() {
if (!process.env.POSTGRES_URL) {
throw new PostgresError("Database connection configured incorrectly")
}
return new Client({
connectionString: process.env.POSTGRES_URL
});
}

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);
}