mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-08-23 23:02:17 +10:00
refactor: ponytail audit
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { m } from "motion/react";
|
||||
|
||||
type ActionButtonProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export function ActionButton({ children }: ActionButtonProps) {
|
||||
return (
|
||||
<m.div
|
||||
className="will-change-transform"
|
||||
whileHover={{ y: -1, scale: 1.01 }}
|
||||
whileTap={{ scale: 0.99 }}
|
||||
transition={{ duration: 0.14, ease: "easeOut" }}
|
||||
>
|
||||
{children}
|
||||
</m.div>
|
||||
);
|
||||
}
|
||||
@@ -21,9 +21,7 @@ export function PasskeysSection() {
|
||||
});
|
||||
|
||||
const registerPasskeyMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
return await authClient.passkey.addPasskey();
|
||||
},
|
||||
mutationFn: () => authClient.passkey.addPasskey(),
|
||||
onSuccess: async ({ data, error }) => {
|
||||
if (error) {
|
||||
toast.error(
|
||||
@@ -77,9 +75,7 @@ export function PasskeysSection() {
|
||||
});
|
||||
|
||||
const deletePasskeyMutation = useMutation({
|
||||
mutationFn: async (id: string) => {
|
||||
return await authClient.passkey.deletePasskey({ id });
|
||||
},
|
||||
mutationFn: (id: string) => authClient.passkey.deletePasskey({ id }),
|
||||
onSuccess: async ({ error }) => {
|
||||
if (error) {
|
||||
toast.error(
|
||||
|
||||
@@ -1,41 +1,18 @@
|
||||
import { Trans } from "@lingui/react/macro";
|
||||
import { PasswordIcon, PencilSimpleLineIcon } from "@phosphor-icons/react";
|
||||
import { Link, useNavigate } from "@tanstack/react-router";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
import { m } from "motion/react";
|
||||
import { useCallback } from "react";
|
||||
import { Button } from "@reactive-resume/ui/components/button";
|
||||
import { useDialogStore } from "@/dialogs/store";
|
||||
import { ActionButton } from "./action-button";
|
||||
import { useAuthAccounts } from "./hooks";
|
||||
|
||||
// ponytail: m.div wrapper is identical for both branches — extracted, match(boolean) removed
|
||||
function ActionButton({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<m.div
|
||||
className="will-change-transform"
|
||||
whileHover={{ y: -1, scale: 1.01 }}
|
||||
whileTap={{ scale: 0.99 }}
|
||||
transition={{ duration: 0.14, ease: "easeOut" }}
|
||||
>
|
||||
{children}
|
||||
</m.div>
|
||||
);
|
||||
}
|
||||
|
||||
export function PasswordSection() {
|
||||
const navigate = useNavigate();
|
||||
const { openDialog } = useDialogStore();
|
||||
const { hasAccount } = useAuthAccounts();
|
||||
|
||||
const hasPassword = hasAccount("credential");
|
||||
|
||||
const handleUpdatePassword = useCallback(() => {
|
||||
if (hasPassword) {
|
||||
openDialog("auth.change-password", undefined);
|
||||
} else {
|
||||
void navigate({ to: "/auth/forgot-password" });
|
||||
}
|
||||
}, [hasPassword, navigate, openDialog]);
|
||||
|
||||
return (
|
||||
<m.div
|
||||
initial={{ y: -20 }}
|
||||
@@ -50,7 +27,7 @@ export function PasswordSection() {
|
||||
|
||||
<ActionButton>
|
||||
{hasPassword ? (
|
||||
<Button variant="outline" onClick={handleUpdatePassword}>
|
||||
<Button variant="outline" onClick={() => openDialog("auth.change-password", undefined)}>
|
||||
<PencilSimpleLineIcon />
|
||||
<Trans>Update Password</Trans>
|
||||
</Button>
|
||||
|
||||
@@ -2,25 +2,11 @@ import type { AuthProvider } from "@reactive-resume/auth/types";
|
||||
import { Trans } from "@lingui/react/macro";
|
||||
import { LinkBreakIcon, LinkIcon } from "@phosphor-icons/react";
|
||||
import { m } from "motion/react";
|
||||
import { useCallback } from "react";
|
||||
import { Button } from "@reactive-resume/ui/components/button";
|
||||
import { Separator } from "@reactive-resume/ui/components/separator";
|
||||
import { ActionButton } from "./action-button";
|
||||
import { getProviderIcon, getProviderName, useAuthAccounts, useAuthProviderActions } from "./hooks";
|
||||
|
||||
// ponytail: shared hover/tap wrapper — identical in both branches, match(boolean) removed
|
||||
function ActionButton({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<m.div
|
||||
className="will-change-transform"
|
||||
whileHover={{ y: -1, scale: 1.01 }}
|
||||
whileTap={{ scale: 0.99 }}
|
||||
transition={{ duration: 0.14, ease: "easeOut" }}
|
||||
>
|
||||
{children}
|
||||
</m.div>
|
||||
);
|
||||
}
|
||||
|
||||
type SocialProviderSectionProps = {
|
||||
provider: AuthProvider;
|
||||
name?: string;
|
||||
@@ -37,15 +23,6 @@ export function SocialProviderSection({ provider, name, animationDelay = 0 }: So
|
||||
const account = getAccountByProviderId(provider);
|
||||
const isConnected = hasAccount(provider);
|
||||
|
||||
const handleLink = useCallback(async () => {
|
||||
await link(provider);
|
||||
}, [link, provider]);
|
||||
|
||||
const handleUnlink = useCallback(async () => {
|
||||
if (!account?.accountId) return;
|
||||
await unlink(provider, account.accountId);
|
||||
}, [account, unlink, provider]);
|
||||
|
||||
return (
|
||||
<m.div
|
||||
className="will-change-[transform,opacity]"
|
||||
@@ -63,14 +40,19 @@ export function SocialProviderSection({ provider, name, animationDelay = 0 }: So
|
||||
|
||||
<ActionButton>
|
||||
{isConnected ? (
|
||||
<Button variant="outline" onClick={handleUnlink}>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
if (account?.accountId) void unlink(provider, account.accountId);
|
||||
}}
|
||||
>
|
||||
<LinkBreakIcon />
|
||||
<Trans comment="Authentication settings action to unlink a connected social login provider">
|
||||
Disconnect
|
||||
</Trans>
|
||||
</Button>
|
||||
) : (
|
||||
<Button variant="outline" onClick={handleLink}>
|
||||
<Button variant="outline" onClick={() => void link(provider)}>
|
||||
<LinkIcon />
|
||||
<Trans comment="Authentication settings action to link a social login provider">Connect</Trans>
|
||||
</Button>
|
||||
|
||||
@@ -1,27 +1,13 @@
|
||||
import { Trans } from "@lingui/react/macro";
|
||||
import { KeyIcon, LockOpenIcon, ToggleLeftIcon, ToggleRightIcon } from "@phosphor-icons/react";
|
||||
import { m } from "motion/react";
|
||||
import { useCallback } from "react";
|
||||
import { Button } from "@reactive-resume/ui/components/button";
|
||||
import { Separator } from "@reactive-resume/ui/components/separator";
|
||||
import { useDialogStore } from "@/dialogs/store";
|
||||
import { authClient } from "@/libs/auth/client";
|
||||
import { ActionButton } from "./action-button";
|
||||
import { useAuthAccounts } from "./hooks";
|
||||
|
||||
// ponytail: shared hover/tap wrapper — identical in both branches, match(boolean) removed
|
||||
function ActionButton({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<m.div
|
||||
className="will-change-transform"
|
||||
whileHover={{ y: -1, scale: 1.01 }}
|
||||
whileTap={{ scale: 0.99 }}
|
||||
transition={{ duration: 0.14, ease: "easeOut" }}
|
||||
>
|
||||
{children}
|
||||
</m.div>
|
||||
);
|
||||
}
|
||||
|
||||
export function TwoFactorSection() {
|
||||
const { openDialog } = useDialogStore();
|
||||
const { hasAccount } = useAuthAccounts();
|
||||
@@ -30,14 +16,6 @@ export function TwoFactorSection() {
|
||||
const hasPassword = hasAccount("credential");
|
||||
const hasTwoFactor = session?.user.twoFactorEnabled ?? false;
|
||||
|
||||
const handleTwoFactorAction = useCallback(() => {
|
||||
if (hasTwoFactor) {
|
||||
openDialog("auth.two-factor.disable", undefined);
|
||||
} else {
|
||||
openDialog("auth.two-factor.enable", undefined);
|
||||
}
|
||||
}, [hasTwoFactor, openDialog]);
|
||||
|
||||
if (!hasPassword) return null;
|
||||
|
||||
return (
|
||||
@@ -56,7 +34,10 @@ export function TwoFactorSection() {
|
||||
</h2>
|
||||
|
||||
<ActionButton>
|
||||
<Button variant="outline" onClick={handleTwoFactorAction}>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => openDialog(hasTwoFactor ? "auth.two-factor.disable" : "auth.two-factor.enable", undefined)}
|
||||
>
|
||||
{hasTwoFactor ? (
|
||||
<>
|
||||
<ToggleLeftIcon />
|
||||
|
||||
@@ -6,12 +6,13 @@ import { orpc } from "@/libs/orpc/client";
|
||||
* Replaces the predicate that was duplicated across the import dialog, agent setup, and AI settings.
|
||||
*/
|
||||
export function useHasUsableAiProvider() {
|
||||
const { data: providers, isLoading } = useQuery(orpc.aiProviders.list.queryOptions());
|
||||
const { data: providers, isLoading, error } = useQuery(orpc.aiProviders.list.queryOptions());
|
||||
const usableProviders = (providers ?? []).filter((provider) => provider.enabled && provider.testStatus === "success");
|
||||
|
||||
return {
|
||||
error,
|
||||
hasUsableProvider: usableProviders.length > 0,
|
||||
usableProviders,
|
||||
isLoading,
|
||||
usableProviders,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user