diff --git a/src/Client/src/components/AboutTabs.tsx b/src/Client/src/components/AboutTabs.tsx index 0ae4fbd..8bf1a75 100644 --- a/src/Client/src/components/AboutTabs.tsx +++ b/src/Client/src/components/AboutTabs.tsx @@ -1,7 +1,158 @@ import { Tab } from "@headlessui/react"; -import { Fragment } from "react"; -import Subtitle from "./Subtitle"; +import { Fragment, ReactNode } from "react"; +import { SiAzurefunctions, SiMicrosoftazure, SiReact } from "react-icons/si"; +import { Link } from "react-router-dom"; import buildClassNames from "../helpers/cssClassFormatter"; +import AnchorLink from "./AnchorLink"; +import List from "./List"; +import ListItem from "./ListItem"; +import Subtitle from "./Subtitle"; +import Text from "./Text"; + +interface AboutTab { + tabName: string; + title: ReactNode; + subtitle: string; + content: ReactNode[]; +} + +const tabs: AboutTab[] = [ + { + tabName: "Front-end", + title: ( + + Front-end + + ), + subtitle: "React + TypeScript", + content: [ + + This app was originally made using{" "} + + .NET Blazor WASM + {" "} + however I recently decided to start learning{" "} + React and saw this as + a good oppurtunity to solidify some knowledge by re-writing this app + from the ground up using React with{" "} + + TypeScript + + . + , + + Overall I've found building front-end apps far more enjoyable using + React & TypeScript dispite the steep learning-curve coming from a purely + .NET & C# background. Both the developer experience and performance of + the app have improved dramatically after switching front-end + technologies. + , + + This app is styled using a cool CSS framework called{" "} + TailwindCSS.{" "} + PostCSS is used + alongside Tailwind to generate a lightweight stylesheet based only on + the parts of the framework that are used, as oppose to including a + everything the framework offers. + , + ], + }, + { + tabName: "Back-end", + title: ( + + Back-end + + ), + subtitle: ".NET Azure Functions API", + content: [ + + There is a very minimal API used as the back-end of this app to allow + users to contact me directly via the{" "} + + contact + {" "} + page. This will be expanded to serve the technical blog I'm building as + a new feature that will be available soon. + , + The contact API endpoint currently:, + + + Validates a{" "} + + Google reCAPTCHA + {" "} + token to protect against fraudulent submissions. + + + Builds a HTML email from the information provided in the form. + + + Sends an email directly to my inbox using the{" "} + SendGrid{" "} + API. + + , + + The API is written in .NET 8 using{" "} + + Azure Serverless Functions + {" "} + with a HTTP trigger to act as an API endpoint. For larger scale projects + I would almost always opt for a fully-featured{" "} + + Web API + + , however Azure Functions provide automatic elastic scaling with + consumption-based billing and a generous free-tier, making them perfect + for smaller projects like this. + , + ], + }, + { + tabName: "Hosting", + title: ( + + Hosting + + ), + subtitle: "Microsoft Azure Static Web App", + content: [ + + The goal of this project was to learn some new technologies and host the + app as cheaply as possible. With this in mind I decided to go with a{" "} + + Static Web App + {" "} + hosted on Microsoft Azure. Static Web Apps offer global distribution of + static assets (the Blazor Webassembly app in this case) and offer + integrated hosting for Azure Function App APIs. + , + + Another cool feature of Static Web Apps is Azure's integration with + GitHub actions to deploy both the client and server simultaneously and + provide automatically deployed staging environments for pull-requests + opened to the main branch. This made testing deployed changes much + easier and cheaper than deploying an isolated testing/GA environment + before releasing to the live version of the app. + , + + Using Static Web Apps on Azure has meant that I have been able to build, + deploy and serve this site and API completely free (with the exception + of the domain name). The next thing on the roadmap is building a simple + blog using an{" "} + + Azure SQL database + {" "} + where I'll document the full process of writing and deploying this app + so check back again soon. + , + ], + }, +]; export default function AboutTabs() { return ( @@ -9,56 +160,35 @@ export default function AboutTabs() {
- - buildClassNames( - selected - ? "border-gray-300 text-gray-200" - : "border-transparent hover:border-gray-300 hover:text-gray-200", - "whitespace-nowrap border-b-2 py-6 text-sm font-medium" - ) - } - > - Front-end - - - buildClassNames( - selected - ? "border-gray-300 text-gray-200" - : "border-transparent hover:border-gray-300 hover:text-gray-200", - "whitespace-nowrap border-b-2 py-6 text-sm font-medium" - ) - } - > - Back-end - - - buildClassNames( - selected - ? "border-gray-300 text-gray-200" - : "border-transparent hover:border-gray-300 hover:text-gray-200", - "whitespace-nowrap border-b-2 py-6 text-sm font-medium" - ) - } - > - Hosting - + {tabs.map((tab) => ( + + buildClassNames( + selected + ? "border-gray-300 text-gray-200" + : "border-transparent hover:border-gray-300 hover:text-gray-200", + "whitespace-nowrap border-b-4 py-6 text-sm font-medium" + ) + } + > + {tab.tabName} + + ))}
- - Front-end - - - Back-end - - - Hosting - + {tabs.map((tab) => ( + + {tab.title} +

Tech: {tab.subtitle}

+ {tab.content.map((content, index) => ( + {content} + ))} +
+ ))}
); diff --git a/src/Client/src/components/Link.tsx b/src/Client/src/components/AnchorLink.tsx similarity index 82% rename from src/Client/src/components/Link.tsx rename to src/Client/src/components/AnchorLink.tsx index d6178ce..91a799c 100644 --- a/src/Client/src/components/Link.tsx +++ b/src/Client/src/components/AnchorLink.tsx @@ -7,7 +7,12 @@ interface Props { className?: string | null; } -export default function Link({ children, href, target, className }: Props) { +export default function AnchorLink({ + children, + href, + target, + className, +}: Props) { const defaultStyles = "underline underline-offset-2 hover:underline-offset-4"; const styles = buildClassNames(className ? className : "", defaultStyles); diff --git a/src/Client/src/components/ContactForm.tsx b/src/Client/src/components/ContactForm.tsx index 6b3573c..ebedcb7 100644 --- a/src/Client/src/components/ContactForm.tsx +++ b/src/Client/src/components/ContactForm.tsx @@ -1,5 +1,5 @@ import { FaRegPaperPlane } from "react-icons/fa6"; -import Link from "./Link"; +import AnchorLink from "./AnchorLink"; import TextInput from "./TextInput"; import TextAreaInput from "./TextAreaInput"; import Button from "./Button"; @@ -37,13 +37,19 @@ export default function ContactForm() {
This site is protected by reCAPTCHA and the Google{" "} - + Privacy Policy - {" "} + {" "} and{" "} - + Terms of Service - {" "} + {" "} apply.
diff --git a/src/Client/src/components/List.tsx b/src/Client/src/components/List.tsx new file mode 100644 index 0000000..7ebacd5 --- /dev/null +++ b/src/Client/src/components/List.tsx @@ -0,0 +1,15 @@ +import { ReactElement } from "react"; +import buildClassNames from "../helpers/cssClassFormatter"; +import { ListItemProps } from "./ListItem"; + +interface Props { + className?: string | null; + children: ReactElement | Array>; +} + +export default function List({ className, children }: Props) { + const defaultStyles = "list-disc pl-8 space-y-2"; + const styles = buildClassNames(className ? className : "", defaultStyles); + + return ; +} diff --git a/src/Client/src/components/ListItem.tsx b/src/Client/src/components/ListItem.tsx new file mode 100644 index 0000000..5fea678 --- /dev/null +++ b/src/Client/src/components/ListItem.tsx @@ -0,0 +1,9 @@ +import { ReactNode } from "react"; + +export interface ListItemProps { + children: ReactNode; +} + +export default function ListItem({ children }: ListItemProps) { + return
  • {children}
  • ; +} diff --git a/src/Client/src/components/Subtitle.tsx b/src/Client/src/components/Subtitle.tsx index 6593962..842f72d 100644 --- a/src/Client/src/components/Subtitle.tsx +++ b/src/Client/src/components/Subtitle.tsx @@ -1,12 +1,13 @@ +import { ReactNode } from "react"; import buildClassNames from "../helpers/cssClassFormatter"; interface Props { - children: string; + children: ReactNode; className?: string | null; } export default function Subtitle({ children, className }: Props) { - const defaultStyles = "text-2xl py-4 font-semibold"; + const defaultStyles = "flex items-center text-2xl py-4 font-semibold"; const styles = buildClassNames(className ? className : "", defaultStyles); return

    {children}

    ; diff --git a/src/Client/src/components/Text.tsx b/src/Client/src/components/Text.tsx index c5abf0c..3ff197d 100644 --- a/src/Client/src/components/Text.tsx +++ b/src/Client/src/components/Text.tsx @@ -7,7 +7,7 @@ interface Props { } export default function Text({ children, className }: Props) { - const defaultStyles = "text-lg py-2"; + const defaultStyles = "text-lg py-3"; const styles = buildClassNames(className ? className : "", defaultStyles); return

    {children}

    ; diff --git a/src/Client/src/pages/AboutPage.tsx b/src/Client/src/pages/AboutPage.tsx index e293c9b..2cd3277 100644 --- a/src/Client/src/pages/AboutPage.tsx +++ b/src/Client/src/pages/AboutPage.tsx @@ -1,5 +1,5 @@ import AboutTabs from "../components/AboutTabs"; -import Link from "../components/Link"; +import AnchorLink from "../components/AnchorLink"; import Text from "../components/Text"; import Title from "../components/Title"; @@ -11,7 +11,10 @@ export default function AboutPage() { Below is an overview of how this simple app is made and what technologies are used. If you'd like to dive straight in, the full project is available on my{" "} - GitHub. + + GitHub + + . I'm planning to integrate a simple blog as part of this app that will diff --git a/src/Client/src/pages/HomePage.tsx b/src/Client/src/pages/HomePage.tsx index 9be6c7f..dc9b4db 100644 --- a/src/Client/src/pages/HomePage.tsx +++ b/src/Client/src/pages/HomePage.tsx @@ -1,4 +1,4 @@ -import Link from "../components/Link"; +import AnchorLink from "../components/AnchorLink"; import Subtitle from "../components/Subtitle"; import Text from "../components/Text"; import Title from "../components/Title"; @@ -19,7 +19,7 @@ export default function HomePage() { I've worked with businesses at all sizes and stages and I'm currently heading up the tech as CTO at a cool startup called{" "} - un:hurd music. + un:hurd music. );