UI updates
This commit is contained in:
@@ -0,0 +1,120 @@
|
|||||||
|
---
|
||||||
|
// Contact Form Component
|
||||||
|
---
|
||||||
|
<div id="form-message" class="hidden mb-4"></div>
|
||||||
|
|
||||||
|
<form id="contact-form" class="space-y-6 contact-form-wrapper">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label for="name" class="block text-sm text-black/70 dark:text-white/60">
|
||||||
|
Name
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
required
|
||||||
|
class="w-full px-4 py-3 rounded-md border border-black/5 dark:border-white/5 bg-transparent text-black dark:text-white placeholder-black/30 dark:placeholder-white/30 focus:outline-none focus:border-black/20 dark:focus:border-white/20 transition-all duration-300"
|
||||||
|
placeholder="Your name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label for="email" class="block text-sm text-black/70 dark:text-white/60">
|
||||||
|
Email
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
required
|
||||||
|
class="w-full px-4 py-3 rounded-md border border-black/5 dark:border-white/5 bg-transparent text-black dark:text-white placeholder-black/30 dark:placeholder-white/30 focus:outline-none focus:border-black/20 dark:focus:border-white/20 transition-all duration-300"
|
||||||
|
placeholder="your.email@example.com"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label for="message" class="block text-sm text-black/70 dark:text-white/60">
|
||||||
|
Message
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
id="message"
|
||||||
|
name="message"
|
||||||
|
required
|
||||||
|
rows="5"
|
||||||
|
class="w-full px-4 py-3 rounded-md border border-black/5 dark:border-white/5 bg-transparent text-black dark:text-white placeholder-black/30 dark:placeholder-white/30 focus:outline-none focus:border-black/20 dark:focus:border-white/20 transition-all duration-300 resize-none"
|
||||||
|
placeholder="Your message..."
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="px-8 py-3 rounded-md bg-black/90 dark:bg-white/90 text-white dark:text-black font-medium hover:bg-black dark:hover:bg-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black/50 dark:focus:ring-white/50 transition-all duration-300 disabled:opacity-40 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
<span id="button-text">Send Message</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const form = document.getElementById('contact-form') as HTMLFormElement;
|
||||||
|
const buttonText = document.getElementById('button-text') as HTMLSpanElement;
|
||||||
|
const formMessage = document.getElementById('form-message') as HTMLDivElement;
|
||||||
|
|
||||||
|
if (form) {
|
||||||
|
form.addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Get form data
|
||||||
|
const formData = new FormData(form);
|
||||||
|
const name = formData.get('name') as string;
|
||||||
|
const email = formData.get('email') as string;
|
||||||
|
const message = formData.get('message') as string;
|
||||||
|
|
||||||
|
// Disable button and show loading state
|
||||||
|
const submitButton = form.querySelector('button[type="submit"]') as HTMLButtonElement;
|
||||||
|
submitButton.disabled = true;
|
||||||
|
buttonText.textContent = 'Sending...';
|
||||||
|
formMessage.className = 'hidden';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Send to API
|
||||||
|
const response = await fetch('https://notify.api.standard.nz/', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name,
|
||||||
|
email,
|
||||||
|
message,
|
||||||
|
destination: 'michael'
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
// Success
|
||||||
|
formMessage.textContent = 'Message sent successfully! I\'ll get back to you soon.';
|
||||||
|
formMessage.className = 'text-sm px-4 py-2.5 rounded-md bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-300 border border-green-200 dark:border-green-800/30';
|
||||||
|
form.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
// Error response
|
||||||
|
throw new Error('Failed to send message');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Network or other error
|
||||||
|
formMessage.textContent = 'Failed to send message. Please try again or email me directly.';
|
||||||
|
formMessage.className = 'text-sm px-4 py-2.5 rounded-md bg-red-50 dark:bg-red-900/20 text-red-700 dark:text-red-300 border border-red-200 dark:border-red-800/30';
|
||||||
|
} finally {
|
||||||
|
// Re-enable button
|
||||||
|
submitButton.disabled = false;
|
||||||
|
buttonText.textContent = 'Send Message';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
@@ -18,12 +18,6 @@ const isHomepage = Astro.url.pathname === '/';
|
|||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
<nav class="flex gap-1">
|
<nav class="flex gap-1">
|
||||||
<Link href="/blog">
|
|
||||||
blog
|
|
||||||
</Link>
|
|
||||||
<span>
|
|
||||||
{`/`}
|
|
||||||
</span>
|
|
||||||
<Link href="/work">
|
<Link href="/work">
|
||||||
work
|
work
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@ import type { Site, Metadata, Socials } from "@types";
|
|||||||
|
|
||||||
export const SITE: Site = {
|
export const SITE: Site = {
|
||||||
NAME: "Michael Rausch",
|
NAME: "Michael Rausch",
|
||||||
EMAIL: "michael@rausch.nz",
|
EMAIL: "m@michaelraus.ch",
|
||||||
NUM_POSTS_ON_HOMEPAGE: 3,
|
NUM_POSTS_ON_HOMEPAGE: 3,
|
||||||
NUM_WORKS_ON_HOMEPAGE: 2,
|
NUM_WORKS_ON_HOMEPAGE: 2,
|
||||||
NUM_PROJECTS_ON_HOMEPAGE: 3,
|
NUM_PROJECTS_ON_HOMEPAGE: 3,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import PageLayout from "@layouts/PageLayout.astro";
|
|||||||
import ArrowCard from "@components/ArrowCard.astro";
|
import ArrowCard from "@components/ArrowCard.astro";
|
||||||
import WorkCard from "@components/WorkCard.astro";
|
import WorkCard from "@components/WorkCard.astro";
|
||||||
import Link from "@components/Link.astro";
|
import Link from "@components/Link.astro";
|
||||||
|
import ContactForm from "@components/ContactForm.astro";
|
||||||
import { dateRange } from "@lib/utils";
|
import { dateRange } from "@lib/utils";
|
||||||
import { SITE, HOME, SOCIALS } from "@consts";
|
import { SITE, HOME, SOCIALS } from "@consts";
|
||||||
import EmojiScroller from "@components/EmojiScroller.astro";
|
import EmojiScroller from "@components/EmojiScroller.astro";
|
||||||
@@ -35,13 +36,13 @@ const work = await Promise.all(
|
|||||||
|
|
||||||
<PageLayout title={HOME.TITLE} description={HOME.DESCRIPTION}>
|
<PageLayout title={HOME.TITLE} description={HOME.DESCRIPTION}>
|
||||||
<Container>
|
<Container>
|
||||||
<h1 class="animate font-bold text-black dark:text-white text-4xl md:text-5xl font-serif leading-tight">
|
<h1 class="animate font-bold text-black dark:text-white text-4xl md:text-5xl font-serif leading-tight mb-5">
|
||||||
Hi, I'm Michael <EmojiScroller/>
|
Hi, I'm Michael <EmojiScroller/>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="space-y-16">
|
<div class="space-y-16">
|
||||||
<section>
|
<section>
|
||||||
<article class="space-y-4">
|
<article class="space-y-4">
|
||||||
<p class="animate">
|
<p class="animate text-lg">
|
||||||
I'm a software engineer at the University of Canterbury, working on <Link href="https://uconline.ac.nz" external class="text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300">Tuihono UC | UC Online</Link>.
|
I'm a software engineer at the University of Canterbury, working on <Link href="https://uconline.ac.nz" external class="text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300">Tuihono UC | UC Online</Link>.
|
||||||
|
|
||||||
I specialize in full-stack development. While my primary expertise is in software engineering, I also have a strong interest in cloud infrastructure and DevOps. Based in Christchurch, New Zealand
|
I specialize in full-stack development. While my primary expertise is in software engineering, I also have a strong interest in cloud infrastructure and DevOps. Based in Christchurch, New Zealand
|
||||||
@@ -118,7 +119,8 @@ const work = await Promise.all(
|
|||||||
reach out on social media or send me an email.
|
reach out on social media or send me an email.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
<ul class="flex flex-wrap gap-2">
|
<ContactForm />
|
||||||
|
<ul class="flex flex-wrap gap-2 pt-5">
|
||||||
{SOCIALS.map(SOCIAL => (
|
{SOCIALS.map(SOCIAL => (
|
||||||
<li class="flex gap-x-2 text-nowrap">
|
<li class="flex gap-x-2 text-nowrap">
|
||||||
<Link href={SOCIAL.HREF} external aria-label={`${SITE.NAME} on ${SOCIAL.NAME}`}>
|
<Link href={SOCIAL.HREF} external aria-label={`${SITE.NAME} on ${SOCIAL.NAME}`}>
|
||||||
|
|||||||
Reference in New Issue
Block a user