design: better mobile footer display, clean up welcome card + add link, add grading to security checks, clean up services display

This commit is contained in:
Aidan 2025-04-22 09:50:43 -04:00
parent de2a172013
commit 3d49aaad16
5 changed files with 99 additions and 34 deletions

View File

@ -190,12 +190,12 @@ export function LinkGitea({ linked }: { linked: boolean }) {
>here</Link>. >here</Link>.
</p> </p>
{unlinkLoading ? ( {unlinkLoading ? (
<Button variant="destructive" disabled> <Button variant="destructive" disabled className="w-full">
<Loader2 className="animate-spin" /> <Loader2 className="animate-spin" />
Unlinking... Unlinking...
</Button> </Button>
) : ( ) : (
<Button variant="destructive" onClick={onUnlink}> <Button variant="destructive" onClick={onUnlink} className="w-full">
<SiGitea /> <SiGitea />
Unlink Gitea Account Unlink Gitea Account
</Button> </Button>

View File

@ -1,25 +1,26 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react"
import Cookies from "js-cookie"; import Cookies from "js-cookie"
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button"
import { Check } from "lucide-react"; import { Check } from "lucide-react"
import Link from "next/link"
export const WelcomeCard = () => { export const WelcomeCard = () => {
const [visible, setVisible] = useState(true); const [visible, setVisible] = useState(true)
useEffect(() => { useEffect(() => {
const isRead = Cookies.get("welcome-read"); const isRead = Cookies.get("welcome-read")
if (isRead === "true") { if (isRead === "true") {
setVisible(false); setVisible(false)
} }
}, []); }, [])
const handleMarkAsRead = () => { const handleMarkAsRead = () => {
Cookies.set("welcome-read", "true"); Cookies.set("welcome-read", "true")
setVisible(false); setVisible(false)
}; }
if (!visible) return null; if (!visible) return null
return ( return (
<Card className="col-span-full md:col-span-1"> <Card className="col-span-full md:col-span-1">
@ -31,7 +32,7 @@ export const WelcomeCard = () => {
Thanks for logging in! Here you can manage your account and the services available to you. Thanks for logging in! Here you can manage your account and the services available to you.
</p> </p>
<p className="text-sm mt-4"> <p className="text-sm mt-4">
Were thrilled to have you on board, and if you need <i>anything</i>, dont hesitate to contact support (see the sidebar). Were thrilled to have you on board, and if you need <i>anything</i>, dont hesitate to <Link href="/account/dashboard/support" className="underline hover:text-muted-foreground transition-all" target="_blank">contact our support team</Link>!
</p> </p>
<p className="text-sm mt-4">Thats all, have a great day!</p> <p className="text-sm mt-4">Thats all, have a great day!</p>
</CardContent> </CardContent>
@ -41,5 +42,5 @@ export const WelcomeCard = () => {
</Button> </Button>
</CardFooter> </CardFooter>
</Card> </Card>
); )
}; }

View File

@ -27,11 +27,11 @@ export function Footer() {
</p> </p>
</div> </div>
{renderTime !== null ? ( {renderTime !== null ? (
<p className="text-sm"> <p className="hidden md:block text-sm">
Page rendered in {renderTime.toFixed(2)} ms Page rendered in {renderTime.toFixed(2)} ms
</p> </p>
) : ( ) : (
<p className="text-sm"> <p className="hidden md:block text-sm">
Calculating render time... Calculating render time...
</p> </p>
)} )}

View File

@ -1,14 +1,22 @@
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { AlertCircle, CheckCircleIcon, XCircleIcon, Loader2, ShieldCheck, Search, Lightbulb } from "lucide-react" import { AlertCircle, CheckCircleIcon, XCircleIcon, Loader2, ShieldCheck, Search, Lightbulb } from "lucide-react"
import { useState } from "react" import { useState, useEffect } from "react"
import { SiAuthentik } from "react-icons/si" import { SiAuthentik } from "react-icons/si"
import { type SecurityResults } from "@/app/api/users/security/route" import { type SecurityResults } from "@/app/api/users/security/route"
interface CategoryChecks {
passed: number
failed: number
total: number
}
export const SecurityTab = () => { export const SecurityTab = () => {
const [scanning, setScanning] = useState(false) const [scanning, setScanning] = useState(false)
const [scanResults, setScanResults] = useState<SecurityResults | null>(null) const [scanResults, setScanResults] = useState<SecurityResults | null>(null)
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
const [authentikChecks, setAuthentikChecks] = useState<CategoryChecks>({ passed: 0, failed: 0, total: 0 })
const [overallChecks, setOverallChecks] = useState<CategoryChecks>({ passed: 0, failed: 0, total: 0 })
const scanAcc = async () => { const scanAcc = async () => {
setScanning(true) setScanning(true)
@ -30,6 +38,41 @@ export const SecurityTab = () => {
// If user has no 2FA methods setup, this will be true // If user has no 2FA methods setup, this will be true
const insufficient2FA = scanResults?.authentik?.authenticators.length === 0 const insufficient2FA = scanResults?.authentik?.authenticators.length === 0
useEffect(() => {
if (scanResults) {
const authentik: CategoryChecks = {
passed: 0,
failed: 0,
total: 0
}
// Password age
authentik.total++
if (!shouldResetPass) {
authentik.passed++
} else {
authentik.failed++
}
// 2FA
authentik.total++
if (!insufficient2FA) {
authentik.passed++
} else {
authentik.failed++
}
setAuthentikChecks(authentik)
setOverallChecks(authentik)
}
}, [scanResults, shouldResetPass, insufficient2FA])
const calculatePercentage = (passed: number, total: number) => {
if (total === 0) return 0
return Math.round((passed / total) * 100)
}
return ( return (
<div className="grid gap-6 grid-cols-1 md:grid-cols-2"> <div className="grid gap-6 grid-cols-1 md:grid-cols-2">
<Card> <Card>
@ -51,9 +94,16 @@ export const SecurityTab = () => {
</div> </div>
) : scanResults ? ( ) : scanResults ? (
<> <>
<div className="flex items-center gap-2"> <div className="flex items-center justify-between mb-4">
<SiAuthentik size={20} /> <div className="flex items-center gap-2">
<h3 className="text-xl">Authentik</h3> <SiAuthentik size={20} />
<h3 className="text-xl">Authentik</h3>
</div>
<div className="flex items-center gap-2">
<span className="text-sm font-medium">
{authentikChecks.passed}/{authentikChecks.total} checks passed
</span>
</div>
</div> </div>
{shouldResetPass ? ( {shouldResetPass ? (
@ -79,6 +129,20 @@ export const SecurityTab = () => {
<p className="text-sm"><span className="font-bold">{scanResults?.authentik?.authenticators.length}</span> two-factor authentication methods setup</p> <p className="text-sm"><span className="font-bold">{scanResults?.authentik?.authenticators.length}</span> two-factor authentication methods setup</p>
</div> </div>
)} )}
<div className="mt-6 pt-4 border-t">
<div className="flex items-center justify-between">
<span className="text-sm font-medium">Results</span>
<div className="flex flex-col items-end">
<span className="text-2xl font-bold">
{calculatePercentage(overallChecks.passed, overallChecks.total)}%
</span>
<span className="text-sm text-muted-foreground">
{overallChecks.passed}/{overallChecks.total} checks passed
</span>
</div>
</div>
</div>
</> </>
) : ( ) : (
<> <>

View File

@ -15,9 +15,9 @@ export const ServicesTab = () => (
<CardDescription className="pt-4">Send, read, and manage your email account from a web browser! Powered by Roundcube and LibreCloud Mail.</CardDescription> <CardDescription className="pt-4">Send, read, and manage your email account from a web browser! Powered by Roundcube and LibreCloud Mail.</CardDescription>
</CardHeader> </CardHeader>
<CardFooter> <CardFooter>
<Button> <Button className="cursor-pointer">
<ExternalLink className="h-4 w-4" /> <ExternalLink className="h-4 w-4" />
<Link href="https://mail.librecloud.cc/"> <Link href="https://mail.librecloud.cc/" target="_blank">
Open App Open App
</Link> </Link>
</Button> </Button>
@ -33,9 +33,9 @@ export const ServicesTab = () => (
<CardDescription className="pt-4">Host unlimited repositories and run Actions on our Git server, powered by Gitea.</CardDescription> <CardDescription className="pt-4">Host unlimited repositories and run Actions on our Git server, powered by Gitea.</CardDescription>
</CardHeader> </CardHeader>
<CardFooter> <CardFooter>
<Button> <Button className="cursor-pointer">
<ExternalLink className="h-4 w-4" /> <ExternalLink className="h-4 w-4" />
<Link href="https://git.pontusmail.org/"> <Link href="https://git.pontusmail.org/" target="_blank">
Open App Open App
</Link> </Link>
</Button> </Button>
@ -51,9 +51,9 @@ export const ServicesTab = () => (
<CardDescription className="pt-4">Securely store your passwords, notes, and 2FA codes with Vaultwarden. Data is encrypted at rest.</CardDescription> <CardDescription className="pt-4">Securely store your passwords, notes, and 2FA codes with Vaultwarden. Data is encrypted at rest.</CardDescription>
</CardHeader> </CardHeader>
<CardFooter> <CardFooter>
<Button> <Button className="cursor-pointer">
<ExternalLink className="h-4 w-4" /> <ExternalLink className="h-4 w-4" />
<Link href="https://pass.librecloud.cc/"> <Link href="https://pass.librecloud.cc/" target="_blank">
Open App Open App
</Link> </Link>
</Button> </Button>
@ -69,9 +69,9 @@ export const ServicesTab = () => (
<CardDescription className="pt-4">Manage your single-sign-on account for all LibreCloud services.</CardDescription> <CardDescription className="pt-4">Manage your single-sign-on account for all LibreCloud services.</CardDescription>
</CardHeader> </CardHeader>
<CardFooter> <CardFooter>
<Button> <Button className="cursor-pointer">
<ExternalLink className="h-4 w-4" /> <ExternalLink className="h-4 w-4" />
<Link href="https://auth.librecloud.cc/"> <Link href="https://auth.librecloud.cc/" target="_blank">
Open App Open App
</Link> </Link>
</Button> </Button>
@ -87,9 +87,9 @@ export const ServicesTab = () => (
<CardDescription className="pt-4">Store, share, edit, and synchronize files with Nextcloud.</CardDescription> <CardDescription className="pt-4">Store, share, edit, and synchronize files with Nextcloud.</CardDescription>
</CardHeader> </CardHeader>
<CardFooter> <CardFooter>
<Button> <Button className="cursor-pointer">
<ExternalLink className="h-4 w-4" /> <ExternalLink className="h-4 w-4" />
<Link href="https://files.librecloud.cc/"> <Link href="https://files.librecloud.cc/" target="_blank">
Open App Open App
</Link> </Link>
</Button> </Button>