feat: Slate & Ice Modern aesthetic redesign #94

Merged
chihlasm merged 19 commits from feat/slate-ice-redesign into main 2026-03-05 01:44:25 +00:00
4 changed files with 53 additions and 2 deletions
Showing only changes of commit 993814521a - Show all commits

View File

@@ -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,

View File

@@ -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')
},

View File

@@ -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)

View File

@@ -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>