Seth0330 commited on
Commit
fbbf1b5
·
verified ·
1 Parent(s): c010eca

Create ShareModal.jsx

Browse files
frontend/src/components/ShareModal.jsx ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState } from "react";
2
+ import { motion, AnimatePresence } from "framer-motion";
3
+ import { X, Mail, Send, Loader2 } from "lucide-react";
4
+ import { Button } from "@/components/ui/button";
5
+ import { Input } from "@/components/ui/input";
6
+
7
+ export default function ShareModal({ isOpen, onClose, onShare, extractionId }) {
8
+ const [email, setEmail] = useState("");
9
+ const [isLoading, setIsLoading] = useState(false);
10
+ const [error, setError] = useState("");
11
+ const [success, setSuccess] = useState(false);
12
+
13
+ const handleSubmit = async (e) => {
14
+ e.preventDefault();
15
+ setError("");
16
+ setSuccess(false);
17
+
18
+ // Basic email validation
19
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
20
+ if (!email.trim()) {
21
+ setError("Please enter a recipient email address");
22
+ return;
23
+ }
24
+ if (!emailRegex.test(email.trim())) {
25
+ setError("Please enter a valid email address");
26
+ return;
27
+ }
28
+
29
+ setIsLoading(true);
30
+ try {
31
+ await onShare(extractionId, email.trim());
32
+ setSuccess(true);
33
+ setEmail("");
34
+ // Close modal after 2 seconds
35
+ setTimeout(() => {
36
+ setSuccess(false);
37
+ onClose();
38
+ }, 2000);
39
+ } catch (err) {
40
+ setError(err.message || "Failed to share extraction. Please try again.");
41
+ } finally {
42
+ setIsLoading(false);
43
+ }
44
+ };
45
+
46
+ const handleClose = () => {
47
+ if (!isLoading) {
48
+ setEmail("");
49
+ setError("");
50
+ setSuccess(false);
51
+ onClose();
52
+ }
53
+ };
54
+
55
+ if (!isOpen) return null;
56
+
57
+ return (
58
+ <AnimatePresence>
59
+ <div className="fixed inset-0 z-50 flex items-center justify-center">
60
+ {/* Backdrop */}
61
+ <motion.div
62
+ initial={{ opacity: 0 }}
63
+ animate={{ opacity: 1 }}
64
+ exit={{ opacity: 0 }}
65
+ className="absolute inset-0 bg-black/50 backdrop-blur-sm"
66
+ onClick={handleClose}
67
+ />
68
+
69
+ {/* Modal */}
70
+ <motion.div
71
+ initial={{ opacity: 0, scale: 0.95, y: 20 }}
72
+ animate={{ opacity: 1, scale: 1, y: 0 }}
73
+ exit={{ opacity: 0, scale: 0.95, y: 20 }}
74
+ className="relative z-10 w-full max-w-md mx-4 bg-white rounded-2xl shadow-2xl overflow-hidden"
75
+ onClick={(e) => e.stopPropagation()}
76
+ >
77
+ {/* Header */}
78
+ <div className="px-6 py-4 border-b border-slate-200 flex items-center justify-between">
79
+ <h2 className="text-xl font-semibold text-slate-900">Share Output</h2>
80
+ <button
81
+ onClick={handleClose}
82
+ disabled={isLoading}
83
+ className="p-2 rounded-lg hover:bg-slate-100 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
84
+ >
85
+ <X className="h-5 w-5 text-slate-500" />
86
+ </button>
87
+ </div>
88
+
89
+ {/* Content */}
90
+ <div className="px-6 py-6">
91
+ {success ? (
92
+ <motion.div
93
+ initial={{ opacity: 0, scale: 0.9 }}
94
+ animate={{ opacity: 1, scale: 1 }}
95
+ className="text-center py-8"
96
+ >
97
+ <div className="w-16 h-16 mx-auto mb-4 rounded-full bg-emerald-100 flex items-center justify-center">
98
+ <Send className="h-8 w-8 text-emerald-600" />
99
+ </div>
100
+ <h3 className="text-lg font-semibold text-slate-900 mb-2">
101
+ Share Sent Successfully!
102
+ </h3>
103
+ <p className="text-sm text-slate-600">
104
+ The recipient will receive an email with a link to view the extraction.
105
+ </p>
106
+ </motion.div>
107
+ ) : (
108
+ <form onSubmit={handleSubmit} className="space-y-4">
109
+ <div>
110
+ <label
111
+ htmlFor="recipient-email"
112
+ className="block text-sm font-medium text-slate-700 mb-2"
113
+ >
114
+ Recipient Email
115
+ </label>
116
+ <div className="relative">
117
+ <Mail className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-slate-400" />
118
+ <Input
119
+ id="recipient-email"
120
+ type="email"
121
+ value={email}
122
+ onChange={(e) => setEmail(e.target.value)}
123
+ placeholder="Enter recipient email address"
124
+ className="pl-10 h-12 rounded-xl border-slate-200 focus:border-indigo-500 focus:ring-indigo-500"
125
+ disabled={isLoading}
126
+ autoFocus
127
+ />
128
+ </div>
129
+ {error && (
130
+ <motion.p
131
+ initial={{ opacity: 0, y: -10 }}
132
+ animate={{ opacity: 1, y: 0 }}
133
+ className="mt-2 text-sm text-red-600"
134
+ >
135
+ {error}
136
+ </motion.p>
137
+ )}
138
+ </div>
139
+
140
+ <div className="pt-4 flex gap-3">
141
+ <Button
142
+ type="button"
143
+ variant="outline"
144
+ onClick={handleClose}
145
+ disabled={isLoading}
146
+ className="flex-1 h-11 rounded-xl"
147
+ >
148
+ Cancel
149
+ </Button>
150
+ <Button
151
+ type="submit"
152
+ disabled={isLoading || !email.trim()}
153
+ className="flex-1 h-11 rounded-xl bg-gradient-to-r from-indigo-600 to-violet-600 hover:from-indigo-700 hover:to-violet-700"
154
+ >
155
+ {isLoading ? (
156
+ <>
157
+ <Loader2 className="h-4 w-4 mr-2 animate-spin" />
158
+ Sending...
159
+ </>
160
+ ) : (
161
+ <>
162
+ <Send className="h-4 w-4 mr-2" />
163
+ Send
164
+ </>
165
+ )}
166
+ </Button>
167
+ </div>
168
+ </form>
169
+ )}
170
+ </div>
171
+ </motion.div>
172
+ </div>
173
+ </AnimatePresence>
174
+ );
175
+ }
176
+