557 lines
25 KiB
HTML
557 lines
25 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Custom Audio Player with SVGs</title>
|
|
<script
|
|
src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"
|
|
defer
|
|
></script>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<style>
|
|
.error-input {
|
|
border-color: #ef4444 !important; /* Tailwind's red-500 */
|
|
box-shadow: 0 0 0 1px #ef4444; /* optional subtle red glow */
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="bg-white dark:bg-gray-900 min-h-screen p-4">
|
|
<section
|
|
class="relative overflow-hidden min-h-screen bg-white dark:bg-gray-900 flex items-center justify-center py-12"
|
|
>
|
|
<!-- Background elements -->
|
|
<div class="absolute inset-0 overflow-hidden">
|
|
<div
|
|
class="absolute -top-1/2 -right-1/2 bg-blue-400/20 dark:bg-blue-600/20 blur-3xl w-96 h-96 rounded-full"
|
|
></div>
|
|
<div
|
|
class="absolute -bottom-1/2 -left-1/2 bg-purple-400/20 dark:bg-purple-600/20 blur-3xl w-96 h-96 rounded-full"
|
|
></div>
|
|
</div>
|
|
|
|
<div class="relative z-10 mx-auto px-4 w-full max-w-2xl">
|
|
<!-- Header -->
|
|
<div class="text-center mb-8">
|
|
<h1
|
|
class="text-3xl font-bold text-gray-900 leading-tight sm:text-4xl dark:text-white"
|
|
>
|
|
Complete Your Profile
|
|
</h1>
|
|
<p class="mt-2 text-gray-600 dark:text-gray-400">
|
|
Tell us more about yourself to personalize your experience
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Profile Setup Form Container -->
|
|
<div
|
|
class="bg-white/80 dark:bg-gray-800/80 backdrop-blur-sm rounded-2xl shadow-xl p-8 border border-gray-200/50 dark:border-gray-700/50"
|
|
x-data="{
|
|
submitting: false,
|
|
showSuccess: false,
|
|
formData: {
|
|
avatar: '',
|
|
firstName: '',
|
|
lastName: '',
|
|
bio: '',
|
|
location: '',
|
|
website: '',
|
|
jobTitle: '',
|
|
company: '',
|
|
facebook: '',
|
|
linkedin: ''
|
|
},
|
|
errors: {},
|
|
|
|
validateField(fieldName, value) {
|
|
this.errors[fieldName] = '';
|
|
|
|
if (fieldName === 'firstName') {
|
|
if (!value.trim()) return 'First name is required';
|
|
}
|
|
|
|
if (fieldName === 'lastName') {
|
|
if (!value.trim()) return 'Last name is required';
|
|
}
|
|
|
|
if (fieldName === 'bio') {
|
|
if (value.length > 200) return 'Bio must be less than 200 characters';
|
|
}
|
|
|
|
if (fieldName === 'website') {
|
|
if (value && !/^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([/\w .-]*)*\/?$/.test(value)) {
|
|
return 'Please enter a valid website URL';
|
|
}
|
|
}
|
|
|
|
if (fieldName === 'facebook') {
|
|
if (value && !/^(https?:\/\/)?(www\.)?facebook\.com\/[a-zA-Z0-9(\.\?)?]/.test(value)) {
|
|
return 'Please enter a valid Facebook URL';
|
|
}
|
|
}
|
|
|
|
return '';
|
|
},
|
|
|
|
validateForm() {
|
|
this.errors = {};
|
|
let isValid = true;
|
|
|
|
// Validate required fields
|
|
Object.keys(this.formData).forEach(field => {
|
|
if (field === 'firstName' || field === 'lastName') {
|
|
const error = this.validateField(field, this.formData[field]);
|
|
if (error) {
|
|
this.errors[field] = error;
|
|
isValid = false;
|
|
}
|
|
}
|
|
});
|
|
|
|
// Validate optional fields if they have values
|
|
['bio', 'website', 'facebook'].forEach(field => {
|
|
if (this.formData[field]) {
|
|
const error = this.validateField(field, this.formData[field]);
|
|
if (error) {
|
|
this.errors[field] = error;
|
|
isValid = false;
|
|
}
|
|
}
|
|
});
|
|
|
|
return isValid;
|
|
},
|
|
|
|
async submitForm() {
|
|
if (!this.validateForm()) {
|
|
// Scroll to first error
|
|
const firstError = document.querySelector('.error-input');
|
|
if (firstError) {
|
|
firstError.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
firstError.focus();
|
|
}
|
|
return;
|
|
}
|
|
|
|
this.submitting = true;
|
|
|
|
// Simulate API call
|
|
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
|
|
// Show success message
|
|
this.showSuccess = true;
|
|
this.submitting = false;
|
|
},
|
|
|
|
handleAvatarUpload(event) {
|
|
const file = event.target.files[0];
|
|
if (file) {
|
|
if (file.size > 2 * 1024 * 1024) { // 2MB limit
|
|
alert('File size must be less than 2MB');
|
|
return;
|
|
}
|
|
|
|
const reader = new FileReader();
|
|
reader.onload = (e) => {
|
|
this.formData.avatar = e.target.result;
|
|
};
|
|
reader.readAsDataURL(file);
|
|
}
|
|
}
|
|
}"
|
|
>
|
|
<!-- Profile Setup Form -->
|
|
<form @submit.prevent="submitForm" x-show="!showSuccess" x-transition>
|
|
<div class="space-y-8">
|
|
<!-- Avatar Section -->
|
|
<div class="text-center">
|
|
<div class="avatar-upload inline-block mb-4 relative">
|
|
<div
|
|
class="relative w-32 h-32 mx-auto rounded-full border-2 border-dashed border-gray-300 dark:border-gray-600 flex items-center justify-center overflow-hidden"
|
|
>
|
|
<!-- Profile Image -->
|
|
<template x-if="formData.avatar">
|
|
<img
|
|
:src="formData.avatar"
|
|
alt="Profile preview"
|
|
class="w-full h-full object-cover rounded-full"
|
|
/>
|
|
</template>
|
|
|
|
<!-- Placeholder Icon -->
|
|
<template x-if="!formData.avatar">
|
|
<div
|
|
class="flex items-center justify-center w-full h-full text-gray-400"
|
|
>
|
|
<svg
|
|
class="w-12 h-12"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
|
|
></path>
|
|
</svg>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Hover Overlay -->
|
|
<div
|
|
class="absolute inset-0 bg-black bg-opacity-30 rounded-full flex items-center justify-center opacity-0 hover:opacity-100 transition-opacity duration-200 cursor-pointer"
|
|
>
|
|
<svg
|
|
class="w-8 h-8 text-white"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"
|
|
></path>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"
|
|
></path>
|
|
</svg>
|
|
</div>
|
|
|
|
<!-- Hidden File Input -->
|
|
<input
|
|
type="file"
|
|
accept="image/*"
|
|
@change="handleAvatarUpload"
|
|
class="absolute inset-0 w-full h-full opacity-0 cursor-pointer rounded-full"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<p class="text-sm text-gray-600 dark:text-gray-400">
|
|
Click on the avatar to upload a profile picture
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Personal Information -->
|
|
<div>
|
|
<h3
|
|
class="text-lg font-semibold text-gray-900 dark:text-white mb-4"
|
|
>
|
|
Personal Information
|
|
</h3>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label
|
|
for="firstName"
|
|
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
|
|
>First Name</label
|
|
>
|
|
<input
|
|
type="text"
|
|
id="firstName"
|
|
x-model="formData.firstName"
|
|
@blur="errors.firstName = validateField('firstName', formData.firstName)"
|
|
:class="errors.firstName ? 'error-input' : ''"
|
|
class="w-full px-4 py-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none transition-all duration-200"
|
|
placeholder="John"
|
|
/>
|
|
<div
|
|
x-show="errors.firstName"
|
|
x-text="errors.firstName"
|
|
class="mt-1 text-sm text-red-600 dark:text-red-400"
|
|
></div>
|
|
</div>
|
|
<div>
|
|
<label
|
|
for="lastName"
|
|
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
|
|
>Last Name</label
|
|
>
|
|
<input
|
|
type="text"
|
|
id="lastName"
|
|
x-model="formData.lastName"
|
|
@blur="errors.lastName = validateField('lastName', formData.lastName)"
|
|
:class="errors.lastName ? 'error-input' : ''"
|
|
class="w-full px-4 py-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none transition-all duration-200"
|
|
placeholder="Doe"
|
|
/>
|
|
<div
|
|
x-show="errors.lastName"
|
|
x-text="errors.lastName"
|
|
class="mt-1 text-sm text-red-600 dark:text-red-400"
|
|
></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Bio -->
|
|
<div>
|
|
<label
|
|
for="bio"
|
|
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
|
|
>Bio</label
|
|
>
|
|
<textarea
|
|
id="bio"
|
|
x-model="formData.bio"
|
|
rows="3"
|
|
@blur="errors.bio = validateField('bio', formData.bio)"
|
|
:class="errors.bio ? 'error-input' : ''"
|
|
class="w-full px-4 py-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none transition-all duration-200"
|
|
placeholder="Tell us a bit about yourself..."
|
|
></textarea>
|
|
<div class="flex justify-between mt-1">
|
|
<div
|
|
x-show="errors.bio"
|
|
x-text="errors.bio"
|
|
class="text-sm text-red-600 dark:text-red-400"
|
|
></div>
|
|
<div
|
|
class="text-sm text-gray-500 dark:text-gray-400"
|
|
x-text="`${200 - formData.bio.length} characters remaining`"
|
|
></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Professional Information -->
|
|
<div>
|
|
<h3
|
|
class="text-lg font-semibold text-gray-900 dark:text-white mb-4"
|
|
>
|
|
Professional Information
|
|
</h3>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label
|
|
for="jobTitle"
|
|
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
|
|
>Job Title</label
|
|
>
|
|
<input
|
|
type="text"
|
|
id="jobTitle"
|
|
x-model="formData.jobTitle"
|
|
class="w-full px-4 py-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none transition-all duration-200"
|
|
placeholder="Software Engineer"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label
|
|
for="company"
|
|
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
|
|
>Company</label
|
|
>
|
|
<input
|
|
type="text"
|
|
id="company"
|
|
x-model="formData.company"
|
|
class="w-full px-4 py-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none transition-all duration-200"
|
|
placeholder="Acme Inc."
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Location & Website -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label
|
|
for="location"
|
|
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
|
|
>Location</label
|
|
>
|
|
<input
|
|
type="text"
|
|
id="location"
|
|
x-model="formData.location"
|
|
class="w-full px-4 py-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none transition-all duration-200"
|
|
placeholder="New York, NY"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label
|
|
for="website"
|
|
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
|
|
>Website</label
|
|
>
|
|
<input
|
|
type="text"
|
|
id="website"
|
|
x-model="formData.website"
|
|
@blur="errors.website = validateField('website', formData.website)"
|
|
:class="errors.website ? 'error-input' : ''"
|
|
class="w-full px-4 py-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none transition-all duration-200"
|
|
placeholder="https://example.com"
|
|
/>
|
|
<div
|
|
x-show="errors.website"
|
|
x-text="errors.website"
|
|
class="mt-1 text-sm text-red-600 dark:text-red-400"
|
|
></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Social Links -->
|
|
<div>
|
|
<h3
|
|
class="text-lg font-semibold text-gray-900 dark:text-white mb-4"
|
|
>
|
|
Social Links
|
|
</h3>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label
|
|
for="facebook"
|
|
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1 flex items-center"
|
|
>
|
|
<svg
|
|
class="w-4 h-4 mr-2 text-blue-600"
|
|
fill="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"
|
|
/>
|
|
</svg>
|
|
Facebook
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="facebook"
|
|
x-model="formData.facebook"
|
|
@blur="errors.facebook = validateField('facebook', formData.facebook)"
|
|
:class="errors.facebook ? 'error-input' : ''"
|
|
class="w-full px-4 py-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none transition-all duration-200"
|
|
placeholder="https://facebook.com/username"
|
|
/>
|
|
<div
|
|
x-show="errors.facebook"
|
|
x-text="errors.facebook"
|
|
class="mt-1 text-sm text-red-600 dark:text-red-400"
|
|
></div>
|
|
</div>
|
|
<div>
|
|
<label
|
|
for="linkedin"
|
|
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1 flex items-center"
|
|
>
|
|
<svg
|
|
class="w-4 h-4 mr-2 text-blue-600"
|
|
fill="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z"
|
|
/>
|
|
</svg>
|
|
LinkedIn
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="linkedin"
|
|
x-model="formData.linkedin"
|
|
class="w-full px-4 py-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none transition-all duration-200"
|
|
placeholder="linkedin.com/in/username"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Action Buttons -->
|
|
<div class="pt-4">
|
|
<button
|
|
type="submit"
|
|
:disabled="submitting"
|
|
class="w-full px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-70 disabled:cursor-not-allowed transition-colors duration-200 font-medium flex items-center justify-center"
|
|
>
|
|
<span x-show="!submitting">Complete Profile</span>
|
|
<span x-show="submitting" class="flex items-center">
|
|
<svg
|
|
class="animate-spin -ml-1 mr-2 h-4 w-4 text-white"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<circle
|
|
class="opacity-25"
|
|
cx="12"
|
|
cy="12"
|
|
r="10"
|
|
stroke="currentColor"
|
|
stroke-width="4"
|
|
></circle>
|
|
<path
|
|
class="opacity-75"
|
|
fill="currentColor"
|
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
></path>
|
|
</svg>
|
|
Saving Profile...
|
|
</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
|
|
<!-- Success Message -->
|
|
<div
|
|
x-show="showSuccess"
|
|
x-transition:enter="transition ease-out duration-300"
|
|
x-transition:enter-start="opacity-0 transform scale-95"
|
|
x-transition:enter-end="opacity-100 transform scale-100"
|
|
class="text-center py-8"
|
|
>
|
|
<div class="mb-4 flex justify-center">
|
|
<div
|
|
class="w-16 h-16 rounded-full bg-green-100 dark:bg-green-900 flex items-center justify-center success-checkmark"
|
|
>
|
|
<svg
|
|
class="w-8 h-8 text-green-600 dark:text-green-400"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M5 13l4 4L19 7"
|
|
></path>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<h3 class="text-2xl font-bold text-gray-900 dark:text-white mb-2">
|
|
Profile Updated Successfully!
|
|
</h3>
|
|
<p class="text-gray-600 dark:text-gray-400 mb-6">
|
|
Your profile has been updated with the new information.
|
|
</p>
|
|
<div class="flex space-x-4">
|
|
<a
|
|
href="#"
|
|
class="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-200 font-medium text-center"
|
|
>
|
|
View Profile
|
|
</a>
|
|
<a
|
|
href="#"
|
|
class="flex-1 px-4 py-2 bg-blue-600 text-white text-center rounded-lg hover:bg-blue-700 transition-colors duration-200 font-medium"
|
|
>Go to Dashboard</a
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<p class="text-center">
|
|
Get More Components | Templates in multiple Frameowkr by visiting
|
|
<a href="https://wavykits.com" class="text-blue-600">wavykits.com</a>
|
|
</p>
|
|
</body>
|
|
</html>
|