3 Commits

Author SHA1 Message Date
Mikayla Dobson
38d9ae2d48 Merge pull request #4 from innocuous-symmetry/staging
Staging
2024-05-27 12:37:55 -05:00
7e543aebc0 tweak to working details 2024-05-27 17:37:24 +00:00
e70c6736f6 content updates 2024-05-27 14:03:53 +00:00
14 changed files with 80 additions and 157 deletions

1
.gitignore vendored
View File

@@ -5,6 +5,7 @@
/.pnp
.pnp.js
package-lock.json
pnpm-lock.yaml
# testing
/coverage

View File

@@ -1,4 +1,5 @@
'use client';
import { submitMessage } from "@/server/actions/contact.actions";
import { useMemo, useState } from "react";
export default function ContactPage() {
@@ -15,7 +16,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 className="w-full">
<form className="w-full" action={async () => await submitMessage({ from: email, text: message })}>
<div className="flex w-full">
<div className="flex flex-col w-1/2 mr-2">
<label htmlFor="name">Name</label>

View File

@@ -4,7 +4,6 @@ export default async function ListenIndex() {
return (
<div>
<h1>Listen</h1>
{/* @ts-ignore server component */}
<InProgress />
</div>
)

View File

@@ -1,34 +0,0 @@
// 'use client';
import ProjectRepository from "@/server/actions/project.actions";
import Image from "next/image";
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 (
<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>
)
}

View File

@@ -37,9 +37,6 @@ export default function Navbar() {
<div className="hidden md:inline-flex justify-end w-3/4">
<NavbarButton href="/about" label="About" />
<NavbarButton href="/projects" label="Projects" />
<NavbarButton href="/read" label="Read" />
<NavbarButton href="/listen" label="Listen" />
<NavbarButton href="/links" label="Links" />
<NavbarButton href="/contact" label="Contact" />
</div>
@@ -60,8 +57,8 @@ export default function Navbar() {
<p className='text-lg text-right text-white text-opacity-80 hover:text-opacity-100 uppercase p-2 border-opacity-50 hover:border-opacity-75'>About</p>
</Link>
<Link onClick={() => setMobileMenuOpen(false)} passHref href="/projects" className="w-auto px-2">
<p className='text-lg text-right text-white text-opacity-80 hover:text-opacity-100 hover:border-opacity-75 uppercase p-2 border-opacity-50'>Projects</p>
<Link onClick={() => setMobileMenuOpen(false)} passHref href="/links" className="w-auto px-2">
<p className='text-lg text-right text-white text-opacity-80 hover:text-opacity-100 hover:border-opacity-75 uppercase p-2 border-opacity-50'>Links</p>
</Link>
<Link onClick={() => setMobileMenuOpen(false)} passHref href="/contact" className="w-auto px-2">

View File

@@ -1,24 +1,42 @@
import Link from "next/link";
import Card from "../ui/Card";
const Experience = () => {
export default function Experience() {
return (
<section className="w-full">
<Card>
<Link target="_blank" referrerPolicy="no-referrer" href="https://vertafore.com" className="uppercase text-2xl text-rose-600 hover:text-rose-400 active:text-rose-600">Vertafore</Link>
<p className="font-light italic text-black dark:text-rose-300">Software Engineer</p>
<p className="dark:text-white">May 2024 - present</p>
<div className="h-[1px] w-full my-3 bg-rose-600 dark:bg-rose-300" />
<p className="dark:text-white leading-relaxed font-light">Contributing as part of a development team building an insurance tech solution.</p>
</Card>
<Card>
<Link target="_blank" referrerPolicy="no-referrer" href="https://epicstockmedia.com" className="uppercase text-2xl text-rose-600 hover:text-rose-400 active:text-rose-600">Epic Stock Media</Link>
<p className="font-light italic text-black dark:text-rose-300">Software Engineer</p>
<p className="dark:text-white">Dec 2023 - May 2024</p>
<div className="h-[1px] w-full my-3 bg-rose-600 dark:bg-rose-300" />
<p className="dark:text-white leading-relaxed font-light">Building a dedicated tool for sound design professionals to browse, stream, and download from a large library of audio assets in an on-premise managed cloud solution.</p>
</Card>
<Card>
<Link target="_blank" referrerPolicy="no-referrer" href="https://dropper.studio" className="uppercase text-2xl text-rose-600 hover:text-rose-400 active:text-rose-600">Dropper Studio</Link>
<p className="font-light italic text-black dark:text-rose-300">Nashville, TN (hybrid) - Software Engineer</p>
<p className="font-light italic text-black dark:text-rose-300">Software Engineer</p>
<p className="dark:text-white">March 2023 - present</p>
<div className="h-[1px] w-full my-3 bg-rose-600 dark:bg-rose-300" />
<p className="dark:text-white leading-relaxed font-light">Building a full-stack e-commerce platform for the music industry. Experience includes: producing a functional proof of concept from design specifications; constructing a scalable, performant full-stack architecture; and project/team management skills.</p>
<Link href="/about/work/dropper" className="text-rose-600 hover:text-rose-400 active:text-rose-300">Learn more about my work with Dropper</Link>
</Card>
<Card>
<Link target="_blank" referrerPolicy="no-referrer" href="https://dization.com/" className="uppercase text-2xl text-rose-600 hover:text-rose-400 active:text-rose-600">Dization, Inc.</Link>
<p className="font-light italic text-black dark:text-rose-300">Pittsburgh, PA (remote) - Software Engineer (intern)</p>
<p className="font-light italic text-black dark:text-rose-300">Software Engineer (intern)</p>
<p className="dark:text-white">October 2022 - March 2023</p>
<div className="h-[1px] w-full my-3 bg-rose-600 dark:bg-rose-300" />
@@ -30,17 +48,13 @@ const Experience = () => {
<Card>
<h3 className="uppercase text-2xl text-rose-600">Metazu Studio</h3>
<p className="font-light italic text-black dark:text-rose-300">Nashville, TN (hybrid) - Software Engineer (consultant)</p>
<p className="font-light italic text-black dark:text-rose-300">Software Engineer (consultant)</p>
<p className="dark:text-white">March 2022 - December 2022</p>
<div className="h-[1px] w-full my-3 bg-rose-600 dark:bg-rose-300" />
<p className="dark:text-white leading-relaxed font-light">Consulted on small teams for the design and engineering of full-stack web applications for clients. Used technologies including Node.js, React, MongoDB, and PostgreSQL.</p>
</Card>
<Link href="/about/work" className="text-rose-300 hover:text-rose-500 active:text-rose-300 bg-slate-950 p-2 rounded-lg shadow-lg">See more about my experience</Link>
</section>
)
}
export default Experience;

View File

@@ -13,9 +13,9 @@ const Projects = () => (
</Card>
<Card>
<h3 className="uppercase text-2xl text-rose-600">Recipin</h3>
<h3 className="uppercase text-2xl text-rose-600">Unbinder</h3>
<p className="font-light italic text-rose-300">October 2022 - present</p>
<p className="text-rose-300">React, Express, TypeScript, PostgreSQL</p>
<p className="text-rose-300">ASP.NET</p>
<div className="h-[1px] w-full my-3 bg-rose-600 dark:bg-rose-300" />

View File

@@ -15,8 +15,8 @@ const Skills = () => (
<div className="opacity-0 group-open:opacity-100 transition-opacity duration-500 flex flex-wrap">
<Chip label="Typescript" href="https://github.com/innocuous-symmetry?tab=repositories&language=typescript" />
<Chip label="React" href="https://github.com/innocuous-symmetry?tab=repositories&q=react" />
<Chip label="AWS S3 SDK" />
<Chip label="Next.js" />
<Chip label="AWS S3 SDK" />
<Chip label="tRPC" />
<Chip label="React Query" />
<Chip label="jQuery" />
@@ -53,7 +53,6 @@ const Skills = () => (
<div className="opacity-0 group-open:opacity-100 transition-opacity duration-500 flex flex-wrap">
<Chip label="Micropython" href="https://github.com/innocuous-symmetry/picosynth" />
<Chip label="Flask" />
<Chip label="Flet" />
</div>
</details>
</article>
@@ -80,6 +79,7 @@ const Skills = () => (
<RxChevronDown className="transition group-open:rotate-180" />
</summary>
<div className="opacity-0 group-open:opacity-100 transition-opacity duration-500 flex flex-wrap">
<Chip label="Virtualization / Proxmox" />
<Chip label="Docker" />
<Chip label="Github Actions" />
<Chip label="Git / Github" />
@@ -125,24 +125,11 @@ const Skills = () => (
<RxChevronDown className="transition group-open:rotate-180" />
</summary>
<div className="opacity-0 group-open:opacity-100 transition-opacity duration-500 flex flex-wrap">
<Chip label="Ruby / Rails" />
<Chip label="C# / ASP.NET" />
<Chip label="Golang" />
</div>
</details>
</article>
{/* <article className="mt-2">
<details className="group">
<summary className="flex items-center text-rose-600 dark:text-rose-300 uppercase tracking-wide text-lg mb-2 list-none">
<p className="mr-2">Natural Language Processing</p>
<RxChevronDown className="transition group-open:rotate-180" />
</summary>
<div className="opacity-0 group-open:opacity-100 transition-opacity duration-500 flex flex-wrap">
<p className="py-0.5 px-2 bg-rose-900 rounded-xl m-0.5">NLPT</p>
<p className="py-0.5 px-2 bg-rose-900 rounded-xl m-0.5">Spacy</p>
</div>
</details>
</article> */}
</div>
<details className="group bg-slate-400 dark:bg-slate-800 dark:bg-opacity-40 p-3 my-4 rounded-lg">

16
env.mjs
View File

@@ -3,15 +3,17 @@ import { z } from 'zod';
const env = createEnv({
server: {
POSTGRES_URL: z.string().url(),
POSTGRES_USER: z.string(),
POSTGRES_PASSWORD: z.string(),
SMTP_USER: z.string().optional(),
SMTP_PASS: z.string().optional(),
SMTP_TO: z.string().optional(),
SMTP_HOST: z.string().optional(),
},
runtimeEnv: {
POSTGRES_URL: process.env.POSTGRES_URL,
POSTGRES_USER: process.env.POSTGRES_USER,
POSTGRES_PASSWORD: process.env.POSTGRES_PASSWORD,
}
SMTP_USER: process.env.SMTP_USER,
SMTP_PASS: process.env.SMTP_PASS,
SMTP_TO: process.env.SMTP_TO,
SMTP_HOST: process.env.SMTP_HOST,
},
})
export { env }

View File

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

View File

@@ -15,7 +15,7 @@
"eslint": "^8.46.0",
"eslint-config-next": "^13.4.12",
"next": "^13.4.12",
"pg": "^8.11.3",
"nodemailer": "^6.9.13",
"postcss": "^8.4.31",
"react": "18.2.0",
"react-dom": "18.2.0",
@@ -26,6 +26,7 @@
},
"devDependencies": {
"@types/node": "20.2.5",
"@types/nodemailer": "^6.4.15",
"@types/pg": "^8.10.3",
"@types/react": "18.2.7",
"@types/react-dom": "18.2.4",

View File

@@ -0,0 +1,32 @@
'use server';
import { createTransport } from "nodemailer";
import SMTPTransport from "nodemailer/lib/smtp-transport";
import { env } from "../../env.mjs"
export async function submitMessage({ from, text }: { from: string, text: string }) {
const options = {
host: env.SMTP_HOST,
port: 587,
auth: {
user: env.SMTP_USER,
pass: env.SMTP_PASS,
},
} as SMTPTransport.Options;
// console.log({ options });
// const transport = createTransport(options);
// const result = await transport.sendMail({
// subject: "Contact Form Submission | mikayla.dev",
// to: env.SMTP_TO,
// from,
// text,
// });
// const failed = result.rejected.concat(result.pending).filter(Boolean);
// if (failed.length) {
// throw new Error("Failed to send email verification");
// }
}

View File

@@ -1,56 +0,0 @@
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,24 +0,0 @@
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;
}
}