feat: improve password protection UX with toggle switch and clear option
This commit is contained in:
@@ -57,7 +57,7 @@ export default function SiteSettingsPage() {
|
|||||||
const [scriptCopied, setScriptCopied] = useState(false)
|
const [scriptCopied, setScriptCopied] = useState(false)
|
||||||
const [linkCopied, setLinkCopied] = useState(false)
|
const [linkCopied, setLinkCopied] = useState(false)
|
||||||
const [showVerificationModal, setShowVerificationModal] = useState(false)
|
const [showVerificationModal, setShowVerificationModal] = useState(false)
|
||||||
const [isPasswordMasked, setIsPasswordMasked] = useState(false)
|
const [isPasswordEnabled, setIsPasswordEnabled] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadSite()
|
loadSite()
|
||||||
@@ -76,7 +76,9 @@ export default function SiteSettingsPage() {
|
|||||||
excluded_paths: (data.excluded_paths || []).join('\n')
|
excluded_paths: (data.excluded_paths || []).join('\n')
|
||||||
})
|
})
|
||||||
if (data.has_password) {
|
if (data.has_password) {
|
||||||
setIsPasswordMasked(true)
|
setIsPasswordEnabled(true)
|
||||||
|
} else {
|
||||||
|
setIsPasswordEnabled(false)
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
toast.error('Failed to load site: ' + (error.message || 'Unknown error'))
|
toast.error('Failed to load site: ' + (error.message || 'Unknown error'))
|
||||||
@@ -99,7 +101,8 @@ export default function SiteSettingsPage() {
|
|||||||
name: formData.name,
|
name: formData.name,
|
||||||
timezone: formData.timezone,
|
timezone: formData.timezone,
|
||||||
is_public: formData.is_public,
|
is_public: formData.is_public,
|
||||||
password: isPasswordMasked ? undefined : (formData.password || undefined),
|
password: isPasswordEnabled ? (formData.password || undefined) : undefined,
|
||||||
|
clear_password: !isPasswordEnabled,
|
||||||
excluded_paths: excludedPathsArray
|
excluded_paths: excludedPathsArray
|
||||||
})
|
})
|
||||||
toast.success('Site updated successfully')
|
toast.success('Site updated successfully')
|
||||||
@@ -447,27 +450,45 @@ export default function SiteSettingsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<PasswordInput
|
<div className="flex items-center justify-between mb-4">
|
||||||
id="password"
|
<div>
|
||||||
label="Password Protection (Optional)"
|
<h3 className="text-sm font-medium text-neutral-900 dark:text-white">Password Protection</h3>
|
||||||
value={isPasswordMasked ? '********' : formData.password}
|
<p className="text-xs text-neutral-500 mt-1">Restrict access to this dashboard.</p>
|
||||||
onChange={(value) => setFormData({ ...formData, password: value })}
|
</div>
|
||||||
onFocus={() => {
|
<label className="relative inline-flex items-center cursor-pointer">
|
||||||
if (isPasswordMasked) {
|
<input
|
||||||
setIsPasswordMasked(false)
|
type="checkbox"
|
||||||
setFormData({ ...formData, password: '' })
|
checked={isPasswordEnabled}
|
||||||
}
|
onChange={(e) => {
|
||||||
}}
|
setIsPasswordEnabled(e.target.checked);
|
||||||
onBlur={() => {
|
if (!e.target.checked) setFormData({...formData, password: ''});
|
||||||
if (!formData.password && site.has_password) {
|
}}
|
||||||
setIsPasswordMasked(true)
|
className="sr-only peer"
|
||||||
}
|
/>
|
||||||
}}
|
<div className="w-11 h-6 bg-neutral-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-brand-orange/20 dark:peer-focus:ring-brand-orange/20 rounded-full peer dark:bg-neutral-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-brand-orange"></div>
|
||||||
placeholder={site.has_password ? "Change password" : "Leave empty to keep existing password (if any)"}
|
</label>
|
||||||
/>
|
</div>
|
||||||
<p className="mt-2 text-xs text-neutral-500">
|
|
||||||
Set a password to restrict access to the public dashboard.
|
<AnimatePresence>
|
||||||
</p>
|
{isPasswordEnabled && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, height: 0 }}
|
||||||
|
animate={{ opacity: 1, height: 'auto' }}
|
||||||
|
exit={{ opacity: 0, height: 0 }}
|
||||||
|
className="overflow-hidden"
|
||||||
|
>
|
||||||
|
<PasswordInput
|
||||||
|
id="password"
|
||||||
|
value={formData.password}
|
||||||
|
onChange={(value) => setFormData({ ...formData, password: value })}
|
||||||
|
placeholder={site.has_password ? "Change password (leave empty to keep current)" : "Set a password"}
|
||||||
|
/>
|
||||||
|
<p className="mt-2 text-xs text-neutral-500">
|
||||||
|
Visitors will need to enter this password to view the dashboard.
|
||||||
|
</p>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export interface UpdateSiteRequest {
|
|||||||
timezone?: string
|
timezone?: string
|
||||||
is_public?: boolean
|
is_public?: boolean
|
||||||
password?: string
|
password?: string
|
||||||
|
clear_password?: boolean
|
||||||
excluded_paths?: string[]
|
excluded_paths?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user