From 090c0d1c3c1b6bf2f569eb4c9e1164523f048319 Mon Sep 17 00:00:00 2001
From: Mohamed Bassem <me@mbassem.com>
Date: Sat, 7 Jun 2025 15:11:29 +0000
Subject: feat(web): Redesign the user settings page

---
 apps/web/components/settings/ChangePassword.tsx | 225 ++++++++++++++++--------
 1 file changed, 150 insertions(+), 75 deletions(-)

(limited to 'apps/web/components/settings/ChangePassword.tsx')

diff --git a/apps/web/components/settings/ChangePassword.tsx b/apps/web/components/settings/ChangePassword.tsx
index f8c2b8dd..703b9c16 100644
--- a/apps/web/components/settings/ChangePassword.tsx
+++ b/apps/web/components/settings/ChangePassword.tsx
@@ -1,6 +1,7 @@
 "use client";
 
 import type { z } from "zod";
+import { useState } from "react";
 import { ActionButton } from "@/components/ui/action-button";
 import {
   Form,
@@ -15,12 +16,19 @@ import { toast } from "@/components/ui/use-toast";
 import { useTranslation } from "@/lib/i18n/client";
 import { api } from "@/lib/trpc";
 import { zodResolver } from "@hookform/resolvers/zod";
+import { Eye, EyeOff, Lock } from "lucide-react";
 import { useForm } from "react-hook-form";
 
 import { zChangePasswordSchema } from "@karakeep/shared/types/users";
 
+import { Button } from "../ui/button";
+import { Card, CardContent, CardHeader, CardTitle } from "../ui/card";
+
 export function ChangePassword() {
   const { t } = useTranslation();
+  const [showCurrentPassword, setShowCurrentPassword] = useState(false);
+  const [showNewPassword, setShowNewPassword] = useState(false);
+  const [showConfirmPassword, setShowConfirmPassword] = useState(false);
   const form = useForm<z.infer<typeof zChangePasswordSchema>>({
     resolver: zodResolver(zChangePasswordSchema),
     defaultValues: {
@@ -55,83 +63,150 @@ export function ChangePassword() {
   }
 
   return (
-    <div className="flex flex-col sm:flex-row">
-      <div className="mb-4 w-full text-lg font-medium sm:w-1/3">
-        {t("settings.info.change_password")}
-      </div>
-      <Form {...form}>
-        <form
-          onSubmit={form.handleSubmit(onSubmit)}
-          className="flex w-full flex-col gap-2"
-        >
-          <FormField
-            control={form.control}
-            name="currentPassword"
-            render={({ field }) => {
-              return (
-                <FormItem className="flex-1">
-                  <FormLabel>{t("settings.info.current_password")}</FormLabel>
-                  <FormControl>
-                    <Input
-                      type="password"
-                      placeholder={t("settings.info.current_password")}
-                      {...field}
-                    />
-                  </FormControl>
-                  <FormMessage />
-                </FormItem>
-              );
-            }}
-          />
-          <FormField
-            control={form.control}
-            name="newPassword"
-            render={({ field }) => {
-              return (
-                <FormItem className="flex-1">
-                  <FormLabel>{t("settings.info.new_password")}</FormLabel>
-                  <FormControl>
-                    <Input
-                      type="password"
-                      placeholder={t("settings.info.new_password")}
-                      {...field}
-                    />
-                  </FormControl>
-                  <FormMessage />
-                </FormItem>
-              );
-            }}
-          />
-          <FormField
-            control={form.control}
-            name="newPasswordConfirm"
-            render={({ field }) => {
-              return (
-                <FormItem className="flex-1">
-                  <FormLabel>
-                    {t("settings.info.confirm_new_password")}
+    <Card>
+      <CardHeader>
+        <CardTitle className="flex items-center gap-2">
+          <Lock className="h-5 w-5" />
+          Security
+        </CardTitle>
+      </CardHeader>
+      <CardContent className="space-y-6">
+        <Form {...form}>
+          <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
+            <FormField
+              control={form.control}
+              name="currentPassword"
+              render={({ field }) => (
+                <FormItem className="space-y-2">
+                  <FormLabel
+                    htmlFor="current-password"
+                    className="text-sm font-medium"
+                  >
+                    {t("settings.info.current_password")}
                   </FormLabel>
-                  <FormControl>
-                    <Input
-                      type="Password"
-                      placeholder={t("settings.info.confirm_new_password")}
-                      {...field}
-                    />
-                  </FormControl>
+                  <div className="relative">
+                    <FormControl>
+                      <Input
+                        id="current-password"
+                        type={showCurrentPassword ? "text" : "password"}
+                        placeholder={t("settings.info.current_password")}
+                        className="h-11 pr-10"
+                        {...field}
+                      />
+                    </FormControl>
+                    <Button
+                      type="button"
+                      variant="ghost"
+                      size="sm"
+                      className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
+                      onClick={() =>
+                        setShowCurrentPassword(!showCurrentPassword)
+                      }
+                    >
+                      {showCurrentPassword ? (
+                        <EyeOff className="h-4 w-4" />
+                      ) : (
+                        <Eye className="h-4 w-4" />
+                      )}
+                    </Button>
+                  </div>
                   <FormMessage />
                 </FormItem>
-              );
-            }}
-          />
-          <ActionButton
-            className="mt-4 h-10 w-max px-8"
-            type="submit"
-            loading={mutator.isPending}
-          >
-            {t("actions.save")}
-          </ActionButton>
-        </form>
-      </Form>
-    </div>
+              )}
+            />
+
+            <div className="grid gap-4 md:grid-cols-2">
+              <FormField
+                control={form.control}
+                name="newPassword"
+                render={({ field }) => (
+                  <FormItem className="space-y-2">
+                    <FormLabel
+                      htmlFor="new-password"
+                      className="text-sm font-medium"
+                    >
+                      {t("settings.info.new_password")}
+                    </FormLabel>
+                    <div className="relative">
+                      <FormControl>
+                        <Input
+                          id="new-password"
+                          type={showNewPassword ? "text" : "password"}
+                          placeholder={t("settings.info.new_password")}
+                          className="h-11 pr-10"
+                          {...field}
+                        />
+                      </FormControl>
+                      <Button
+                        type="button"
+                        variant="ghost"
+                        size="sm"
+                        className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
+                        onClick={() => setShowNewPassword(!showNewPassword)}
+                      >
+                        {showNewPassword ? (
+                          <EyeOff className="h-4 w-4" />
+                        ) : (
+                          <Eye className="h-4 w-4" />
+                        )}
+                      </Button>
+                    </div>
+                    <FormMessage />
+                  </FormItem>
+                )}
+              />
+
+              <FormField
+                control={form.control}
+                name="newPasswordConfirm"
+                render={({ field }) => (
+                  <FormItem className="space-y-2">
+                    <FormLabel
+                      htmlFor="confirm-password"
+                      className="text-sm font-medium"
+                    >
+                      {t("settings.info.confirm_new_password")}
+                    </FormLabel>
+                    <div className="relative">
+                      <FormControl>
+                        <Input
+                          id="confirm-password"
+                          type={showConfirmPassword ? "text" : "password"}
+                          placeholder={t("settings.info.confirm_new_password")}
+                          className="h-11 pr-10"
+                          {...field}
+                        />
+                      </FormControl>
+                      <Button
+                        type="button"
+                        variant="ghost"
+                        size="sm"
+                        className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
+                        onClick={() =>
+                          setShowConfirmPassword(!showConfirmPassword)
+                        }
+                      >
+                        {showConfirmPassword ? (
+                          <EyeOff className="h-4 w-4" />
+                        ) : (
+                          <Eye className="h-4 w-4" />
+                        )}
+                      </Button>
+                    </div>
+                    <FormMessage />
+                  </FormItem>
+                )}
+              />
+            </div>
+
+            <div className="flex justify-end">
+              <ActionButton type="submit" loading={mutator.isPending}>
+                {t("actions.save")}
+              </ActionButton>
+            </div>
+          </form>
+        </Form>
+      </CardContent>
+    </Card>
   );
 }
-- 
cgit v1.2.3-70-g09d2

