feat: password change flow improvement, design improvements, css cleanup, move to shadcn canary

This commit is contained in:
Aidan 2025-04-10 21:15:38 -04:00
parent 916a4757aa
commit 8af9d140da
6 changed files with 233 additions and 178 deletions

View File

@ -1,71 +1,71 @@
@import 'tailwindcss';
@import "tw-animate-css";
@custom-variant dark (&:is(.dark *));
@theme {
--color-border: hsl(var(--border));
--color-input: hsl(var(--input));
--color-ring: hsl(var(--ring));
--color-background: hsl(var(--background));
--color-foreground: hsl(var(--foreground));
--color-background: oklch(var(--background));
--color-foreground: oklch(var(--foreground));
--color-card: oklch(var(--card));
--color-card-foreground: oklch(var(--card-foreground));
--color-popover: oklch(var(--popover));
--color-popover-foreground: oklch(var(--popover-foreground));
--color-primary: oklch(var(--primary));
--color-primary-foreground: oklch(var(--primary-foreground));
--color-secondary: oklch(var(--secondary));
--color-secondary-foreground: oklch(var(--secondary-foreground));
--color-muted: oklch(var(--muted));
--color-muted-foreground: oklch(var(--muted-foreground));
--color-accent: oklch(var(--accent));
--color-accent-foreground: oklch(var(--accent-foreground));
--color-destructive: oklch(var(--destructive));
--color-destructive-foreground: oklch(var(--destructive-foreground));
--color-border: oklch(var(--border));
--color-input: oklch(var(--input));
--color-ring: oklch(var(--ring));
--color-primary: hsl(var(--primary));
--color-primary-foreground: hsl(var(--primary-foreground));
/* Sidebar */
--color-sidebar: oklch(var(--sidebar));
--color-sidebar-foreground: oklch(var(--sidebar-foreground));
--color-sidebar-primary: oklch(var(--sidebar-primary));
--color-sidebar-primary-foreground: oklch(var(--sidebar-primary-foreground));
--color-sidebar-accent: oklch(var(--sidebar-accent));
--color-sidebar-accent-foreground: oklch(var(--sidebar-accent-foreground));
--color-sidebar-border: oklch(var(--sidebar-border));
--color-sidebar-ring: oklch(var(--sidebar-ring));
--color-secondary: hsl(var(--secondary));
--color-secondary-foreground: hsl(var(--secondary-foreground));
/* Chart */
--color-chart-1: oklch(var(--chart-1));
--color-chart-2: oklch(var(--chart-2));
--color-chart-3: oklch(var(--chart-3));
--color-chart-4: oklch(var(--chart-4));
--color-chart-5: oklch(var(--chart-5));
--color-destructive: hsl(var(--destructive));
--color-destructive-foreground: hsl(var(--destructive-foreground));
--color-muted: hsl(var(--muted));
--color-muted-foreground: hsl(var(--muted-foreground));
--color-accent: hsl(var(--accent));
--color-accent-foreground: hsl(var(--accent-foreground));
--color-popover: hsl(var(--popover));
--color-popover-foreground: hsl(var(--popover-foreground));
--color-card: hsl(var(--card));
--color-card-foreground: hsl(var(--card-foreground));
--color-sidebar: hsl(var(--sidebar-background));
--color-sidebar-foreground: hsl(var(--sidebar-foreground));
--color-sidebar-primary: hsl(var(--sidebar-primary));
--color-sidebar-primary-foreground: hsl(var(--sidebar-primary-foreground));
--color-sidebar-accent: hsl(var(--sidebar-accent));
--color-sidebar-accent-foreground: hsl(var(--sidebar-accent-foreground));
--color-sidebar-border: hsl(var(--sidebar-border));
--color-sidebar-ring: hsl(var(--sidebar-ring));
--radius-lg: var(--radius);
--radius-md: calc(var(--radius) - 2px);
/* Border radius */
--radius: 0.625rem;
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--font-sans:
var(--font-sans), ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji',
/* Typography */
--font-sans: var(--font-sans), ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji',
'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
/* Animations */
--animate-accordion-down: accordion-down 0.2s ease-out;
--animate-accordion-up: accordion-up 0.2s ease-out;
}
@keyframes accordion-down {
from {
height: 0;
}
to {
height: var(--radix-accordion-content-height);
}
}
@keyframes accordion-up {
from {
height: var(--radix-accordion-content-height);
}
to {
height: 0;
}
}
/* Keyframes */
@keyframes accordion-down {
from { height: 0; }
to { height: var(--radix-accordion-content-height); }
}
@keyframes accordion-up {
from { height: var(--radix-accordion-content-height); }
to { height: 0; }
}
@utility container {
@ -79,14 +79,6 @@
}
}
/*
The default border color has changed to `currentColor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
@ -95,103 +87,90 @@
::file-selector-button {
border-color: var(--color-gray-200, currentColor);
}
}
@layer base {
:root {
--background: 30 20% 98%;
--foreground: 222.2 84% 4.9%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--accent: 210 60% 95%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--ring: 215 20.2% 65.1%;
--radius: 0.5rem;
--sidebar-background: 215 25% 97%;
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 215 10% 92%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 88%;
--sidebar-ring: 217.2 91.2% 59.8%;
--background: 1 0 0;
--foreground: 0.129 0.042 264.695;
--card: 1 0 0;
--card-foreground: 0.129 0.042 264.695;
--popover: 1 0 0;
--popover-foreground: 0.129 0.042 264.695;
--primary: 0.208 0.042 265.755;
--primary-foreground: 0.984 0.003 247.858;
--secondary: 0.968 0.007 247.896;
--secondary-foreground: 0.208 0.042 265.755;
--muted: 0.968 0.007 247.896;
--muted-foreground: 0.554 0.046 257.417;
--accent: 0.968 0.007 247.896;
--accent-foreground: 0.208 0.042 265.755;
--destructive: 0.577 0.245 27.325;
--destructive-foreground: 0.984 0.003 247.858;
--border: 0.929 0.013 255.508;
--input: 0.929 0.013 255.508;
--ring: 0.704 0.04 256.788;
/* Chart */
--chart-1: 0.646 0.222 41.116;
--chart-2: 0.6 0.118 184.704;
--chart-3: 0.398 0.07 227.392;
--chart-4: 0.828 0.189 84.429;
--chart-5: 0.769 0.188 70.08;
/* Sidebar */
--sidebar: 0.984 0.003 247.858;
--sidebar-foreground: 0.129 0.042 264.695;
--sidebar-primary: 0.208 0.042 265.755;
--sidebar-primary-foreground: 0.984 0.003 247.858;
--sidebar-accent: 0.968 0.007 247.896;
--sidebar-accent-foreground: 0.208 0.042 265.755;
--sidebar-border: 0.929 0.013 255.508;
--sidebar-ring: 0.704 0.04 256.788;
}
/* Dark theme */
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 85.7% 97.3%;
--ring: 217.2 32.6% 17.5%;
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 224.3 76.3% 48%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%;
--background: 0.129 0.042 264.695;
--foreground: 0.984 0.003 247.858;
--card: 0.208 0.042 265.755;
--card-foreground: 0.984 0.003 247.858;
--popover: 0.208 0.042 265.755;
--popover-foreground: 0.984 0.003 247.858;
--primary: 0.929 0.013 255.508;
--primary-foreground: 0.208 0.042 265.755;
--secondary: 0.279 0.041 260.031;
--secondary-foreground: 0.984 0.003 247.858;
--muted: 0.279 0.041 260.031;
--muted-foreground: 0.704 0.04 256.788;
--accent: 0.279 0.041 260.031;
--accent-foreground: 0.984 0.003 247.858;
--destructive: 0.704 0.191 22.216;
--destructive-foreground: 0.984 0.003 247.858;
--border: 1 0 0 / 10%;
--input: 1 0 0 / 15%;
--ring: 0.551 0.027 264.364;
/* Chart */
--chart-1: 0.488 0.243 264.376;
--chart-2: 0.696 0.17 162.48;
--chart-3: 0.769 0.188 70.08;
--chart-4: 0.627 0.265 303.9;
--chart-5: 0.645 0.246 16.439;
/* Sidebar */
--sidebar: 0.208 0.042 265.755;
--sidebar-foreground: 0.984 0.003 247.858;
--sidebar-primary: 0.488 0.243 264.376;
--sidebar-primary-foreground: 0.984 0.003 247.858;
--sidebar-accent: 0.279 0.041 260.031;
--sidebar-accent-foreground: 0.984 0.003 247.858;
--sidebar-border: 1 0 0 / 10%;
--sidebar-ring: 0.551 0.027 264.364;
}
}
@layer base {
* {
@apply border-border;
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}

View File

@ -4,7 +4,7 @@
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"config": "",
"css": "app/globals.css",
"baseColor": "slate",
"cssVariables": true,

View File

@ -5,12 +5,23 @@ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/com
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
import { Label } from "@/components/ui/label"
import { Mail, Key, Loader2 } from "lucide-react"
import { Key, Loader2, User } from "lucide-react"
import Link from "next/link"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger
} from "@/components/ui/dialog"
export function ChangePassword() {
const [newPassword, setNewPassword] = useState("");
const [loading, setLoading] = useState(false);
const [message, setMessage] = useState<string | null>(null);
const [open, setOpen] = useState(false);
const handlePasswordChange = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
@ -29,6 +40,11 @@ export function ChangePassword() {
if (response.ok && resData.success) {
setMessage("Password Updated");
setLoading(false);
// Close dialog after change
setTimeout(() => {
setOpen(false);
setNewPassword("");
}, 1500);
} else if (resData.error) {
setMessage(resData.error);
setLoading(false);
@ -46,28 +62,63 @@ export function ChangePassword() {
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center"><Mail size={15} className="mr-1" /> Change Email Password</CardTitle>
<CardDescription>Please note, this will <b>NOT</b> change your Authentik password.</CardDescription>
{/* TODO: please tell me you added password resets to authentik by now */}
<CardTitle className="flex items-center text-2xl">
<User className="mr-1" />
My Account
</CardTitle>
<CardDescription>LibreCloud makes it easy to manage your account</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handlePasswordChange} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="new-password">New Password</Label>
<Input
id="new-password"
type="password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
className="mt-1.5"
/>
</div>
<Button type="submit" disabled={loading || newPassword.length < 8}>
{/* TODO: this should have the usual error message style */
loading ? <><Loader2 className="animate-spin" /> Changing...</> : <><Key /> Change Password</>}
</Button>
{message && <p className="text-sm text-center">{message}</p>}
</form>
<h2 className="text-lg font-bold">Actions</h2>
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button className="mt-2">
<Key />
Change Password
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Change Your Password</DialogTitle>
<DialogDescription>
<span className="font-bold mr-1">This only applies to your Authentik account.</span>
Make sure it's secure, and consider using
<Link
href="https://pass.librecloud.cc"
target="_blank"
className="ml-1 underline hover:text-primary transition-all"
>
LibreCloud Pass
</Link> to keep it safe.
</DialogDescription>
</DialogHeader>
<form onSubmit={handlePasswordChange} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="new-password">New Password</Label>
<Input
id="new-password"
type="password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
className="mt-1.5"
/>
<p className="text-xs text-muted-foreground">
Password must be at least 8 characters long.
</p>
</div>
{message && (
<p className={`text-sm text-center ${message.includes("Updated") ? "text-green-500" : "text-red-500"}`}>
{message}
</p>
)}
<DialogFooter>
<Button type="submit" disabled={loading || newPassword.length < 8}>
{loading ? <><Loader2 className="animate-spin mr-2" /> Changing...</> : <><Key className="mr-2" /> Change Password</>}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
</CardContent>
</Card>
);

View File

@ -98,8 +98,9 @@ export function LinkGitea({ linked }: { linked: boolean }) {
return (
<Card>
<CardHeader>
<CardTitle>
Link Gitea Account
<CardTitle className="flex items-center text-2xl">
<SiGitea className="mr-2" />
Gitea Link
</CardTitle>
<CardDescription>
To link your Gitea account to your LibreCloud account, add your p0ntus mail account to your Gitea account, then click the button.
@ -164,7 +165,10 @@ export function LinkGitea({ linked }: { linked: boolean }) {
return (
<Card>
<CardHeader>
<CardTitle>Git Link</CardTitle>
<CardTitle className="flex items-center text-2xl">
<SiGitea className="mr-2" />
Gitea Link
</CardTitle>
<CardDescription>
Your Gitea account is currently linked to your LibreCloud account.
</CardDescription>
@ -178,7 +182,12 @@ export function LinkGitea({ linked }: { linked: boolean }) {
</Alert>
)}
<p className="text-sm mb-4">
Unlinking your Gitea account will not delete your Gitea account. You can delete your Gitea account <Link href="https://try.gitea.com/user/sign_up" target="_blank" className="underline hover:text-muted-foreground">here</Link>.
Unlinking your Gitea account will not delete your Gitea account. You can delete your Gitea account
<Link
href="https://git.pontusmail.org/user/settings/account"
target="_blank"
className="underline hover:text-muted-foreground transition-all ml-1"
>here</Link>.
</p>
{unlinkLoading ? (
<Button variant="destructive" disabled>

View File

@ -3,7 +3,8 @@ import { Button } from "@/components/ui/button"
import {
Mail,
Headset,
Heart
Heart,
Scale
} from "lucide-react"
import Link from "next/link"
@ -26,6 +27,7 @@ export const QuickLinks = () => {
</Button>
</Link>
<Link
target="_blank"
href="https://mail.librecloud.cc"
>
<Button
@ -37,6 +39,7 @@ export const QuickLinks = () => {
</Button>
</Link>
<Link
target="_blank"
href={process.env.NEXT_PUBLIC_DONATE_URL || "https://donate.stripe.com/6oE8yxaPk6yXbpS145"}
>
<Button
@ -47,6 +50,18 @@ export const QuickLinks = () => {
Donate
</Button>
</Link>
<Link
target="_blank"
href="/legal"
>
<Button
variant="secondary"
className="w-full mb-2 cursor-pointer"
>
<Scale />
Legal
</Button>
</Link>
</CardContent>
</Card>
);

View File

@ -32,7 +32,7 @@
"framer-motion": "^12.6.3",
"geist": "^1.3.1",
"js-cookie": "^3.0.5",
"lucide-react": "^0.474.0",
"lucide-react": "^0.487.0",
"motion": "^12.6.3",
"next": "^15.3.0",
"next-auth": "^5.0.0-beta.25",
@ -45,7 +45,8 @@
"react-hook-form": "^7.55.0",
"react-icons": "^5.5.0",
"react-typed": "^2.0.12",
"tailwind-merge": "^2.6.0",
"tailwind-merge": "^3.2.0",
"tw-animate-css": "^1.2.5",
"validator": "^13.15.0",
"zod": "^3.24.2"
},