diff --git a/apps/web/src/components/Dashboard.tsx b/apps/web/src/components/Dashboard.tsx index c6f2eac2..fd267877 100644 --- a/apps/web/src/components/Dashboard.tsx +++ b/apps/web/src/components/Dashboard.tsx @@ -1,6 +1,7 @@ -import { useTheme } from "next-themes"; import { useRouter } from "next/navigation"; +import { t } from "@lingui/core/macro"; import { env } from "next-runtime-env"; +import { useTheme } from "next-themes"; import { useEffect, useRef, useState } from "react"; import { TbLayoutSidebarLeftCollapse, @@ -8,7 +9,6 @@ import { TbLayoutSidebarRightCollapse, TbLayoutSidebarRightExpand, } from "react-icons/tb"; -import { t } from "@lingui/core/macro"; import { authClient } from "@kan/auth/client"; @@ -16,10 +16,10 @@ import { useClickOutside } from "~/hooks/useClickOutside"; import { useModal } from "~/providers/modal"; import { useWorkspace, WorkspaceProvider } from "~/providers/workspace"; import { api } from "~/utils/api"; +import { ChangePasswordFormConfirmation } from "~/views/settings/components/ChangePasswordConfirmation"; import Button from "./Button"; import Modal from "./modal"; import SideNavigation from "./SideNavigation"; -import { ChangePasswordFormConfirmation } from "~/views/settings/components/ChangePasswordConfirmation"; interface DashboardProps { children: React.ReactNode; @@ -106,7 +106,9 @@ export default function Dashboard({ useEffect(() => { if (hasLoaded && availableWorkspaces.length === 0) { if (env("NEXT_PUBLIC_KAN_ENV") === "cloud") { - router.push(`/onboarding/select-plan?returnUrl=${encodeURIComponent(window.location.pathname)}`); + router.push( + `/onboarding/select-plan?returnUrl=${encodeURIComponent(window.location.pathname)}`, + ); } else { openModal("NEW_WORKSPACE", undefined, undefined, false); } @@ -123,7 +125,6 @@ export default function Dashboard({ isCredentialsEnabled && user.hasMagicLinkAccount && !user.hasPassword && - typeof window !== "undefined" && !sessionStorage.getItem("set_password_prompted") ) { sessionStorage.setItem("set_password_prompted", "1"); @@ -226,14 +227,22 @@ export default function Dashboard({ - + {user?.hasPassword ? (
-

{t`Password already set`}

+

{t`Password already set`}

{t`Your account already has a password. You can change it from your account settings.`}

-
diff --git a/apps/web/src/views/settings/components/ChangePasswordConfirmation.tsx b/apps/web/src/views/settings/components/ChangePasswordConfirmation.tsx index a22c5df4..541ccfc1 100644 --- a/apps/web/src/views/settings/components/ChangePasswordConfirmation.tsx +++ b/apps/web/src/views/settings/components/ChangePasswordConfirmation.tsx @@ -39,11 +39,11 @@ const buildSchema = (hasPassword: boolean) => { ); }; -type FormValues = { +interface FormValues { currentPassword?: string; newPassword: string; confirmPassword: string; -}; +} interface Props { hasPassword: boolean; @@ -97,13 +97,11 @@ export function ChangePasswordFormConfirmation({ hasPassword }: Props) { icon: "success", }); - // Clear the session prompt flag so future magic link logins - // don't re-show the set-password modal (password is now set) - if (!hasPassword && typeof window !== "undefined") { + if (!hasPassword) { sessionStorage.removeItem("set_password_prompted"); } - utils.invalidate(); + utils.user.getUser.invalidate(); reset(); router.push("/"); }, @@ -118,7 +116,9 @@ export function ChangePasswordFormConfirmation({ hasPassword }: Props) { } else { closeModal(); showPopup({ - header: hasPassword ? t`Error Changing Password` : t`Error Setting Password`, + header: hasPassword + ? t`Error Changing Password` + : t`Error Setting Password`, message: t`An unexpected error occurred. Please try again later.`, icon: "error", }); @@ -138,7 +138,7 @@ export function ChangePasswordFormConfirmation({ hasPassword }: Props) { return (
-

+

{hasPassword ? t`Change Password` : t`Set Password`}

diff --git a/packages/api/src/routers/user.ts b/packages/api/src/routers/user.ts index 918231fb..ce56e710 100644 --- a/packages/api/src/routers/user.ts +++ b/packages/api/src/routers/user.ts @@ -2,9 +2,9 @@ import { TRPCError } from "@trpc/server"; import { z } from "zod"; import * as userRepo from "@kan/db/repository/user.repo"; +import { generateAvatarUrl } from "@kan/shared/utils"; import { createTRPCRouter, protectedProcedure } from "../trpc"; -import { generateAvatarUrl } from "@kan/shared/utils"; export const userRouter = createTRPCRouter({ getUser: protectedProcedure @@ -119,6 +119,17 @@ export const userRouter = createTRPCRouter({ }; }), setPassword: protectedProcedure + .meta({ + openapi: { + method: "POST", + path: "/users/me/password", + summary: "Set password", + description: + "Sets a password for a user who signed up via magic link and has no password yet", + tags: ["Users"], + protect: true, + }, + }) .input( z.object({ newPassword: z @@ -152,7 +163,14 @@ export const userRouter = createTRPCRouter({ }); } - await ctx.auth.api.setPassword({ newPassword: input.newPassword }); + try { + await ctx.auth.api.setPassword({ newPassword: input.newPassword }); + } catch { + throw new TRPCError({ + message: "Failed to set password", + code: "INTERNAL_SERVER_ERROR", + }); + } return { success: true }; }), diff --git a/packages/db/src/repository/user.repo.ts b/packages/db/src/repository/user.repo.ts index d191848c..38e6cc39 100644 --- a/packages/db/src/repository/user.repo.ts +++ b/packages/db/src/repository/user.repo.ts @@ -4,6 +4,9 @@ import { v4 as uuidv4 } from "uuid"; import type { dbClient } from "@kan/db/client"; import { account, apikey, users } from "@kan/db/schema"; +const PROVIDER_CREDENTIAL = "credential"; +const PROVIDER_MAGIC_LINK = "magic-link"; + export const getCount = async (db: dbClient) => { const result = await db.select({ count: count() }).from(users); @@ -39,7 +42,7 @@ export const getById = async (db: dbClient, userId: string) => { .where( and( eq(account.userId, userId), - eq(account.providerId, "credential"), + eq(account.providerId, PROVIDER_CREDENTIAL), isNotNull(account.password), ), ) @@ -50,7 +53,7 @@ export const getById = async (db: dbClient, userId: string) => { .where( and( eq(account.userId, userId), - eq(account.providerId, "magic-link"), + eq(account.providerId, PROVIDER_MAGIC_LINK), ), ) .limit(1),