mirror of
https://github.com/Cian-H/am-d-model.eu.git
synced 2025-12-22 21:41:57 +00:00
Added rough draft of contact page
This commit is contained in:
@@ -25,7 +25,8 @@
|
|||||||
"vite": "^5.4.11"
|
"vite": "^5.4.11"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/forms": "^0.5.10"
|
"@tailwindcss/forms": "^0.5.10",
|
||||||
|
"lucide-svelte": "^0.471.0"
|
||||||
},
|
},
|
||||||
"private": true
|
"private": true
|
||||||
}
|
}
|
||||||
|
|||||||
180
src/lib/components/ContactForm.svelte
Normal file
180
src/lib/components/ContactForm.svelte
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
<script>
|
||||||
|
import { Send, Loader2 } from "lucide-svelte";
|
||||||
|
|
||||||
|
let formData = {
|
||||||
|
name: "",
|
||||||
|
email: "",
|
||||||
|
subject: "",
|
||||||
|
message: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
let errors = {};
|
||||||
|
let isSubmitting = false;
|
||||||
|
let submitted = false;
|
||||||
|
|
||||||
|
const validateForm = () => {
|
||||||
|
const newErrors = {};
|
||||||
|
if (!formData.name.trim()) newErrors.name = "Name is required";
|
||||||
|
if (!formData.email.trim()) {
|
||||||
|
newErrors.email = "Email is required";
|
||||||
|
} else if (!/\S+@\S+\.\S+/.test(formData.email)) {
|
||||||
|
newErrors.email = "Please enter a valid email";
|
||||||
|
}
|
||||||
|
if (!formData.subject.trim()) newErrors.subject = "Subject is required";
|
||||||
|
if (!formData.message.trim()) newErrors.message = "Message is required";
|
||||||
|
|
||||||
|
errors = newErrors;
|
||||||
|
return Object.keys(newErrors).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!validateForm()) return;
|
||||||
|
|
||||||
|
isSubmitting = true;
|
||||||
|
// Simulate API call - replace with your actual API endpoint
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||||
|
isSubmitting = false;
|
||||||
|
submitted = true;
|
||||||
|
|
||||||
|
// Reset form
|
||||||
|
formData = {
|
||||||
|
name: "",
|
||||||
|
email: "",
|
||||||
|
subject: "",
|
||||||
|
message: "",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInput = (e) => {
|
||||||
|
const target = e.target;
|
||||||
|
formData[target.name] = target.value;
|
||||||
|
// Clear error when user starts typing
|
||||||
|
if (errors[target.name]) {
|
||||||
|
errors[target.name] = "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if submitted}
|
||||||
|
<div class="form-container {$$props.class}">
|
||||||
|
<div class="text-center success-message">
|
||||||
|
<h2 class="text-2xl font-semibold text-green-600 mb-4">
|
||||||
|
Thank You!
|
||||||
|
</h2>
|
||||||
|
<p class="text-gray-600 mb-4">
|
||||||
|
Your message has been sent successfully.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
on:click={() => (submitted = false)}
|
||||||
|
class="px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700 transition-colors"
|
||||||
|
>
|
||||||
|
Send Another Message
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="form-container {$$props.class}">
|
||||||
|
<h2 class="text-2xl font-semibold text-gray-800 mb-6">Contact Form</h2>
|
||||||
|
|
||||||
|
<form on:submit={handleSubmit} class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
for="name"
|
||||||
|
class="block text-sm font-medium text-gray-700 mb-1"
|
||||||
|
>
|
||||||
|
Name
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
bind:value={formData.name}
|
||||||
|
on:input={handleInput}
|
||||||
|
class="w-full px-3 py-2 border rounded-md {errors.name
|
||||||
|
? 'error'
|
||||||
|
: 'border-gray-300'}"
|
||||||
|
/>
|
||||||
|
{#if errors.name}
|
||||||
|
<p class="mt-1 text-sm text-red-500">{errors.name}</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
for="email"
|
||||||
|
class="block text-sm font-medium text-gray-700 mb-1"
|
||||||
|
>
|
||||||
|
Email
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
bind:value={formData.email}
|
||||||
|
on:input={handleInput}
|
||||||
|
class="w-full px-3 py-2 border rounded-md {errors.email
|
||||||
|
? 'error'
|
||||||
|
: 'border-gray-300'}"
|
||||||
|
/>
|
||||||
|
{#if errors.email}
|
||||||
|
<p class="mt-1 text-sm text-red-500">{errors.email}</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
for="subject"
|
||||||
|
class="block text-sm font-medium text-gray-700 mb-1"
|
||||||
|
>
|
||||||
|
Subject
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="subject"
|
||||||
|
name="subject"
|
||||||
|
bind:value={formData.subject}
|
||||||
|
on:input={handleInput}
|
||||||
|
class="w-full px-3 py-2 border rounded-md {errors.subject
|
||||||
|
? 'error'
|
||||||
|
: 'border-gray-300'}"
|
||||||
|
/>
|
||||||
|
{#if errors.subject}
|
||||||
|
<p class="mt-1 text-sm text-red-500">{errors.subject}</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
for="message"
|
||||||
|
class="block text-sm font-medium text-gray-700 mb-1"
|
||||||
|
>
|
||||||
|
Message
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
id="message"
|
||||||
|
name="message"
|
||||||
|
bind:value={formData.message}
|
||||||
|
on:input={handleInput}
|
||||||
|
rows={4}
|
||||||
|
class="w-full px-3 py-2 border rounded-md {errors.message
|
||||||
|
? 'error'
|
||||||
|
: 'border-gray-300'}"
|
||||||
|
/>
|
||||||
|
{#if errors.message}
|
||||||
|
<p class="mt-1 text-sm text-red-500">{errors.message}</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" disabled={isSubmitting} class="submit-button">
|
||||||
|
{#if isSubmitting}
|
||||||
|
<Loader2 class="animate-spin" size={20} />
|
||||||
|
<span>Sending...</span>
|
||||||
|
{:else}
|
||||||
|
<Send size={20} />
|
||||||
|
<span>Send Message</span>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
64
src/lib/styles/ContactForm.scss
Normal file
64
src/lib/styles/ContactForm.scss
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
.form-container {
|
||||||
|
|
||||||
|
input,
|
||||||
|
textarea {
|
||||||
|
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.error {
|
||||||
|
border-color: #ef4444;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
background-color: #2563eb;
|
||||||
|
color: white;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
background-color: #1d4ed8;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
background-color: #93c5fd;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add subtle animation for success message
|
||||||
|
.success-message {
|
||||||
|
animation: fadeIn 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +1,32 @@
|
|||||||
Contact page placeholder
|
<script>
|
||||||
|
import "./ContactPage.scss";
|
||||||
|
import NavigationHeader from "$lib/components/NavigationHeader.svelte";
|
||||||
|
import NavigationFooter from "$lib/components/NavigationFooter.svelte";
|
||||||
|
import ContactForm from "$lib/components/ContactForm.svelte";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="contact-page">
|
||||||
|
<NavigationHeader />
|
||||||
|
<div class="content">
|
||||||
|
<div class="contact-form-column">
|
||||||
|
<h1>Contact Us</h1>
|
||||||
|
<h2>We look forward to hearing from you!</h2>
|
||||||
|
<p>
|
||||||
|
Interested in contributing, collaborating, or otherwise getting
|
||||||
|
in touch with the AM-D-Model team? We are always looking for
|
||||||
|
input on our project from others in the wider L-PBF research
|
||||||
|
community. Please use the form below to email our corresponding
|
||||||
|
researcher and they will reply as soon as they are able.
|
||||||
|
</p>
|
||||||
|
<ContactForm class="contact-form" />
|
||||||
|
</div>
|
||||||
|
<div class="contact-image-column">
|
||||||
|
<img
|
||||||
|
class="contact-image"
|
||||||
|
src="contact.webp"
|
||||||
|
alt="A photograph of our lovely colleague who is eager to hear from you"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<NavigationFooter />
|
||||||
|
</div>
|
||||||
|
|||||||
48
src/routes/contact/ContactPage.scss
Normal file
48
src/routes/contact/ContactPage.scss
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
.contact-page,
|
||||||
|
.contact-page * {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-page {
|
||||||
|
background: var(--cpalettecomplimentary);
|
||||||
|
width: 100vw;
|
||||||
|
min-width: 250px;
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
width: 100%;
|
||||||
|
padding: 5% 5% 5% 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-form-column {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-form {
|
||||||
|
position: relative;
|
||||||
|
padding-top: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-image-column {
|
||||||
|
position: relative;
|
||||||
|
width: 50%;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
padding: 32px 0 32px 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-image {
|
||||||
|
position: sticky;
|
||||||
|
border-radius: var(--contact-corner-round);
|
||||||
|
width: 100%;
|
||||||
|
top: 64px;
|
||||||
|
max-width: var(--contact-img-size);
|
||||||
|
max-height: var(--contact-img-size);
|
||||||
|
}
|
||||||
BIN
static/contact.webp
Normal file
BIN
static/contact.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Reference in New Issue
Block a user