Updates
This commit is contained in:
@@ -1,61 +1,61 @@
|
||||
/** # MODULE TRACE
|
||||
======================================================================
|
||||
* Detected 1 files that call this module. The files are listed below:
|
||||
======================================================================
|
||||
* `import` Statement Found in [HomepageComponent.tsx] => file:///d:\GitHub\personal_site\app\(components)\HomepageComponent.tsx
|
||||
==== MODULE TRACE END ==== */
|
||||
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
|
||||
export default function AboutSection() {
|
||||
return (
|
||||
<div
|
||||
className="max-w-6xl w-full flex flex-col items-center pb-40 generic-scroll page-section"
|
||||
id="about-section"
|
||||
>
|
||||
<div className="h-44"></div>
|
||||
|
||||
<div className="flex flex-col xl:flex-row w-full gap-8">
|
||||
<div className="w-full xl:w-[40%]"></div>
|
||||
|
||||
<div className="w-full xl:w-[60%] px-4 md:px-6 py-2">
|
||||
<section className="flex flex-col items-start gap-4">
|
||||
<h2 className="mb-0">About Me</h2>
|
||||
|
||||
<span className="text-[24px]">Quick learner, adaptable, problem solver, curious. I strive to know the system, rather than the status quo. I thrive in difficult situations and complex hurdles: problem solving is now second nature to me.</span>
|
||||
|
||||
<hr className="w-full my-8" />
|
||||
|
||||
<h3 className="m-0">_ Code Ben</h3>
|
||||
<span className="text-[24px]">In the last two years I've developed from a complete designer to pro software engineer. After countless days of writing code, debugging, testing, building projects, server administration, deployment, CI/CD, etc, I've developed the most vital skill of all: adaptability. The ability to asimilate new knowledge at record pace: the tech industry moves really fast: you either keepup, or fall behind.</span>
|
||||
|
||||
<hr className="w-full my-8" />
|
||||
|
||||
<h3 className="m-0">Graphic Design</h3>
|
||||
<span className="text-[24px]">
|
||||
After spending about 5 years in the design industry, I've picked up a few vital concepts about UI/UX design. My design path still sips into my developer life: and I must say, it's the perfect harmony of modern tech. Some of my designs can be found on my{" "}
|
||||
<a
|
||||
href="https://99designs.com/profiles/tben"
|
||||
target="_blank"
|
||||
>
|
||||
99designs
|
||||
</a>{" "}
|
||||
profile.
|
||||
</span>
|
||||
|
||||
<hr className="w-full my-8" />
|
||||
|
||||
<a
|
||||
href="/about"
|
||||
className="button outlined"
|
||||
>
|
||||
Learn More About Me
|
||||
</a>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
/** # MODULE TRACE
|
||||
======================================================================
|
||||
* Detected 1 files that call this module. The files are listed below:
|
||||
======================================================================
|
||||
* `import` Statement Found in [HomepageComponent.tsx] => file:///d:\GitHub\personal_site\app\(components)\HomepageComponent.tsx
|
||||
==== MODULE TRACE END ==== */
|
||||
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
|
||||
export default function AboutSection() {
|
||||
return (
|
||||
<div
|
||||
className="max-w-6xl w-full flex flex-col items-center pb-40 generic-scroll page-section"
|
||||
id="about-section"
|
||||
>
|
||||
<div className="h-44"></div>
|
||||
|
||||
<div className="flex flex-col xl:flex-row w-full gap-8">
|
||||
<div className="w-full xl:w-[40%]"></div>
|
||||
|
||||
<div className="w-full xl:w-[60%] px-4 md:px-6 py-2">
|
||||
<section className="flex flex-col items-start gap-4">
|
||||
<h2 className="mb-0">About Me</h2>
|
||||
|
||||
<span className="text-[24px]">Quick learner, adaptable, problem solver, curious. I strive to know the system, rather than the status quo. I thrive in difficult situations and complex hurdles: problem solving is now second nature to me.</span>
|
||||
|
||||
<hr className="w-full my-8" />
|
||||
|
||||
<h3 className="m-0">_ Code Ben</h3>
|
||||
<span className="text-[24px]">In the last two years I've developed from a complete designer to pro software engineer. After countless days of writing code, debugging, testing, building projects, server administration, deployment, CI/CD, etc, I've developed the most vital skill of all: adaptability. The ability to asimilate new knowledge at record pace: the tech industry moves really fast: you either keepup, or fall behind.</span>
|
||||
|
||||
<hr className="w-full my-8" />
|
||||
|
||||
<h3 className="m-0">Graphic Design</h3>
|
||||
<span className="text-[24px]">
|
||||
After spending about 5 years in the design industry, I've picked up a few vital concepts about UI/UX design. My design path still sips into my developer life: and I must say, it's the perfect harmony of modern tech. Some of my designs can be found on my{" "}
|
||||
<a
|
||||
href="https://99designs.com/profiles/tben"
|
||||
target="_blank"
|
||||
>
|
||||
99designs
|
||||
</a>{" "}
|
||||
profile.
|
||||
</span>
|
||||
|
||||
<hr className="w-full my-8" />
|
||||
|
||||
<a
|
||||
href="/about"
|
||||
className="button outlined"
|
||||
>
|
||||
Learn More About Me
|
||||
</a>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,65 +1,65 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
|
||||
import submitContactForm from "../../functions/frontend/submitContactForm";
|
||||
|
||||
export default function ContactForm() {
|
||||
let [success, setSuccess] = React.useState<string | null>(null);
|
||||
let [loading, setLoading] = React.useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<form
|
||||
autoComplete="on"
|
||||
onSubmit={(e: any) => {
|
||||
submitContactForm(e, setSuccess, setLoading);
|
||||
}}
|
||||
>
|
||||
{loading && <div className="flex items-center justify-center w-full h-full absolute top-0 left-0 bg-[black]/90">Sending Mail...</div>}
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Your Name"
|
||||
autoComplete="name"
|
||||
required
|
||||
/>
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Your Email Address"
|
||||
autoComplete="email"
|
||||
required
|
||||
/>
|
||||
<textarea
|
||||
name="message"
|
||||
id="contact-form-message"
|
||||
cols={30}
|
||||
rows={10}
|
||||
placeholder="Message"
|
||||
></textarea>
|
||||
<button type="submit">Submit</button>
|
||||
{success === "Success" && (
|
||||
<div className="message-response">
|
||||
Success!!!{" "}
|
||||
<button
|
||||
onClick={() => {
|
||||
window.location.reload();
|
||||
}}
|
||||
>
|
||||
Reload
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{success === "Failed" && (
|
||||
<div className="message-response failed">
|
||||
Failed{" "}
|
||||
<button
|
||||
onClick={() => {
|
||||
window.location.reload();
|
||||
}}
|
||||
>
|
||||
Reload
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
|
||||
import submitContactForm from "../../functions/frontend/submitContactForm";
|
||||
|
||||
export default function ContactForm() {
|
||||
let [success, setSuccess] = React.useState<string | null>(null);
|
||||
let [loading, setLoading] = React.useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<form
|
||||
autoComplete="on"
|
||||
onSubmit={(e: any) => {
|
||||
submitContactForm(e, setSuccess, setLoading);
|
||||
}}
|
||||
>
|
||||
{loading && <div className="flex items-center justify-center w-full h-full absolute top-0 left-0 bg-[black]/90">Sending Mail...</div>}
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Your Name"
|
||||
autoComplete="name"
|
||||
required
|
||||
/>
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Your Email Address"
|
||||
autoComplete="email"
|
||||
required
|
||||
/>
|
||||
<textarea
|
||||
name="message"
|
||||
id="contact-form-message"
|
||||
cols={30}
|
||||
rows={10}
|
||||
placeholder="Message"
|
||||
></textarea>
|
||||
<button type="submit">Submit</button>
|
||||
{success === "Success" && (
|
||||
<div className="message-response">
|
||||
Success!!!{" "}
|
||||
<button
|
||||
onClick={() => {
|
||||
window.location.reload();
|
||||
}}
|
||||
>
|
||||
Reload
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{success === "Failed" && (
|
||||
<div className="message-response failed">
|
||||
Failed{" "}
|
||||
<button
|
||||
onClick={() => {
|
||||
window.location.reload();
|
||||
}}
|
||||
>
|
||||
Reload
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import ContactForm from "./ContactForm";
|
||||
|
||||
export default function ContactMeSection() {
|
||||
return (
|
||||
<div className="max-w-6xl w-full flex items-start page-section">
|
||||
<div className="flex flex-col items-start gap-6 w-full xl:w-[50%]">
|
||||
<div className="h-[150px]"></div>
|
||||
|
||||
<h2 className="m-0">Why So Serious?</h2>
|
||||
|
||||
<div className="flex flex-col-reverse xl:flex-col items-start gap-4">
|
||||
<span className="text-[24px]">Let's talk.</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-start w-full">
|
||||
<ContactForm />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import ContactForm from "./ContactForm";
|
||||
|
||||
export default function ContactMeSection() {
|
||||
return (
|
||||
<div className="max-w-6xl w-full flex items-start page-section">
|
||||
<div className="flex flex-col items-start gap-6 w-full xl:w-[50%]">
|
||||
<div className="h-[150px]"></div>
|
||||
|
||||
<h2 className="m-0">Why So Serious?</h2>
|
||||
|
||||
<div className="flex flex-col-reverse xl:flex-col items-start gap-4">
|
||||
<span className="text-[24px]">Let's talk.</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-start w-full">
|
||||
<ContactForm />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+124
-124
@@ -1,124 +1,124 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import Image from "next/image";
|
||||
import { gsap } from "gsap";
|
||||
import { TextPlugin } from "gsap/all";
|
||||
gsap.registerPlugin(TextPlugin);
|
||||
|
||||
export default function Hero() {
|
||||
/**
|
||||
* Handle animations on page load
|
||||
*/
|
||||
React.useEffect(() => {
|
||||
const tl = gsap.timeline();
|
||||
|
||||
gsap.fromTo(
|
||||
"#hero-sub-text",
|
||||
{
|
||||
opacity: 0,
|
||||
},
|
||||
{
|
||||
opacity: 1,
|
||||
delay: 0.5,
|
||||
duration: 1,
|
||||
}
|
||||
);
|
||||
gsap.to("#hero-text", {
|
||||
text: "I'm Benjamin Toby, a Software Engineer and UI/UX expert.",
|
||||
delay: 1,
|
||||
duration: 2,
|
||||
ease: "none",
|
||||
});
|
||||
gsap.fromTo(
|
||||
".hero-button-link",
|
||||
{
|
||||
opacity: 0,
|
||||
},
|
||||
{
|
||||
opacity: 1,
|
||||
delay: 3,
|
||||
duration: 1,
|
||||
stagger: 0.5,
|
||||
}
|
||||
);
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Render component
|
||||
*/
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col xl:flex-row items-center gap-0 justify-center -mt-[80px] xl:-mt-[160px] w-full relative pt-[500px] xl:pt-0 page-section"
|
||||
id="hero-content-wrapper"
|
||||
>
|
||||
<div
|
||||
className="rounded-full max-w-[450px] absolute md:relative flex items-center md:items-start justify-center mr-0 xl:-mr-14 bg-[#3e3f9c] overflow-hidden"
|
||||
id="main-image"
|
||||
style={{
|
||||
transform: "scale(1.2)",
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/images/my-photo-stroked.png"
|
||||
width={500}
|
||||
height={562}
|
||||
alt="Benjamin Toby Image"
|
||||
className="contrast-[140%] grayscale mt-20 flex items-center justify-center object-contain"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="max-w-2xl relative z-10 mt-10 w-full"
|
||||
id="hero-text-section"
|
||||
>
|
||||
<span
|
||||
id="hero-sub-text"
|
||||
className="uppercase bg-primary_light text-[black]/80 font-bold px-3 py-1 tracking-wide"
|
||||
>
|
||||
Howdy Tech Enthusiasts
|
||||
</span>
|
||||
<div className="h-4"></div>
|
||||
<h1
|
||||
className="text-5xl md:text-[58px] leading-[1.1] gsap-text h-auto xl:h-[220px]"
|
||||
id="hero-text"
|
||||
></h1>
|
||||
|
||||
<div className="h-10 flex xl:hidden"></div>
|
||||
|
||||
<div className="gap-4 flex items-center flex-wrap">
|
||||
<a
|
||||
href="https://www.linkedin.com/in/benjamin-toby/"
|
||||
target="_blank"
|
||||
className="button grow hero-button-link"
|
||||
>
|
||||
Linkedin
|
||||
</a>
|
||||
<a
|
||||
href="/contact"
|
||||
style={{
|
||||
backgroundColor: "transparent",
|
||||
color: "white",
|
||||
border: "2px solid white",
|
||||
}}
|
||||
className="button grow hero-button-link"
|
||||
>
|
||||
Contact Me
|
||||
</a>
|
||||
<a
|
||||
href="/documents/Resume-Benjamin-Toby-Linkedin.pdf"
|
||||
download={true}
|
||||
className="button grow hero-button-link"
|
||||
style={{
|
||||
backgroundColor: "transparent",
|
||||
color: "white",
|
||||
border: "2px solid white",
|
||||
}}
|
||||
>
|
||||
See my resume
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import Image from "next/image";
|
||||
import { gsap } from "gsap";
|
||||
import { TextPlugin } from "gsap/all";
|
||||
gsap.registerPlugin(TextPlugin);
|
||||
|
||||
export default function Hero() {
|
||||
/**
|
||||
* Handle animations on page load
|
||||
*/
|
||||
React.useEffect(() => {
|
||||
const tl = gsap.timeline();
|
||||
|
||||
gsap.fromTo(
|
||||
"#hero-sub-text",
|
||||
{
|
||||
opacity: 0,
|
||||
},
|
||||
{
|
||||
opacity: 1,
|
||||
delay: 0.5,
|
||||
duration: 1,
|
||||
}
|
||||
);
|
||||
gsap.to("#hero-text", {
|
||||
text: "I'm Benjamin Toby, a Software Engineer and UI/UX expert.",
|
||||
delay: 1,
|
||||
duration: 2,
|
||||
ease: "none",
|
||||
});
|
||||
gsap.fromTo(
|
||||
".hero-button-link",
|
||||
{
|
||||
opacity: 0,
|
||||
},
|
||||
{
|
||||
opacity: 1,
|
||||
delay: 3,
|
||||
duration: 1,
|
||||
stagger: 0.5,
|
||||
}
|
||||
);
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Render component
|
||||
*/
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col xl:flex-row items-center gap-0 justify-center -mt-[80px] xl:-mt-[160px] w-full relative pt-[500px] xl:pt-0 page-section"
|
||||
id="hero-content-wrapper"
|
||||
>
|
||||
<div
|
||||
className="rounded-full max-w-[450px] absolute md:relative flex items-center md:items-start justify-center mr-0 xl:-mr-14 bg-[#3e3f9c] overflow-hidden"
|
||||
id="main-image"
|
||||
style={{
|
||||
transform: "scale(1.2)",
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/images/my-photo-stroked.png"
|
||||
width={500}
|
||||
height={562}
|
||||
alt="Benjamin Toby Image"
|
||||
className="contrast-[140%] grayscale mt-20 flex items-center justify-center object-contain"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="max-w-2xl relative z-10 mt-10 w-full"
|
||||
id="hero-text-section"
|
||||
>
|
||||
<span
|
||||
id="hero-sub-text"
|
||||
className="uppercase bg-primary_light text-[black]/80 font-bold px-3 py-1 tracking-wide"
|
||||
>
|
||||
Howdy Tech Enthusiasts
|
||||
</span>
|
||||
<div className="h-4"></div>
|
||||
<h1
|
||||
className="text-5xl md:text-[58px] leading-[1.1] gsap-text h-auto xl:h-[220px]"
|
||||
id="hero-text"
|
||||
></h1>
|
||||
|
||||
<div className="h-10 flex xl:hidden"></div>
|
||||
|
||||
<div className="gap-4 flex items-center flex-wrap">
|
||||
<a
|
||||
href="https://www.linkedin.com/in/benjamin-toby/"
|
||||
target="_blank"
|
||||
className="button grow hero-button-link"
|
||||
>
|
||||
Linkedin
|
||||
</a>
|
||||
<a
|
||||
href="/contact"
|
||||
style={{
|
||||
backgroundColor: "transparent",
|
||||
color: "white",
|
||||
border: "2px solid white",
|
||||
}}
|
||||
className="button grow hero-button-link"
|
||||
>
|
||||
Contact Me
|
||||
</a>
|
||||
<a
|
||||
href="/documents/Resume-Benjamin-Toby-Linkedin.pdf"
|
||||
download={true}
|
||||
className="button grow hero-button-link"
|
||||
style={{
|
||||
backgroundColor: "transparent",
|
||||
color: "white",
|
||||
border: "2px solid white",
|
||||
}}
|
||||
>
|
||||
See my resume
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,56 +1,56 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import Hero from "./Hero";
|
||||
import AboutSection from "./AboutSection";
|
||||
import { gsap } from "gsap";
|
||||
import MyWorkSection from "./MyWorkSection";
|
||||
import MySkillsSection from "./MySkillsSection";
|
||||
import homepageTimeline from "../(utils)/homepage-timeline";
|
||||
import { SiteContext } from "../../layouts/general_layout/GeneralLayout";
|
||||
import ContactMeSection from "./ContactMeSection";
|
||||
|
||||
export type Project = {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
image?: string | null;
|
||||
url?: string | null;
|
||||
full_description?: string;
|
||||
};
|
||||
|
||||
type ChildProps = {
|
||||
projects: Project[];
|
||||
};
|
||||
|
||||
export default function HomepageComponent({ projects }: ChildProps) {
|
||||
const layoutContext: any = React.useContext(SiteContext);
|
||||
|
||||
if (layoutContext) layoutContext.projects = projects;
|
||||
|
||||
const comp = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
let ctx = gsap.context(() => {
|
||||
homepageTimeline({ componentRef: comp });
|
||||
}, comp);
|
||||
|
||||
return () => ctx.revert();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div
|
||||
id="homepage-content-wrapper"
|
||||
ref={comp}
|
||||
className="z-0 xl:z-50 mb-32"
|
||||
>
|
||||
<Hero />
|
||||
<AboutSection />
|
||||
<MySkillsSection />
|
||||
<MyWorkSection />
|
||||
<ContactMeSection />
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import Hero from "./Hero";
|
||||
import AboutSection from "./AboutSection";
|
||||
import { gsap } from "gsap";
|
||||
import MyWorkSection from "./MyWorkSection";
|
||||
import MySkillsSection from "./MySkillsSection";
|
||||
import homepageTimeline from "../(utils)/homepage-timeline";
|
||||
import { SiteContext } from "../../layouts/general_layout/GeneralLayout";
|
||||
import ContactMeSection from "./ContactMeSection";
|
||||
|
||||
export type Project = {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
image?: string | null;
|
||||
url?: string | null;
|
||||
full_description?: string;
|
||||
};
|
||||
|
||||
type ChildProps = {
|
||||
projects: Project[];
|
||||
};
|
||||
|
||||
export default function HomepageComponent({ projects }: ChildProps) {
|
||||
const layoutContext: any = React.useContext(SiteContext);
|
||||
|
||||
if (layoutContext) layoutContext.projects = projects;
|
||||
|
||||
const comp = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
let ctx = gsap.context(() => {
|
||||
homepageTimeline({ componentRef: comp });
|
||||
}, comp);
|
||||
|
||||
return () => ctx.revert();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div
|
||||
id="homepage-content-wrapper"
|
||||
ref={comp}
|
||||
className="z-0 xl:z-50 mb-32"
|
||||
>
|
||||
<Hero />
|
||||
<AboutSection />
|
||||
<MySkillsSection />
|
||||
<MyWorkSection />
|
||||
<ContactMeSection />
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,91 +1,91 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
|
||||
export default function MySkillsSection() {
|
||||
const webDevStack = require("../(utils)/web-dev-stack.json");
|
||||
const uiStack = require("../(utils)/ui-ux-stack.json");
|
||||
|
||||
const [targetStack, setTargetStack] = React.useState("dev");
|
||||
|
||||
return (
|
||||
<div className="max-w-6xl w-full flex flex-col items-start pb-40 page-section">
|
||||
<div className="h-[200px]"></div>
|
||||
|
||||
<div className="flex flex-col items-start gap-6 w-full xl:w-[50%]">
|
||||
<h2 className="mb-0">My Skills</h2>
|
||||
|
||||
<span className="text-[24px]">I am well-versed in the full spectrum of design and development. While I started out as a designer, I have focused more on development in the last few years.</span>
|
||||
|
||||
<hr className="w-full" />
|
||||
|
||||
<h3 className="m-0">{targetStack?.match(/dev/i) ? "Web Dev Tech Stack" : "UI/UX tech stack"}</h3>
|
||||
|
||||
<div className="flex flex-col xl:flex-col items-start gap-4 w-full">
|
||||
{/* <h3>My Tech Stack</h3> */}
|
||||
<div className="flex flex-wrap w-full gap-2">
|
||||
<div
|
||||
className={"p-4 text-lg cursor-pointer grow" + (targetStack.match(/dev/i) ? " bg-[white] text-[black] font-bold" : " border border-solid border-white/10 hover:opacity-60 ")}
|
||||
onClick={() => {
|
||||
setTargetStack("dev");
|
||||
}}
|
||||
>
|
||||
<div>Web Dev Stack</div>
|
||||
</div>
|
||||
<div
|
||||
className={"p-4 text-lg cursor-pointer grow" + (targetStack.match(/design/i) ? " bg-[white] text-[black] font-bold" : " border border-solid border-white/10 hover:opacity-60 ")}
|
||||
onClick={() => {
|
||||
setTargetStack("design");
|
||||
}}
|
||||
>
|
||||
<div>UI/UX Stack</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul
|
||||
style={{ maxWidth: "800px" }}
|
||||
className="pl-6"
|
||||
>
|
||||
{targetStack?.match(/dev/i) ? (
|
||||
<React.Fragment>
|
||||
{webDevStack.map((item: { title: string; description: string }, index: number) => (
|
||||
<li
|
||||
key={index}
|
||||
className="mb-4"
|
||||
>
|
||||
<h4 className="m-0 text-[#BBE572]">
|
||||
{item.title}
|
||||
{/* <TextShuffler textInput={item.title} /> */}
|
||||
</h4>
|
||||
<span className="opacity-80">
|
||||
{item.description}
|
||||
{/* <TextShuffler textInput={item.description} /> */}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
{uiStack.map((item: { title: string; description: string }, index: number) => (
|
||||
<li
|
||||
key={index}
|
||||
className="mb-4"
|
||||
>
|
||||
<h4 className="m-0 text-[#BBE572]">
|
||||
{item.title}
|
||||
{/* <TextShuffler textInput={item.title} /> */}
|
||||
</h4>
|
||||
<span className="opacity-80">
|
||||
{item.description}
|
||||
{/* <TextShuffler textInput={item.description} /> */}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
|
||||
export default function MySkillsSection() {
|
||||
const webDevStack = require("../(utils)/web-dev-stack.json");
|
||||
const uiStack = require("../(utils)/ui-ux-stack.json");
|
||||
|
||||
const [targetStack, setTargetStack] = React.useState("dev");
|
||||
|
||||
return (
|
||||
<div className="max-w-6xl w-full flex flex-col items-start pb-40 page-section">
|
||||
<div className="h-[200px]"></div>
|
||||
|
||||
<div className="flex flex-col items-start gap-6 w-full xl:w-[50%]">
|
||||
<h2 className="mb-0">My Skills</h2>
|
||||
|
||||
<span className="text-[24px]">I am well-versed in the full spectrum of design and development. While I started out as a designer, I have focused more on development in the last few years.</span>
|
||||
|
||||
<hr className="w-full" />
|
||||
|
||||
<h3 className="m-0">{targetStack?.match(/dev/i) ? "Web Dev Tech Stack" : "UI/UX tech stack"}</h3>
|
||||
|
||||
<div className="flex flex-col xl:flex-col items-start gap-4 w-full">
|
||||
{/* <h3>My Tech Stack</h3> */}
|
||||
<div className="flex flex-wrap w-full gap-2">
|
||||
<div
|
||||
className={"p-4 text-lg cursor-pointer grow" + (targetStack.match(/dev/i) ? " bg-[white] text-[black] font-bold" : " border border-solid border-white/10 hover:opacity-60 ")}
|
||||
onClick={() => {
|
||||
setTargetStack("dev");
|
||||
}}
|
||||
>
|
||||
<div>Web Dev Stack</div>
|
||||
</div>
|
||||
<div
|
||||
className={"p-4 text-lg cursor-pointer grow" + (targetStack.match(/design/i) ? " bg-[white] text-[black] font-bold" : " border border-solid border-white/10 hover:opacity-60 ")}
|
||||
onClick={() => {
|
||||
setTargetStack("design");
|
||||
}}
|
||||
>
|
||||
<div>UI/UX Stack</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul
|
||||
style={{ maxWidth: "800px" }}
|
||||
className="pl-6"
|
||||
>
|
||||
{targetStack?.match(/dev/i) ? (
|
||||
<React.Fragment>
|
||||
{webDevStack.map((item: { title: string; description: string }, index: number) => (
|
||||
<li
|
||||
key={index}
|
||||
className="mb-4"
|
||||
>
|
||||
<h4 className="m-0 text-[#BBE572]">
|
||||
{item.title}
|
||||
{/* <TextShuffler textInput={item.title} /> */}
|
||||
</h4>
|
||||
<span className="opacity-80">
|
||||
{item.description}
|
||||
{/* <TextShuffler textInput={item.description} /> */}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
{uiStack.map((item: { title: string; description: string }, index: number) => (
|
||||
<li
|
||||
key={index}
|
||||
className="mb-4"
|
||||
>
|
||||
<h4 className="m-0 text-[#BBE572]">
|
||||
{item.title}
|
||||
{/* <TextShuffler textInput={item.title} /> */}
|
||||
</h4>
|
||||
<span className="opacity-80">
|
||||
{item.description}
|
||||
{/* <TextShuffler textInput={item.description} /> */}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { Project } from "./HomepageComponent";
|
||||
import Image from "next/image";
|
||||
|
||||
export default function MyWorkCard({ project }: { project: Project }) {
|
||||
return (
|
||||
<div className="bg-primary/10 w-full shadow-2xl shadow-[black] relative">
|
||||
<Image
|
||||
src={project.image || ""}
|
||||
alt={project.title}
|
||||
width={600}
|
||||
height={300}
|
||||
className="object-cover object-top w-full"
|
||||
/>
|
||||
<div className="p-10">
|
||||
<h3 className="m-0">{project.title}</h3>
|
||||
<p className="m-0">{project.description}</p>
|
||||
</div>
|
||||
|
||||
<a
|
||||
href={project.url || "#"}
|
||||
className="absolute z-10 top-[340px] right-6 w-10 h-10 p-2 flex items-center justify-center rounded-full bg-[white]"
|
||||
target="_blank"
|
||||
>
|
||||
<img
|
||||
src="/images/external-link-dark.png"
|
||||
alt="External Link Icon"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { Project } from "./HomepageComponent";
|
||||
import Image from "next/image";
|
||||
|
||||
export default function MyWorkCard({ project }: { project: Project }) {
|
||||
return (
|
||||
<div className="bg-primary/10 w-full shadow-2xl shadow-[black] relative">
|
||||
<Image
|
||||
src={project.image || ""}
|
||||
alt={project.title}
|
||||
width={600}
|
||||
height={300}
|
||||
className="object-cover object-top w-full"
|
||||
/>
|
||||
<div className="p-10">
|
||||
<h3 className="m-0">{project.title}</h3>
|
||||
<p className="m-0">{project.description}</p>
|
||||
</div>
|
||||
|
||||
<a
|
||||
href={project.url || "#"}
|
||||
className="absolute z-10 top-[340px] right-6 w-10 h-10 p-2 flex items-center justify-center rounded-full bg-[white]"
|
||||
target="_blank"
|
||||
>
|
||||
<img
|
||||
src="/images/external-link-dark.png"
|
||||
alt="External Link Icon"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { SiteContext } from "../../layouts/general_layout/GeneralLayout";
|
||||
import { Project } from "./HomepageComponent";
|
||||
import MyWorkCard from "./MyWorkCard";
|
||||
|
||||
export default function MyWorkSection() {
|
||||
const layoutContext: { projects: Project[] } = React.useContext(SiteContext);
|
||||
const projects = layoutContext.projects;
|
||||
|
||||
return (
|
||||
<div className="max-w-6xl w-full flex flex-col xl:flex-row items-start pb-40 page-section">
|
||||
<div className="flex flex-col items-start gap-6 w-full xl:w-[50%]"></div>
|
||||
<div className="flex flex-col items-start gap-6 w-full xl:w-[50%]">
|
||||
<div className="h-[150px]"></div>
|
||||
|
||||
<h2 className="m-0">Some of my work</h2>
|
||||
|
||||
<div className="flex flex-col-reverse xl:flex-col items-start gap-4">
|
||||
<span className="text-[24px]">I've been creating and building awesome designs and applications since 2016. Here are a few picks:</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-start gap-44 mt-10">
|
||||
{projects &&
|
||||
projects.slice(0, 4).map((project, index) => (
|
||||
<MyWorkCard
|
||||
key={index}
|
||||
project={project}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<a
|
||||
href="/work"
|
||||
className="button outlined mt-10"
|
||||
>
|
||||
See More
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { SiteContext } from "../../layouts/general_layout/GeneralLayout";
|
||||
import { Project } from "./HomepageComponent";
|
||||
import MyWorkCard from "./MyWorkCard";
|
||||
|
||||
export default function MyWorkSection() {
|
||||
const layoutContext: { projects: Project[] } = React.useContext(SiteContext);
|
||||
const projects = layoutContext.projects;
|
||||
|
||||
return (
|
||||
<div className="max-w-6xl w-full flex flex-col xl:flex-row items-start pb-40 page-section">
|
||||
<div className="flex flex-col items-start gap-6 w-full xl:w-[50%]"></div>
|
||||
<div className="flex flex-col items-start gap-6 w-full xl:w-[50%]">
|
||||
<div className="h-[150px]"></div>
|
||||
|
||||
<h2 className="m-0">Some of my work</h2>
|
||||
|
||||
<div className="flex flex-col-reverse xl:flex-col items-start gap-4">
|
||||
<span className="text-[24px]">I've been creating and building awesome designs and applications since 2016. Here are a few picks:</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-start gap-44 mt-10">
|
||||
{projects &&
|
||||
projects.slice(0, 4).map((project, index) => (
|
||||
<MyWorkCard
|
||||
key={index}
|
||||
project={project}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<a
|
||||
href="/work"
|
||||
className="button outlined mt-10"
|
||||
>
|
||||
See More
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
+149
-149
@@ -1,149 +1,149 @@
|
||||
import { gsap } from "gsap";
|
||||
import ScrollTrigger from "gsap/ScrollTrigger";
|
||||
import TextPlugin from "gsap/TextPlugin";
|
||||
|
||||
/**
|
||||
* Register plugins
|
||||
*/
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
gsap.registerPlugin(TextPlugin);
|
||||
|
||||
/**
|
||||
* Animates the hero section
|
||||
*/
|
||||
export function hero() {
|
||||
/**
|
||||
* Animate hero section
|
||||
*/
|
||||
gsap.fromTo(
|
||||
"#main-image",
|
||||
{
|
||||
z: -100,
|
||||
scale: 1.2,
|
||||
},
|
||||
{
|
||||
z: 0,
|
||||
duration: 1.5,
|
||||
scale: 1,
|
||||
}
|
||||
);
|
||||
|
||||
gsap.fromTo(
|
||||
"#hero-text-section",
|
||||
{
|
||||
z: -100,
|
||||
scale: 1.2,
|
||||
},
|
||||
{
|
||||
z: 0,
|
||||
duration: 2.5,
|
||||
scale: 1,
|
||||
}
|
||||
);
|
||||
|
||||
gsap.fromTo(
|
||||
"#main-image",
|
||||
{
|
||||
y: 0,
|
||||
// scale: 1,
|
||||
},
|
||||
{
|
||||
scrollTrigger: {
|
||||
scrub: 1,
|
||||
},
|
||||
y: 150,
|
||||
// scale: 0.8,
|
||||
}
|
||||
);
|
||||
|
||||
gsap.fromTo(
|
||||
"#hero-text-section",
|
||||
{
|
||||
y: 0,
|
||||
// scale: 1,
|
||||
},
|
||||
{
|
||||
scrollTrigger: {
|
||||
scrub: 2,
|
||||
},
|
||||
y: 100,
|
||||
// scale: 0.8,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the about section
|
||||
*/
|
||||
export function about() {
|
||||
gsap.fromTo(
|
||||
"#about-section",
|
||||
{
|
||||
y: 40,
|
||||
scale: 0.8,
|
||||
},
|
||||
{
|
||||
scrollTrigger: {
|
||||
scrub: 4,
|
||||
},
|
||||
y: 0,
|
||||
scale: 1.1,
|
||||
}
|
||||
);
|
||||
|
||||
gsap.to("#about-me-label", {
|
||||
scrollTrigger: {
|
||||
scrub: 2,
|
||||
},
|
||||
y: 300,
|
||||
scale: 0.8,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the about section
|
||||
*/
|
||||
export function genericScroll() {
|
||||
gsap.fromTo(
|
||||
".generic-scroll",
|
||||
{
|
||||
y: 0,
|
||||
scale: 0.9,
|
||||
},
|
||||
{
|
||||
scrollTrigger: {
|
||||
scrub: 3,
|
||||
},
|
||||
y: 100,
|
||||
scale: 1,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the about section
|
||||
*/
|
||||
export function appear() {
|
||||
gsap.fromTo(
|
||||
".appear",
|
||||
{
|
||||
y: 40,
|
||||
scale: 0.8,
|
||||
opacity: 0,
|
||||
},
|
||||
{
|
||||
y: 0,
|
||||
scale: 1,
|
||||
opacity: 1,
|
||||
duration: 2,
|
||||
delay: (i, target) => {
|
||||
const datasetDelay = target?.dataset?.delay;
|
||||
|
||||
if (datasetDelay) {
|
||||
return datasetDelay;
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
import { gsap } from "gsap";
|
||||
import ScrollTrigger from "gsap/ScrollTrigger";
|
||||
import TextPlugin from "gsap/TextPlugin";
|
||||
|
||||
/**
|
||||
* Register plugins
|
||||
*/
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
gsap.registerPlugin(TextPlugin);
|
||||
|
||||
/**
|
||||
* Animates the hero section
|
||||
*/
|
||||
export function hero() {
|
||||
/**
|
||||
* Animate hero section
|
||||
*/
|
||||
gsap.fromTo(
|
||||
"#main-image",
|
||||
{
|
||||
z: -100,
|
||||
scale: 1.2,
|
||||
},
|
||||
{
|
||||
z: 0,
|
||||
duration: 1.5,
|
||||
scale: 1,
|
||||
}
|
||||
);
|
||||
|
||||
gsap.fromTo(
|
||||
"#hero-text-section",
|
||||
{
|
||||
z: -100,
|
||||
scale: 1.2,
|
||||
},
|
||||
{
|
||||
z: 0,
|
||||
duration: 2.5,
|
||||
scale: 1,
|
||||
}
|
||||
);
|
||||
|
||||
gsap.fromTo(
|
||||
"#main-image",
|
||||
{
|
||||
y: 0,
|
||||
// scale: 1,
|
||||
},
|
||||
{
|
||||
scrollTrigger: {
|
||||
scrub: 1,
|
||||
},
|
||||
y: 150,
|
||||
// scale: 0.8,
|
||||
}
|
||||
);
|
||||
|
||||
gsap.fromTo(
|
||||
"#hero-text-section",
|
||||
{
|
||||
y: 0,
|
||||
// scale: 1,
|
||||
},
|
||||
{
|
||||
scrollTrigger: {
|
||||
scrub: 2,
|
||||
},
|
||||
y: 100,
|
||||
// scale: 0.8,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the about section
|
||||
*/
|
||||
export function about() {
|
||||
gsap.fromTo(
|
||||
"#about-section",
|
||||
{
|
||||
y: 40,
|
||||
scale: 0.8,
|
||||
},
|
||||
{
|
||||
scrollTrigger: {
|
||||
scrub: 4,
|
||||
},
|
||||
y: 0,
|
||||
scale: 1.1,
|
||||
}
|
||||
);
|
||||
|
||||
gsap.to("#about-me-label", {
|
||||
scrollTrigger: {
|
||||
scrub: 2,
|
||||
},
|
||||
y: 300,
|
||||
scale: 0.8,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the about section
|
||||
*/
|
||||
export function genericScroll() {
|
||||
gsap.fromTo(
|
||||
".generic-scroll",
|
||||
{
|
||||
y: 0,
|
||||
scale: 0.9,
|
||||
},
|
||||
{
|
||||
scrollTrigger: {
|
||||
scrub: 3,
|
||||
},
|
||||
y: 100,
|
||||
scale: 1,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the about section
|
||||
*/
|
||||
export function appear() {
|
||||
gsap.fromTo(
|
||||
".appear",
|
||||
{
|
||||
y: 40,
|
||||
scale: 0.8,
|
||||
opacity: 0,
|
||||
},
|
||||
{
|
||||
y: 0,
|
||||
scale: 1,
|
||||
opacity: 1,
|
||||
duration: 2,
|
||||
delay: (i, target) => {
|
||||
const datasetDelay = target?.dataset?.delay;
|
||||
|
||||
if (datasetDelay) {
|
||||
return datasetDelay;
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
+208
-208
@@ -1,208 +1,208 @@
|
||||
import { gsap } from "gsap";
|
||||
import { ScrollTrigger, Observer } from "gsap/all";
|
||||
gsap.registerPlugin(ScrollTrigger, Observer);
|
||||
|
||||
export default function homepageTimeline({ componentRef }: { componentRef: { current: HTMLDivElement | null } }) {
|
||||
const mm = gsap.matchMedia();
|
||||
|
||||
const compHeight = componentRef.current?.clientHeight;
|
||||
|
||||
const isMobile = window.innerWidth <= 1200;
|
||||
|
||||
// mm.add("(min-width: 1200px)", () => {
|
||||
ScrollTrigger.create({
|
||||
trigger: "#homepage-content-wrapper",
|
||||
start: "200px 200px",
|
||||
end: "bottom 90%",
|
||||
pin: isMobile ? null : "#main-image",
|
||||
scrub: 1,
|
||||
onUpdate: (self) => {
|
||||
const scrollTop = compHeight ? self.progress * compHeight : 0;
|
||||
|
||||
/**
|
||||
* Origin
|
||||
*/
|
||||
if (!isMobile) {
|
||||
if (scrollTop < 300) {
|
||||
gsap.to("nav", {
|
||||
opacity: 1,
|
||||
pointerEvents: "visible",
|
||||
});
|
||||
gsap.to("header", {
|
||||
zIndex: 2000,
|
||||
});
|
||||
} else {
|
||||
gsap.to("nav", {
|
||||
opacity: 0,
|
||||
pointerEvents: "none",
|
||||
});
|
||||
gsap.to("header", {
|
||||
zIndex: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Mobile
|
||||
*/
|
||||
if (isMobile && scrollTop > 100) {
|
||||
gsap.to("#main-image", {
|
||||
opacity: 0.1,
|
||||
duration: 1,
|
||||
});
|
||||
} else {
|
||||
gsap.to("#main-image", {
|
||||
opacity: 1,
|
||||
duration: 1,
|
||||
});
|
||||
}
|
||||
},
|
||||
onEnter: (self) => {},
|
||||
});
|
||||
|
||||
/**
|
||||
* Position color map
|
||||
*/
|
||||
const positionColorMap: {
|
||||
x: number;
|
||||
imgBg: string;
|
||||
bodyBg: string;
|
||||
duration: number;
|
||||
imgSrc?: string;
|
||||
}[] = [
|
||||
{
|
||||
x: 0,
|
||||
imgBg: "#3e3f9c",
|
||||
bodyBg: "#181515",
|
||||
duration: 1,
|
||||
imgSrc: "/images/my-photo-stroked.png",
|
||||
},
|
||||
{
|
||||
x: -50,
|
||||
imgBg: "#FE6847",
|
||||
bodyBg: "#292a6b",
|
||||
duration: 1,
|
||||
imgSrc: "/images/my-photo-3.png",
|
||||
},
|
||||
{
|
||||
x: 600,
|
||||
imgBg: "#688e26",
|
||||
bodyBg: "#181515",
|
||||
duration: 1.2,
|
||||
imgSrc: "/images/programming-laptop.png",
|
||||
},
|
||||
{
|
||||
x: -50,
|
||||
imgBg: "#1B2CC1",
|
||||
bodyBg: "#091540",
|
||||
duration: 1.2,
|
||||
imgSrc: "/images/projects-section-image.png",
|
||||
},
|
||||
{
|
||||
x: 600,
|
||||
imgBg: "#ffd87d",
|
||||
bodyBg: "#3e3f9c",
|
||||
duration: 1.2,
|
||||
imgSrc: "/images/why-so-serious.png",
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Activate section based on scroll position
|
||||
*/
|
||||
const sections = document.querySelectorAll(".page-section");
|
||||
|
||||
function activateSection(index: number) {
|
||||
const targetPositionMap = positionColorMap[index];
|
||||
|
||||
if (!targetPositionMap) {
|
||||
return;
|
||||
}
|
||||
|
||||
gsap.to("#main-image", {
|
||||
backgroundColor: targetPositionMap.imgBg,
|
||||
x: isMobile ? 0 : targetPositionMap.x,
|
||||
duration: targetPositionMap.duration,
|
||||
});
|
||||
gsap.to("body", {
|
||||
backgroundColor: targetPositionMap.bodyBg,
|
||||
});
|
||||
|
||||
if (targetPositionMap?.imgSrc) {
|
||||
changeMainImage(targetPositionMap.imgSrc);
|
||||
}
|
||||
|
||||
if (index >= 2) {
|
||||
gsap.to("#main-image img", {
|
||||
filter: "none",
|
||||
duration: 1,
|
||||
});
|
||||
} else {
|
||||
gsap.to("#main-image img", {
|
||||
filter: "grayscale(100%) contrast(140%)",
|
||||
duration: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
sections.forEach((section, index) => {
|
||||
ScrollTrigger.create({
|
||||
trigger: section,
|
||||
start: "top 50%",
|
||||
end: "bottom 50%",
|
||||
onEnter: (self) => {
|
||||
activateSection(index);
|
||||
},
|
||||
onEnterBack: (self) => {
|
||||
activateSection(index);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Entry animation timeline
|
||||
*/
|
||||
const entryTimeline = gsap.timeline();
|
||||
|
||||
entryTimeline
|
||||
.from("#main-image", {
|
||||
clipPath: "circle(0)",
|
||||
})
|
||||
.to("#main-image", {
|
||||
clipPath: "circle(100%)",
|
||||
scale: 1,
|
||||
duration: 1,
|
||||
});
|
||||
|
||||
gsap.to("#main-image", {
|
||||
y: 20,
|
||||
repeat: -1,
|
||||
duration: 2,
|
||||
yoyo: true,
|
||||
ease: "sine.inOut",
|
||||
});
|
||||
// });
|
||||
}
|
||||
|
||||
async function changeMainImage(src: string) {
|
||||
const mainImage: HTMLImageElement | null = document.querySelector("#main-image img");
|
||||
|
||||
if (mainImage) {
|
||||
await gsap.to(mainImage, {
|
||||
opacity: 0,
|
||||
duration: 0.5,
|
||||
});
|
||||
|
||||
if (mainImage) {
|
||||
mainImage.setAttribute("src", src);
|
||||
|
||||
mainImage.onload = () => {
|
||||
gsap.to(mainImage, {
|
||||
opacity: 1,
|
||||
duration: 0.5,
|
||||
delay: 0.5,
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
import { gsap } from "gsap";
|
||||
import { ScrollTrigger, Observer } from "gsap/all";
|
||||
gsap.registerPlugin(ScrollTrigger, Observer);
|
||||
|
||||
export default function homepageTimeline({ componentRef }: { componentRef: { current: HTMLDivElement | null } }) {
|
||||
const mm = gsap.matchMedia();
|
||||
|
||||
const compHeight = componentRef.current?.clientHeight;
|
||||
|
||||
const isMobile = window.innerWidth <= 1200;
|
||||
|
||||
// mm.add("(min-width: 1200px)", () => {
|
||||
ScrollTrigger.create({
|
||||
trigger: "#homepage-content-wrapper",
|
||||
start: "200px 200px",
|
||||
end: "bottom 90%",
|
||||
pin: isMobile ? null : "#main-image",
|
||||
scrub: 1,
|
||||
onUpdate: (self) => {
|
||||
const scrollTop = compHeight ? self.progress * compHeight : 0;
|
||||
|
||||
/**
|
||||
* Origin
|
||||
*/
|
||||
if (!isMobile) {
|
||||
if (scrollTop < 300) {
|
||||
gsap.to("nav", {
|
||||
opacity: 1,
|
||||
pointerEvents: "visible",
|
||||
});
|
||||
gsap.to("header", {
|
||||
zIndex: 2000,
|
||||
});
|
||||
} else {
|
||||
gsap.to("nav", {
|
||||
opacity: 0,
|
||||
pointerEvents: "none",
|
||||
});
|
||||
gsap.to("header", {
|
||||
zIndex: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Mobile
|
||||
*/
|
||||
if (isMobile && scrollTop > 100) {
|
||||
gsap.to("#main-image", {
|
||||
opacity: 0.1,
|
||||
duration: 1,
|
||||
});
|
||||
} else {
|
||||
gsap.to("#main-image", {
|
||||
opacity: 1,
|
||||
duration: 1,
|
||||
});
|
||||
}
|
||||
},
|
||||
onEnter: (self) => {},
|
||||
});
|
||||
|
||||
/**
|
||||
* Position color map
|
||||
*/
|
||||
const positionColorMap: {
|
||||
x: number;
|
||||
imgBg: string;
|
||||
bodyBg: string;
|
||||
duration: number;
|
||||
imgSrc?: string;
|
||||
}[] = [
|
||||
{
|
||||
x: 0,
|
||||
imgBg: "#3e3f9c",
|
||||
bodyBg: "#181515",
|
||||
duration: 1,
|
||||
imgSrc: "/images/my-photo-stroked.png",
|
||||
},
|
||||
{
|
||||
x: -50,
|
||||
imgBg: "#FE6847",
|
||||
bodyBg: "#292a6b",
|
||||
duration: 1,
|
||||
imgSrc: "/images/my-photo-3.png",
|
||||
},
|
||||
{
|
||||
x: 600,
|
||||
imgBg: "#688e26",
|
||||
bodyBg: "#181515",
|
||||
duration: 1.2,
|
||||
imgSrc: "/images/programming-laptop.png",
|
||||
},
|
||||
{
|
||||
x: -50,
|
||||
imgBg: "#1B2CC1",
|
||||
bodyBg: "#091540",
|
||||
duration: 1.2,
|
||||
imgSrc: "/images/projects-section-image.png",
|
||||
},
|
||||
{
|
||||
x: 600,
|
||||
imgBg: "#ffd87d",
|
||||
bodyBg: "#3e3f9c",
|
||||
duration: 1.2,
|
||||
imgSrc: "/images/why-so-serious.png",
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Activate section based on scroll position
|
||||
*/
|
||||
const sections = document.querySelectorAll(".page-section");
|
||||
|
||||
function activateSection(index: number) {
|
||||
const targetPositionMap = positionColorMap[index];
|
||||
|
||||
if (!targetPositionMap) {
|
||||
return;
|
||||
}
|
||||
|
||||
gsap.to("#main-image", {
|
||||
backgroundColor: targetPositionMap.imgBg,
|
||||
x: isMobile ? 0 : targetPositionMap.x,
|
||||
duration: targetPositionMap.duration,
|
||||
});
|
||||
gsap.to("body", {
|
||||
backgroundColor: targetPositionMap.bodyBg,
|
||||
});
|
||||
|
||||
if (targetPositionMap?.imgSrc) {
|
||||
changeMainImage(targetPositionMap.imgSrc);
|
||||
}
|
||||
|
||||
if (index >= 2) {
|
||||
gsap.to("#main-image img", {
|
||||
filter: "none",
|
||||
duration: 1,
|
||||
});
|
||||
} else {
|
||||
gsap.to("#main-image img", {
|
||||
filter: "grayscale(100%) contrast(140%)",
|
||||
duration: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
sections.forEach((section, index) => {
|
||||
ScrollTrigger.create({
|
||||
trigger: section,
|
||||
start: "top 50%",
|
||||
end: "bottom 50%",
|
||||
onEnter: (self) => {
|
||||
activateSection(index);
|
||||
},
|
||||
onEnterBack: (self) => {
|
||||
activateSection(index);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Entry animation timeline
|
||||
*/
|
||||
const entryTimeline = gsap.timeline();
|
||||
|
||||
entryTimeline
|
||||
.from("#main-image", {
|
||||
clipPath: "circle(0)",
|
||||
})
|
||||
.to("#main-image", {
|
||||
clipPath: "circle(100%)",
|
||||
scale: 1,
|
||||
duration: 1,
|
||||
});
|
||||
|
||||
gsap.to("#main-image", {
|
||||
y: 20,
|
||||
repeat: -1,
|
||||
duration: 2,
|
||||
yoyo: true,
|
||||
ease: "sine.inOut",
|
||||
});
|
||||
// });
|
||||
}
|
||||
|
||||
async function changeMainImage(src: string) {
|
||||
const mainImage: HTMLImageElement | null = document.querySelector("#main-image img");
|
||||
|
||||
if (mainImage) {
|
||||
await gsap.to(mainImage, {
|
||||
opacity: 0,
|
||||
duration: 0.5,
|
||||
});
|
||||
|
||||
if (mainImage) {
|
||||
mainImage.setAttribute("src", src);
|
||||
|
||||
mainImage.onload = () => {
|
||||
gsap.to(mainImage, {
|
||||
opacity: 1,
|
||||
duration: 0.5,
|
||||
delay: 0.5,
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
[
|
||||
{
|
||||
"title": "Adobe Photoshop",
|
||||
"description": "Web design, image manipulation, image compositing, and more."
|
||||
},
|
||||
{
|
||||
"title": "Adobe Illustrator",
|
||||
"description": "Vector graphic of all types"
|
||||
},
|
||||
{
|
||||
"title": "Figma",
|
||||
"description": "Web, UI, UX design."
|
||||
},
|
||||
{
|
||||
"title": "Affinity Designer",
|
||||
"description": "Vector graphics."
|
||||
},
|
||||
{
|
||||
"title": "After Effects",
|
||||
"description": "Motion graphics and animation"
|
||||
},
|
||||
{
|
||||
"title": "Adobe XD",
|
||||
"description": "UI/UX design"
|
||||
},
|
||||
{
|
||||
"title": "Webflow",
|
||||
"description": "Visual Web coding"
|
||||
}
|
||||
]
|
||||
[
|
||||
{
|
||||
"title": "Adobe Photoshop",
|
||||
"description": "Web design, image manipulation, image compositing, and more."
|
||||
},
|
||||
{
|
||||
"title": "Adobe Illustrator",
|
||||
"description": "Vector graphic of all types"
|
||||
},
|
||||
{
|
||||
"title": "Figma",
|
||||
"description": "Web, UI, UX design."
|
||||
},
|
||||
{
|
||||
"title": "Affinity Designer",
|
||||
"description": "Vector graphics."
|
||||
},
|
||||
{
|
||||
"title": "After Effects",
|
||||
"description": "Motion graphics and animation"
|
||||
},
|
||||
{
|
||||
"title": "Adobe XD",
|
||||
"description": "UI/UX design"
|
||||
},
|
||||
{
|
||||
"title": "Webflow",
|
||||
"description": "Visual Web coding"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
[
|
||||
{
|
||||
"title": "HTML, CSS, Javascript",
|
||||
"description": "The basics, the bedrock of all websites."
|
||||
},
|
||||
{
|
||||
"title": "React JS",
|
||||
"description": "JavaScript library for high-performance web applications"
|
||||
},
|
||||
{
|
||||
"title": "Next JS",
|
||||
"description": "High performance React and Node js web framework for building blazing flast and performant web applications"
|
||||
},
|
||||
{
|
||||
"title": "Tailwind CSS",
|
||||
"description": "Lighting fast mobile first styling"
|
||||
},
|
||||
{
|
||||
"title": "Node JS",
|
||||
"description": "JavaScript runtime for the server. For creating backend architectures and APIs"
|
||||
},
|
||||
{
|
||||
"title": "Ubuntu Linux",
|
||||
"description": "Secure server management with ubuntu and Linux"
|
||||
},
|
||||
{
|
||||
"title": "Nginx",
|
||||
"description": "Super secure web server, reverse proxy and load balancer"
|
||||
},
|
||||
{
|
||||
"title": "MySQL",
|
||||
"description": "Tried and tested data storage, querying, and management."
|
||||
},
|
||||
{
|
||||
"title": "Git and Github",
|
||||
"description": "Version control"
|
||||
}
|
||||
]
|
||||
[
|
||||
{
|
||||
"title": "HTML, CSS, Javascript",
|
||||
"description": "The basics, the bedrock of all websites."
|
||||
},
|
||||
{
|
||||
"title": "React JS",
|
||||
"description": "JavaScript library for high-performance web applications"
|
||||
},
|
||||
{
|
||||
"title": "Next JS",
|
||||
"description": "High performance React and Node js web framework for building blazing flast and performant web applications"
|
||||
},
|
||||
{
|
||||
"title": "Tailwind CSS",
|
||||
"description": "Lighting fast mobile first styling"
|
||||
},
|
||||
{
|
||||
"title": "Node JS",
|
||||
"description": "JavaScript runtime for the server. For creating backend architectures and APIs"
|
||||
},
|
||||
{
|
||||
"title": "Ubuntu Linux",
|
||||
"description": "Secure server management with ubuntu and Linux"
|
||||
},
|
||||
{
|
||||
"title": "Nginx",
|
||||
"description": "Super secure web server, reverse proxy and load balancer"
|
||||
},
|
||||
{
|
||||
"title": "MySQL",
|
||||
"description": "Tried and tested data storage, querying, and management."
|
||||
},
|
||||
{
|
||||
"title": "Git and Github",
|
||||
"description": "Version control"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
|
||||
export default function Hero() {
|
||||
return (
|
||||
<div className="-mt-10 appear">
|
||||
<div
|
||||
className="flex flex-col items-start gap-0 max-w-6xl w-full relative generic-scroll"
|
||||
// id="hero-text-section"
|
||||
>
|
||||
<span className="text-primary-light text-lg">About Me</span>
|
||||
<h1
|
||||
className="text-5xl leading-snug"
|
||||
id="hero-text"
|
||||
>
|
||||
Ben of All Trades, Master of All
|
||||
</h1>
|
||||
<span className="hero-sub-text">Quick learner, adaptable, problem solver, curious. I strive to know the system, rather than the status quo. My credo is: no problem too great, no knowledge too vast, no logic too complex. I thrive in difficult situations and complex hurdles: problem solving is now second nature to me: if you can think it, it can be done.</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
|
||||
export default function Hero() {
|
||||
return (
|
||||
<div className="-mt-10 appear">
|
||||
<div
|
||||
className="flex flex-col items-start gap-0 max-w-6xl w-full relative generic-scroll"
|
||||
// id="hero-text-section"
|
||||
>
|
||||
<span className="text-primary-light text-lg">About Me</span>
|
||||
<h1 className="text-5xl leading-snug" id="hero-text">
|
||||
I live on the bleeding edge of technology
|
||||
</h1>
|
||||
<span className="hero-sub-text">
|
||||
Quick learner, adaptable, problem solver, curious. I strive
|
||||
to know the system, rather than the status quo. My credo is:
|
||||
no problem too great, no knowledge too vast, no logic too
|
||||
complex. I thrive in difficult situations and complex
|
||||
hurdles: problem solving is now second nature to me: if you
|
||||
can think it, it can be done.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,98 +1,98 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
|
||||
export default function MoreAboutMe() {
|
||||
const webDevStack = require("../../(utils)/web-dev-stack.json");
|
||||
const uiStack = require("../../(utils)/ui-ux-stack.json");
|
||||
|
||||
const [targetStack, setTargetStack] = React.useState("dev");
|
||||
|
||||
return (
|
||||
<div className="max-w-6xl w-full flex flex-col items-center pb-40 generic-scroll">
|
||||
<div className="h-20"></div>
|
||||
|
||||
<div
|
||||
className="flex flex-col w-full gap-8 appear"
|
||||
data-delay={2}
|
||||
>
|
||||
<div className="w-full">
|
||||
<h2>
|
||||
More About Me
|
||||
{/* <TextShuffler textInput="More About Me" /> */}
|
||||
</h2>
|
||||
|
||||
<div className="flex flex-col items-start gap-4">
|
||||
<div className="flex w-full gap-4">
|
||||
<div
|
||||
className={"p-4 cursor-pointer hover:opacity-60 grow" + (targetStack.match(/dev/i) ? " bg-[#343680]" : " border border-solid border-white/10")}
|
||||
onClick={() => {
|
||||
setTargetStack("dev");
|
||||
}}
|
||||
>
|
||||
<div>Web Dev Stack</div>
|
||||
</div>
|
||||
<div
|
||||
className={"p-4 cursor-pointer hover:opacity-60 grow" + (targetStack.match(/design/i) ? " bg-[#343680]" : " border border-solid border-white/10")}
|
||||
onClick={() => {
|
||||
setTargetStack("design");
|
||||
}}
|
||||
>
|
||||
<div>UI/UX Stack</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full bg-[#343680] px-4 md:px-6 py-2">
|
||||
<section>
|
||||
<h3>
|
||||
{targetStack?.match(/dev/i) ? "Web Dev Tech Stack" : "UI/UX tech stack"}
|
||||
{/* <TextShuffler textInput={targetStack?.match(/dev/i) ? "Web Dev Tech Stack" : "UI/UX tech stack"} /> */}
|
||||
</h3>
|
||||
<hr />
|
||||
<ul style={{ maxWidth: "800px" }}>
|
||||
{targetStack?.match(/dev/i) ? (
|
||||
<React.Fragment>
|
||||
{webDevStack.map((item: { title: string; description: string }, index: number) => (
|
||||
<li
|
||||
key={index}
|
||||
className="mb-4"
|
||||
>
|
||||
<h4 className="m-0">
|
||||
{item.title}
|
||||
{/* <TextShuffler textInput={item.title} /> */}
|
||||
</h4>
|
||||
<span className="opacity-80">
|
||||
{item.description}
|
||||
{/* <TextShuffler textInput={item.description} /> */}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
{uiStack.map((item: { title: string; description: string }, index: number) => (
|
||||
<li
|
||||
key={index}
|
||||
className="mb-4"
|
||||
>
|
||||
<h4 className="m-0">
|
||||
{item.title}
|
||||
{/* <TextShuffler textInput={item.title} /> */}
|
||||
</h4>
|
||||
<span className="opacity-80">
|
||||
{item.description}
|
||||
{/* <TextShuffler textInput={item.description} /> */}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
|
||||
export default function MoreAboutMe() {
|
||||
const webDevStack = require("../../(utils)/web-dev-stack.json");
|
||||
const uiStack = require("../../(utils)/ui-ux-stack.json");
|
||||
|
||||
const [targetStack, setTargetStack] = React.useState("dev");
|
||||
|
||||
return (
|
||||
<div className="max-w-6xl w-full flex flex-col items-center pb-40 generic-scroll">
|
||||
<div className="h-20"></div>
|
||||
|
||||
<div
|
||||
className="flex flex-col w-full gap-8 appear"
|
||||
data-delay={2}
|
||||
>
|
||||
<div className="w-full">
|
||||
<h2>
|
||||
More About Me
|
||||
{/* <TextShuffler textInput="More About Me" /> */}
|
||||
</h2>
|
||||
|
||||
<div className="flex flex-col items-start gap-4">
|
||||
<div className="flex w-full gap-4">
|
||||
<div
|
||||
className={"p-4 cursor-pointer hover:opacity-60 grow" + (targetStack.match(/dev/i) ? " bg-[#343680]" : " border border-solid border-white/10")}
|
||||
onClick={() => {
|
||||
setTargetStack("dev");
|
||||
}}
|
||||
>
|
||||
<div>Web Dev Stack</div>
|
||||
</div>
|
||||
<div
|
||||
className={"p-4 cursor-pointer hover:opacity-60 grow" + (targetStack.match(/design/i) ? " bg-[#343680]" : " border border-solid border-white/10")}
|
||||
onClick={() => {
|
||||
setTargetStack("design");
|
||||
}}
|
||||
>
|
||||
<div>UI/UX Stack</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full bg-[#343680] px-4 md:px-6 py-2">
|
||||
<section>
|
||||
<h3>
|
||||
{targetStack?.match(/dev/i) ? "Web Dev Tech Stack" : "UI/UX tech stack"}
|
||||
{/* <TextShuffler textInput={targetStack?.match(/dev/i) ? "Web Dev Tech Stack" : "UI/UX tech stack"} /> */}
|
||||
</h3>
|
||||
<hr />
|
||||
<ul style={{ maxWidth: "800px" }}>
|
||||
{targetStack?.match(/dev/i) ? (
|
||||
<React.Fragment>
|
||||
{webDevStack.map((item: { title: string; description: string }, index: number) => (
|
||||
<li
|
||||
key={index}
|
||||
className="mb-4"
|
||||
>
|
||||
<h4 className="m-0">
|
||||
{item.title}
|
||||
{/* <TextShuffler textInput={item.title} /> */}
|
||||
</h4>
|
||||
<span className="opacity-80">
|
||||
{item.description}
|
||||
{/* <TextShuffler textInput={item.description} /> */}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
{uiStack.map((item: { title: string; description: string }, index: number) => (
|
||||
<li
|
||||
key={index}
|
||||
className="mb-4"
|
||||
>
|
||||
<h4 className="m-0">
|
||||
{item.title}
|
||||
{/* <TextShuffler textInput={item.title} /> */}
|
||||
</h4>
|
||||
<span className="opacity-80">
|
||||
{item.description}
|
||||
{/* <TextShuffler textInput={item.description} /> */}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+12
-12
@@ -1,12 +1,12 @@
|
||||
import React from "react";
|
||||
import MoreAboutMe from "./(components)/MoreAboutMe";
|
||||
import Hero from "./(components)/Hero";
|
||||
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Hero />
|
||||
<MoreAboutMe />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
import React from "react";
|
||||
import MoreAboutMe from "./(components)/MoreAboutMe";
|
||||
import Hero from "./(components)/Hero";
|
||||
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Hero />
|
||||
<MoreAboutMe />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
+17
-17
@@ -1,17 +1,17 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
// export async function GET(request: Request, context?: {}) {
|
||||
// console.log(request.headers);
|
||||
|
||||
// return NextResponse.json({ name: "Benjamin Toby" });
|
||||
// }
|
||||
export async function GET(request: Request) {
|
||||
return new Response(JSON.stringify({ name: "Benjamin Toby" }), {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
|
||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
||||
},
|
||||
});
|
||||
}
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
// export async function GET(request: Request, context?: {}) {
|
||||
// console.log(request.headers);
|
||||
|
||||
// return NextResponse.json({ name: "Benjamin Toby" });
|
||||
// }
|
||||
export async function GET(request: Request) {
|
||||
return new Response(JSON.stringify({ name: "Benjamin Toby" }), {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
|
||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
+178
-178
@@ -1,178 +1,178 @@
|
||||
/**
|
||||
* ==============================================================================
|
||||
* Imports
|
||||
* ==============================================================================
|
||||
*/
|
||||
import React from "react";
|
||||
import Head from "next/head";
|
||||
import type { InferGetStaticPropsType, GetStaticProps, GetStaticPaths } from "next";
|
||||
const datasquirel = require("datasquirel");
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
|
||||
import GeneralLayout from "../../layouts/general_layout/GeneralLayout";
|
||||
import TextShuffler from "../../components/actions/TextShuffler";
|
||||
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
|
||||
/**
|
||||
* ==============================================================================
|
||||
* Main Component { Functional }
|
||||
* ==============================================================================
|
||||
* @param {Object} props - Server props
|
||||
*/
|
||||
export default function BlogIndex({ blogPost }: InferGetStaticPropsType<typeof getStaticProps>) {
|
||||
// ## Get Contexts
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
|
||||
// ## Javascript Variables
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
|
||||
// ## React Hooks { useState, useEffect, useRef, etc ... }
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
|
||||
// ## Function Return
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Head>
|
||||
<title>{blogPost.title} | Tben.me Blog</title>
|
||||
<meta
|
||||
name="description"
|
||||
content={blogPost.excerpt}
|
||||
/>
|
||||
</Head>
|
||||
<GeneralLayout>
|
||||
<div className="flex flex-col items-start gap-2 mb-8 max-w-6xl w-full">
|
||||
<button
|
||||
className="bg-transparent text-white/50 border-2 border-solid border-white/20"
|
||||
onClick={(e) => {
|
||||
window.history.back();
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
<h1 className="m-0">
|
||||
<TextShuffler textInput={blogPost.title} />
|
||||
</h1>
|
||||
<span className="text-lg">
|
||||
<TextShuffler textInput={blogPost.excerpt} />
|
||||
</span>
|
||||
<span className="text-base opacity-50">
|
||||
<TextShuffler textInput={blogPost.date_created.substring(0, 24)} />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span
|
||||
className="flex flex-col items-start max-w-6xl w-full gap-4 text-xl"
|
||||
dangerouslySetInnerHTML={{ __html: blogPost.body }}
|
||||
></span>
|
||||
</GeneralLayout>
|
||||
</React.Fragment>
|
||||
);
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
}
|
||||
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
|
||||
/**
|
||||
* Server Side Props or Static Props
|
||||
* ==============================================================================
|
||||
*/
|
||||
export const getStaticProps: GetStaticProps = async ({ params }) => {
|
||||
// ## Environment processes
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
|
||||
// ## User Authentication
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
|
||||
// ## Page/Site Data Data Fetching
|
||||
const postsResponse = await datasquirel.get({
|
||||
key: process.env.DATASQUIREL_API_KEY,
|
||||
db: "tbenme",
|
||||
query: `select * from blog_posts WHERE slug='${params?.single}'`,
|
||||
});
|
||||
|
||||
const post = postsResponse.payload[0];
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
|
||||
// ## Server Props Return
|
||||
return {
|
||||
props: {
|
||||
blogPost: post,
|
||||
},
|
||||
revalidate: 3600,
|
||||
};
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
};
|
||||
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
|
||||
/**
|
||||
* Server Side Props or Static Props
|
||||
* ==============================================================================
|
||||
*/
|
||||
export const getStaticPaths: GetStaticPaths = async () => {
|
||||
/**
|
||||
* Data fetching
|
||||
*
|
||||
* @abstract fetch date from the server or externnal source
|
||||
*/
|
||||
const postsResponse = await datasquirel.get({
|
||||
key: process.env.DATASQUIREL_API_KEY,
|
||||
db: "tbenme",
|
||||
query: `select slug from blog_posts`,
|
||||
});
|
||||
|
||||
const posts: { slug: string }[] | null = postsResponse.payload;
|
||||
|
||||
const paths = posts?.map((entry) => {
|
||||
return {
|
||||
params: { single: entry.slug },
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
paths: paths ? paths : [],
|
||||
fallback: "blocking",
|
||||
};
|
||||
};
|
||||
/**
|
||||
* ==============================================================================
|
||||
* Imports
|
||||
* ==============================================================================
|
||||
*/
|
||||
import React from "react";
|
||||
import Head from "next/head";
|
||||
import type { InferGetStaticPropsType, GetStaticProps, GetStaticPaths } from "next";
|
||||
const datasquirel = require("datasquirel");
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
|
||||
import GeneralLayout from "../../layouts/general_layout/GeneralLayout";
|
||||
import TextShuffler from "../../components/actions/TextShuffler";
|
||||
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
|
||||
/**
|
||||
* ==============================================================================
|
||||
* Main Component { Functional }
|
||||
* ==============================================================================
|
||||
* @param {Object} props - Server props
|
||||
*/
|
||||
export default function BlogIndex({ blogPost }: InferGetStaticPropsType<typeof getStaticProps>) {
|
||||
// ## Get Contexts
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
|
||||
// ## Javascript Variables
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
|
||||
// ## React Hooks { useState, useEffect, useRef, etc ... }
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
|
||||
// ## Function Return
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Head>
|
||||
<title>{blogPost.title} | Tben.me Blog</title>
|
||||
<meta
|
||||
name="description"
|
||||
content={blogPost.excerpt}
|
||||
/>
|
||||
</Head>
|
||||
<GeneralLayout>
|
||||
<div className="flex flex-col items-start gap-2 mb-8 max-w-6xl w-full">
|
||||
<button
|
||||
className="bg-transparent text-white/50 border-2 border-solid border-white/20"
|
||||
onClick={(e) => {
|
||||
window.history.back();
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
<h1 className="m-0">
|
||||
<TextShuffler textInput={blogPost.title} />
|
||||
</h1>
|
||||
<span className="text-lg">
|
||||
<TextShuffler textInput={blogPost.excerpt} />
|
||||
</span>
|
||||
<span className="text-base opacity-50">
|
||||
<TextShuffler textInput={blogPost.date_created.substring(0, 24)} />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span
|
||||
className="flex flex-col items-start max-w-6xl w-full gap-4 text-xl"
|
||||
dangerouslySetInnerHTML={{ __html: blogPost.body }}
|
||||
></span>
|
||||
</GeneralLayout>
|
||||
</React.Fragment>
|
||||
);
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
}
|
||||
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
|
||||
/**
|
||||
* Server Side Props or Static Props
|
||||
* ==============================================================================
|
||||
*/
|
||||
export const getStaticProps: GetStaticProps = async ({ params }) => {
|
||||
// ## Environment processes
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
|
||||
// ## User Authentication
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
|
||||
// ## Page/Site Data Data Fetching
|
||||
const postsResponse = await datasquirel.get({
|
||||
key: process.env.DATASQUIREL_API_KEY,
|
||||
db: "tbenme",
|
||||
query: `select * from blog_posts WHERE slug='${params?.single}'`,
|
||||
});
|
||||
|
||||
const post = postsResponse.payload[0];
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
|
||||
// ## Server Props Return
|
||||
return {
|
||||
props: {
|
||||
blogPost: post,
|
||||
},
|
||||
revalidate: 3600,
|
||||
};
|
||||
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
/** ********************************************** */
|
||||
};
|
||||
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
/** ****************************************************************************** */
|
||||
|
||||
/**
|
||||
* Server Side Props or Static Props
|
||||
* ==============================================================================
|
||||
*/
|
||||
export const getStaticPaths: GetStaticPaths = async () => {
|
||||
/**
|
||||
* Data fetching
|
||||
*
|
||||
* @abstract fetch date from the server or externnal source
|
||||
*/
|
||||
const postsResponse = await datasquirel.get({
|
||||
key: process.env.DATASQUIREL_API_KEY,
|
||||
db: "tbenme",
|
||||
query: `select slug from blog_posts`,
|
||||
});
|
||||
|
||||
const posts: { slug: string }[] | null = postsResponse.payload;
|
||||
|
||||
const paths = posts?.map((entry) => {
|
||||
return {
|
||||
params: { single: entry.slug },
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
paths: paths ? paths : [],
|
||||
fallback: "blocking",
|
||||
};
|
||||
};
|
||||
|
||||
+72
-72
@@ -1,72 +1,72 @@
|
||||
/////////////////////////////////////////////
|
||||
//* IMPORTS
|
||||
/////////////////////////////////////////////
|
||||
import React from "react";
|
||||
import { Metadata } from "next";
|
||||
const datasquirel = require("datasquirel");
|
||||
|
||||
import { headers, cookies } from "next/headers";
|
||||
|
||||
/////////////////////////////////////////////
|
||||
//* Metadata
|
||||
/////////////////////////////////////////////
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Blog posts | Tben.me",
|
||||
description: "Tech talks and tutorials by Tben",
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////
|
||||
//* Main Function
|
||||
/////////////////////////////////////////////
|
||||
/**
|
||||
* Blog page index
|
||||
* ==============================================================================
|
||||
*/
|
||||
export default async function BlogIndex() {
|
||||
//* Data fetching
|
||||
/////////////////////////////////////////////
|
||||
const postsResponse = await datasquirel.get({
|
||||
key: process.env.DATASQUIREL_API_KEY,
|
||||
db: process.env.DB_NAME,
|
||||
query: "select title,slug,excerpt,date_created from blog_posts limit 10",
|
||||
});
|
||||
|
||||
const posts = postsResponse?.success ? postsResponse.payload : [];
|
||||
try {
|
||||
const test = await fetch("http://localhost:5000/api/test");
|
||||
const result = await test.json();
|
||||
console.log(result);
|
||||
} catch (error: any) {
|
||||
console.log(error.message);
|
||||
}
|
||||
|
||||
console.log("Referer:", headers().get("referer"));
|
||||
console.log("Host:", headers().get("host"));
|
||||
// console.log(nextHeaders.cookies());
|
||||
|
||||
//* Main Function Return
|
||||
/////////////////////////////////////////////
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="flex flex-col items-start max-w-6xl w-full">
|
||||
<h1 className="mb-8">My Blog</h1>
|
||||
<div className="flex flex-col items-start w-full gap-4">
|
||||
{posts.map((post: { slug: string; title: string; excerpt: string; date_created: string }, index: number) => (
|
||||
<a
|
||||
key={index}
|
||||
href={`/blog/${post.slug}`}
|
||||
className="flex flex-col items-start gap-2 w-full hover:bg-blue-600 border border-solid border-white/20 p-8 transition-all bg-primary/10"
|
||||
>
|
||||
<h2 className="m-0">{post.title}</h2>
|
||||
<span className="opacity-80">{post.excerpt}</span>
|
||||
<span className="text-sm opacity-50">{post.date_created.substring(0, 24)}</span>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
/** ********************************************** */
|
||||
}
|
||||
/////////////////////////////////////////////
|
||||
//* IMPORTS
|
||||
/////////////////////////////////////////////
|
||||
import React from "react";
|
||||
import { Metadata } from "next";
|
||||
const datasquirel = require("datasquirel");
|
||||
|
||||
import { headers, cookies } from "next/headers";
|
||||
|
||||
/////////////////////////////////////////////
|
||||
//* Metadata
|
||||
/////////////////////////////////////////////
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Blog posts | Tben.me",
|
||||
description: "Tech talks and tutorials by Tben",
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////
|
||||
//* Main Function
|
||||
/////////////////////////////////////////////
|
||||
/**
|
||||
* Blog page index
|
||||
* ==============================================================================
|
||||
*/
|
||||
export default async function BlogIndex() {
|
||||
//* Data fetching
|
||||
/////////////////////////////////////////////
|
||||
const postsResponse = await datasquirel.get({
|
||||
key: process.env.DATASQUIREL_API_KEY,
|
||||
db: process.env.DB_NAME,
|
||||
query: "select title,slug,excerpt,date_created from blog_posts limit 10",
|
||||
});
|
||||
|
||||
const posts = postsResponse?.success ? postsResponse.payload : [];
|
||||
try {
|
||||
const test = await fetch("http://localhost:5000/api/test");
|
||||
const result = await test.json();
|
||||
console.log(result);
|
||||
} catch (error: any) {
|
||||
console.log(error.message);
|
||||
}
|
||||
|
||||
console.log("Referer:", headers().get("referer"));
|
||||
console.log("Host:", headers().get("host"));
|
||||
// console.log(nextHeaders.cookies());
|
||||
|
||||
//* Main Function Return
|
||||
/////////////////////////////////////////////
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="flex flex-col items-start max-w-6xl w-full">
|
||||
<h1 className="mb-8">My Blog</h1>
|
||||
<div className="flex flex-col items-start w-full gap-4">
|
||||
{posts.map((post: { slug: string; title: string; excerpt: string; date_created: string }, index: number) => (
|
||||
<a
|
||||
key={index}
|
||||
href={`/blog/${post.slug}`}
|
||||
className="flex flex-col items-start gap-2 w-full hover:bg-blue-600 border border-solid border-white/20 p-8 transition-all bg-primary/10"
|
||||
>
|
||||
<h2 className="m-0">{post.title}</h2>
|
||||
<span className="opacity-80">{post.excerpt}</span>
|
||||
<span className="text-sm opacity-50">{post.date_created.substring(0, 24)}</span>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
/** ********************************************** */
|
||||
}
|
||||
|
||||
+51
-51
@@ -1,51 +1,51 @@
|
||||
/** # MODULE TRACE
|
||||
======================================================================
|
||||
* No imports found for this Module
|
||||
==== MODULE TRACE END ==== */
|
||||
|
||||
// General imports
|
||||
import { Metadata } from "next";
|
||||
import GeneralLayout from "../layouts/general_layout/GeneralLayout";
|
||||
|
||||
// Styles imports
|
||||
import "../styles/main.css";
|
||||
import "../styles/tw_main.css";
|
||||
|
||||
// Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: "Homepage",
|
||||
description: "Software engineer, UI/UX designer, Full Stack Web Developer, Web/graphic/motion designer, React Developer, Next JS developer, Node JS developer, Javascript Developer, Linux Ubuntu, DevOps, Nginx, MySQL developer, Freelancer",
|
||||
keywords: "UI/UX designer, Full Stack Web Developer, Web/graphic/motion designer, React Developer, NextJS developer, Node JS developer, Javascript Developer, Linux Ubuntu, DevOps, Nginx, MySQL developer, Freelancer",
|
||||
};
|
||||
|
||||
// Main Layout Component
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
{/* <script
|
||||
defer
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"
|
||||
></script>
|
||||
<script
|
||||
defer
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js"
|
||||
></script>
|
||||
<script
|
||||
defer
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/TextPlugin.min.js"
|
||||
></script> */}
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/gh/studio-freight/lenis@1/bundled/lenis.min.js"></script>
|
||||
|
||||
<script
|
||||
src="/scripts/main.js"
|
||||
defer
|
||||
></script>
|
||||
</head>
|
||||
<body>
|
||||
<GeneralLayout>{children}</GeneralLayout>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
/** # MODULE TRACE
|
||||
======================================================================
|
||||
* No imports found for this Module
|
||||
==== MODULE TRACE END ==== */
|
||||
|
||||
// General imports
|
||||
import { Metadata } from "next";
|
||||
import GeneralLayout from "../layouts/general_layout/GeneralLayout";
|
||||
|
||||
// Styles imports
|
||||
import "../styles/main.css";
|
||||
import "../styles/tw_main.css";
|
||||
|
||||
// Metadata
|
||||
export const metadata: Metadata = {
|
||||
title: "Homepage",
|
||||
description: "Software engineer, UI/UX designer, Full Stack Web Developer, Web/graphic/motion designer, React Developer, Next JS developer, Node JS developer, Javascript Developer, Linux Ubuntu, DevOps, Nginx, MySQL developer, Freelancer",
|
||||
keywords: "UI/UX designer, Full Stack Web Developer, Web/graphic/motion designer, React Developer, NextJS developer, Node JS developer, Javascript Developer, Linux Ubuntu, DevOps, Nginx, MySQL developer, Freelancer",
|
||||
};
|
||||
|
||||
// Main Layout Component
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
{/* <script
|
||||
defer
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"
|
||||
></script>
|
||||
<script
|
||||
defer
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js"
|
||||
></script>
|
||||
<script
|
||||
defer
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/TextPlugin.min.js"
|
||||
></script> */}
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/gh/studio-freight/lenis@1/bundled/lenis.min.js"></script>
|
||||
|
||||
<script
|
||||
src="/scripts/main.js"
|
||||
defer
|
||||
></script>
|
||||
</head>
|
||||
<body>
|
||||
<GeneralLayout>{children}</GeneralLayout>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
+21
-21
@@ -1,21 +1,21 @@
|
||||
import React from "react";
|
||||
import HomepageComponent from "./(components)/HomepageComponent";
|
||||
|
||||
const datasquirel = require("datasquirel");
|
||||
|
||||
export const revalidate = 3600;
|
||||
|
||||
type DsqlResponse = {
|
||||
success: boolean;
|
||||
payload: any;
|
||||
};
|
||||
|
||||
export default async function Homepage() {
|
||||
const projects: DsqlResponse = await datasquirel.get({
|
||||
db: process.env.DB_NAME,
|
||||
key: process.env.DATASQUIREL_API_KEY,
|
||||
query: "SELECT * FROM portfolio ORDER BY project_order ASC",
|
||||
});
|
||||
|
||||
return <HomepageComponent projects={projects.payload} />;
|
||||
}
|
||||
import React from "react";
|
||||
import HomepageComponent from "./(components)/HomepageComponent";
|
||||
|
||||
const datasquirel = require("datasquirel");
|
||||
|
||||
export const revalidate = 3600;
|
||||
|
||||
type DsqlResponse = {
|
||||
success: boolean;
|
||||
payload: any;
|
||||
};
|
||||
|
||||
export default async function Homepage() {
|
||||
const projects: DsqlResponse = await datasquirel.get({
|
||||
db: process.env.DB_NAME,
|
||||
key: process.env.DATASQUIREL_API_KEY,
|
||||
query: "SELECT * FROM portfolio ORDER BY project_order ASC",
|
||||
});
|
||||
|
||||
return <HomepageComponent projects={projects.payload} />;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user