| '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> |
| ); |
| } |
|
|