♻️ Use safe action (#1832)

This commit is contained in:
Luke Vella 2025-07-16 15:00:01 +01:00 committed by GitHub
parent c9b5527432
commit 08ce80fb8d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 61 additions and 61 deletions

View file

@ -0,0 +1,23 @@
"use server";
import { prisma } from "@rallly/database";
import { revalidateTag } from "next/cache";
import { instanceSettingsTag } from "@/features/instance-settings/constants";
import { instanceSettingsSchema } from "@/features/instance-settings/schema";
import { adminActionClient } from "@/features/safe-action/server";
export const updateInstanceSettingsAction = adminActionClient
.metadata({
actionName: "update_instance_settings",
})
.inputSchema(instanceSettingsSchema)
.action(async ({ parsedInput }) => {
await prisma.instanceSettings.update({
where: {
id: 1,
},
data: parsedInput,
});
revalidateTag(instanceSettingsTag);
});

View file

@ -26,10 +26,11 @@ import {
SettingsGroupTitle, SettingsGroupTitle,
} from "@/components/settings-group"; } from "@/components/settings-group";
import { Trans } from "@/components/trans"; import { Trans } from "@/components/trans";
import { updateInstanceSettings } from "@/features/instance-settings/mutations";
import type { InstanceSettings } from "@/features/instance-settings/schema"; import type { InstanceSettings } from "@/features/instance-settings/schema";
import { instanceSettingsSchema } from "@/features/instance-settings/schema"; import { instanceSettingsSchema } from "@/features/instance-settings/schema";
import { useSafeAction } from "@/features/safe-action/client";
import { useTranslation } from "@/i18n/client"; import { useTranslation } from "@/i18n/client";
import { updateInstanceSettingsAction } from "./actions";
export function InstanceSettingsForm({ export function InstanceSettingsForm({
defaultValue, defaultValue,
@ -41,6 +42,8 @@ export function InstanceSettingsForm({
resolver: zodResolver(instanceSettingsSchema), resolver: zodResolver(instanceSettingsSchema),
}); });
const updateInstanceSettings = useSafeAction(updateInstanceSettingsAction);
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
@ -49,7 +52,7 @@ export function InstanceSettingsForm({
name="instance-settings-form" name="instance-settings-form"
onSubmit={form.handleSubmit(async (data) => { onSubmit={form.handleSubmit(async (data) => {
try { try {
await updateInstanceSettings(data); await updateInstanceSettings.executeAsync(data);
form.reset(data); form.reset(data);
} catch (error) { } catch (error) {
console.error(error); console.error(error);

View file

@ -1,35 +1,37 @@
"use server"; "use server";
import { requireUser } from "@/auth/queries";
import type { Feedback } from "@/features/feedback/schema";
import { feedbackSchema } from "@/features/feedback/schema"; import { feedbackSchema } from "@/features/feedback/schema";
import { authActionClient } from "@/features/safe-action/server";
import { getEmailClient } from "@/utils/emails"; import { getEmailClient } from "@/utils/emails";
import { rateLimit } from "../rate-limit"; import { rateLimit } from "../rate-limit";
export const submitFeedback = async (formData: Feedback) => { export const submitFeedbackAction = authActionClient
const { success } = await rateLimit("submitFeedback", 3, "1h"); .metadata({
actionName: "submitFeedback",
})
.inputSchema(feedbackSchema)
.action(async ({ ctx, parsedInput }) => {
const { success } = await rateLimit("submitFeedback", 3, "1h");
if (!success) { if (!success) {
return { return {
error: "Rate limit exceeded" as const, error: "Rate limit exceeded" as const,
}; };
} }
const user = await requireUser(); try {
try { const { content } = parsedInput;
const { content } = feedbackSchema.parse(formData); getEmailClient().sendEmail({
getEmailClient().sendEmail({ to: "feedback@rallly.co",
to: "feedback@rallly.co", subject: "Feedback",
subject: "Feedback", text: `User: ${ctx.user.name} (${ctx.user.email})\n\n${content}`,
text: `User: ${user.name} (${user.email})\n\n${content}`, });
}); return {
return { success: true,
success: true, };
}; } catch {
} catch { return {
return { error: "Invalid Form Data" as const,
error: "Invalid Form Data" as const, };
}; }
} });
};

View file

@ -24,12 +24,14 @@ import { useForm } from "react-hook-form";
import { Trans } from "@/components/trans"; import { Trans } from "@/components/trans";
import { useSafeAction } from "@/features/safe-action/client";
import { isSelfHosted } from "@/utils/constants"; import { isSelfHosted } from "@/utils/constants";
import { submitFeedback } from "../actions"; import { submitFeedbackAction } from "../actions";
import type { Feedback } from "../schema"; import type { Feedback } from "../schema";
import { feedbackSchema } from "../schema"; import { feedbackSchema } from "../schema";
export function FeedbackToggle() { export function FeedbackToggle() {
const submitFeedback = useSafeAction(submitFeedbackAction);
const form = useForm<Feedback>({ const form = useForm<Feedback>({
resolver: zodResolver(feedbackSchema), resolver: zodResolver(feedbackSchema),
}); });
@ -72,17 +74,7 @@ export function FeedbackToggle() {
</DialogHeader> </DialogHeader>
<Form {...form}> <Form {...form}>
<form <form onSubmit={form.handleSubmit(submitFeedback.executeAsync)}>
onSubmit={form.handleSubmit(async (data) => {
const res = await submitFeedback(data);
if (res.error) {
form.setError("content", {
message: res.error,
});
}
})}
>
<FormField <FormField
control={form.control} control={form.control}
name="content" name="content"

View file

@ -1,20 +0,0 @@
"use server";
import type { InstanceSettings } from "@rallly/database";
import { prisma } from "@rallly/database";
import { revalidateTag } from "next/cache";
import { requireAdmin } from "@/auth/queries";
import { instanceSettingsTag } from "./constants";
export async function updateInstanceSettings(data: Partial<InstanceSettings>) {
await requireAdmin();
await prisma.instanceSettings.update({
where: {
id: 1,
},
data,
});
revalidateTag(instanceSettingsTag);
}