| import React, { useState, useEffect } from 'react'; |
| import { Heart, Lock, Unlock, Key, Send, Copy, Check, Calendar, User } from 'lucide-react'; |
|
|
| const LoveMessageSystem = () => { |
| const [currentPartner, setCurrentPartner] = useState(null); |
| const [partnershipId, setPartnershipId] = useState(null); |
| const [messages, setMessages] = useState([]); |
| const [newMessage, setNewMessage] = useState(''); |
| const [unlockKey, setUnlockKey] = useState(''); |
| const [inputUnlockKey, setInputUnlockKey] = useState(''); |
| const [showSetup, setShowSetup] = useState(true); |
| const [partnerName, setPartnerName] = useState(''); |
| const [copied, setCopied] = useState(false); |
| const [notification, setNotification] = useState(''); |
|
|
| |
| useEffect(() => { |
| const savedPartnerId = localStorage.getItem('partnerId'); |
| const savedPartnershipId = localStorage.getItem('partnershipId'); |
| |
| if (savedPartnerId && savedPartnershipId) { |
| setCurrentPartner(parseInt(savedPartnerId)); |
| setPartnershipId(savedPartnershipId); |
| setShowSetup(false); |
| loadMessages(savedPartnershipId); |
| loadUnlockKey(savedPartnershipId, parseInt(savedPartnerId)); |
| } |
| }, []); |
|
|
| const generateId = () => { |
| return Math.random().toString(36).substr(2, 9); |
| }; |
|
|
| const generateUnlockKey = () => { |
| return Math.random().toString(36).substr(2, 12).toUpperCase(); |
| }; |
|
|
| const createPartnership = async (partner) => { |
| const newPartnershipId = generateId(); |
| const newUnlockKey = generateUnlockKey(); |
| |
| setCurrentPartner(partner); |
| setPartnershipId(newPartnershipId); |
| setUnlockKey(newUnlockKey); |
| |
| localStorage.setItem('partnerId', partner.toString()); |
| localStorage.setItem('partnershipId', newPartnershipId); |
| |
| |
| const partnership = { |
| id: newPartnershipId, |
| partner1_unlock_key: partner === 1 ? newUnlockKey : null, |
| partner2_unlock_key: partner === 2 ? newUnlockKey : null, |
| created_at: new Date().toISOString() |
| }; |
| |
| localStorage.setItem(`partnership_${newPartnershipId}`, JSON.stringify(partnership)); |
| setShowSetup(false); |
| showNotification(`Welcome, Partner ${partner}! Share this ID with your partner: ${newPartnershipId}`); |
| }; |
|
|
| const joinPartnership = async (existingId, partner) => { |
| const partnership = localStorage.getItem(`partnership_${existingId}`); |
| |
| if (partnership) { |
| const newUnlockKey = generateUnlockKey(); |
| const partnershipData = JSON.parse(partnership); |
| |
| if (partner === 1) { |
| partnershipData.partner1_unlock_key = newUnlockKey; |
| } else { |
| partnershipData.partner2_unlock_key = newUnlockKey; |
| } |
| |
| localStorage.setItem(`partnership_${existingId}`, JSON.stringify(partnershipData)); |
| |
| setCurrentPartner(partner); |
| setPartnershipId(existingId); |
| setUnlockKey(newUnlockKey); |
| |
| localStorage.setItem('partnerId', partner.toString()); |
| localStorage.setItem('partnershipId', existingId); |
| |
| setShowSetup(false); |
| loadMessages(existingId); |
| showNotification(`Successfully joined partnership!`); |
| } else { |
| showNotification('Partnership ID not found!'); |
| } |
| }; |
|
|
| const loadMessages = (pId) => { |
| const stored = localStorage.getItem(`messages_${pId}`); |
| if (stored) { |
| setMessages(JSON.parse(stored)); |
| } |
| }; |
|
|
| const loadUnlockKey = (pId, partner) => { |
| const partnership = localStorage.getItem(`partnership_${pId}`); |
| if (partnership) { |
| const data = JSON.parse(partnership); |
| const key = partner === 1 ? data.partner1_unlock_key : data.partner2_unlock_key; |
| setUnlockKey(key || ''); |
| } |
| }; |
|
|
| const saveMessages = (pId, msgs) => { |
| localStorage.setItem(`messages_${pId}`, JSON.stringify(msgs)); |
| }; |
|
|
| const sendMessage = async () => { |
| if (!newMessage.trim()) return; |
|
|
| const message = { |
| id: generateId(), |
| partnership_id: partnershipId, |
| sender: currentPartner, |
| content: newMessage, |
| sent_at: new Date().toISOString(), |
| unlocked: false |
| }; |
|
|
| const updatedMessages = [...messages, message]; |
| setMessages(updatedMessages); |
| saveMessages(partnershipId, updatedMessages); |
| setNewMessage(''); |
| showNotification('Message sent! 💕'); |
| }; |
|
|
| const checkAutoUnlock = (sentDate) => { |
| const sent = new Date(sentDate); |
| const now = new Date(); |
| const daysDiff = Math.floor((now - sent) / (1000 * 60 * 60 * 24)); |
| return daysDiff >= 30; |
| }; |
|
|
| const useUnlockKey = () => { |
| if (!inputUnlockKey.trim()) { |
| showNotification('Please enter an unlock key'); |
| return; |
| } |
|
|
| const partnership = localStorage.getItem(`partnership_${partnershipId}`); |
| if (!partnership) return; |
|
|
| const data = JSON.parse(partnership); |
| const otherPartnerKey = currentPartner === 1 ? data.partner2_unlock_key : data.partner1_unlock_key; |
|
|
| if (inputUnlockKey === otherPartnerKey) { |
| |
| const updatedMessages = messages.map(msg => ({ |
| ...msg, |
| unlocked: true |
| })); |
| setMessages(updatedMessages); |
| saveMessages(partnershipId, updatedMessages); |
|
|
| |
| if (currentPartner === 1) { |
| data.partner2_unlock_key = generateUnlockKey(); |
| } else { |
| data.partner1_unlock_key = generateUnlockKey(); |
| } |
| localStorage.setItem(`partnership_${partnershipId}`, JSON.stringify(data)); |
|
|
| setInputUnlockKey(''); |
| showNotification('Messages unlocked! 🎉'); |
| } else { |
| showNotification('Invalid unlock key!'); |
| } |
| }; |
|
|
| const regenerateKey = () => { |
| const newKey = generateUnlockKey(); |
| setUnlockKey(newKey); |
|
|
| const partnership = localStorage.getItem(`partnership_${partnershipId}`); |
| if (partnership) { |
| const data = JSON.parse(partnership); |
| if (currentPartner === 1) { |
| data.partner1_unlock_key = newKey; |
| } else { |
| data.partner2_unlock_key = newKey; |
| } |
| localStorage.setItem(`partnership_${partnershipId}`, JSON.stringify(data)); |
| showNotification('New unlock key generated! 🔑'); |
| } |
| }; |
|
|
| const copyToClipboard = (text) => { |
| navigator.clipboard.writeText(text); |
| setCopied(true); |
| setTimeout(() => setCopied(false), 2000); |
| }; |
|
|
| const showNotification = (msg) => { |
| setNotification(msg); |
| setTimeout(() => setNotification(''), 3000); |
| }; |
|
|
| const myMessages = messages.filter(m => m.sender === currentPartner); |
| const partnerMessages = messages.filter(m => m.sender !== currentPartner && m.sender !== null); |
|
|
| if (showSetup) { |
| return ( |
| <div className="min-h-screen bg-gradient-to-br from-pink-100 via-purple-50 to-rose-100 p-8"> |
| <div className="max-w-md mx-auto"> |
| <div className="text-center mb-8"> |
| <Heart className="w-16 h-16 text-rose-500 mx-auto mb-4" /> |
| <h1 className="text-4xl font-bold text-rose-600 mb-2">Love Messages</h1> |
| <p className="text-gray-600">Connect with your partner</p> |
| </div> |
| |
| <div className="bg-white rounded-2xl shadow-xl p-8 space-y-6"> |
| <div> |
| <h2 className="text-2xl font-bold text-gray-800 mb-4">Create New Partnership</h2> |
| <div className="space-y-3"> |
| <button |
| onClick={() => createPartnership(1)} |
| className="w-full bg-rose-500 text-white py-3 rounded-xl font-semibold hover:bg-rose-600 transition" |
| > |
| I'm Partner 1 |
| </button> |
| <button |
| onClick={() => createPartnership(2)} |
| className="w-full bg-purple-500 text-white py-3 rounded-xl font-semibold hover:bg-purple-600 transition" |
| > |
| I'm Partner 2 |
| </button> |
| </div> |
| </div> |
| |
| <div className="border-t pt-6"> |
| <h2 className="text-2xl font-bold text-gray-800 mb-4">Join Existing</h2> |
| <input |
| type="text" |
| placeholder="Enter Partnership ID" |
| value={partnerName} |
| onChange={(e) => setPartnerName(e.target.value)} |
| className="w-full px-4 py-3 border border-gray-300 rounded-xl mb-3 focus:ring-2 focus:ring-rose-500 focus:border-transparent" |
| /> |
| <div className="space-y-3"> |
| <button |
| onClick={() => joinPartnership(partnerName, 1)} |
| className="w-full bg-rose-400 text-white py-3 rounded-xl font-semibold hover:bg-rose-500 transition" |
| > |
| Join as Partner 1 |
| </button> |
| <button |
| onClick={() => joinPartnership(partnerName, 2)} |
| className="w-full bg-purple-400 text-white py-3 rounded-xl font-semibold hover:bg-purple-500 transition" |
| > |
| Join as Partner 2 |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| ); |
| } |
|
|
| return ( |
| <div className="min-h-screen bg-gradient-to-br from-pink-100 via-purple-50 to-rose-100 p-4 md:p-8"> |
| {notification && ( |
| <div className="fixed top-4 right-4 bg-white shadow-lg rounded-xl px-6 py-3 z-50 animate-pulse"> |
| <p className="text-gray-800 font-semibold">{notification}</p> |
| </div> |
| )} |
| |
| <div className="max-w-6xl mx-auto"> |
| <div className="text-center mb-8"> |
| <Heart className="w-12 h-12 text-rose-500 mx-auto mb-4" /> |
| <h1 className="text-3xl md:text-4xl font-bold text-rose-600 mb-2"> |
| Partner {currentPartner} Dashboard |
| </h1> |
| <p className="text-gray-600 text-sm">Partnership ID: {partnershipId}</p> |
| </div> |
| |
| <div className="grid md:grid-cols-2 gap-6 mb-8"> |
| {/* Send Message Section */} |
| <div className="bg-white rounded-2xl shadow-xl p-6"> |
| <h2 className="text-2xl font-bold text-gray-800 mb-4 flex items-center gap-2"> |
| <Send className="w-6 h-6 text-rose-500" /> |
| Send Love Message |
| </h2> |
| <textarea |
| value={newMessage} |
| onChange={(e) => setNewMessage(e.target.value)} |
| placeholder="Write your daily love message..." |
| className="w-full h-32 px-4 py-3 border border-gray-300 rounded-xl mb-4 focus:ring-2 focus:ring-rose-500 focus:border-transparent resize-none" |
| /> |
| <button |
| onClick={sendMessage} |
| className="w-full bg-rose-500 text-white py-3 rounded-xl font-semibold hover:bg-rose-600 transition flex items-center justify-center gap-2" |
| > |
| <Heart className="w-5 h-5" /> |
| Send Message |
| </button> |
| </div> |
| |
| {/* Unlock Key Section */} |
| <div className="bg-white rounded-2xl shadow-xl p-6"> |
| <h2 className="text-2xl font-bold text-gray-800 mb-4 flex items-center gap-2"> |
| <Key className="w-6 h-6 text-purple-500" /> |
| Your Unlock Key |
| </h2> |
| <div className="bg-purple-50 rounded-xl p-4 mb-4"> |
| <p className="text-sm text-gray-600 mb-2">Share this key with your partner:</p> |
| <div className="flex items-center gap-2"> |
| <code className="flex-1 bg-white px-4 py-2 rounded-lg font-mono text-lg font-bold text-purple-600"> |
| {unlockKey} |
| </code> |
| <button |
| onClick={() => copyToClipboard(unlockKey)} |
| className="bg-purple-500 text-white p-2 rounded-lg hover:bg-purple-600 transition" |
| > |
| {copied ? <Check className="w-5 h-5" /> : <Copy className="w-5 h-5" />} |
| </button> |
| </div> |
| </div> |
| <button |
| onClick={regenerateKey} |
| className="w-full bg-purple-500 text-white py-3 rounded-xl font-semibold hover:bg-purple-600 transition mb-4" |
| > |
| Generate New Key |
| </button> |
| |
| <div className="border-t pt-4"> |
| <p className="text-sm text-gray-600 mb-2">Use partner's key to unlock:</p> |
| <input |
| type="text" |
| value={inputUnlockKey} |
| onChange={(e) => setInputUnlockKey(e.target.value)} |
| placeholder="Enter unlock key" |
| className="w-full px-4 py-2 border border-gray-300 rounded-xl mb-2 focus:ring-2 focus:ring-purple-500 focus:border-transparent" |
| /> |
| <button |
| onClick={useUnlockKey} |
| className="w-full bg-green-500 text-white py-3 rounded-xl font-semibold hover:bg-green-600 transition flex items-center justify-center gap-2" |
| > |
| <Unlock className="w-5 h-5" /> |
| Unlock Messages |
| </button> |
| </div> |
| </div> |
| </div> |
| |
| {/* Messages Display */} |
| <div className="grid md:grid-cols-2 gap-6"> |
| {/* My Messages */} |
| <div className="bg-white rounded-2xl shadow-xl p-6"> |
| <h2 className="text-2xl font-bold text-gray-800 mb-4 flex items-center gap-2"> |
| <User className="w-6 h-6 text-rose-500" /> |
| My Messages ({myMessages.length}) |
| </h2> |
| <div className="space-y-3 max-h-96 overflow-y-auto"> |
| {myMessages.map((msg) => ( |
| <div key={msg.id} className="bg-rose-50 rounded-xl p-4 border-2 border-rose-200"> |
| <div className="flex items-center justify-between mb-2"> |
| <span className="text-xs text-gray-500 flex items-center gap-1"> |
| <Calendar className="w-3 h-3" /> |
| {new Date(msg.sent_at).toLocaleDateString()} |
| </span> |
| </div> |
| <p className="text-gray-800">{msg.content}</p> |
| </div> |
| ))} |
| {myMessages.length === 0 && ( |
| <p className="text-gray-400 text-center py-8">No messages sent yet</p> |
| )} |
| </div> |
| </div> |
| |
| {/* Partner's Messages */} |
| <div className="bg-white rounded-2xl shadow-xl p-6"> |
| <h2 className="text-2xl font-bold text-gray-800 mb-4 flex items-center gap-2"> |
| <Heart className="w-6 h-6 text-purple-500" /> |
| Partner's Messages ({partnerMessages.length}) |
| </h2> |
| <div className="space-y-3 max-h-96 overflow-y-auto"> |
| {partnerMessages.map((msg) => { |
| const autoUnlock = checkAutoUnlock(msg.sent_at); |
| const isUnlocked = msg.unlocked || autoUnlock; |
| const daysRemaining = 30 - Math.floor((new Date() - new Date(msg.sent_at)) / (1000 * 60 * 60 * 24)); |
| |
| return ( |
| <div |
| key={msg.id} |
| className={`rounded-xl p-4 border-2 ${ |
| isUnlocked ? 'bg-green-50 border-green-200' : 'bg-gray-100 border-gray-300' |
| }`} |
| > |
| <div className="flex items-center justify-between mb-2"> |
| <span className="text-xs text-gray-500 flex items-center gap-1"> |
| <Calendar className="w-3 h-3" /> |
| {new Date(msg.sent_at).toLocaleDateString()} |
| </span> |
| {isUnlocked ? ( |
| <Unlock className="w-4 h-4 text-green-500" /> |
| ) : ( |
| <Lock className="w-4 h-4 text-gray-500" /> |
| )} |
| </div> |
| {isUnlocked ? ( |
| <p className="text-gray-800">{msg.content}</p> |
| ) : ( |
| <div className="text-center py-4"> |
| <Lock className="w-8 h-8 text-gray-400 mx-auto mb-2" /> |
| <p className="text-gray-500 text-sm"> |
| Unlocks in {daysRemaining} days |
| </p> |
| <p className="text-gray-400 text-xs mt-1"> |
| Or use unlock key |
| </p> |
| </div> |
| )} |
| </div> |
| ); |
| })} |
| {partnerMessages.length === 0 && ( |
| <p className="text-gray-400 text-center py-8"> |
| Waiting for partner's messages... |
| </p> |
| )} |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| ); |
| }; |
|
|
| export default LoveMessageSystem; |