| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368 |
- import React, { useState, useEffect } from 'react';
- import { useNavigate } from 'react-router-dom';
- import type { ContactData, SocialLinksData } from '../../types';
- import LinkedInIcon from '../../components/icons/LinkedInIcon';
- import XIcon from '../../components/icons/XIcon';
- import GithubIcon from '../../components/icons/GithubIcon';
- import TiktokIcon from '../../components/icons/TiktokIcon';
- import InstagramIcon from '../../components/icons/InstagramIcon';
- import FacebookIcon from '../../components/icons/FacebookIcon';
- import DiscordIcon from '../../components/icons/DiscordIcon';
- import VerifiedIcon from '../../components/icons/VerifiedIcon';
- import VirtualIdCard from '../../components/VirtualIdCard';
- import UploadIcon from '../../components/icons/UploadIcon';
- import AuthenticityScore from '../../components/AuthenticityScore';
- import BrainCircuitIcon from '../../components/icons/BrainCircuitIcon';
- import ArrowRightIcon from '../../components/icons/ArrowRightIcon';
- interface EditPageProps {
- initialData: ContactData;
- onSave: (data: ContactData) => void;
- }
- // Reusable Section Component for editable fields
- const EditSection: React.FC<{title: string, children: React.ReactNode}> = ({ title, children }) => (
- <div className="bg-white rounded-2xl border border-slate-200 p-6 space-y-4">
- <h2 className="text-xl font-bold text-slate-800 mb-4">{title}</h2>
- {children}
- </div>
- );
- const socialPlatforms = [
- {
- key: 'linkedin', name: 'LinkedIn', icon: <LinkedInIcon />,
- brandClasses: {
- iconBg: 'bg-gradient-to-br from-blue-500 to-sky-400',
- button: 'bg-blue-600 hover:bg-blue-700',
- }
- },
- {
- key: 'x', name: 'X (Twitter)', icon: <XIcon />,
- brandClasses: {
- iconBg: 'bg-gradient-to-br from-slate-900 to-slate-700',
- button: 'bg-slate-800 hover:bg-slate-900',
- }
- },
- {
- key: 'github', name: 'GitHub', icon: <GithubIcon />,
- brandClasses: {
- iconBg: 'bg-gradient-to-br from-slate-900 to-slate-700',
- button: 'bg-slate-800 hover:bg-slate-900',
- }
- },
- {
- key: 'tiktok', name: 'TikTok', icon: <TiktokIcon />,
- brandClasses: {
- iconBg: 'bg-gradient-to-br from-black via-rose-500 to-cyan-400',
- button: 'bg-black hover:bg-gray-800',
- }
- },
- {
- key: 'instagram', name: 'Instagram', icon: <InstagramIcon />,
- brandClasses: {
- iconBg: 'bg-gradient-to-br from-purple-500 via-pink-500 to-yellow-500',
- button: 'bg-pink-600 hover:bg-pink-700',
- }
- },
- {
- key: 'facebook', name: 'Facebook', icon: <FacebookIcon />,
- brandClasses: {
- iconBg: 'bg-gradient-to-br from-blue-700 to-blue-500',
- button: 'bg-blue-700 hover:bg-blue-800',
- }
- },
- {
- key: 'discord', name: 'Discord', icon: <DiscordIcon />,
- brandClasses: {
- iconBg: 'bg-gradient-to-br from-indigo-600 to-purple-500',
- button: 'bg-indigo-600 hover:bg-indigo-700',
- }
- },
- ] as const;
- const EditPage: React.FC<EditPageProps> = ({ initialData, onSave }) => {
- const navigate = useNavigate();
- const [formData, setFormData] = useState<ContactData>(initialData);
- useEffect(() => {
- const handleOauthMessage = (event: MessageEvent) => {
- if (event.origin !== window.location.origin) {
- return;
- }
- const { type, platform, profileUrl, error } = event.data;
- if (type === 'oauth-success' && platform && profileUrl) {
- setFormData(prev => ({
- ...prev,
- socials: {
- ...prev.socials,
- [platform]: profileUrl
- }
- }));
- } else if (type === 'oauth-error') {
- console.error(`OAuth Error for ${platform}:`, error);
- alert(`Failed to connect ${platform}. Please try again.`);
- }
- };
-
- window.addEventListener('message', handleOauthMessage);
- return () => {
- window.removeEventListener('message', handleOauthMessage);
- };
- }, []);
- useEffect(() => {
- // Recalculate authenticity score when dependencies change
- const connectedSocials = Object.values(formData.socials).filter(link => !!link).length;
-
- let socialScore = 0;
- if (connectedSocials === 1) {
- socialScore = 15;
- } else if (connectedSocials === 2) {
- socialScore = 30;
- } else if (connectedSocials > 2) {
- socialScore = 30 + (connectedSocials - 2) * 5;
- }
-
- const finalSocialScore = Math.min(socialScore, 45);
- const videoScore = formData.videoIntroUrl ? 15 : 0;
-
- // Only update if the scores have changed to prevent infinite loops
- if (
- finalSocialScore !== formData.authenticityScore.socialBinding ||
- videoScore !== formData.authenticityScore.videoVerification
- ) {
- setFormData(prev => ({
- ...prev,
- authenticityScore: {
- ...prev.authenticityScore,
- socialBinding: finalSocialScore,
- videoVerification: videoScore,
- }
- }));
- }
- }, [formData.socials, formData.videoIntroUrl, formData.authenticityScore]);
- const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
- const { name, value } = e.target;
- setFormData(prev => ({
- ...prev,
- [name]: value,
- }));
- };
- const handleVideoChange = (e: React.ChangeEvent<HTMLInputElement>) => {
- const file = e.target.files?.[0];
- if (file) {
- const videoUrl = URL.createObjectURL(file);
- setFormData(prev => ({
- ...prev,
- videoIntroUrl: videoUrl,
- videoPosterUrl: '' // Reset poster for new video
- }));
- }
- };
- const handleSocialConnect = (platform: keyof SocialLinksData) => {
- const isConnected = !!formData.socials[platform];
- if (isConnected) {
- // Disconnect logic
- const newSocials = { ...formData.socials, [platform]: '' };
- setFormData(prev => ({ ...prev, socials: newSocials }));
- return;
- }
- // --- Real OAuth URLs ---
- const redirectUri = `${window.location.origin}/oauth-callback.html`;
- let oauthUrl = '';
- switch (platform) {
- case 'github':
- oauthUrl = `https://github.com/login/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=${encodeURIComponent(redirectUri)}&scope=read:user&state=github`;
- break;
- case 'linkedin':
- oauthUrl = `https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=${encodeURIComponent(redirectUri)}&state=linkedin&scope=profile%20email%20openid`;
- break;
- case 'x':
- oauthUrl = `https://twitter.com/i/oauth2/authorize?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=${encodeURIComponent(redirectUri)}&scope=users.read%20tweet.read&state=x&code_challenge=challenge&code_challenge_method=plain`;
- break;
- case 'tiktok':
- oauthUrl = `https://www.tiktok.com/v2/auth/authorize?client_key=YOUR_CLIENT_KEY&scope=user.info.basic&response_type=code&redirect_uri=${encodeURIComponent(redirectUri)}&state=tiktok`;
- break;
- case 'instagram':
- oauthUrl = `https://api.instagram.com/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=${encodeURIComponent(redirectUri)}&scope=user_profile,user_media&response_type=code&state=instagram`;
- break;
- case 'facebook':
- oauthUrl = `https://www.facebook.com/v19.0/dialog/oauth?client_id=YOUR_CLIENT_ID&redirect_uri=${encodeURIComponent(redirectUri)}&state=facebook&scope=public_profile,email`;
- break;
- case 'discord':
- oauthUrl = `https://discord.com/api/oauth2/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&scope=identify%20email&state=discord`;
- break;
- }
- // --- Popup Window Logic ---
- const width = 600;
- const height = 700;
- const left = (window.innerWidth - width) / 2;
- const top = (window.innerHeight - height) / 2;
- window.open(
- oauthUrl,
- 'socialLogin',
- `width=${width},height=${height},left=${left},top=${top},toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes`
- );
- };
- const handleSubmit = (e: React.FormEvent) => {
- e.preventDefault();
- onSave(formData);
- navigate('/contact');
- };
- const totalScore = formData.authenticityScore.videoVerification + formData.authenticityScore.socialBinding + formData.authenticityScore.cloneMaturity;
- return (
- <main className="max-w-3xl mx-auto p-4 sm:p-6 md:p-8">
- <form onSubmit={handleSubmit} className="space-y-8">
- <div className="flex justify-between items-center mb-4">
- <h1 className="text-2xl font-bold text-slate-800">Certify My Homepage</h1>
- <div>
- <button type="button" onClick={() => navigate('/contact')} className="px-4 py-2 text-sm font-medium text-slate-700 bg-white border border-slate-300 rounded-md hover:bg-slate-50 mr-2">Cancel</button>
- <button type="submit" className="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700">Save Changes</button>
- </div>
- </div>
- <VirtualIdCard
- name={formData.name}
- avatarUrl={formData.avatarUrl}
- virtualId={formData.virtualId}
- totalScore={totalScore}
- />
- <AuthenticityScore
- videoVerificationScore={formData.authenticityScore.videoVerification}
- socialBindingScore={formData.authenticityScore.socialBinding}
- cloneMaturityScore={formData.authenticityScore.cloneMaturity}
- />
- <div className="bg-sky-50 border border-sky-200 text-sky-800 rounded-xl p-3 text-center text-sm -mt-4">
- <p>At your current score, you'll earn <span className="font-bold">100 tokens/minute</span> from calls.</p>
- </div>
- <div
- onClick={() => navigate('/clone-chat')}
- className="bg-white rounded-2xl border border-slate-200 p-6 group cursor-pointer transition-colors duration-300 ease-in-out hover:bg-slate-50"
- role="button"
- aria-label="Train My Digital Clone"
- >
- <div className="flex items-center gap-4">
- <div className="flex-shrink-0 w-12 h-12 rounded-2xl flex items-center justify-center bg-gradient-to-br from-indigo-500 to-purple-600 text-white group-hover:scale-110 transition-transform duration-300">
- <BrainCircuitIcon />
- </div>
- <div className="flex-grow">
- <h2 className="text-xl font-bold text-slate-800">Train My Digital Clone</h2>
- <p className="text-sm text-slate-600 mt-1">
- Improve your clone's quality score by continuously chatting with it.
- </p>
- <p className="text-sm text-indigo-600 font-medium mt-1">
- A higher score leads to greater call earnings.
- </p>
- </div>
- <div className="text-slate-400 group-hover:text-slate-800 transition-transform duration-300 group-hover:translate-x-1">
- <ArrowRightIcon className="w-5 h-5" />
- </div>
- </div>
- </div>
- <EditSection title="About Me">
- <label htmlFor="bio" className="sr-only">About Me</label>
- <textarea
- id="bio"
- name="bio"
- rows={5}
- className="w-full px-3 py-2 text-slate-700 bg-white border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition"
- placeholder="Tell us about yourself..."
- value={formData.bio}
- onChange={handleInputChange}
- />
- </EditSection>
- <EditSection title="Video Introduction">
- <label htmlFor="video-upload" className="block aspect-video w-full rounded-lg bg-slate-200 relative overflow-hidden group cursor-pointer">
- <video key={formData.videoIntroUrl} poster={formData.videoPosterUrl} src={formData.videoIntroUrl} className="w-full h-full object-cover" />
-
- <div className="absolute inset-0 bg-black/60 flex flex-col items-center justify-center text-white opacity-0 group-hover:opacity-100 transition-opacity duration-300">
- <UploadIcon />
- <span className="mt-2 block font-semibold">Upload New Video</span>
- <span className="text-xs text-slate-300">Click or drag & drop</span>
- </div>
- <input id="video-upload" type="file" onChange={handleVideoChange} accept="video/*" className="sr-only"/>
- </label>
- </EditSection>
-
- <EditSection title="Verified Social Accounts">
- <p className="text-sm text-slate-600">Connect your social accounts to verify your identity. This enhances your Authenticity Score.</p>
- <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
- {socialPlatforms.map((platform) => {
- const isConnected = !!formData.socials[platform.key];
-
- if (!isConnected) {
- return (
- <div key={platform.key} className="flex items-center p-4 rounded-xl border-2 border-dashed border-slate-200 bg-slate-50 transition-colors hover:border-slate-300 hover:bg-slate-100">
- <div className="flex-shrink-0 w-10 h-10 rounded-xl flex items-center justify-center text-slate-400 bg-slate-200">
- {platform.icon}
- </div>
- <div className="ml-4 flex-grow">
- <h3 className="font-bold text-slate-700 text-sm">{platform.name}</h3>
- <p className="text-xs text-slate-500">Not Connected</p>
- </div>
- <button
- type="button"
- onClick={() => handleSocialConnect(platform.key)}
- className={`px-3 py-1.5 text-xs font-semibold text-white ${platform.brandClasses.button} rounded-md transition-transform transform hover:scale-105`}
- >
- Connect
- </button>
- </div>
- );
- }
- return (
- <div key={platform.key} className="flex items-center p-4 rounded-xl border border-slate-200 bg-white">
- <div className={`flex-shrink-0 w-10 h-10 rounded-xl flex items-center justify-center text-white ${platform.brandClasses.iconBg}`}>
- {platform.icon}
- </div>
- <div className="ml-4 flex-grow overflow-hidden">
- <div className="flex items-center">
- <h3 className="font-bold text-slate-800 text-sm">{platform.name}</h3>
- <div className="ml-2 flex items-center bg-green-100 text-green-800 text-xs font-medium px-2 py-0.5 rounded-full">
- <VerifiedIcon className="w-3 h-3 mr-1" />
- Verified
- </div>
- </div>
- <p className="text-xs text-slate-500 font-mono truncate max-w-[120px] sm:max-w-xs">{formData.socials[platform.key].replace(/^https?:\/\/(www\.)?/, '')}</p>
- </div>
- <button
- type="button"
- onClick={() => handleSocialConnect(platform.key)}
- className="text-xs font-semibold text-red-600 hover:text-red-800 hover:bg-red-100 px-2 py-1 rounded-md transition-colors"
- >
- Disconnect
- </button>
- </div>
- );
- })}
- </div>
- </EditSection>
- </form>
- </main>
- );
- };
- export default EditPage;
|