sozsoft-platform/ui/src/views/public/Demo.tsx
2026-06-04 17:13:55 +03:00

381 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useEffect, useState } from "react";
import {
FaBuilding,
FaUser,
FaEnvelope,
FaPhone,
FaMapPin,
FaUsers,
FaRegComment,
FaPaperPlane,
FaCheckCircle
} from 'react-icons/fa';
import { useLocalization } from "@/utils/hooks/useLocalization";
import { createDemoAsync } from "@/services/demo.service";
import { DemoDto } from "@/proxy/demo/models";
interface DemoModalProps {
isOpen: boolean;
onClose: () => void;
}
const Demo: React.FC<DemoModalProps> = ({ isOpen, onClose }) => {
const { translate } = useLocalization()
const [formData, setFormData] = useState<DemoDto>({
id: crypto.randomUUID(),
organizationName: "",
name: "",
email: "",
phoneNumber: "",
address: "",
numberOfBranches: 0,
numberOfUsers: 0,
message: "",
});
const [errors, setErrors] = useState<Partial<DemoDto>>({});
const [isSubmitted, setIsSubmitted] = useState(false);
const handleInputChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
const { name, value } = e.target;
setFormData((prev) => ({
...prev,
[name]: value,
}));
if (errors[name as keyof DemoDto]) {
setErrors((prev) => ({
...prev,
[name]: "",
}));
}
};
const validateForm = (): boolean => {
const newErrors: Partial<DemoDto> = {};
if (!formData.organizationName.trim())
newErrors.organizationName = "Organization name is required";
if (!formData.name.trim()) newErrors.name = "Full 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.phoneNumber.trim()) newErrors.phoneNumber = "Phone number is required";
if (!formData.address.trim()) newErrors.address = "Address is required";
if (!formData.message.trim()) newErrors.message = "Message is required";
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!validateForm()) return;
try {
await createDemoAsync(formData);
setIsSubmitted(true);
onClose(); // modal'ı otomatik kapat
} catch (error) {
console.error("Gönderim hatası:", error);
alert("Sunucuya ulaşılamıyor.");
}
};
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") {
onClose();
}
};
if (isOpen) {
window.addEventListener("keydown", handleKeyDown);
}
return () => {
window.removeEventListener("keydown", handleKeyDown);
};
}, [isOpen, onClose]);
// 🎉 Gönderim sonrası teşekkür ekranı
if (isSubmitted) {
return (
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto py-5 z-50 dark:bg-gray-950/70">
<div className="mx-auto bg-white rounded-xl shadow-lg max-w-md w-full relative dark:bg-gray-900">
<div className="max-w-md w-full bg-gradient-to-br from-blue-50 to-indigo-100 rounded-3xl p-8 text-center border border-blue-200 shadow-xl dark:from-gray-900 dark:to-gray-800 dark:border-gray-700">
{/* Kapat Butonu */}
<button
onClick={() => setIsSubmitted(false)}
className="absolute top-4 right-4 text-gray-500 hover:text-gray-800 text-2xl dark:text-gray-400 dark:hover:text-gray-100"
>
&times;
</button>
<div className="w-16 h-16 bg-green-500 rounded-full flex items-center justify-center mx-auto mb-6">
<FaCheckCircle className="w-8 h-8 text-white" />
</div>
<h2 className="text-2xl font-bold text-gray-800 mb-4 dark:text-gray-100">
{translate('::Public.demo.thankYou')}
</h2>
<p className="text-gray-600 mb-6 dark:text-gray-300">
{translate('::Public.demo.resultMessage')}
</p>
<button
onClick={() => {
setIsSubmitted(false);
setFormData({
id: crypto.randomUUID(),
organizationName: "",
name: "",
email: "",
phoneNumber: "",
address: "",
numberOfBranches: 0,
numberOfUsers: 0,
message: "",
});
}}
className="px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white rounded-xl transition-all duration-300"
>
{translate('::Public.demo.newDemo')}
</button>
</div>
</div>
</div>
);
}
// Modal kapalıysa render etme
if (!isOpen) return null;
// 🎯 Normal form ekranı
return (
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto py-5 z-50 dark:bg-gray-950/70">
<div className="mx-auto bg-white rounded-xl shadow-lg max-w-2xl w-full relative dark:bg-gray-900">
<button
onClick={onClose}
title="Kapat"
aria-label="Kapat"
className="absolute top-4 right-4 text-gray-500 hover:text-gray-800 text-2xl dark:text-gray-400 dark:hover:text-gray-100"
>
&times;
</button>
<form
onSubmit={handleSubmit}
className="bg-white rounded-3xl p-6 lg:p-8 shadow-xl border border-gray-100 dark:border-gray-700 dark:bg-gray-900"
>
<div className="mb-6 pr-10">
<h2 className="text-2xl font-bold text-gray-900 dark:text-gray-100">
{translate('::Public.demo.title')}
</h2>
<p className="mt-2 text-sm text-gray-500 dark:text-gray-400">
{translate('::Public.demo.subtitle')}
</p>
</div>
<div className="space-y-3">
{/* Organization Name */}
<div>
<label className="block text-gray-700 text-sm font-medium mb-2 dark:text-gray-200">
{translate('::Public.common.company')} *
</label>
<div className="relative">
<FaBuilding className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
<input
type="text"
autoFocus
name="organizationName"
value={formData.organizationName}
onChange={handleInputChange}
className={`w-full pl-11 pr-4 py-2.5 bg-gray-50 border ${
errors.organizationName
? "border-red-500 focus:border-red-500 focus:ring-red-500/20"
: "border-gray-200 focus:border-blue-500 focus:ring-blue-500/20 dark:border-gray-700"
} rounded-xl text-gray-800 placeholder-gray-500 focus:outline-none transition-all duration-300 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400`}
placeholder={translate('::Public.demo.organizationName')}
/>
</div>
</div>
{/* Full Name */}
<div>
<label className="block text-gray-700 text-sm font-medium mb-2 dark:text-gray-200">
{translate('::Public.common.fullName')} *
</label>
<div className="relative">
<FaUser className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
<input
type="text"
name="fullName"
value={formData.name}
onChange={handleInputChange}
className={`w-full pl-11 pr-4 py-2.5 bg-gray-50 border ${
errors.name
? "border-red-500 focus:border-red-500 focus:ring-red-500/20"
: "border-gray-200 focus:border-blue-500 focus:ring-blue-500/20 dark:border-gray-700"
} rounded-xl text-gray-800 placeholder-gray-500 focus:outline-none transition-all duration-300 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400`}
placeholder={translate('::Public.demo.fullName')}
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-gray-700 text-sm font-medium mb-2 dark:text-gray-200">
{translate('::Abp.Account.EmailAddress')} *
</label>
<div className="relative">
<FaEnvelope className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
<input
type="email"
name="email"
value={formData.email}
onChange={handleInputChange}
className={`w-full pl-11 pr-4 py-2.5 bg-gray-50 border ${
errors.email
? "border-red-500 focus:border-red-500 focus:ring-red-500/20"
: "border-gray-200 focus:border-blue-500 focus:ring-blue-500/20 dark:border-gray-700"
} rounded-xl text-gray-800 placeholder-gray-500 focus:outline-none transition-all duration-300 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400`}
placeholder={translate('::Public.demo.email')}
/>
</div>
</div>
<div>
<label className="block text-gray-700 text-sm font-medium mb-2 dark:text-gray-200">
{translate('::Abp.Identity.User.UserInformation.PhoneNumber')} *
</label>
<div className="relative">
<FaPhone className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
<input
type="tel"
name="phoneNumber"
value={formData.phoneNumber}
onChange={handleInputChange}
className={`w-full pl-11 pr-4 py-2.5 bg-gray-50 border ${
errors.phoneNumber
? "border-red-500 focus:border-red-500 focus:ring-red-500/20"
: "border-gray-200 focus:border-blue-500 focus:ring-blue-500/20 dark:border-gray-700"
} rounded-xl text-gray-800 placeholder-gray-500 focus:outline-none transition-all duration-300 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400`}
placeholder={translate('::Public.demo.phoneNumber')}
/>
</div>
</div>
</div>
{/* Address */}
<div>
<label className="block text-gray-700 text-sm font-medium mb-2 dark:text-gray-200">
{translate('::App.Address')} *
</label>
<div className="relative">
<FaMapPin className="absolute left-3 top-3 w-5 h-5 text-gray-400" />
<textarea
name="address"
value={formData.address}
onChange={handleInputChange}
rows={3}
className={`w-full pl-11 pr-4 py-2.5 bg-gray-50 border ${
errors.address
? "border-red-500 focus:border-red-500 focus:ring-red-500/20"
: "border-gray-200 focus:border-blue-500 focus:ring-blue-500/20 dark:border-gray-700"
} rounded-xl text-gray-800 placeholder-gray-500 focus:outline-none transition-all duration-300 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400`}
placeholder={translate('::Public.demo.address')}
/>
</div>
</div>
{/* Branches & Users Row */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-gray-700 text-sm font-medium mb-2 dark:text-gray-200">
{translate('::Public.common.branchCount')} *
</label>
<div className="relative">
<FaBuilding className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
<input
type="number"
name="numberOfBranches"
value={formData.numberOfBranches}
onChange={handleInputChange}
className={`w-full pl-11 pr-4 py-2.5 bg-gray-50 border ${
errors.numberOfBranches
? "border-red-500 focus:border-red-500 focus:ring-red-500/20"
: "border-gray-200 focus:border-blue-500 focus:ring-blue-500/20 dark:border-gray-700"
} rounded-xl text-gray-800 placeholder-gray-500 focus:outline-none transition-all duration-300 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400`}
placeholder={translate('::Public.demo.branches')}
min="1"
/>
</div>
</div>
<div>
<label className="block text-gray-700 text-sm font-medium mb-2 dark:text-gray-200">
{translate('::Public.common.userCount')} *
</label>
<div className="relative">
<FaUsers className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
<input
type="number"
name="numberOfUsers"
value={formData.numberOfUsers}
onChange={handleInputChange}
className={`w-full pl-11 pr-4 py-2.5 bg-gray-50 border ${
errors.numberOfUsers
? "border-red-500 focus:border-red-500 focus:ring-red-500/20"
: "border-gray-200 focus:border-blue-500 focus:ring-blue-500/20 dark:border-gray-700"
} rounded-xl text-gray-800 placeholder-gray-500 focus:outline-none transition-all duration-300 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400`}
placeholder={translate('::Public.demo.users')}
min="1"
/>
</div>
</div>
</div>
{/* Message */}
<div>
<label className="block text-gray-700 text-sm font-medium mb-2 dark:text-gray-200">
{translate('::Public.common.message')} *
</label>
<div className="relative">
<FaRegComment className="absolute left-3 top-3 w-5 h-5 text-gray-400" />
<textarea
name="message"
value={formData.message}
onChange={handleInputChange}
rows={3}
className={`w-full pl-11 pr-4 py-2.5 bg-gray-50 border ${
errors.message
? "border-red-500 focus:border-red-500 focus:ring-red-500/20"
: "border-gray-200 focus:border-blue-500 focus:ring-blue-500/20 dark:border-gray-700"
} rounded-xl text-gray-800 placeholder-gray-500 focus:outline-none transition-all duration-300 resize-none dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400`}
placeholder={translate('::Public.demo.message')}
/>
</div>
</div>
{/* Submit Button */}
<button
type="submit"
className="w-full bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white font-semibold py-4 px-6 rounded-xl transition-all duration-300 transform hover:scale-[1.02] hover:shadow-lg flex items-center justify-center gap-2"
>
<FaPaperPlane className="w-5 h-5" />
{translate('::Public.demo.send')}
</button>
</div>
</form>
</div>
</div>
);
};
export default Demo;