diff --git a/apps/cms/sanity.cli.ts b/apps/cms/sanity.cli.ts index 8a8878ac..2a6c421d 100644 --- a/apps/cms/sanity.cli.ts +++ b/apps/cms/sanity.cli.ts @@ -5,7 +5,15 @@ import { defineCliConfig } from "sanity/cli"; import { z } from "zod"; -const projectId = z.string().parse(process.env.NEXT_PUBLIC_SANITY_PROJECT_ID); -const dataset = z.string().parse(process.env.NEXT_PUBLIC_SANITY_DATASET); +const projectId = z + .string({ + message: "Environment variable NEXT_PUBLIC_SANITY_PROJECT_ID is required", + }) + .parse(process.env.NEXT_PUBLIC_SANITY_PROJECT_ID); +const dataset = z + .string({ + message: "Environment variable NEXT_PUBLIC_SANITY_DATASET is required", + }) + .parse(process.env.NEXT_PUBLIC_SANITY_DATASET); -export default defineCliConfig({ api: { projectId, dataset } }); +export default defineCliConfig({ api: { dataset, projectId } }); diff --git a/apps/cms/src/config/defaultDocumentNode.ts b/apps/cms/src/config/defaultDocumentNode.ts index 6f697228..4d5ed7dd 100644 --- a/apps/cms/src/config/defaultDocumentNode.ts +++ b/apps/cms/src/config/defaultDocumentNode.ts @@ -1,6 +1,7 @@ -import { Iframe } from "sanity-plugin-iframe-pane"; import type { DefaultDocumentNodeResolver } from "sanity/structure"; +import { Iframe } from "sanity-plugin-iframe-pane"; + export const defaultDocumentNode: DefaultDocumentNodeResolver = ( S, { schemaType }, diff --git a/apps/cms/src/config/sanity.config.ts b/apps/cms/src/config/sanity.config.ts index e0e4e055..e79828bb 100644 --- a/apps/cms/src/config/sanity.config.ts +++ b/apps/cms/src/config/sanity.config.ts @@ -1,9 +1,10 @@ +import * as schemas from "@/schemas"; +import { PUBLIC_SITE_URL } from "@kduprey/config"; import { visionTool } from "@sanity/vision"; import { type WorkspaceOptions, defineConfig } from "sanity"; -import { structureTool } from "sanity/structure"; import { presentationTool } from "sanity/presentation"; -import { PUBLIC_SITE_URL } from "@kduprey/config"; -import * as schemas from "@/schemas"; +import { structureTool } from "sanity/structure"; + import { deskStructure, documentActions, @@ -16,6 +17,9 @@ const schemaTypes = Object.values(schemas); export const PROJECT_ID = "b6x3by70"; const defaultConfig = { + document: { + actions: documentActions, + }, plugins: [ structureTool({ structure: deskStructure, @@ -23,52 +27,49 @@ const defaultConfig = { visionTool(), ], schema: { - types: schemaTypes, templates: schemaTemplatesFilter, - }, - document: { - actions: documentActions, + types: schemaTypes, }, }; const production: WorkspaceOptions = { ...defaultConfig, - name: "production", - title: "Haus of Web, LLC - Production", basePath: "/production", - projectId: PROJECT_ID, dataset: "production", + name: "production", plugins: [ presentationTool({ - resolve: { mainDocuments, locations: locate }, previewUrl: { draftMode: { enable: "https://kentonduprey.com/api/draft", }, }, + resolve: { locations: locate, mainDocuments }, }), ...defaultConfig.plugins, ], + projectId: PROJECT_ID, + title: "Haus of Web, LLC - Production", }; const staging: WorkspaceOptions = { ...defaultConfig, - name: "staging", - title: "Haus of Web, LLC - Staging", basePath: "/staging", - projectId: PROJECT_ID, dataset: "staging", + name: "staging", plugins: [ presentationTool({ - resolve: { mainDocuments, locations: locate }, previewUrl: { draftMode: { enable: `${PUBLIC_SITE_URL}/api/draft`, }, }, + resolve: { locations: locate, mainDocuments }, }), ...defaultConfig.plugins, ], + projectId: PROJECT_ID, + title: "Haus of Web, LLC - Staging", }; export default defineConfig( diff --git a/apps/frontend/next.config.js b/apps/frontend/next.config.js index 34b88bc6..3f11d40d 100644 --- a/apps/frontend/next.config.js +++ b/apps/frontend/next.config.js @@ -15,6 +15,9 @@ module.exports = { locales: ["en"], defaultLocale: "en", }, + eslint: { + ignoreDuringBuilds: true, + }, async redirects() { return [ { diff --git a/apps/frontend/sentry.server.config.ts b/apps/frontend/sentry.server.config.ts index 48154c8d..f59c001e 100644 --- a/apps/frontend/sentry.server.config.ts +++ b/apps/frontend/sentry.server.config.ts @@ -5,15 +5,14 @@ import * as Sentry from "@sentry/nextjs"; Sentry.init({ + // Setting this option to true will print useful information to the console while you're setting up Sentry. + debug: false, + dsn: "https://5262bb791199e68acec91d97608833f3@o1091546.ingest.us.sentry.io/4507523620864000", // Adjust this value in production, or use tracesSampler for greater control tracesSampleRate: 1, - // Setting this option to true will print useful information to the console while you're setting up Sentry. - debug: false, - // Uncomment the line below to enable Spotlight (https://spotlightjs.com) // spotlight: process.env.NODE_ENV === 'development', - }); diff --git a/apps/frontend/src/app/HomeLayout.tsx b/apps/frontend/src/app/HomeLayout.tsx index fa0bcc33..3b2a72f5 100644 --- a/apps/frontend/src/app/HomeLayout.tsx +++ b/apps/frontend/src/app/HomeLayout.tsx @@ -1,6 +1,7 @@ +import type { HomeType } from "@/sanity"; import type { EncodeDataAttributeCallback } from "@sanity/react-loader"; + import { About, Contact, Hero, Navbar, Projects, Skills } from "@/components"; -import type { HomeType } from "@/sanity"; interface HomeLayoutProps { encodeDataAttribute?: EncodeDataAttributeCallback; diff --git a/apps/frontend/src/app/api/contact/route.ts b/apps/frontend/src/app/api/contact/route.ts index d1779692..a6cc1214 100644 --- a/apps/frontend/src/app/api/contact/route.ts +++ b/apps/frontend/src/app/api/contact/route.ts @@ -1,17 +1,18 @@ import type { NextRequest } from "next/server"; + +import { prisma } from "@kduprey/db"; import { NextResponse } from "next/server"; import { z } from "zod"; import { fromZodError } from "zod-validation-error"; -import { prisma } from "@kduprey/db"; export const POST = async (req: NextRequest) => { try { - const { name, email, message, age } = z + const { age, email, message, name } = z .object({ - name: z.string(), + age: z.string().optional(), email: z.string().email(), message: z.string(), - age: z.string().optional(), + name: z.string(), }) .parse(await req.json()); @@ -21,9 +22,9 @@ export const POST = async (req: NextRequest) => { const res = await prisma.contactSubmission.create({ data: { - name, email, message, + name, }, }); diff --git a/apps/frontend/src/app/api/draft/route.ts b/apps/frontend/src/app/api/draft/route.ts index 9208142e..cf697d9f 100644 --- a/apps/frontend/src/app/api/draft/route.ts +++ b/apps/frontend/src/app/api/draft/route.ts @@ -1,13 +1,11 @@ -// ./app/api/draft/route.ts - +import { client, token } from "@/sanity"; import { validatePreviewUrl } from "@sanity/preview-url-secret"; import { draftMode } from "next/headers"; import { redirect } from "next/navigation"; -import { client, token } from "@/sanity"; const clientWithToken = client.withConfig({ token }); -export async function GET(request: Request) { +export const GET = async (request: Request) => { const { isValid, redirectTo = "/" } = await validatePreviewUrl( clientWithToken, request.url, @@ -18,4 +16,4 @@ export async function GET(request: Request) { draftMode().enable(); redirect(redirectTo); -} +}; diff --git a/apps/frontend/src/app/api/revalidate/route.ts b/apps/frontend/src/app/api/revalidate/route.ts index b81a9e7a..6af81dd4 100644 --- a/apps/frontend/src/app/api/revalidate/route.ts +++ b/apps/frontend/src/app/api/revalidate/route.ts @@ -1,3 +1,4 @@ +import { revalidateSecret } from "@/sanity"; import { revalidateTag } from "next/cache"; import { type NextRequest, NextResponse } from "next/server"; import { parseBody } from "next-sanity/webhook"; @@ -10,7 +11,7 @@ export async function POST(req: NextRequest) { try { const { body, isValidSignature } = await parseBody( req, - process.env.SANITY_REVALIDATE_SECRET, + revalidateSecret, ); if (!isValidSignature) { diff --git a/apps/frontend/src/app/api/stripe/billing/meter_events/route.ts b/apps/frontend/src/app/api/stripe/billing/meter_events/route.ts index 330da0e0..b664aafa 100644 --- a/apps/frontend/src/app/api/stripe/billing/meter_events/route.ts +++ b/apps/frontend/src/app/api/stripe/billing/meter_events/route.ts @@ -1,12 +1,12 @@ import { trytm } from "@bdsqqq/try"; -import { NextResponse, type NextRequest } from "next/server"; -import { z } from "zod"; -import { verify } from "jsonwebtoken"; import { stripe } from "@kduprey/config"; +import { verify } from "jsonwebtoken"; +import { type NextRequest, NextResponse } from "next/server"; +import { z } from "zod"; const jwtPayload = z.object({ - stripe_customer_id: z.string(), event_name: z.string(), + stripe_customer_id: z.string(), }); export const POST = async (req: NextRequest) => { @@ -21,12 +21,19 @@ export const POST = async (req: NextRequest) => { const token = z .string({ - required_error: "No JWT present in request", invalid_type_error: "Invalid JWT", + required_error: "No JWT present in request", }) .parse(authJWT.split(" ")[1]); - const payload = verify(token, z.string().parse(process.env.JWT_SECRET)); + const payload = verify( + token, + z + .string({ + message: "Environment variable JWT_SECRET is required", + }) + .parse(process.env.JWT_SECRET), + ); const body = jwtPayload.safeParse(payload); @@ -39,10 +46,10 @@ export const POST = async (req: NextRequest) => { const [stripeRes, stripeErr] = await trytm( stripe.billing.meterEvents.create({ event_name: body.data.event_name, - timestamp: Math.floor(Date.now() / 1000), payload: { stripe_customer_id: body.data.stripe_customer_id, }, + timestamp: Math.floor(Date.now() / 1000), }), ); diff --git a/apps/frontend/src/app/global-error.tsx b/apps/frontend/src/app/global-error.tsx index 1fd8c5c7..278b440f 100644 --- a/apps/frontend/src/app/global-error.tsx +++ b/apps/frontend/src/app/global-error.tsx @@ -4,7 +4,7 @@ import * as Sentry from "@sentry/nextjs"; import NextError from "next/error"; import { useEffect } from "react"; -const GlobalError = ({ error }: { error: Error & { digest?: string } }) => { +const GlobalError = ({ error }: { error: { digest?: string } & Error }) => { useEffect(() => { Sentry.captureException(error); }, [error]); diff --git a/apps/frontend/src/app/globals.css b/apps/frontend/src/app/globals.css index 1fa735a6..9c9169ab 100644 --- a/apps/frontend/src/app/globals.css +++ b/apps/frontend/src/app/globals.css @@ -3,47 +3,47 @@ @tailwind utilities; @layer base { - h1 { - @apply text-3xl; - } - @screen md { - h1 { - @apply text-5xl; - } - } + h1 { + @apply text-3xl; + } + @screen md { + h1 { + @apply text-5xl; + } + } - h2 { - @apply text-2xl; - } - @screen md { - h2 { - @apply text-3xl; - } - } - h3 { - @apply text-xl; - } - @screen md { - h3 { - @apply text-2xl; - } - } - p { - @apply text-gray-500 dark:text-gray-200 md:text-lg; - } - button, - input[type="submit"], - input[type="reset"], - input[type="button"] { - @apply rounded bg-black py-1 px-3 uppercase text-white transition ease-in hover:bg-white hover:text-black hover:ring-1 hover:ring-black dark:border dark:border-gray-300 dark:hover:bg-white dark:hover:text-black !important; - } + h2 { + @apply text-2xl; + } + @screen md { + h2 { + @apply text-3xl; + } + } + h3 { + @apply text-xl; + } + @screen md { + h3 { + @apply text-2xl; + } + } + p { + @apply text-gray-500 dark:text-gray-200 md:text-lg; + } + button, + input[type="submit"], + input[type="reset"], + input[type="button"] { + @apply rounded bg-black py-1 px-3 uppercase text-white transition ease-in hover:bg-white hover:text-black hover:ring-1 hover:ring-black dark:border dark:border-gray-300 dark:hover:bg-white dark:hover:text-black !important; + } } hr { - @apply w-4/5 rounded border-t bg-gray-300 opacity-70; + @apply w-4/5 rounded border-t bg-gray-300 opacity-70; } @layer components { - .input { - @apply rounded border-0 bg-gray-100 transition ease-in focus:border-black focus:bg-white focus:text-black focus:outline-none focus:ring-black dark:border dark:border-gray-300 dark:bg-black dark:text-white; - } + .input { + @apply rounded border-0 bg-gray-100 transition ease-in focus:border-black focus:bg-white focus:text-black focus:outline-none focus:ring-black dark:border dark:border-gray-300 dark:bg-black dark:text-white; + } } diff --git a/apps/frontend/src/app/layout.tsx b/apps/frontend/src/app/layout.tsx index ad22d1bd..0dfe9c12 100644 --- a/apps/frontend/src/app/layout.tsx +++ b/apps/frontend/src/app/layout.tsx @@ -1,18 +1,20 @@ import type { Metadata } from "next"; import type { PropsWithChildren } from "react"; + import { Inter } from "next/font/google"; -import "./globals.css"; import { draftMode } from "next/headers"; import { VisualEditing } from "next-sanity"; +import "./globals.css"; + export const metadata: Metadata = { - title: "Kenton Duprey - Web Developer", description: "Building elegant web solutions for clients and companies", + title: "Kenton Duprey - Web Developer", }; const inter = Inter({ - subsets: ["latin"], style: ["normal"], + subsets: ["latin"], variable: "--font-raleway", }); diff --git a/apps/frontend/src/app/page.tsx b/apps/frontend/src/app/page.tsx index 3dcf5770..77446381 100644 --- a/apps/frontend/src/app/page.tsx +++ b/apps/frontend/src/app/page.tsx @@ -1,5 +1,6 @@ -import { draftMode } from "next/headers"; import dynamic from "next/dynamic"; +import { draftMode } from "next/headers"; + import { loadHomePage } from "../sanity"; import { HomeLayout } from "./HomeLayout"; diff --git a/apps/frontend/src/app/preview.tsx b/apps/frontend/src/app/preview.tsx index 13fdc170..2b65ef4e 100644 --- a/apps/frontend/src/app/preview.tsx +++ b/apps/frontend/src/app/preview.tsx @@ -1,8 +1,9 @@ "use client"; -import { type QueryResponseInitial } from "@sanity/react-loader"; +import { type HomeType, homeQuery } from "@/sanity/data"; import { useQuery } from "@/sanity/utils/useQuery"; -import { homeQuery, type HomeType } from "@/sanity/data"; +import { type QueryResponseInitial } from "@sanity/react-loader"; + import { HomeLayout } from "./HomeLayout"; interface HomePagePreviewProps { diff --git a/apps/frontend/src/app/qr/page.tsx b/apps/frontend/src/app/qr/page.tsx index c640f80c..46ed2db7 100644 --- a/apps/frontend/src/app/qr/page.tsx +++ b/apps/frontend/src/app/qr/page.tsx @@ -1,6 +1,7 @@ +import { SvgIcon } from "@/components"; import Image from "next/image"; import Link from "next/link"; -import { SvgIcon } from "@/components"; + import personalPhoto from "../../../public/me.png"; const page = () => { @@ -11,16 +12,16 @@ const page = () => {
Bio Pic
diff --git a/apps/frontend/src/components/Icon.tsx b/apps/frontend/src/components/Icon.tsx index bf5b6eea..189d21e8 100644 --- a/apps/frontend/src/components/Icon.tsx +++ b/apps/frontend/src/components/Icon.tsx @@ -1,18 +1,19 @@ -import dynamic from "next/dynamic"; import type { IconBaseProps } from "react-icons"; + +import dynamic from "next/dynamic"; import { FaRegCircle } from "react-icons/fa"; import { FiCircle } from "react-icons/fi"; import { SiReact } from "react-icons/si"; interface IconProps { + color?: string; iconName: string; size?: number; - color?: string; } type IconsMapping = Record>; -export const Icon = ({ iconName, size, color }: IconProps) => { +export const Icon = ({ color, iconName, size }: IconProps) => { const Icons: IconsMapping = { fa: dynamic( () => @@ -41,7 +42,7 @@ export const Icon = ({ iconName, size, color }: IconProps) => { }; const DynamicIcon = iconName ? Icons.si : null; return ( -
+
{DynamicIcon ? : null}
); diff --git a/apps/frontend/src/components/ProjectCard.tsx b/apps/frontend/src/components/ProjectCard.tsx index 4c18b6f4..3d9fe96d 100644 --- a/apps/frontend/src/components/ProjectCard.tsx +++ b/apps/frontend/src/components/ProjectCard.tsx @@ -1,14 +1,16 @@ +import type { ProjectType } from "@/sanity"; + import Image from "next/image"; import Link from "next/link"; -import type { ProjectType } from "@/sanity"; + import { Skill } from "./Skill"; export const ProjectCard = ({ - projectImage, - title, description, link, + projectImage, projectSkills, + title, }: ProjectType) => { return (
@@ -18,8 +20,8 @@ export const ProjectCard = ({ loading="lazy" src={projectImage.src} style={{ - maxWidth: "100%", height: "auto", + maxWidth: "100%", objectFit: "cover", }} width={projectImage.dimensions.width} diff --git a/apps/frontend/src/components/Sections/About.tsx b/apps/frontend/src/components/Sections/About.tsx index 441c3c0d..dce1f967 100644 --- a/apps/frontend/src/components/Sections/About.tsx +++ b/apps/frontend/src/components/Sections/About.tsx @@ -1,7 +1,8 @@ -import Image from "next/image"; import type { AboutSectionType } from "@/sanity"; -export const About = ({ headerText, content, bioImage }: AboutSectionType) => { +import Image from "next/image"; + +export const About = ({ bioImage, content, headerText }: AboutSectionType) => { return (
{ height={bioImage.dimensions.height} src={bioImage.src} style={{ - maxWidth: "100%", height: "auto", + maxWidth: "100%", }} width={bioImage.dimensions.width} /> diff --git a/apps/frontend/src/components/Sections/Contact.tsx b/apps/frontend/src/components/Sections/Contact.tsx index cc3f0132..11b59080 100644 --- a/apps/frontend/src/components/Sections/Contact.tsx +++ b/apps/frontend/src/components/Sections/Contact.tsx @@ -1,149 +1,151 @@ "use client"; -import axios from "axios"; import type { - ChangeEvent, - ChangeEventHandler, - FormEvent, - FormEventHandler, + ChangeEvent, + ChangeEventHandler, + FormEvent, + FormEventHandler, } from "react"; + +import axios from "axios"; import { useState } from "react"; import { CgSpinner } from "react-icons/cg"; + import { Socials } from "./Socials"; export const Contact = () => { - const [inputData, setInputData] = useState({ - name: "", - email: "", - message: "", - age: undefined, - }); + const [inputData, setInputData] = useState({ + age: undefined, + email: "", + message: "", + name: "", + }); - const [loading, setLoading] = useState(false); - const [success, setSuccess] = useState(false); - const [usrMsg, setUsrMsg] = useState(""); + const [loading, setLoading] = useState(false); + const [success, setSuccess] = useState(false); + const [usrMsg, setUsrMsg] = useState(""); - const handleChange: ChangeEventHandler = ( - e: ChangeEvent - ) => { - const { name, value } = e.target; - switch (name) { - case "name": - setInputData({ ...inputData, name: value }); - break; - case "email": - setInputData({ ...inputData, email: value }); - break; - case "message": - setInputData({ ...inputData, message: value }); - break; - default: - break; - } - }; + const handleChange: ChangeEventHandler = ( + e: ChangeEvent, + ) => { + const { name, value } = e.target; + switch (name) { + case "name": + setInputData({ ...inputData, name: value }); + break; + case "email": + setInputData({ ...inputData, email: value }); + break; + case "message": + setInputData({ ...inputData, message: value }); + break; + default: + break; + } + }; - const onSubmit: FormEventHandler = (e: FormEvent) => { - e.preventDefault(); - setLoading(true); + const onSubmit: FormEventHandler = (e: FormEvent) => { + e.preventDefault(); + setLoading(true); - axios - .post("/api/contact", inputData) - .then((res) => { - if (res.status === 200) { - setUsrMsg("Your message has been sent!"); - } else { - setUsrMsg("Something went wrong!"); - } - setSuccess(true); - setInputData({ - name: "", - email: "", - message: "", - age: undefined, - }); - setLoading(false); - setTimeout(() => { - setSuccess(false); - }, 2000); - }) - .catch((err: unknown) => { - console.error(err); - }); - }; + axios + .post("/api/contact", inputData) + .then((res) => { + if (res.status === 200) { + setUsrMsg("Your message has been sent!"); + } else { + setUsrMsg("Something went wrong!"); + } + setSuccess(true); + setInputData({ + age: undefined, + email: "", + message: "", + name: "", + }); + setLoading(false); + setTimeout(() => { + setSuccess(false); + }, 2000); + }) + .catch((err: unknown) => { + console.error(err); + }); + }; - return ( -
-

Contact

-
- - - -
-