| 'use client'; | |
| import { useState } from 'react'; | |
| import Modal from './ui/Modal'; | |
| interface PasswordModalProps { | |
| isOpen: boolean; | |
| onClose: () => void; | |
| onSubmit: (password: string) => void; | |
| isLoading: boolean; | |
| } | |
| export default function PasswordModal({ | |
| isOpen, | |
| onClose, | |
| onSubmit, | |
| isLoading | |
| }: PasswordModalProps) { | |
| const [password, setPassword] = useState(''); | |
| const [passwordError, setPasswordError] = useState(''); | |
| const [showPassword, setShowPassword] = useState(false); | |
| const handleSubmit = () => { | |
| setPasswordError(''); | |
| if (!password.trim()) { | |
| setPasswordError('Please enter a password'); | |
| return; | |
| } | |
| if (password.length < 4) { | |
| setPasswordError('Password must be at least 4 characters'); | |
| return; | |
| } | |
| onSubmit(password); | |
| }; | |
| const handleClose = () => { | |
| setPassword(''); | |
| setPasswordError(''); | |
| setShowPassword(false); | |
| onClose(); | |
| }; | |
| const passwordIcon = ( | |
| <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" /> | |
| </svg> | |
| ); | |
| return ( | |
| <Modal | |
| isOpen={isOpen} | |
| onClose={handleClose} | |
| title="Set Password" | |
| icon={passwordIcon} | |
| > | |
| <div className="space-y-5"> | |
| <div className="rounded-lg border border-primary/15 bg-primary/5 px-4 py-3 flex items-start gap-3"> | |
| <div className="p-2 rounded-full bg-primary/10 text-primary"> | |
| <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M12 20a8 8 0 100-16 8 8 0 000 16z" /> | |
| </svg> | |
| </div> | |
| <div> | |
| <p className="text-text-primary font-medium">Add a passcode for extra privacy</p> | |
| <p className="text-text-secondary text-sm">Anyone accessing this clipboard will need the code you choose.</p> | |
| </div> | |
| </div> | |
| <div className="space-y-3"> | |
| <div className="relative"> | |
| <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> | |
| <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-text-secondary/60" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" /> | |
| </svg> | |
| </div> | |
| <input | |
| type={showPassword ? 'text' : 'password'} | |
| value={password} | |
| onChange={(e) => { | |
| setPassword(e.target.value); | |
| if (passwordError) setPasswordError(''); | |
| }} | |
| placeholder="Create a passcode" | |
| className="w-full pl-10 pr-12 py-3 rounded-lg bg-surface/80 border border-surface-hover focus:border-primary focus:ring-4 focus:ring-primary/20 focus:outline-none transition-colors duration-300 ease-in-out text-text-primary placeholder-text-secondary/50 font-mono shadow-inner" | |
| autoFocus | |
| autoComplete="off" | |
| /> | |
| <button | |
| type="button" | |
| onClick={() => setShowPassword((prev) => !prev)} | |
| className="absolute inset-y-0 right-0 px-3 flex items-center text-text-secondary/70 hover:text-text-primary transition-colors" | |
| aria-label={showPassword ? 'Hide password' : 'Show password'} | |
| > | |
| {showPassword ? ( | |
| <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13.875 18.825A10.05 10.05 0 0112 19c-5 0-9.27-3.11-11-7 1.05-2.38 2.9-4.36 5.1-5.66m4.02-1.09A9.98 9.98 0 0112 5c5 0 9.27 3.11 11 7a11.72 11.72 0 01-1.67 2.63M15 12a3 3 0 00-3-3m0 0a3 3 0 013 3m-3-3c-.64 0-1.26.19-1.77.52m0 0L3 21m7.23-8.48a3 3 0 104.24 4.24" /> | |
| </svg> | |
| ) : ( | |
| <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" /> | |
| </svg> | |
| )} | |
| </button> | |
| </div> | |
| <div className="grid grid-cols-2 gap-3 text-xs text-text-secondary"> | |
| <div className="flex items-center gap-2 rounded-md bg-surface/60 border border-surface-hover px-3 py-2"> | |
| <span className="inline-flex h-5 w-5 items-center justify-center rounded-full bg-primary/10 text-primary">4+</span> | |
| <span>At least 4 characters</span> | |
| </div> | |
| <div className="flex items-center gap-2 rounded-md bg-surface/60 border border-surface-hover px-3 py-2"> | |
| <span className="inline-flex h-5 w-5 items-center justify-center rounded-full bg-primary/10 text-primary">✦</span> | |
| <span>Mix letters & numbers</span> | |
| </div> | |
| </div> | |
| {passwordError && ( | |
| <div className="mt-1 text-error text-sm flex items-center"> | |
| <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 mr-1 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> | |
| </svg> | |
| <span>{passwordError}</span> | |
| </div> | |
| )} | |
| </div> | |
| <div className="flex space-x-3"> | |
| <button | |
| type="button" | |
| onClick={handleClose} | |
| className="flex-1 py-2.5 px-4 border border-surface-hover bg-surface/70 hover:bg-surface-hover text-text-primary font-medium rounded-lg transition-all duration-200 hover:-translate-y-0.5" | |
| > | |
| Cancel | |
| </button> | |
| <button | |
| type="button" | |
| onClick={handleSubmit} | |
| disabled={isLoading} | |
| className="flex-1 py-2.5 px-4 bg-gradient-to-r from-primary to-primary-dark hover:brightness-110 text-white font-medium rounded-lg flex items-center justify-center transition-all duration-200 shadow-lg shadow-primary/20 disabled:opacity-50 disabled:cursor-not-allowed" | |
| > | |
| {isLoading ? ( | |
| <span className="flex items-center justify-center"> | |
| <svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> | |
| <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle> | |
| <path className="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> | |
| Creating... | |
| </span> | |
| ) : ( | |
| <span className="flex items-center gap-2"> | |
| <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" /> | |
| </svg> | |
| Create & Lock | |
| </span> | |
| )} | |
| </button> | |
| </div> | |
| </div> | |
| </Modal> | |
| ); | |
| } | |