mirror of
https://github.com/Cian-H/am-d-model.eu.git
synced 2025-12-22 13:41:56 +00:00
Added rough draft of contact page
This commit is contained in:
@@ -25,7 +25,8 @@
|
||||
"vite": "^5.4.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/forms": "^0.5.10"
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"lucide-svelte": "^0.471.0"
|
||||
},
|
||||
"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