Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: auroradream04/aurora-portfolio
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: master
Choose a base ref
...
head repository: Dev-Huang1/Portfolio
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
Can’t automatically merge. Don’t worry, you can still create the pull request.

Commits on Oct 29, 2024

  1. Update config.tsx

    Dev-Huang1 authored Oct 29, 2024
    Copy the full SHA
    c299e80 View commit details
  2. Update HeroTitle.tsx

    Dev-Huang1 authored Oct 29, 2024
    Copy the full SHA
    059fdf1 View commit details
  3. Copy the full SHA
    bc0d421 View commit details
  4. Merge pull request #1 from auroradream04/patch-1

    Update fetchWordpress.ts --- remove authentication
    Dev-Huang1 authored Oct 29, 2024
    Copy the full SHA
    05f9638 View commit details

Commits on Nov 2, 2024

  1. Copy the full SHA
    30ed6a2 View commit details
  2. Copy the full SHA
    522912b View commit details
  3. Update page.tsx

    Dev-Huang1 authored Nov 2, 2024
    Copy the full SHA
    41aa114 View commit details
  4. Copy the full SHA
    4996596 View commit details
  5. Update sitemap.ts

    Dev-Huang1 authored Nov 2, 2024
    Copy the full SHA
    82da123 View commit details
  6. Update config.tsx

    Dev-Huang1 authored Nov 2, 2024
    Copy the full SHA
    58d0966 View commit details
  7. Update ContactForm.tsx

    Dev-Huang1 authored Nov 2, 2024
    Copy the full SHA
    684e6ec View commit details
  8. Update ContactForm.tsx

    Dev-Huang1 authored Nov 2, 2024
    Copy the full SHA
    98712eb View commit details
  9. Update HireMeButton.tsx

    Dev-Huang1 authored Nov 2, 2024
    Copy the full SHA
    2d69547 View commit details
  10. Copy the full SHA
    631591c View commit details
  11. Create Technologies.tsx

    Dev-Huang1 authored Nov 2, 2024
    Copy the full SHA
    cef5083 View commit details
  12. Copy the full SHA
    61579fc View commit details
  13. Copy the full SHA
    ef30eed View commit details
  14. Update page.tsx

    Dev-Huang1 authored Nov 2, 2024
    Copy the full SHA
    45a4864 View commit details
  15. Create MotionTag.tsx

    Dev-Huang1 authored Nov 2, 2024
    Copy the full SHA
    68d45f0 View commit details
  16. Create useIsMobile.ts

    Dev-Huang1 authored Nov 2, 2024
    Copy the full SHA
    979a4dd View commit details
  17. Create separator.tsx

    Dev-Huang1 authored Nov 2, 2024
    Copy the full SHA
    6c5cda9 View commit details
  18. Copy the full SHA
    ffc7223 View commit details
  19. Update SocialIcons.tsx

    Dev-Huang1 authored Nov 2, 2024
    Copy the full SHA
    f8497a7 View commit details
  20. Copy the full SHA
    40e95ad View commit details
  21. Update config.tsx

    Dev-Huang1 authored Nov 2, 2024
    Copy the full SHA
    d7eb063 View commit details
  22. Update config.tsx

    Dev-Huang1 authored Nov 2, 2024
    Copy the full SHA
    619630c View commit details
  23. Update config.tsx

    Dev-Huang1 authored Nov 2, 2024
    Copy the full SHA
    c943ff5 View commit details
  24. Update config.tsx

    Dev-Huang1 authored Nov 2, 2024
    Copy the full SHA
    0c1f9cc View commit details
  25. Update config.tsx

    Dev-Huang1 authored Nov 2, 2024
    Copy the full SHA
    f49c827 View commit details
  26. Update config.tsx

    Dev-Huang1 authored Nov 2, 2024
    Copy the full SHA
    485334c View commit details
  27. Update config.tsx

    Dev-Huang1 authored Nov 2, 2024
    Copy the full SHA
    781b939 View commit details
  28. Update config.tsx

    Dev-Huang1 authored Nov 2, 2024
    Copy the full SHA
    7c633bd View commit details
  29. Update config.tsx

    Dev-Huang1 authored Nov 2, 2024
    Copy the full SHA
    6375969 View commit details
  30. Update config.tsx

    Dev-Huang1 authored Nov 2, 2024
    Copy the full SHA
    76a8993 View commit details
  31. Update HireMeButton.tsx

    Dev-Huang1 authored Nov 2, 2024
    Copy the full SHA
    a380c88 View commit details
  32. update icon

    Dev-Huang1 authored Nov 2, 2024
    Copy the full SHA
    7b5f00c View commit details
  33. Add files via upload

    Dev-Huang1 authored Nov 2, 2024
    Copy the full SHA
    67c3b89 View commit details
  34. Add files via upload

    Dev-Huang1 authored Nov 2, 2024
    Copy the full SHA
    d227d5a View commit details
  35. Add files via upload

    Dev-Huang1 authored Nov 2, 2024
    Copy the full SHA
    d4795aa View commit details

Commits on Nov 3, 2024

  1. Update Technologies.tsx

    Dev-Huang1 authored Nov 3, 2024
    Copy the full SHA
    e78e4df View commit details

Commits on Nov 8, 2024

  1. Update HeroTitle.tsx

    Dev-Huang1 authored Nov 8, 2024
    Copy the full SHA
    e8c2c9d View commit details
  2. Update MotionTag.tsx

    Dev-Huang1 authored Nov 8, 2024
    Copy the full SHA
    e682a1a View commit details
  3. Update Technologies.tsx

    Dev-Huang1 authored Nov 8, 2024
    Copy the full SHA
    9b133af View commit details
  4. Copy the full SHA
    9a2a230 View commit details

Commits on Nov 13, 2024

  1. Update HeroTitle.tsx

    Dev-Huang1 authored Nov 13, 2024
    Copy the full SHA
    988673b View commit details

Commits on Dec 7, 2024

  1. Update config.tsx

    Dev-Huang1 authored Dec 7, 2024
    Copy the full SHA
    3b5997b View commit details

Commits on Dec 21, 2024

  1. Update MotionTag.tsx

    Dev-Huang1 authored Dec 21, 2024
    Copy the full SHA
    d70cab4 View commit details
  2. Update TitleList.tsx

    Dev-Huang1 authored Dec 21, 2024
    Copy the full SHA
    15b9f57 View commit details
  3. Update config.tsx

    Dev-Huang1 authored Dec 21, 2024
    Copy the full SHA
    f705081 View commit details
  4. Update config.tsx

    Dev-Huang1 authored Dec 21, 2024
    Copy the full SHA
    48d7c94 View commit details
124 changes: 0 additions & 124 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,125 +1 @@
# Alvin Chang's Portfolio

This is a personal portfolio website for Alvin Chang, showcasing projects, blogs, and experiences as a full-stack developer. The site is built using Next.js, Tailwind CSS, and other modern web technologies.

🔗 [Live Demo](https://alvinchang.dev)

## Important Notice

While this project's code is open source, please note the following:

- **Attribution Required**: If you use this template for commercial purposes, attribution to Alvin Chang (https://alvinchang.dev) is required.
- **Copyright Notice**: The following elements are copyrighted and NOT available for reuse:
- All logos and branding elements
- Blog content and articles
- Personal information and copy text
- Project descriptions and images
- **Template Usage**: Only the code structure and implementation are available for use under the license terms.

## Table of Contents

- [Features](#features)
- [Technologies Used](#technologies-used)
- [Getting Started](#getting-started)
- [Configuration](#configuration)
- [Scripts](#scripts)
- [Deployment](#deployment)
- [License](#license)
- [Contact](#contact)

## Features

- **Responsive Design**: Optimized for various screen sizes.
- **Dynamic Content**: Fetches and displays blog posts from WordPress.
- **SEO Optimized**: Metadata and Open Graph tags for better search engine visibility.
- **Contact Form**: Allows visitors to send messages directly using Nodemailer and Zoho Mail.
- **Project Showcase**: Highlights various development projects.
- **RSS Feed**: Provides an RSS feed for blog posts.

## Technologies Used

- **Next.js**: React framework for server-side rendering and static site generation.
- **Tailwind CSS**: Utility-first CSS framework for styling.
- **TypeScript**: Typed superset of JavaScript for better code quality.
- **Shiki**: Syntax highlighter for code blocks.
- **Nodemailer**: For sending emails from the contact form.
- **Zoho Mail**: SMTP server for email handling.
- **WordPress**: Headless CMS for blog content management.

## Getting Started

To get a local copy up and running, follow these steps:

### Prerequisites

- Node.js and npm installed on your machine.
- A WordPress site set up for blog content (headless CMS).
- Zoho Mail account for email functionality.

### Installation

1. Clone the repository:
```bash
git clone https://github.com/auroradream04/aurora-portfolio.git
```

2. Navigate to the project directory:
```bash
cd aurora-portfolio
```

3. Install dependencies:
```bash
npm install
```

4. Create a `.env.local` file in the root directory based on the `.env.example` file and add your environment variables.

## Configuration

1. Update the `siteConfig` object in `src/app/config.tsx` with your personal information, projects, and experiences.
2. Set up a WordPress site to host your blog content and update the `WORDPRESS_API_URL` in your `.env.local` file.
3. Configure Zoho Mail credentials in your `.env.local` file for the contact form functionality.

## Scripts

- `npm run dev`: Starts the development server.
- `npm run build`: Builds the application for production.
- `npm start`: Starts the production server.

## Deployment

This project can be deployed on platforms like Vercel, Netlify, or any other hosting service that supports Next.js applications.

## Usage and Attribution

### Permitted Uses
- Use as a template for your personal portfolio
- Study and modify the code structure
- Implement similar features in your own projects

### Requirements
- Attribution must be provided when used commercially
- Remove all personal branding, content, and imagery before use
- Replace all content in `siteConfig` with your own information

### Attribution Format
When using this template commercially, please include the following attribution in your footer or credits page:

```
Template designed by Alvin Chang (https://alvinchang.dev)
```

## License

This project's code structure is available under a modified MIT License with additional requirements for commercial use. See [LICENSE](LICENSE) for details.

**Note**: All personal content, including but not limited to logos, blog content, and personal information, is copyrighted and not available for reuse.

## Contact

For any inquiries, please contact Alvin Chang at alvin@studioaurora.io.

---

If you like this project, please consider giving it a star on GitHub! ⭐
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "aurora-portfolio",
"name": "portfolio",
"version": "0.1.0",
"private": true,
"scripts": {
@@ -14,9 +14,11 @@
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-separator": "^1.0.0",
"@react-three/drei": "^9.114.3",
"@react-three/fiber": "^8.17.10",
"@vercel/analytics": "^1.3.1",
"@vercel/speed-insights": "^1.1.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"framer-motion": "^11.11.7",
Binary file added public/android-chrome-192x192.png

Unable to render rich display

Binary file added public/android-chrome-512x512.png

Unable to render rich display

Binary file modified public/apple-touch-icon.png

Unable to render rich display

Binary file added public/favicon-16x16.png

Unable to render rich display

Binary file added public/favicon-32x32.png

Unable to render rich display

Binary file modified public/favicon-48x48.png

Unable to render rich display

Binary file modified public/favicon.ico
Binary file not shown.
360 changes: 359 additions & 1 deletion public/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/logox.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 1 addition & 21 deletions public/site.webmanifest
Original file line number Diff line number Diff line change
@@ -1,21 +1 @@
{
"name": "MyWebSite",
"short_name": "MySite",
"icons": [
{
"src": "/web-app-manifest-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/web-app-manifest-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"theme_color": "#000000",
"background_color": "#000000",
"display": "standalone"
}
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
99 changes: 0 additions & 99 deletions src/app/blogs/[slug]/page.tsx

This file was deleted.

50 changes: 0 additions & 50 deletions src/app/blogs/page.tsx

This file was deleted.

165 changes: 11 additions & 154 deletions src/app/components/ContactForm.tsx
Original file line number Diff line number Diff line change
@@ -3,89 +3,12 @@
import MotionDiv from "./MotionDiv";
import { slideInFromRight } from "../utils/motion";
import { Send } from "lucide-react";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import * as z from "zod";
import { useState } from "react";

import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import SectionLabel from "./SectionLabel";
import { sendMail } from "../utils/sendMail";
import { toast } from "sonner";

const formSchema = z.object({
name: z.string().min(2, {
message: "Name must be at least 2 characters.",
}),
email: z.string().email({
message: "Please enter a valid email address.",
}),
message: z.string().min(10, {
message: "Message must be at least 10 characters.",
}),
});
import { Button } from "@/components/ui/button";

export default function ContactForm() {
const [isLoading, setIsLoading] = useState(false);
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
name: "",
email: "",
message: "",
},
});

let isMobile = false;
if (typeof window !== "undefined") {
isMobile = window.matchMedia("(max-width: 1024px)").matches;
}

async function onSubmit(values: z.infer<typeof formSchema>) {
setIsLoading(true);
const response = await sendMail(
values.name,
values.email,
values.message
);
setIsLoading(false);

if (response && response.success) {
toast.success(response.message, {
description: "Your message has been sent. I will get back to you as soon as possible.",
duration: 5000,
position: isMobile ? "top-center" : "bottom-right",
classNames: {
icon: "text-green-500",
toast: "bg-background text-slate-100 border-slate-900",
description: "text-slate-350",

}
});
} else if (response && !response.success) {
toast.error(response.message, {
description: "Please try again later.",
duration: 5000,
position: isMobile ? "top-center" : "bottom-right",
classNames: {
icon: "text-red-600",
toast: "bg-background text-slate-100 border-slate-900",
description: "text-slate-350",

}
});
}
form.reset();
function handleButtonClick() {
window.location.href = "mailto:evan.huang000@proton.me";
}

return (
@@ -97,81 +20,15 @@ export default function ContactForm() {
>
<section className="w-full text-sm" id="contact">
<SectionLabel label="CONTACT" />
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="grid grid-cols-2 gap-4"
<div className="col-span-2 flex justify-start">
<Button
onClick={handleButtonClick}
className="bg-gradient-to-r from-orange-600 to-pink-600 hover:from-orange-500 hover:to-pink-500 dark:text-white text-xs px-4 py-2 rounded-sm w-full"
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem className="col-span-2 md:col-span-1">
<FormLabel className="text-slate-100 text-xs">
Name
</FormLabel>
<FormControl>
<Input
placeholder="Alvin Chang"
{...field}
className="bg-[rgba(255,255,255,0.01)] border-[rgba(255,255,255,0.1)] text-white text-xs p-2 rounded-sm focus:ring-orange-500"
/>
</FormControl>
<FormMessage className="text-xs" />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem className="col-span-2 md:col-span-1">
<FormLabel className="text-slate-100 text-xs">
Email
</FormLabel>
<FormControl>
<Input
placeholder="alvin@studioaurora.io"
{...field}
className="bg-[rgba(255,255,255,0.01)] border-[rgba(255,255,255,0.1)] text-white text-xs p-2 rounded-sm focus:ring-orange-500"
/>
</FormControl>
<FormMessage className="text-xs" />
</FormItem>
)}
/>
<FormField
control={form.control}
name="message"
render={({ field }) => (
<FormItem className="col-span-2">
<FormLabel className="text-slate-100 text-xs">
Message
</FormLabel>
<FormControl>
<Textarea
placeholder="I would like to request a quote for a website design..."
{...field}
className="bg-[rgba(255,255,255,0.01)] border-[rgba(255,255,255,0.1)] text-white text-xs p-2 rounded-sm focus:ring-orange-500 w-full"
rows={4}
/>
</FormControl>
<FormMessage className="text-xs" />
</FormItem>
)}
/>
<div className="col-span-2 flex justify-start">
<Button
type="submit"
className="bg-gradient-to-r from-orange-600 to-pink-600 hover:from-orange-500 hover:to-pink-500 dark:text-white text-xs px-4 py-2 rounded-sm w-full"
disabled={isLoading}
>
{isLoading ? "SENDING..." : "SEND MESSAGE"}
<Send className="ml-2 h-4 w-4" />
</Button>
</div>
</form>
</Form>
SEND MESSAGE
<Send className="ml-2 h-4 w-4" />
</Button>
</div>
</section>
</MotionDiv>
);
37 changes: 20 additions & 17 deletions src/app/components/HeroTitle.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"use client";

import { motion } from "framer-motion";
import { slideInFromLeft, slideInFromTop } from "../utils/motion";
import Image from "next/image";
import MotionTag from "./MotionTag";

export default function HeroTitle() {
let isMobile = false;
@@ -12,9 +12,10 @@ export default function HeroTitle() {

return (
<div>
<motion.div
variants={isMobile ? slideInFromTop(0.5) : slideInFromLeft(0.5)}
initial="hidden"
<MotionTag
tag="div"
variants={slideInFromLeft(0.5)}
initial={isMobile ? 'visible' : "hidden"}
animate="visible"
className="text-lg sm:text-xl md:text-2xl font-medium flex items-center w-full justify-center lg:justify-start"
>
@@ -25,25 +26,27 @@ export default function HeroTitle() {
width={28}
height={28}
/>
<h1>Hey there, I'm Alvin! 👋</h1>
</motion.div>
<motion.h1
variants={isMobile ? slideInFromTop(0.6) : slideInFromLeft(0.6)}
initial="hidden"
<h1>Hi, I'm Evan! 👋</h1>
</MotionTag>
<MotionTag
tag="h1"
variants={slideInFromLeft(0.6)}
initial={isMobile ? 'visible' : "hidden"}
animate="visible"
className="text-4xl sm:text-5xl lg:text-5xl 2xl:text-6xl font-bold text-center lg:text-start"
className="text-4xl sm:text-5xl lg:text-[2.75rem] xl:text-5xl 1640:text-[3.25rem] font-bold text-center lg:text-start"
>
Delivering
<span className="gradient-accent"> the best </span>
</motion.h1>
<motion.h1
I'm a
<span className="gradient-accent"> Front-End </span>
</MotionTag>
<MotionTag
tag="h1"
variants={isMobile ? slideInFromTop(0.7) : slideInFromLeft(0.7)}
initial="hidden"
animate="visible"
className="text-4xl sm:text-5xl lg:text-5xl 2xl:text-6xl font-bold text-center lg:text-start mb-2"
className="text-4xl sm:text-5xl lg:text-[2.75rem] xl:text-5xl 1640:text-[3.25rem] font-bold text-center lg:text-start mb-2"
>
in design and code.
</motion.h1>
Developer
</MotionTag>
</div>
);
}
2 changes: 1 addition & 1 deletion src/app/components/HireMeButton.tsx
Original file line number Diff line number Diff line change
@@ -61,7 +61,7 @@ export default function HireMeButton({ isSticky }: TProps) {
className="text-white text-[10px] lg:text-[11px] border border-[rgba(255,255,255,0.3)] font-bold tracking-widest bg-background
px-4 py-2 rounded-md w-full h-full relative z-10"
>
HIRE ME
CONTACT
</button>
</div>
</Link>
24 changes: 0 additions & 24 deletions src/app/components/HomepageBlogs.tsx

This file was deleted.

26 changes: 26 additions & 0 deletions src/app/components/MainTechnology.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { cloneElement } from 'react';

interface MainTechnologyProps {
technology: {
name: string;
icon: JSX.Element;
description: string;
};
}

export default function MainTechnology({ technology }: MainTechnologyProps) {
// Clone the icon element and add additional classes
const IconWithClasses = cloneElement(technology.icon, {
className: `text-3xl transition-all duration-300 group-hover:scale-125 ${technology.icon.props.className || ''}`
});

return (
<div className="group flex flex-col items-center gap-2 text-center" title={technology.name}>
{IconWithClasses}
<div>
<h3 className="text-xs font-bold">{technology.name}</h3>
<p className="text-[11px] text-slate-350 leading-normal">{technology.description}</p>
</div>
</div>
);
}
73 changes: 73 additions & 0 deletions src/app/components/MotionTag.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"use client";

import { motion, useInView, HTMLMotionProps } from "framer-motion";
import { useRef } from "react";
import { useIsMobile } from "../hooks/useIsMobile";

type MotionTags = keyof typeof motion;

interface TProps extends HTMLMotionProps<any> {
tag: MotionTags;
children: React.ReactNode;
variants: any;
initial: any;
animate: any;
className?: string;
once?: boolean;
transition?: any;
}

export default function MotionTag({
tag,
children,
variants,
initial,
animate,
className,
once = true,
transition = { duration: 0.3 },
...props
}: TProps) {
const isMobile = useIsMobile();
const ref = useRef(null);
const isInView = useInView(ref, {
once,
margin: "-150px",
});

return (
<>
{tag === "div" ? (
<motion.div
ref={ref}
variants={variants}
initial={!isMobile ? initial : "visible"}
animate={isInView ? animate : initial}
transition={transition}
className={className}
>
{children}
</motion.div>
) : (
<motion.h1
ref={ref}
variants={variants}
initial={!isMobile ? initial : "visible"}
animate={isInView ? animate : initial}
transition={transition}
className={className}
>
{children}
</motion.h1>
)}
</>
);
}

// ref={ref}
// variants={variants}
// initial={initial}
// animate={isInView ? animate : initial}
// transition={transition}
// className={className}
// viewport={{ once }}
7 changes: 4 additions & 3 deletions src/app/components/SocialIcons.tsx
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
import Link from "next/link";
import { siteConfig } from "../config";
import { slideInFromLeft, slideInFromTop } from "../utils/motion";
import MotionDiv from "./MotionDiv";
import MotionTag from "./MotionTag";

type TProps = {
isSticky?: boolean;
@@ -16,7 +16,8 @@ export default function SocialIcons({ isSticky, noAnimate }: TProps) {
isMobile = window.matchMedia("(max-width: 1024px)").matches;
}
return (
<MotionDiv
<MotionTag
tag="div"
variants={isMobile ? slideInFromTop(1) : slideInFromLeft(1)}
initial={noAnimate ? "visible" : "hidden"}
animate="visible"
@@ -35,6 +36,6 @@ export default function SocialIcons({ isSticky, noAnimate }: TProps) {
</li>
))}
</ul>
</MotionDiv>
</MotionTag>
);
}
4 changes: 2 additions & 2 deletions src/app/components/StarBackground.tsx
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ import * as random from "maath/random/dist/maath-random.esm";
const StarBackground = (props: any) => {
const ref: any = useRef();
const [sphere] = useState(() =>
random.inSphere(new Float32Array(5000), { radius: 1.2 })
random.inSphere(new Float32Array(3000), { radius: 1.2 })
);

useFrame((state, delta) => {
@@ -49,4 +49,4 @@ const StarsCanvas = () => (
</div>
)

export default StarsCanvas;
export default StarsCanvas;
7 changes: 4 additions & 3 deletions src/app/components/TableOfContents.tsx
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ import Link from "next/link";
import { siteConfig } from "../config";
import { useState, useEffect, useRef } from "react";
import { slideInFromLeft } from "../utils/motion";
import MotionDiv from "./MotionDiv";
import MotionTag from "./MotionTag";

export default function TableOfContents() {
const [activeSection, setActiveSection] = useState<string | null>(null);
@@ -62,7 +62,8 @@ export default function TableOfContents() {
};

return (
<MotionDiv
<MotionTag
tag="div"
variants={slideInFromLeft(0.9)}
initial="hidden"
animate="visible"
@@ -96,6 +97,6 @@ export default function TableOfContents() {
</Link>
</div>
))}
</MotionDiv>
</MotionTag>
);
}
93 changes: 93 additions & 0 deletions src/app/components/Technologies.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"use client";

import { siteConfig } from "../config";
import { slideInFromRight } from "../utils/motion";
import MainTechnology from "./MainTechnology";
import MotionTag from "./MotionTag";
import SectionLabel from "./SectionLabel";
import { useState } from "react";
import TechnologyItem from "./TechnologyItem";
import { Separator } from "@/components/ui/separator";

export default function Technologies() {
enum Categories {
MAIN = "Main",
ALL = "All Technologies",
}
const [selectedCategory, setSelectedCategory] = useState(Categories.MAIN);
const technologies = siteConfig.sections.technologies;
const allTechnologies = [...technologies.main, ...technologies.other];

return (
<MotionTag
tag="div"
variants={slideInFromRight(1.4)}
initial="hidden"
animate="visible"
>
<section
id="technologies"
className="w-full flex flex-wrap justify-center lg:justify-start mb-8 lg:mb-12 lg:pl-6"
>
<SectionLabel label="TECHNOLOGIES" />
{selectedCategory === Categories.MAIN ? (
<div className="w-full grid grid-cols-3 gap-4">
{technologies.main.map((technology, index) => {
return (
<MainTechnology
key={index}
technology={technology}
/>
);
})}
</div>
) : (
<>
<div className="w-full grid grid-cols-6 sm:grid-cols-7 md:grid-cols-9 lg:grid-cols-7 gap-2">
{allTechnologies.map((technology, index) => {
return (
<TechnologyItem
key={index}
technology={technology}
className={index === 27 ? 'md:hidden lg:block' : ''}
/>
);
})}
</div>
<div className="w-full flex justify-center mt-4 text-center">
<p className="text-[9px] italic text-slate-350 leading-normal">
Disclaimer: This list contains all the technologies that I have worked with in the past. <br></br>
Not necessarily the technologies that I am most comfortable with.
</p>
</div>
</>
)}
<div
className={`w-full flex justify-center items-center gap-4 ${selectedCategory === Categories.MAIN ? "mt-4" : ""} text-[10px]`}
>
<button
className={`hover:text-white transition ${
selectedCategory === Categories.MAIN
? "text-white"
: "text-slate-350"
}`}
onClick={() => setSelectedCategory(Categories.MAIN)}
>
Main Technologies
</button>
<Separator orientation="vertical" />
<button
className={`hover:text-white transition ${
selectedCategory === Categories.ALL
? "text-white"
: "text-slate-350"
}`}
onClick={() => setSelectedCategory(Categories.ALL)}
>
All Technologies
</button>
</div>
</section>
</MotionTag>
);
}
27 changes: 27 additions & 0 deletions src/app/components/TechnologyItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { cloneElement } from "react";

interface TechnologyItemProps {
technology: {
name: string;
icon: JSX.Element;
description?: string;
};
className?: string;
}

export default function TechnologyItem({ technology, className }: TechnologyItemProps) {
const IconWithClasses = cloneElement(technology.icon, {
className: `w-1/3 h-1/3 ${
technology.icon.props.className || ""
}`,
});

return (
<div
className={`bg-white bg-opacity-[0.04] hover:bg-opacity-[0.08] transition-all duration-300 rounded-sm aspect-square flex justify-center items-center ${className}`}
title={technology.name}
>
{IconWithClasses}
</div>
);
}
2 changes: 1 addition & 1 deletion src/app/components/TitleList.tsx
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@ export default function TitleList({ titles }: { titles: string[] }) {
variants={isMobile ? slideInFromTop(0.8 + index * 0.2) : slideInFromLeft(0.8 + index * 0.2)}
initial="hidden"
animate="visible"
className={`text-xs font-bold ${textColors[index % textColors.length]} ${bgColors[index % bgColors.length]} p-1 px-2 rounded-full flex items-center`}
className={`text-xs lg:text-[0.6rem] xl:text-xs font-bold ${textColors[index % textColors.length]} ${bgColors[index % bgColors.length]} p-1 px-2 rounded-full flex items-center`}
>
<div className="w-2 h-2 bg-green-700 rounded-full mr-1"></div>
{title}
248 changes: 116 additions & 132 deletions src/app/config.tsx

Large diffs are not rendered by default.

46 changes: 0 additions & 46 deletions src/app/feed.xml/route.ts

This file was deleted.

24 changes: 24 additions & 0 deletions src/app/hooks/useIsMobile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"use client";

import { useState, useEffect } from 'react';

export function useIsMobile(breakpoint: number = 768) {
const [isMobile, setIsMobile] = useState(false);

useEffect(() => {
const checkMobile = () => {
setIsMobile(window.innerWidth < breakpoint);
};

// Check on mount
checkMobile();

// Add event listener for window resize
window.addEventListener('resize', checkMobile);

// Cleanup
return () => window.removeEventListener('resize', checkMobile);
}, [breakpoint]);

return isMobile;
}
11 changes: 7 additions & 4 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -10,18 +10,19 @@ import TableOfContents from "./components/TableOfContents";
import HireMeButton from "./components/HireMeButton";
import Footer from "./components/Footer";
import ContactForm from "./components/ContactForm";
import { fetchBlogs } from "./utils/fetchWordpress";
import HomepageBlogs from "./components/HomepageBlogs";
import Technologies from "./components/Technologies";
import { Analytics } from "@vercel/analytics/react";
import { SpeedInsights } from "@vercel/speed-insights/next";

export default async function Home() {

const jsonLd = {
"@context": "https://schema.org",
"@type": "Person",
name: siteConfig.name,
url: siteConfig.url,
keywords: siteConfig.keywords,
};
const blogs = await fetchBlogs();

return (
<main className="w-full min-h-screen px-4 sm:px-20 xl:px-40 2xl:px-80">
@@ -50,11 +51,13 @@ export default async function Home() {
<AboutMe />
<Experiences />
<Projects />
<HomepageBlogs blogs={blogs} />
<Technologies />
<ContactForm />
<Footer />
<SocialIcons />
</main>
<SpeedInsights />
<Analytics/>
</section>
</main>
);
10 changes: 1 addition & 9 deletions src/app/sitemap.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { MetadataRoute } from "next";
import { fetchBlogs } from "./utils/fetchWordpress";
import { TPost } from "./utils/types";

export async function generateSitemaps() {
@@ -9,18 +8,11 @@ export async function generateSitemaps() {
}

export default async function sitemap({ id }: { id: number }): Promise<MetadataRoute.Sitemap> {
const posts = await fetchBlogs();

return [
{
url: "https://alvinchang.dev",
lastModified: new Date(),
},

...posts.map((post: TPost) => ({
url: `https://alvinchang.dev/blogs/${post.slug}`,
lastModified: new Date(post.modified_gmt),
priority: 0.8,
}))
];
}
}
26 changes: 0 additions & 26 deletions src/app/utils/fetchWordpress.ts

This file was deleted.

31 changes: 31 additions & 0 deletions src/components/ui/separator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"use client"

import * as React from "react"
import * as SeparatorPrimitive from "@radix-ui/react-separator"

import { cn } from "@/lib/utils"

const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>(
(
{ className, orientation = "horizontal", decorative = true, ...props },
ref
) => (
<SeparatorPrimitive.Root
ref={ref}
decorative={decorative}
orientation={orientation}
className={cn(
"shrink-0 bg-slate-200 dark:bg-slate-800",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
className
)}
{...props}
/>
)
)
Separator.displayName = SeparatorPrimitive.Root.displayName

export { Separator }