login fixes
This commit is contained in:
@@ -39,6 +39,7 @@ interface ContributionDay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function RightBarContent() {
|
export function RightBarContent() {
|
||||||
|
const { setLeftBarVisible } = useBars();
|
||||||
const [githubCommits, setGithubCommits] = createSignal<GitCommit[]>([]);
|
const [githubCommits, setGithubCommits] = createSignal<GitCommit[]>([]);
|
||||||
const [giteaCommits, setGiteaCommits] = createSignal<GitCommit[]>([]);
|
const [giteaCommits, setGiteaCommits] = createSignal<GitCommit[]>([]);
|
||||||
const [githubActivity, setGithubActivity] = createSignal<ContributionDay[]>(
|
const [githubActivity, setGithubActivity] = createSignal<ContributionDay[]>(
|
||||||
@@ -47,6 +48,12 @@ export function RightBarContent() {
|
|||||||
const [giteaActivity, setGiteaActivity] = createSignal<ContributionDay[]>([]);
|
const [giteaActivity, setGiteaActivity] = createSignal<ContributionDay[]>([]);
|
||||||
const [loading, setLoading] = createSignal(true);
|
const [loading, setLoading] = createSignal(true);
|
||||||
|
|
||||||
|
const handleLinkClick = () => {
|
||||||
|
if (typeof window !== "undefined" && window.innerWidth < 768) {
|
||||||
|
setLeftBarVisible(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
// Fetch all data client-side only to avoid hydration mismatch
|
// Fetch all data client-side only to avoid hydration mismatch
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
@@ -83,7 +90,9 @@ export function RightBarContent() {
|
|||||||
<Typewriter keepAlive={false} class="z-50 px-4 md:pt-4">
|
<Typewriter keepAlive={false} class="z-50 px-4 md:pt-4">
|
||||||
<ul class="flex flex-col gap-4">
|
<ul class="flex flex-col gap-4">
|
||||||
<li class="hover:text-subtext0 w-fit transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-110 hover:font-bold">
|
<li class="hover:text-subtext0 w-fit transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-110 hover:font-bold">
|
||||||
<a href="/contact">Contact Me</a>
|
<a href="/contact" onClick={handleLinkClick}>
|
||||||
|
Contact Me
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
@@ -114,6 +123,7 @@ export function RightBarContent() {
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="/resume"
|
href="/resume"
|
||||||
|
onClick={handleLinkClick}
|
||||||
class="hover:text-subtext0 flex items-center gap-3 transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-105"
|
class="hover:text-subtext0 flex items-center gap-3 transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-105"
|
||||||
>
|
>
|
||||||
<span class="shaker rounded-full p-2">
|
<span class="shaker rounded-full p-2">
|
||||||
@@ -177,6 +187,12 @@ export function LeftBar() {
|
|||||||
const [isMounted, setIsMounted] = createSignal(false);
|
const [isMounted, setIsMounted] = createSignal(false);
|
||||||
const [signOutLoading, setSignOutLoading] = createSignal(false);
|
const [signOutLoading, setSignOutLoading] = createSignal(false);
|
||||||
|
|
||||||
|
const handleLinkClick = () => {
|
||||||
|
if (typeof window !== "undefined" && window.innerWidth < 768) {
|
||||||
|
setLeftBarVisible(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleSignOut = async () => {
|
const handleSignOut = async () => {
|
||||||
setSignOutLoading(true);
|
setSignOutLoading(true);
|
||||||
try {
|
try {
|
||||||
@@ -337,7 +353,9 @@ export function LeftBar() {
|
|||||||
<div class="flex h-full flex-col overflow-y-auto">
|
<div class="flex h-full flex-col overflow-y-auto">
|
||||||
<Typewriter speed={10} keepAlive={10000} class="z-50 pr-8 pl-4">
|
<Typewriter speed={10} keepAlive={10000} class="z-50 pr-8 pl-4">
|
||||||
<h3 class="hover:text-subtext0 w-fit pt-6 text-center text-3xl underline transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-105">
|
<h3 class="hover:text-subtext0 w-fit pt-6 text-center text-3xl underline transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-105">
|
||||||
<a href="/">{formatDomainName(env.VITE_DOMAIN)}</a>
|
<a href="/" onClick={handleLinkClick}>
|
||||||
|
{formatDomainName(env.VITE_DOMAIN)}
|
||||||
|
</a>
|
||||||
</h3>
|
</h3>
|
||||||
</Typewriter>
|
</Typewriter>
|
||||||
|
|
||||||
@@ -368,6 +386,7 @@ export function LeftBar() {
|
|||||||
{(post) => (
|
{(post) => (
|
||||||
<a
|
<a
|
||||||
href={`/blog/${post.title}`}
|
href={`/blog/${post.title}`}
|
||||||
|
onClick={handleLinkClick}
|
||||||
class="hover:text-subtext0 block transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-105 hover:font-bold"
|
class="hover:text-subtext0 block transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-105 hover:font-bold"
|
||||||
>
|
>
|
||||||
<Typewriter class="flex flex-col" keepAlive={false}>
|
<Typewriter class="flex flex-col" keepAlive={false}>
|
||||||
@@ -402,17 +421,25 @@ export function LeftBar() {
|
|||||||
<Typewriter keepAlive={false}>
|
<Typewriter keepAlive={false}>
|
||||||
<ul class="flex flex-col gap-4 py-6">
|
<ul class="flex flex-col gap-4 py-6">
|
||||||
<li class="hover:text-subtext0 w-fit transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-110 hover:font-bold">
|
<li class="hover:text-subtext0 w-fit transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-110 hover:font-bold">
|
||||||
<a href="/">Home</a>
|
<a href="/" onClick={handleLinkClick}>
|
||||||
|
Home
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="hover:text-subtext0 w-fit transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-110 hover:font-bold">
|
<li class="hover:text-subtext0 w-fit transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-110 hover:font-bold">
|
||||||
<a href="/blog">Blog</a>
|
<a href="/blog" onClick={handleLinkClick}>
|
||||||
|
Blog
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="hover:text-subtext0 w-fit transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-110 hover:font-bold">
|
<li class="hover:text-subtext0 w-fit transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-110 hover:font-bold">
|
||||||
<Show
|
<Show
|
||||||
when={isMounted() && userInfo()?.isAuthenticated}
|
when={isMounted() && userInfo()?.isAuthenticated}
|
||||||
fallback={<a href="/login">Login</a>}
|
fallback={
|
||||||
|
<a href="/login" onClick={handleLinkClick}>
|
||||||
|
Login
|
||||||
|
</a>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<a href="/account">
|
<a href="/account" onClick={handleLinkClick}>
|
||||||
Account
|
Account
|
||||||
<Show when={userInfo()?.email}>
|
<Show when={userInfo()?.email}>
|
||||||
<span class="text-subtext0 text-sm font-normal">
|
<span class="text-subtext0 text-sm font-normal">
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ export default function PostForm(props: PostFormProps) {
|
|||||||
props.initialData?.body
|
props.initialData?.body
|
||||||
);
|
);
|
||||||
const [hasSaved, setHasSaved] = createSignal(props.mode === "edit");
|
const [hasSaved, setHasSaved] = createSignal(props.mode === "edit");
|
||||||
|
const [createdPostId, setCreatedPostId] = createSignal<number | undefined>(
|
||||||
|
props.postId
|
||||||
|
);
|
||||||
|
|
||||||
// Mark initial load as complete after data is loaded (for edit mode)
|
// Mark initial load as complete after data is loaded (for edit mode)
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
@@ -75,12 +78,13 @@ export default function PostForm(props: PostFormProps) {
|
|||||||
)) as string;
|
)) as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.mode === "edit") {
|
if (props.mode === "edit" || createdPostId()) {
|
||||||
|
// Update existing post (either in edit mode or if already created)
|
||||||
await api.database.updatePost.mutate({
|
await api.database.updatePost.mutate({
|
||||||
id: props.postId!,
|
id: createdPostId() || props.postId!,
|
||||||
title: titleVal.replaceAll(" ", "_"),
|
title: titleVal.replaceAll(" ", "_"),
|
||||||
subtitle: subtitle() || "",
|
subtitle: subtitle() || "",
|
||||||
body: body() || "",
|
body: body() || "Hello, World!",
|
||||||
banner_photo:
|
banner_photo:
|
||||||
bannerImageKey !== ""
|
bannerImageKey !== ""
|
||||||
? bannerImageKey
|
? bannerImageKey
|
||||||
@@ -92,21 +96,20 @@ export default function PostForm(props: PostFormProps) {
|
|||||||
author_id: props.userID
|
author_id: props.userID
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Create mode: only save once
|
// Create mode: only save once (first autosave)
|
||||||
if (!hasSaved()) {
|
const result = await api.database.createPost.mutate({
|
||||||
await api.database.createPost.mutate({
|
|
||||||
category: "blog",
|
category: "blog",
|
||||||
title: titleVal.replaceAll(" ", "_"),
|
title: titleVal.replaceAll(" ", "_"),
|
||||||
subtitle: subtitle() || null,
|
subtitle: subtitle() || null,
|
||||||
body: body() || null,
|
body: body() || "Hello, World!",
|
||||||
banner_photo: bannerImageKey !== "" ? bannerImageKey : null,
|
banner_photo: bannerImageKey !== "" ? bannerImageKey : null,
|
||||||
published: published(),
|
published: published(),
|
||||||
tags: tags().length > 0 ? tags() : null,
|
tags: tags().length > 0 ? tags() : null,
|
||||||
author_id: props.userID
|
author_id: props.userID
|
||||||
});
|
});
|
||||||
|
setCreatedPostId(result.data as number);
|
||||||
setHasSaved(true);
|
setHasSaved(true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
showAutoSaveTrigger();
|
showAutoSaveTrigger();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -216,12 +219,13 @@ export default function PostForm(props: PostFormProps) {
|
|||||||
)) as string;
|
)) as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.mode === "edit") {
|
if (props.mode === "edit" || createdPostId()) {
|
||||||
|
// Update existing post (either in edit mode or if autosave created it)
|
||||||
await api.database.updatePost.mutate({
|
await api.database.updatePost.mutate({
|
||||||
id: props.postId!,
|
id: createdPostId() || props.postId!,
|
||||||
title: title().replaceAll(" ", "_"),
|
title: title().replaceAll(" ", "_"),
|
||||||
subtitle: subtitle() || null,
|
subtitle: subtitle() || null,
|
||||||
body: body() || null,
|
body: body() || "Hello, World!",
|
||||||
banner_photo:
|
banner_photo:
|
||||||
bannerImageKey !== ""
|
bannerImageKey !== ""
|
||||||
? bannerImageKey
|
? bannerImageKey
|
||||||
@@ -233,16 +237,18 @@ export default function PostForm(props: PostFormProps) {
|
|||||||
author_id: props.userID
|
author_id: props.userID
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await api.database.createPost.mutate({
|
// Create new post
|
||||||
|
const result = await api.database.createPost.mutate({
|
||||||
category: "blog",
|
category: "blog",
|
||||||
title: title().replaceAll(" ", "_"),
|
title: title().replaceAll(" ", "_"),
|
||||||
subtitle: subtitle() || null,
|
subtitle: subtitle() || null,
|
||||||
body: body() || null,
|
body: body() || "Hello, World!",
|
||||||
banner_photo: bannerImageKey !== "" ? bannerImageKey : null,
|
banner_photo: bannerImageKey !== "" ? bannerImageKey : null,
|
||||||
published: published(),
|
published: published(),
|
||||||
tags: tags().length > 0 ? tags() : null,
|
tags: tags().length > 0 ? tags() : null,
|
||||||
author_id: props.userID
|
author_id: props.userID
|
||||||
});
|
});
|
||||||
|
setCreatedPostId(result.data as number);
|
||||||
}
|
}
|
||||||
|
|
||||||
navigate(`/blog/${encodeURIComponent(title().replaceAll(" ", "_"))}`);
|
navigate(`/blog/${encodeURIComponent(title().replaceAll(" ", "_"))}`);
|
||||||
|
|||||||
@@ -1382,7 +1382,7 @@ export default function TextEditor(props: TextEditorProps) {
|
|||||||
{/* Language Selector Dropdown */}
|
{/* Language Selector Dropdown */}
|
||||||
<Show when={showLanguageSelector()}>
|
<Show when={showLanguageSelector()}>
|
||||||
<div
|
<div
|
||||||
class="language-selector bg-mantle text-text border-surface2 fixed z-[110] max-h-64 w-48 overflow-y-auto rounded border shadow-lg"
|
class="language-selector bg-mantle text-text border-surface2 fixed z-110 max-h-64 w-48 overflow-y-auto rounded border shadow-lg"
|
||||||
style={{
|
style={{
|
||||||
top: `${languageSelectorPosition().top}px`,
|
top: `${languageSelectorPosition().top}px`,
|
||||||
left: `${languageSelectorPosition().left}px`
|
left: `${languageSelectorPosition().left}px`
|
||||||
@@ -1405,7 +1405,7 @@ export default function TextEditor(props: TextEditorProps) {
|
|||||||
{/* Table Grid Selector */}
|
{/* Table Grid Selector */}
|
||||||
<Show when={showTableMenu()}>
|
<Show when={showTableMenu()}>
|
||||||
<div
|
<div
|
||||||
class="table-menu fixed z-[110]"
|
class="table-menu fixed z-110"
|
||||||
style={{
|
style={{
|
||||||
top: `${tableMenuPosition().top}px`,
|
top: `${tableMenuPosition().top}px`,
|
||||||
left: `${tableMenuPosition().left}px`
|
left: `${tableMenuPosition().left}px`
|
||||||
@@ -1418,7 +1418,7 @@ export default function TextEditor(props: TextEditorProps) {
|
|||||||
{/* Mermaid Template Selector */}
|
{/* Mermaid Template Selector */}
|
||||||
<Show when={showMermaidTemplates()}>
|
<Show when={showMermaidTemplates()}>
|
||||||
<div
|
<div
|
||||||
class="mermaid-menu bg-mantle text-text border-surface2 fixed z-[110] max-h-96 w-56 overflow-y-auto rounded border shadow-lg"
|
class="mermaid-menu bg-mantle text-text border-surface2 fixed z-110 max-h-96 w-56 overflow-y-auto rounded border shadow-lg"
|
||||||
style={{
|
style={{
|
||||||
top: `${mermaidMenuPosition().top}px`,
|
top: `${mermaidMenuPosition().top}px`,
|
||||||
left: `${mermaidMenuPosition().left}px`
|
left: `${mermaidMenuPosition().left}px`
|
||||||
@@ -1897,11 +1897,11 @@ export default function TextEditor(props: TextEditorProps) {
|
|||||||
{/* Keyboard Help Modal */}
|
{/* Keyboard Help Modal */}
|
||||||
<Show when={showKeyboardHelp()}>
|
<Show when={showKeyboardHelp()}>
|
||||||
<div
|
<div
|
||||||
class="bg-opacity-50 fixed inset-0 z-[110] flex items-center justify-center bg-black"
|
class="bg-opacity-50 fixed inset-0 z-110 flex items-center justify-center bg-black"
|
||||||
onClick={() => setShowKeyboardHelp(false)}
|
onClick={() => setShowKeyboardHelp(false)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="bg-base border-surface2 max-h-[80vh] w-full max-w-2xl overflow-y-auto rounded-lg border p-6 shadow-2xl"
|
class="bg-base border-surface2 max-h-[80dvh] w-full max-w-2xl overflow-y-auto rounded-lg border p-6 shadow-2xl"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import CountdownCircleTimer from "~/components/CountdownCircleTimer";
|
|||||||
import Eye from "~/components/icons/Eye";
|
import Eye from "~/components/icons/Eye";
|
||||||
import EyeSlash from "~/components/icons/EyeSlash";
|
import EyeSlash from "~/components/icons/EyeSlash";
|
||||||
import { validatePassword } from "~/lib/validation";
|
import { validatePassword } from "~/lib/validation";
|
||||||
|
import { api } from "~/lib/api";
|
||||||
|
|
||||||
export default function PasswordResetPage() {
|
export default function PasswordResetPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -67,32 +68,26 @@ export default function PasswordResetPage() {
|
|||||||
setPasswordChangeLoading(true);
|
setPasswordChangeLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch("/api/trpc/auth.resetPassword", {
|
const result = await api.auth.resetPassword.mutate({
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({
|
|
||||||
token: token,
|
token: token,
|
||||||
newPassword,
|
newPassword,
|
||||||
newPasswordConfirmation: newPasswordConf
|
newPasswordConfirmation: newPasswordConf
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await response.json();
|
if (result.success) {
|
||||||
|
|
||||||
if (response.ok && result.result?.data) {
|
|
||||||
setCountDown(true);
|
setCountDown(true);
|
||||||
} else {
|
} else {
|
||||||
const errorMsg = result.error?.message || "Failed to reset password";
|
setError("Failed to reset password");
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error("Password reset error:", err);
|
||||||
|
const errorMsg = err.message || "An error occurred. Please try again.";
|
||||||
if (errorMsg.includes("expired") || errorMsg.includes("token")) {
|
if (errorMsg.includes("expired") || errorMsg.includes("token")) {
|
||||||
setShowRequestNewEmail(true);
|
setShowRequestNewEmail(true);
|
||||||
setError("Token has expired");
|
setError("Token has expired");
|
||||||
} else {
|
} else {
|
||||||
setError(errorMsg);
|
setError(errorMsg);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Password reset error:", err);
|
|
||||||
setError("An error occurred. Please try again.");
|
|
||||||
} finally {
|
} finally {
|
||||||
setPasswordChangeLoading(false);
|
setPasswordChangeLoading(false);
|
||||||
}
|
}
|
||||||
@@ -192,27 +187,48 @@ export default function PasswordResetPage() {
|
|||||||
>
|
>
|
||||||
<div class="flex w-full max-w-md flex-col justify-center px-4">
|
<div class="flex w-full max-w-md flex-col justify-center px-4">
|
||||||
{/* New Password Input */}
|
{/* New Password Input */}
|
||||||
<div class="input-group relative mx-4">
|
<div class="flex justify-center">
|
||||||
|
<div class="input-group mx-4 flex">
|
||||||
<input
|
<input
|
||||||
ref={newPasswordRef}
|
ref={newPasswordRef}
|
||||||
name="newPassword"
|
name="newPassword"
|
||||||
type={showPasswordInput() ? "text" : "password"}
|
type={showPasswordInput() ? "text" : "password"}
|
||||||
required
|
required
|
||||||
|
autofocus
|
||||||
onInput={handleNewPasswordChange}
|
onInput={handleNewPasswordChange}
|
||||||
onBlur={handlePasswordBlur}
|
onBlur={handlePasswordBlur}
|
||||||
disabled={passwordChangeLoading()}
|
disabled={passwordChangeLoading()}
|
||||||
placeholder=" "
|
placeholder=" "
|
||||||
class="underlinedInput w-full bg-transparent pr-10"
|
class="underlinedInput bg-transparent"
|
||||||
/>
|
/>
|
||||||
<span class="bar"></span>
|
<span class="bar"></span>
|
||||||
<label class="underlinedInputLabel">New Password</label>
|
<label class="underlinedInputLabel">New Password</label>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowPasswordInput(!showPasswordInput())}
|
onClick={() => {
|
||||||
class="absolute top-2 right-0 text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200"
|
setShowPasswordInput(!showPasswordInput());
|
||||||
|
newPasswordRef?.focus();
|
||||||
|
}}
|
||||||
|
class="absolute mt-14 ml-60"
|
||||||
>
|
>
|
||||||
<Show when={showPasswordInput()} fallback={<Eye />}>
|
<Show
|
||||||
<EyeSlash />
|
when={showPasswordInput()}
|
||||||
|
fallback={
|
||||||
|
<EyeSlash
|
||||||
|
height={24}
|
||||||
|
width={24}
|
||||||
|
strokeWidth={1}
|
||||||
|
class="stroke-zinc-900 dark:stroke-white"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Eye
|
||||||
|
height={24}
|
||||||
|
width={24}
|
||||||
|
strokeWidth={1}
|
||||||
|
class="stroke-zinc-900 dark:stroke-white"
|
||||||
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -221,13 +237,14 @@ export default function PasswordResetPage() {
|
|||||||
<div
|
<div
|
||||||
class={`${
|
class={`${
|
||||||
showPasswordLengthWarning() ? "" : "opacity-0 select-none"
|
showPasswordLengthWarning() ? "" : "opacity-0 select-none"
|
||||||
} mt-2 text-center text-sm text-red-500 transition-opacity duration-200 ease-in-out`}
|
} text-center text-red-500 transition-opacity duration-200 ease-in-out`}
|
||||||
>
|
>
|
||||||
Password too short! Min Length: 8
|
Password too short! Min Length: 8
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Password Confirmation Input */}
|
{/* Password Confirmation Input */}
|
||||||
<div class="input-group relative mx-4 mt-6">
|
<div class="-mt-4 flex justify-center">
|
||||||
|
<div class="input-group mx-4 flex">
|
||||||
<input
|
<input
|
||||||
ref={newPasswordConfRef}
|
ref={newPasswordConfRef}
|
||||||
name="newPasswordConf"
|
name="newPasswordConf"
|
||||||
@@ -236,19 +253,38 @@ export default function PasswordResetPage() {
|
|||||||
required
|
required
|
||||||
disabled={passwordChangeLoading()}
|
disabled={passwordChangeLoading()}
|
||||||
placeholder=" "
|
placeholder=" "
|
||||||
class="underlinedInput w-full bg-transparent pr-10"
|
class="underlinedInput bg-transparent"
|
||||||
/>
|
/>
|
||||||
<span class="bar"></span>
|
<span class="bar"></span>
|
||||||
<label class="underlinedInputLabel">Password Confirmation</label>
|
<label class="underlinedInputLabel">
|
||||||
|
Password Confirmation
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() => {
|
||||||
setShowPasswordConfInput(!showPasswordConfInput())
|
setShowPasswordConfInput(!showPasswordConfInput());
|
||||||
}
|
newPasswordConfRef?.focus();
|
||||||
class="absolute top-2 right-0 text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200"
|
}}
|
||||||
|
class="absolute mt-14 ml-60"
|
||||||
>
|
>
|
||||||
<Show when={showPasswordConfInput()} fallback={<Eye />}>
|
<Show
|
||||||
<EyeSlash />
|
when={showPasswordConfInput()}
|
||||||
|
fallback={
|
||||||
|
<EyeSlash
|
||||||
|
height={24}
|
||||||
|
width={24}
|
||||||
|
strokeWidth={1}
|
||||||
|
class="stroke-zinc-900 dark:stroke-white"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Eye
|
||||||
|
height={24}
|
||||||
|
width={24}
|
||||||
|
strokeWidth={1}
|
||||||
|
class="stroke-zinc-900 dark:stroke-white"
|
||||||
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -262,7 +298,7 @@ export default function PasswordResetPage() {
|
|||||||
newPasswordConfRef.value.length >= 6
|
newPasswordConfRef.value.length >= 6
|
||||||
? ""
|
? ""
|
||||||
: "opacity-0 select-none"
|
: "opacity-0 select-none"
|
||||||
} mt-2 text-center text-sm text-red-500 transition-opacity duration-200 ease-in-out`}
|
} text-center text-red-500 transition-opacity duration-200 ease-in-out`}
|
||||||
>
|
>
|
||||||
Passwords do not match!
|
Passwords do not match!
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -600,8 +600,8 @@ export const authRouter = createTRPCRouter({
|
|||||||
|
|
||||||
const conn = ConnectionFactory();
|
const conn = ConnectionFactory();
|
||||||
const res = await conn.execute({
|
const res = await conn.execute({
|
||||||
sql: "SELECT * FROM User WHERE email = ? AND provider = ?",
|
sql: "SELECT * FROM User WHERE email = ?",
|
||||||
args: [email, "email"]
|
args: [email]
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.rows.length === 0) {
|
if (res.rows.length === 0) {
|
||||||
@@ -629,6 +629,17 @@ export const authRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If provider is unknown/null, update it to "email" since they're logging in with password
|
||||||
|
if (
|
||||||
|
!user.provider ||
|
||||||
|
!["email", "google", "github", "apple"].includes(user.provider)
|
||||||
|
) {
|
||||||
|
await conn.execute({
|
||||||
|
sql: "UPDATE User SET provider = ? WHERE id = ?",
|
||||||
|
args: ["email", user.id]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Create JWT token with appropriate expiry
|
// Create JWT token with appropriate expiry
|
||||||
const expiresIn = rememberMe ? "14d" : "12h";
|
const expiresIn = rememberMe ? "14d" : "12h";
|
||||||
const token = await createJWT(user.id, expiresIn);
|
const token = await createJWT(user.id, expiresIn);
|
||||||
@@ -940,10 +951,37 @@ export const authRouter = createTRPCRouter({
|
|||||||
const conn = ConnectionFactory();
|
const conn = ConnectionFactory();
|
||||||
const passwordHash = await hashPassword(newPassword);
|
const passwordHash = await hashPassword(newPassword);
|
||||||
|
|
||||||
|
// Get user to check current provider
|
||||||
|
const userRes = await conn.execute({
|
||||||
|
sql: "SELECT provider FROM User WHERE id = ?",
|
||||||
|
args: [payload.id]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (userRes.rows.length === 0) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
message: "User not found"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentProvider = (userRes.rows[0] as any).provider;
|
||||||
|
|
||||||
|
// Only update provider to "email" if it's null, undefined, or not a known OAuth provider
|
||||||
|
if (
|
||||||
|
!currentProvider ||
|
||||||
|
!["google", "github", "apple"].includes(currentProvider)
|
||||||
|
) {
|
||||||
|
await conn.execute({
|
||||||
|
sql: "UPDATE User SET password_hash = ?, provider = ? WHERE id = ?",
|
||||||
|
args: [passwordHash, "email", payload.id]
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Keep existing OAuth provider, just update password
|
||||||
await conn.execute({
|
await conn.execute({
|
||||||
sql: "UPDATE User SET password_hash = ? WHERE id = ?",
|
sql: "UPDATE User SET password_hash = ? WHERE id = ?",
|
||||||
args: [passwordHash, payload.id]
|
args: [passwordHash, payload.id]
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Clear any session cookies
|
// Clear any session cookies
|
||||||
setCookie(ctx.event.nativeEvent, "emailToken", "", {
|
setCookie(ctx.event.nativeEvent, "emailToken", "", {
|
||||||
|
|||||||
Reference in New Issue
Block a user