feat: Slate & Ice Modern aesthetic redesign #94
@@ -7,6 +7,7 @@ from fastapi.security import OAuth2PasswordRequestForm
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from app.core.config import settings
|
||||
from app.core.settings_manager import SettingsManager
|
||||
from app.core.database import get_db
|
||||
from app.core.rate_limit import limiter
|
||||
from app.core.security import (
|
||||
@@ -595,6 +596,15 @@ async def reset_password(
|
||||
return {"message": "Password has been reset successfully"}
|
||||
|
||||
|
||||
@router.get("/email/verification-status")
|
||||
async def get_verification_status(
|
||||
db: Annotated[AsyncSession, Depends(get_db)]
|
||||
):
|
||||
"""Check if email verification is enabled on the platform."""
|
||||
enabled = await SettingsManager.get("email_verification_enabled", db, default=True)
|
||||
return {"enabled": enabled}
|
||||
|
||||
|
||||
@router.post("/email/send-verification")
|
||||
@limiter.limit("3/minute")
|
||||
async def send_verification_email(
|
||||
@@ -603,6 +613,13 @@ async def send_verification_email(
|
||||
db: Annotated[AsyncSession, Depends(get_db)]
|
||||
):
|
||||
"""Send an email verification link to the current user."""
|
||||
verification_enabled = await SettingsManager.get("email_verification_enabled", db, default=True)
|
||||
if not verification_enabled:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Email verification is currently disabled"
|
||||
)
|
||||
|
||||
if current_user.email_verified_at is not None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
|
||||
@@ -59,6 +59,11 @@ export const authApi = {
|
||||
return response.data
|
||||
},
|
||||
|
||||
async getVerificationStatus(): Promise<{ enabled: boolean }> {
|
||||
const response = await apiClient.get<{ enabled: boolean }>('/auth/email/verification-status')
|
||||
return response.data
|
||||
},
|
||||
|
||||
async sendVerificationEmail(): Promise<void> {
|
||||
await apiClient.post('/auth/email/send-verification')
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState } from 'react'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { AlertTriangle, X, Loader2 } from 'lucide-react'
|
||||
import { authApi } from '@/api/auth'
|
||||
import { useAuthStore } from '@/store/authStore'
|
||||
@@ -9,8 +9,15 @@ export function EmailVerificationBanner() {
|
||||
const user = useAuthStore((s) => s.user)
|
||||
const [dismissed, setDismissed] = useState(false)
|
||||
const [isSending, setIsSending] = useState(false)
|
||||
const [verificationEnabled, setVerificationEnabled] = useState(true)
|
||||
|
||||
if (!user || user.email_verified_at || dismissed) return null
|
||||
useEffect(() => {
|
||||
authApi.getVerificationStatus()
|
||||
.then((data) => setVerificationEnabled(data.enabled))
|
||||
.catch(() => {})
|
||||
}, [])
|
||||
|
||||
if (!user || user.email_verified_at || dismissed || !verificationEnabled) return null
|
||||
|
||||
const handleResend = async () => {
|
||||
setIsSending(true)
|
||||
|
||||
@@ -18,6 +18,7 @@ export function SettingsPage() {
|
||||
|
||||
const maintenanceMode = Boolean(settings.maintenance_mode)
|
||||
const maintenanceMessage = String(settings.maintenance_message || '')
|
||||
const emailVerificationEnabled = settings.email_verification_enabled !== false
|
||||
|
||||
const handleSave = async () => {
|
||||
setSaving(true)
|
||||
@@ -46,6 +47,27 @@ export function SettingsPage() {
|
||||
<PageHeader title="Platform Settings" description="Global platform configuration" />
|
||||
|
||||
<div className="max-w-xl space-y-6 bg-card border border-border rounded-xl p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="font-medium text-foreground">Email Verification</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
When enabled, unverified users see a banner prompting them to verify their email.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setSettings({ ...settings, email_verification_enabled: !emailVerificationEnabled })}
|
||||
className={cn(
|
||||
'h-6 w-10 rounded-full transition-colors',
|
||||
emailVerificationEnabled ? 'bg-gradient-brand' : 'bg-accent'
|
||||
)}
|
||||
>
|
||||
<div className={cn(
|
||||
'h-4 w-4 rounded-full bg-white transition-transform',
|
||||
emailVerificationEnabled ? 'translate-x-5' : 'translate-x-1'
|
||||
)} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="font-medium text-foreground">Maintenance Mode</h3>
|
||||
|
||||
Reference in New Issue
Block a user