feat: Added visual confirmation when saving settings and moved button to the top right
This commit is contained in:
@@ -72,6 +72,7 @@ const fetchCredentials = async (service: "spotify" | "deezer"): Promise<Credenti
|
|||||||
export function DownloadsTab({ config, isLoading }: DownloadsTabProps) {
|
export function DownloadsTab({ config, isLoading }: DownloadsTabProps) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [validationError, setValidationError] = useState<string>("");
|
const [validationError, setValidationError] = useState<string>("");
|
||||||
|
const [saveStatus, setSaveStatus] = useState<"idle" | "success" | "error">("idle");
|
||||||
|
|
||||||
// Fetch watch config
|
// Fetch watch config
|
||||||
const { data: watchConfig } = useQuery({
|
const { data: watchConfig } = useQuery({
|
||||||
@@ -97,10 +98,14 @@ export function DownloadsTab({ config, isLoading }: DownloadsTabProps) {
|
|||||||
mutationFn: saveDownloadConfig,
|
mutationFn: saveDownloadConfig,
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success("Download settings saved successfully!");
|
toast.success("Download settings saved successfully!");
|
||||||
|
setSaveStatus("success");
|
||||||
|
setTimeout(() => setSaveStatus("idle"), 3000);
|
||||||
queryClient.invalidateQueries({ queryKey: ["config"] });
|
queryClient.invalidateQueries({ queryKey: ["config"] });
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
toast.error(`Failed to save settings: ${error.message}`);
|
toast.error(`Failed to save settings: ${error.message}`);
|
||||||
|
setSaveStatus("error");
|
||||||
|
setTimeout(() => setSaveStatus("idle"), 3000);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -173,6 +178,24 @@ export function DownloadsTab({ config, isLoading }: DownloadsTabProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-8">
|
<form onSubmit={handleSubmit(onSubmit)} className="space-y-8">
|
||||||
|
<div className="flex items-center justify-end mb-4">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
{saveStatus === "success" && (
|
||||||
|
<span className="text-success text-sm">Saved</span>
|
||||||
|
)}
|
||||||
|
{saveStatus === "error" && (
|
||||||
|
<span className="text-error text-sm">Save failed</span>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={mutation.isPending || !!validationError}
|
||||||
|
className="px-4 py-2 bg-button-primary hover:bg-button-primary-hover text-button-primary-text rounded-md disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{mutation.isPending ? "Saving..." : "Save Download Settings"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Download Settings */}
|
{/* Download Settings */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h3 className="text-xl font-semibold text-content-primary dark:text-content-primary-dark">Download Behavior</h3>
|
<h3 className="text-xl font-semibold text-content-primary dark:text-content-primary-dark">Download Behavior</h3>
|
||||||
@@ -360,14 +383,6 @@ export function DownloadsTab({ config, isLoading }: DownloadsTabProps) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
disabled={mutation.isPending || !!validationError}
|
|
||||||
className="px-4 py-2 bg-button-primary hover:bg-button-primary-hover text-button-primary-text rounded-md disabled:opacity-50"
|
|
||||||
>
|
|
||||||
{mutation.isPending ? "Saving..." : "Save Download Settings"}
|
|
||||||
</button>
|
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useRef } from "react";
|
import { useRef, useState } from "react";
|
||||||
import { useForm, type SubmitHandler } from "react-hook-form";
|
import { useForm, type SubmitHandler } from "react-hook-form";
|
||||||
import { authApiClient } from "../../lib/api-client";
|
import { authApiClient } from "../../lib/api-client";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@@ -79,15 +79,20 @@ export function FormattingTab({ config, isLoading }: FormattingTabProps) {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const dirInputRef = useRef<HTMLInputElement | null>(null);
|
const dirInputRef = useRef<HTMLInputElement | null>(null);
|
||||||
const trackInputRef = useRef<HTMLInputElement | null>(null);
|
const trackInputRef = useRef<HTMLInputElement | null>(null);
|
||||||
|
const [saveStatus, setSaveStatus] = useState<"idle" | "success" | "error">("idle");
|
||||||
|
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationFn: saveFormattingConfig,
|
mutationFn: saveFormattingConfig,
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success("Formatting settings saved!");
|
toast.success("Formatting settings saved!");
|
||||||
|
setSaveStatus("success");
|
||||||
|
setTimeout(() => setSaveStatus("idle"), 3000);
|
||||||
queryClient.invalidateQueries({ queryKey: ["config"] });
|
queryClient.invalidateQueries({ queryKey: ["config"] });
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
toast.error(`Failed to save settings: ${error.message}`);
|
toast.error(`Failed to save settings: ${error.message}`);
|
||||||
|
setSaveStatus("error");
|
||||||
|
setTimeout(() => setSaveStatus("idle"), 3000);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -120,6 +125,24 @@ export function FormattingTab({ config, isLoading }: FormattingTabProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-8">
|
<form onSubmit={handleSubmit(onSubmit)} className="space-y-8">
|
||||||
|
<div className="flex items-center justify-end mb-4">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
{saveStatus === "success" && (
|
||||||
|
<span className="text-success text-sm">Saved</span>
|
||||||
|
)}
|
||||||
|
{saveStatus === "error" && (
|
||||||
|
<span className="text-error text-sm">Save failed</span>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={mutation.isPending}
|
||||||
|
className="px-4 py-2 bg-button-primary hover:bg-button-primary-hover text-button-primary-text rounded-md disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{mutation.isPending ? "Saving..." : "Save Formatting Settings"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h3 className="text-xl font-semibold text-content-primary dark:text-content-primary-dark">File Naming</h3>
|
<h3 className="text-xl font-semibold text-content-primary dark:text-content-primary-dark">File Naming</h3>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
@@ -185,14 +208,6 @@ export function FormattingTab({ config, isLoading }: FormattingTabProps) {
|
|||||||
<input id="spotifyMetadataToggle" type="checkbox" {...register("spotifyMetadata")} className="h-6 w-6 rounded" />
|
<input id="spotifyMetadataToggle" type="checkbox" {...register("spotifyMetadata")} className="h-6 w-6 rounded" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
disabled={mutation.isPending}
|
|
||||||
className="px-4 py-2 bg-button-primary hover:bg-button-primary-hover text-button-primary-text rounded-md disabled:opacity-50"
|
|
||||||
>
|
|
||||||
{mutation.isPending ? "Saving..." : "Save Formatting Settings"}
|
|
||||||
</button>
|
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { authApiClient } from "../../lib/api-client";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { useSettings } from "../../contexts/settings-context";
|
import { useSettings } from "../../contexts/settings-context";
|
||||||
import { useEffect } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
// --- Type Definitions ---
|
// --- Type Definitions ---
|
||||||
interface Credential {
|
interface Credential {
|
||||||
@@ -56,13 +56,21 @@ export function GeneralTab({ config, isLoading: isConfigLoading }: GeneralTabPro
|
|||||||
}
|
}
|
||||||
}, [config, reset]);
|
}, [config, reset]);
|
||||||
|
|
||||||
|
const [saveStatus, setSaveStatus] = useState<"idle" | "success" | "error">("idle");
|
||||||
|
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationFn: saveGeneralConfig,
|
mutationFn: saveGeneralConfig,
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success("General settings saved!");
|
toast.success("General settings saved!");
|
||||||
|
setSaveStatus("success");
|
||||||
|
setTimeout(() => setSaveStatus("idle"), 3000);
|
||||||
queryClient.invalidateQueries({ queryKey: ["config"] });
|
queryClient.invalidateQueries({ queryKey: ["config"] });
|
||||||
},
|
},
|
||||||
onError: (e: Error) => toast.error(`Failed to save: ${e.message}`),
|
onError: (e: Error) => {
|
||||||
|
toast.error(`Failed to save: ${e.message}`);
|
||||||
|
setSaveStatus("error");
|
||||||
|
setTimeout(() => setSaveStatus("idle"), 3000);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit: SubmitHandler<GeneralSettings> = (data) => {
|
const onSubmit: SubmitHandler<GeneralSettings> = (data) => {
|
||||||
@@ -74,6 +82,24 @@ export function GeneralTab({ config, isLoading: isConfigLoading }: GeneralTabPro
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-8">
|
<form onSubmit={handleSubmit(onSubmit)} className="space-y-8">
|
||||||
|
<div className="flex items-center justify-end mb-4">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
{saveStatus === "success" && (
|
||||||
|
<span className="text-success text-sm">Saved</span>
|
||||||
|
)}
|
||||||
|
{saveStatus === "error" && (
|
||||||
|
<span className="text-error text-sm">Save failed</span>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={mutation.isPending}
|
||||||
|
className="px-4 py-2 bg-button-primary hover:bg-button-primary-hover text-button-primary-text rounded-md disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{mutation.isPending ? "Saving..." : "Save General Settings"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h3 className="text-xl font-semibold text-content-primary dark:text-content-primary-dark">Service Defaults</h3>
|
<h3 className="text-xl font-semibold text-content-primary dark:text-content-primary-dark">Service Defaults</h3>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
@@ -140,14 +166,6 @@ export function GeneralTab({ config, isLoading: isConfigLoading }: GeneralTabPro
|
|||||||
The explicit content filter is controlled by an environment variable and cannot be changed here.
|
The explicit content filter is controlled by an environment variable and cannot be changed here.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
disabled={mutation.isPending}
|
|
||||||
className="px-4 py-2 bg-button-primary hover:bg-button-primary-hover text-button-primary-text rounded-md disabled:opacity-50"
|
|
||||||
>
|
|
||||||
{mutation.isPending ? "Saving..." : "Save General Settings"}
|
|
||||||
</button>
|
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useForm, Controller } from "react-hook-form";
|
import { useForm, Controller } from "react-hook-form";
|
||||||
import { authApiClient } from "../../lib/api-client";
|
import { authApiClient } from "../../lib/api-client";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@@ -46,14 +46,21 @@ function SpotifyApiForm() {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { data, isLoading } = useQuery({ queryKey: ["spotifyApiConfig"], queryFn: fetchSpotifyApiConfig });
|
const { data, isLoading } = useQuery({ queryKey: ["spotifyApiConfig"], queryFn: fetchSpotifyApiConfig });
|
||||||
const { register, handleSubmit, reset } = useForm<SpotifyApiSettings>();
|
const { register, handleSubmit, reset } = useForm<SpotifyApiSettings>();
|
||||||
|
const [saveStatus, setSaveStatus] = useState<"idle" | "success" | "error">("idle");
|
||||||
|
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationFn: saveSpotifyApiConfig,
|
mutationFn: saveSpotifyApiConfig,
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success("Spotify API settings saved!");
|
toast.success("Spotify API settings saved!");
|
||||||
|
setSaveStatus("success");
|
||||||
|
setTimeout(() => setSaveStatus("idle"), 3000);
|
||||||
queryClient.invalidateQueries({ queryKey: ["spotifyApiConfig"] });
|
queryClient.invalidateQueries({ queryKey: ["spotifyApiConfig"] });
|
||||||
},
|
},
|
||||||
onError: (e) => toast.error(`Failed to save: ${e.message}`),
|
onError: (e) => {
|
||||||
|
toast.error(`Failed to save: ${e.message}`);
|
||||||
|
setSaveStatus("error");
|
||||||
|
setTimeout(() => setSaveStatus("idle"), 3000);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -66,6 +73,24 @@ function SpotifyApiForm() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
||||||
|
<div className="flex items-center justify-end mb-2">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
{saveStatus === "success" && (
|
||||||
|
<span className="text-success text-sm">Saved</span>
|
||||||
|
)}
|
||||||
|
{saveStatus === "error" && (
|
||||||
|
<span className="text-error text-sm">Save failed</span>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={mutation.isPending}
|
||||||
|
className="px-4 py-2 bg-button-primary hover:bg-button-primary-hover text-button-primary-text rounded-md disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{mutation.isPending ? "Saving..." : "Save Spotify API"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<label htmlFor="client_id" className="text-content-primary dark:text-content-primary-dark">Client ID</label>
|
<label htmlFor="client_id" className="text-content-primary dark:text-content-primary-dark">Client ID</label>
|
||||||
<input
|
<input
|
||||||
@@ -86,13 +111,6 @@ function SpotifyApiForm() {
|
|||||||
placeholder="Optional"
|
placeholder="Optional"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
disabled={mutation.isPending}
|
|
||||||
className="px-4 py-2 bg-button-primary hover:bg-button-primary-hover text-button-primary-text rounded-md disabled:opacity-50"
|
|
||||||
>
|
|
||||||
{mutation.isPending ? "Saving..." : "Save Spotify API"}
|
|
||||||
</button>
|
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -102,14 +120,21 @@ function WebhookForm() {
|
|||||||
const { data, isLoading } = useQuery({ queryKey: ["webhookConfig"], queryFn: fetchWebhookConfig });
|
const { data, isLoading } = useQuery({ queryKey: ["webhookConfig"], queryFn: fetchWebhookConfig });
|
||||||
const { register, handleSubmit, control, reset, watch } = useForm<WebhookSettings>();
|
const { register, handleSubmit, control, reset, watch } = useForm<WebhookSettings>();
|
||||||
const currentUrl = watch("url");
|
const currentUrl = watch("url");
|
||||||
|
const [saveStatus, setSaveStatus] = useState<"idle" | "success" | "error">("idle");
|
||||||
|
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationFn: saveWebhookConfig,
|
mutationFn: saveWebhookConfig,
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
// No toast needed since the function shows one
|
// No toast needed since the function shows one
|
||||||
|
setSaveStatus("success");
|
||||||
|
setTimeout(() => setSaveStatus("idle"), 3000);
|
||||||
queryClient.invalidateQueries({ queryKey: ["webhookConfig"] });
|
queryClient.invalidateQueries({ queryKey: ["webhookConfig"] });
|
||||||
},
|
},
|
||||||
onError: (e) => toast.error(`Failed to save: ${e.message}`),
|
onError: (e) => {
|
||||||
|
toast.error(`Failed to save: ${e.message}`);
|
||||||
|
setSaveStatus("error");
|
||||||
|
setTimeout(() => setSaveStatus("idle"), 3000);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const testMutation = useMutation({
|
const testMutation = useMutation({
|
||||||
@@ -130,6 +155,24 @@ function WebhookForm() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
|
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
|
||||||
|
<div className="flex items-center justify-end mb-2">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
{saveStatus === "success" && (
|
||||||
|
<span className="text-success text-sm">Saved</span>
|
||||||
|
)}
|
||||||
|
{saveStatus === "error" && (
|
||||||
|
<span className="text-error text-sm">Save failed</span>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={mutation.isPending}
|
||||||
|
className="px-4 py-2 bg-button-primary hover:bg-button-primary-hover text-button-primary-text rounded-md disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{mutation.isPending ? "Saving..." : "Save Webhook"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<label htmlFor="webhookUrl" className="text-content-primary dark:text-content-primary-dark">Webhook URL</label>
|
<label htmlFor="webhookUrl" className="text-content-primary dark:text-content-primary-dark">Webhook URL</label>
|
||||||
<input
|
<input
|
||||||
@@ -168,13 +211,6 @@ function WebhookForm() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
disabled={mutation.isPending}
|
|
||||||
className="px-4 py-2 bg-button-primary hover:bg-button-primary-hover text-button-primary-text rounded-md disabled:opacity-50"
|
|
||||||
>
|
|
||||||
{mutation.isPending ? "Saving..." : "Save Webhook"}
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => testMutation.mutate(currentUrl)}
|
onClick={() => testMutation.mutate(currentUrl)}
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ const saveWatchConfig = async (data: Partial<WatchSettings>) => {
|
|||||||
export function WatchTab() {
|
export function WatchTab() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [validationError, setValidationError] = useState<string>("");
|
const [validationError, setValidationError] = useState<string>("");
|
||||||
|
const [saveStatus, setSaveStatus] = useState<"idle" | "success" | "error">("idle");
|
||||||
|
|
||||||
const { data: config, isLoading } = useQuery({
|
const { data: config, isLoading } = useQuery({
|
||||||
queryKey: ["watchConfig"],
|
queryKey: ["watchConfig"],
|
||||||
@@ -87,10 +88,14 @@ export function WatchTab() {
|
|||||||
mutationFn: saveWatchConfig,
|
mutationFn: saveWatchConfig,
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success("Watch settings saved successfully!");
|
toast.success("Watch settings saved successfully!");
|
||||||
|
setSaveStatus("success");
|
||||||
|
setTimeout(() => setSaveStatus("idle"), 3000);
|
||||||
queryClient.invalidateQueries({ queryKey: ["watchConfig"] });
|
queryClient.invalidateQueries({ queryKey: ["watchConfig"] });
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
toast.error(`Failed to save settings: ${error.message}`);
|
toast.error(`Failed to save settings: ${error.message}`);
|
||||||
|
setSaveStatus("error");
|
||||||
|
setTimeout(() => setSaveStatus("idle"), 3000);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -155,6 +160,24 @@ export function WatchTab() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-8">
|
<form onSubmit={handleSubmit(onSubmit)} className="space-y-8">
|
||||||
|
<div className="flex items-center justify-end mb-4">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
{saveStatus === "success" && (
|
||||||
|
<span className="text-success text-sm">Saved</span>
|
||||||
|
)}
|
||||||
|
{saveStatus === "error" && (
|
||||||
|
<span className="text-error text-sm">Save failed</span>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={mutation.isPending || !!validationError}
|
||||||
|
className="px-4 py-2 bg-button-primary hover:bg-button-primary-hover text-button-primary-text rounded-md disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{mutation.isPending ? "Saving..." : "Save Watch Settings"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h3 className="text-xl font-semibold text-content-primary dark:text-content-primary-dark">Watchlist Behavior</h3>
|
<h3 className="text-xl font-semibold text-content-primary dark:text-content-primary-dark">Watchlist Behavior</h3>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
@@ -234,14 +257,6 @@ export function WatchTab() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
disabled={mutation.isPending || !!validationError}
|
|
||||||
className="px-4 py-2 bg-button-primary hover:bg-button-primary-hover text-button-primary-text rounded-md disabled:opacity-50"
|
|
||||||
>
|
|
||||||
{mutation.isPending ? "Saving..." : "Save Watch Settings"}
|
|
||||||
</button>
|
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user