diff --git a/src/client/pages/auth/register.tsx b/src/client/pages/auth/register.tsx
index ab19b12f..5520fd4d 100644
--- a/src/client/pages/auth/register.tsx
+++ b/src/client/pages/auth/register.tsx
@@ -1,5 +1,6 @@
import { Response } from '@/lib/api/response';
import { fetchApi } from '@/lib/fetchApi';
+import useUser from '@/lib/client/hooks/useUser';
import { useTitle } from '@/lib/client/hooks/useTitle';
import {
Button,
@@ -18,8 +19,8 @@ import {
import { useForm } from '@mantine/form';
import { notifications, showNotification } from '@mantine/notifications';
import { IconLogin, IconPlus, IconUserPlus, IconX } from '@tabler/icons-react';
-import { useEffect, useState } from 'react';
-import { Link, useLocation, useNavigate } from 'react-router-dom';
+import { useEffect } from 'react';
+import { Link, Navigate, useLocation, useNavigate } from 'react-router-dom';
import useSWR, { mutate } from 'swr';
import GenericError from '../../error/GenericError';
import { getWebClient } from '@/lib/api/detect';
@@ -31,8 +32,6 @@ export function Component() {
const location = useLocation();
const navigate = useNavigate();
- const [loading, setLoading] = useState(true);
-
const {
data: config,
error: configError,
@@ -59,6 +58,8 @@ export function Component() {
},
);
+ const { user, loading: userLoading } = useUser();
+
const form = useForm({
initialValues: {
username: '',
@@ -74,17 +75,6 @@ export function Component() {
}),
});
- useEffect(() => {
- (async () => {
- const res = await fetch('/api/user');
- if (res.ok) {
- navigate('/dashboard');
- } else {
- setLoading(false);
- }
- })();
- }, []);
-
useEffect(() => {
if (!config) return;
@@ -138,7 +128,11 @@ export function Component() {
}
};
- if (loading || configLoading) return ;
+ if (userLoading || configLoading) return ;
+
+ if (user) {
+ return ;
+ }
if (!config || configError) {
return (
diff --git a/src/components/file/DashboardFileType/useFileContent.ts b/src/components/file/DashboardFileType/useFileContent.ts
index a1b2bb7e..a9eb470d 100644
--- a/src/components/file/DashboardFileType/useFileContent.ts
+++ b/src/components/file/DashboardFileType/useFileContent.ts
@@ -1,10 +1,32 @@
import type { File as DbFile } from '@/lib/db/models/file';
-import { useCallback, useEffect, useState } from 'react';
+import useSWR from 'swr';
import { isDbFile } from './useFileUrls';
const MAX_BYTES = 1 * 1024 * 1024;
const FILE_BIG = '\n...\nThe file is too big to display click the download icon to view/download it.';
+async function readBlobText(file: File) {
+ const raw = await new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.onerror = () => reject(new Error('Failed to read file'));
+ reader.onload = () => resolve((reader.result ?? '') as string);
+ reader.readAsText(file);
+ });
+
+ return raw.length > MAX_BYTES ? raw.slice(0, MAX_BYTES) + FILE_BIG : raw;
+}
+
+async function readText(fileUrl: string) {
+ const res = await fetch(fileUrl, {
+ headers: {
+ Range: `bytes=0-${MAX_BYTES}`,
+ },
+ });
+
+ if (!res.ok) throw new Error('Failed to fetch file');
+ return await res.text();
+}
+
export default function useFileContent({
enabled,
file,
@@ -14,41 +36,32 @@ export default function useFileContent({
file: DbFile | File;
fileUrl: string;
}) {
- const [content, setContent] = useState('');
+ const { data, error } = useSWR(
+ () => {
+ if (!enabled) return null;
- const loadText = useCallback(async () => {
- try {
- if (!isDbFile(file)) {
- const reader = new FileReader();
- reader.onload = () => {
- const raw = reader.result as string;
- setContent(raw.length > MAX_BYTES ? raw.slice(0, MAX_BYTES) + FILE_BIG : raw);
- };
- reader.readAsText(file as File);
- return;
- }
+ if (isDbFile(file)) return ['dbfile', file.id] as const;
+
+ const f = file as File;
+ return ['blobfile', f.name] as const;
+ },
+ async () => {
+ if (!isDbFile(file)) return readBlobText(file as File);
if (file.size > MAX_BYTES) {
- const res = await fetch(fileUrl, { headers: { Range: `bytes=0-${MAX_BYTES}` } });
- if (!res.ok) throw new Error('Failed to fetch file');
- const text = await res.text();
- setContent(text + FILE_BIG);
- return;
+ const text = await readText(fileUrl);
+ return text + FILE_BIG;
}
- const res = await fetch(fileUrl);
- if (!res.ok) throw new Error('Failed to fetch file');
- const text = await res.text();
- setContent(text);
- } catch {
- setContent('Error loading file.');
- }
- }, [file, fileUrl]);
+ return readText(fileUrl);
+ },
+ {
+ revalidateOnFocus: false,
+ shouldRetryOnError: false,
+ },
+ );
- useEffect(() => {
- if (!enabled) return;
- loadText();
- }, [enabled, loadText]);
+ if (error) return 'Error loading file.';
- return content;
+ return data ?? '';
}
diff --git a/src/components/pages/serverSettings/index.tsx b/src/components/pages/serverSettings/index.tsx
index df7ee941..519034e8 100644
--- a/src/components/pages/serverSettings/index.tsx
+++ b/src/components/pages/serverSettings/index.tsx
@@ -189,7 +189,7 @@ export default function DashboardServerSettings() {
const location = useLocation();
const navigate = useNavigate();
- const { data, isLoading } = useSWR('/api/server/settings');
+ const { data } = useSWR('/api/server/settings');
const [opened, { toggle }] = useDisclosure(false);
const toSettingSection = useCallback((settingKey: string) => {
@@ -328,7 +328,7 @@ export default function DashboardServerSettings() {
}
>
-
+
) : (
diff --git a/src/components/pages/serverSettings/parts/Chunks.tsx b/src/components/pages/serverSettings/parts/Chunks.tsx
index 26b4624f..8c2017fc 100644
--- a/src/components/pages/serverSettings/parts/Chunks.tsx
+++ b/src/components/pages/serverSettings/parts/Chunks.tsx
@@ -1,27 +1,34 @@
-import { Response } from '@/lib/api/response';
+import type { Response } from '@/lib/api/response';
import { Button, LoadingOverlay, Stack, Switch, TextInput } from '@mantine/core';
import { useForm } from '@mantine/form';
import { IconDeviceFloppy } from '@tabler/icons-react';
-import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { settingsOnSubmit } from '../settingsOnSubmit';
+import useServerSettings from '../useServerSettings';
-export default function Chunks({
- swr: { data, isLoading },
-}: {
- swr: { data: Response['/api/server/settings'] | undefined; isLoading: boolean };
-}) {
+export default function Chunks() {
+ const { data, isLoading } = useServerSettings();
+
+ return (
+ <>
+
+ {data ? : null}
+ >
+ );
+}
+
+function Form({ data, isLoading }: { data: Response['/api/server/settings']; isLoading: boolean }) {
const navigate = useNavigate();
const form = useForm({
initialValues: {
- chunksEnabled: true,
- chunksMax: '95mb',
- chunksSize: '25mb',
+ chunksEnabled: data.settings.chunksEnabled,
+ chunksMax: data.settings.chunksMax,
+ chunksSize: data.settings.chunksSize,
},
enhanceGetInputProps: (payload: any): object => ({
disabled:
- data?.tampered?.includes(payload.field) ||
+ data.tampered.includes(payload.field) ||
(payload.field !== 'chunksEnabled' && !form.values.chunksEnabled) ||
false,
}),
@@ -29,49 +36,35 @@ export default function Chunks({
const onSubmit = settingsOnSubmit(navigate, form);
- useEffect(() => {
- if (!data) return;
-
- form.setValues({
- chunksEnabled: data.settings.chunksEnabled ?? true,
- chunksMax: data.settings.chunksMax ?? '',
- chunksSize: data.settings.chunksSize ?? '',
- });
- }, [data]);
-
return (
- <>
-
+
- >
+ }>
+ Save
+
+
);
}
diff --git a/src/components/pages/serverSettings/parts/Core.tsx b/src/components/pages/serverSettings/parts/Core.tsx
index 62fd421b..613c250b 100644
--- a/src/components/pages/serverSettings/parts/Core.tsx
+++ b/src/components/pages/serverSettings/parts/Core.tsx
@@ -1,32 +1,34 @@
-import { Response } from '@/lib/api/response';
+import type { Response } from '@/lib/api/response';
import { Button, LoadingOverlay, Stack, Switch, TextInput } from '@mantine/core';
import { useForm } from '@mantine/form';
import { IconDeviceFloppy } from '@tabler/icons-react';
-import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { settingsOnSubmit } from '../settingsOnSubmit';
+import useServerSettings from '../useServerSettings';
-export default function Core({
- swr: { data, isLoading },
-}: {
- swr: { data: Response['/api/server/settings'] | undefined; isLoading: boolean };
-}) {
+export default function Core() {
+ const { data, isLoading } = useServerSettings();
+
+ return (
+ <>
+
+ {data ? : null}
+ >
+ );
+}
+
+function Form({ data, isLoading }: { data: Response['/api/server/settings']; isLoading: boolean }) {
const navigate = useNavigate();
- const form = useForm<{
- coreReturnHttpsUrls: boolean;
- coreDefaultDomain: string | null | undefined;
- coreTempDirectory: string;
- coreTrustProxy: boolean;
- }>({
+ const form = useForm({
initialValues: {
- coreReturnHttpsUrls: false,
- coreDefaultDomain: '',
- coreTempDirectory: '/tmp/zipline',
- coreTrustProxy: false,
+ coreReturnHttpsUrls: data.settings.coreReturnHttpsUrls,
+ coreDefaultDomain: data.settings.coreDefaultDomain,
+ coreTempDirectory: data.settings.coreTempDirectory,
+ coreTrustProxy: data.settings.coreTrustProxy,
},
enhanceGetInputProps: (payload) => ({
- disabled: data?.tampered?.includes(payload.field) || false,
+ disabled: data.tampered.includes(payload.field) || false,
}),
});
@@ -40,55 +42,40 @@ export default function Core({
return settingsOnSubmit(navigate, form)(values);
};
- useEffect(() => {
- if (!data) return;
-
- form.setValues({
- coreReturnHttpsUrls: data.settings.coreReturnHttpsUrls ?? false,
- coreDefaultDomain: data.settings.coreDefaultDomain ?? '',
- coreTempDirectory: data.settings.coreTempDirectory ?? '/tmp/zipline',
- coreTrustProxy: data.settings.coreTrustProxy ?? false,
- });
- }, [data]);
-
return (
- <>
-
+
- >
+ }>
+ Save
+
+
);
}
diff --git a/src/components/pages/serverSettings/parts/Discord.tsx b/src/components/pages/serverSettings/parts/Discord.tsx
index 1bc1d56a..05e8b059 100644
--- a/src/components/pages/serverSettings/parts/Discord.tsx
+++ b/src/components/pages/serverSettings/parts/Discord.tsx
@@ -1,4 +1,4 @@
-import { Response } from '@/lib/api/response';
+import type { Response } from '@/lib/api/response';
import {
Button,
Collapse,
@@ -13,24 +13,31 @@ import {
} from '@mantine/core';
import { useForm } from '@mantine/form';
import { IconDeviceFloppy } from '@tabler/icons-react';
-import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { settingsOnSubmit } from '../settingsOnSubmit';
+import useServerSettings from '../useServerSettings';
type DiscordEmbed = Record;
-export default function Discord({
- swr: { data, isLoading },
-}: {
- swr: { data: Response['/api/server/settings'] | undefined; isLoading: boolean };
-}) {
+export default function Discord() {
+ const { data, isLoading } = useServerSettings();
+
+ return (
+ <>
+
+ {data ? : null}
+ >
+ );
+}
+
+function Form({ data, isLoading }: { data: Response['/api/server/settings']; isLoading: boolean }) {
const navigate = useNavigate();
const formMain = useForm({
initialValues: {
- discordWebhookUrl: '',
- discordUsername: '',
- discordAvatarUrl: '',
+ discordWebhookUrl: data.settings.discordWebhookUrl,
+ discordUsername: data.settings.discordUsername,
+ discordAvatarUrl: data.settings.discordAvatarUrl,
},
});
@@ -49,42 +56,46 @@ export default function Discord({
const formOnUpload = useForm({
initialValues: {
- discordOnUploadWebhookUrl: '',
- discordOnUploadUsername: '',
- discordOnUploadAvatarUrl: '',
+ discordOnUploadWebhookUrl: data.settings.discordOnUploadWebhookUrl,
+ discordOnUploadUsername: data.settings.discordOnUploadUsername,
+ discordOnUploadAvatarUrl: data.settings.discordOnUploadAvatarUrl,
- discordOnUploadContent: '',
+ discordOnUploadContent: data.settings.discordOnUploadContent,
- discordOnUploadEmbed: false,
- discordOnUploadEmbedTitle: '',
- discordOnUploadEmbedDescription: '',
- discordOnUploadEmbedFooter: '',
- discordOnUploadEmbedColor: '',
- discordOnUploadEmbedThumbnail: false,
- discordOnUploadEmbedImageOrVideo: false,
- discordOnUploadEmbedTimestamp: false,
- discordOnUploadEmbedUrl: false,
+ discordOnUploadEmbed: Boolean(data.settings.discordOnUploadEmbed),
+ discordOnUploadEmbedTitle: (data.settings.discordOnUploadEmbed as DiscordEmbed | null)?.title || '',
+ discordOnUploadEmbedDescription:
+ (data.settings.discordOnUploadEmbed as DiscordEmbed | null)?.description || '',
+ discordOnUploadEmbedFooter: (data.settings.discordOnUploadEmbed as DiscordEmbed | null)?.footer || '',
+ discordOnUploadEmbedColor: (data.settings.discordOnUploadEmbed as DiscordEmbed | null)?.color || '',
+ discordOnUploadEmbedThumbnail: !!(data.settings.discordOnUploadEmbed as DiscordEmbed | null)?.thumbnail,
+ discordOnUploadEmbedImageOrVideo: !!(data.settings.discordOnUploadEmbed as DiscordEmbed | null)
+ ?.imageOrVideo,
+ discordOnUploadEmbedTimestamp: !!(data.settings.discordOnUploadEmbed as DiscordEmbed | null)?.timestamp,
+ discordOnUploadEmbedUrl: !!(data.settings.discordOnUploadEmbed as DiscordEmbed | null)?.url,
},
enhanceGetInputProps: (payload) => ({
- disabled: data?.tampered?.includes(payload.field) || false,
+ disabled: data.tampered.includes(payload.field) || false,
}),
});
const formOnShorten = useForm({
initialValues: {
- discordOnShortenWebhookUrl: '',
- discordOnShortenUsername: '',
- discordOnShortenAvatarUrl: '',
+ discordOnShortenWebhookUrl: data.settings.discordOnShortenWebhookUrl,
+ discordOnShortenUsername: data.settings.discordOnShortenUsername,
+ discordOnShortenAvatarUrl: data.settings.discordOnShortenAvatarUrl,
- discordOnShortenContent: '',
+ discordOnShortenContent: data.settings.discordOnShortenContent,
- discordOnShortenEmbed: false,
- discordOnShortenEmbedTitle: '',
- discordOnShortenEmbedDescription: '',
- discordOnShortenEmbedFooter: '',
- discordOnShortenEmbedColor: '',
- discordOnShortenEmbedTimestamp: false,
- discordOnShortenEmbedUrl: false,
+ discordOnShortenEmbed: Boolean(data.settings.discordOnShortenEmbed),
+ discordOnShortenEmbedTitle: (data.settings.discordOnShortenEmbed as DiscordEmbed | null)?.title || '',
+ discordOnShortenEmbedDescription:
+ (data.settings.discordOnShortenEmbed as DiscordEmbed | null)?.description || '',
+ discordOnShortenEmbedFooter: (data.settings.discordOnShortenEmbed as DiscordEmbed | null)?.footer || '',
+ discordOnShortenEmbedColor: (data.settings.discordOnShortenEmbed as DiscordEmbed | null)?.color || '',
+ discordOnShortenEmbedTimestamp: !!(data.settings.discordOnShortenEmbed as DiscordEmbed | null)
+ ?.timestamp,
+ discordOnShortenEmbedUrl: !!(data.settings.discordOnShortenEmbed as DiscordEmbed | null)?.url,
},
});
@@ -123,56 +134,8 @@ export default function Discord({
return settingsOnSubmit(navigate, type === 'upload' ? formOnUpload : formOnShorten)(sendValues);
};
- useEffect(() => {
- if (!data) return;
-
- formMain.setValues({
- discordWebhookUrl: data.settings.discordWebhookUrl ?? '',
- discordUsername: data.settings.discordUsername ?? '',
- discordAvatarUrl: data.settings.discordAvatarUrl ?? '',
- });
-
- formOnUpload.setValues({
- discordOnUploadWebhookUrl: data.settings.discordOnUploadWebhookUrl ?? '',
- discordOnUploadUsername: data.settings.discordOnUploadUsername ?? '',
- discordOnUploadAvatarUrl: data.settings.discordOnUploadAvatarUrl ?? '',
-
- discordOnUploadContent: data.settings.discordOnUploadContent ?? '',
- discordOnUploadEmbed: data.settings.discordOnUploadEmbed ? true : false,
- discordOnUploadEmbedTitle: (data.settings.discordOnUploadEmbed as DiscordEmbed)?.title ?? '',
- discordOnUploadEmbedDescription:
- (data.settings.discordOnUploadEmbed as DiscordEmbed)?.description ?? '',
- discordOnUploadEmbedFooter: (data.settings.discordOnUploadEmbed as DiscordEmbed)?.footer ?? '',
- discordOnUploadEmbedColor: (data.settings.discordOnUploadEmbed as DiscordEmbed)?.color ?? '',
- discordOnUploadEmbedThumbnail: (data.settings.discordOnUploadEmbed as DiscordEmbed)?.thumbnail ?? false,
- discordOnUploadEmbedImageOrVideo:
- (data.settings.discordOnUploadEmbed as DiscordEmbed)?.imageOrVideo ?? false,
- discordOnUploadEmbedTimestamp: (data.settings.discordOnUploadEmbed as DiscordEmbed)?.timestamp ?? false,
- discordOnUploadEmbedUrl: (data.settings.discordOnUploadEmbed as DiscordEmbed)?.url ?? false,
- });
-
- formOnShorten.setValues({
- discordOnShortenWebhookUrl: data.settings.discordOnShortenWebhookUrl ?? '',
- discordOnShortenUsername: data.settings.discordOnShortenUsername ?? '',
- discordOnShortenAvatarUrl: data.settings.discordOnShortenAvatarUrl ?? '',
-
- discordOnShortenContent: data.settings.discordOnShortenContent ?? '',
- discordOnShortenEmbed: data.settings.discordOnShortenEmbed ? true : false,
- discordOnShortenEmbedTitle: (data.settings.discordOnShortenEmbed as DiscordEmbed)?.title ?? '',
- discordOnShortenEmbedDescription:
- (data.settings.discordOnShortenEmbed as DiscordEmbed)?.description ?? '',
- discordOnShortenEmbedFooter: (data.settings.discordOnShortenEmbed as DiscordEmbed)?.footer ?? '',
- discordOnShortenEmbedColor: (data.settings.discordOnShortenEmbed as DiscordEmbed)?.color ?? '',
- discordOnShortenEmbedTimestamp:
- (data.settings.discordOnShortenEmbed as DiscordEmbed)?.timestamp ?? false,
- discordOnShortenEmbedUrl: (data.settings.discordOnShortenEmbed as DiscordEmbed)?.url ?? false,
- });
- }, [data]);
-
return (
<>
-
-
+
+ {data ? : null}
+ >
+ );
+}
+
+function Form({ data }: { data: Response['/api/server/settings'] }) {
const navigate = useNavigate();
const [submitting, setSubmitting] = useState(false);
@@ -24,7 +29,7 @@ export default function Domains({
const submitSettings = settingsOnSubmit(navigate, form);
- const domains = Array.isArray(data?.settings.domains) ? data!.settings.domains.map(String) : [];
+ const domains = data.settings.domains.map(String);
async function updateDomains(nextDomains: string[]) {
setSubmitting(true);
@@ -56,7 +61,7 @@ export default function Domains({
return (
<>
-
+
+
+ {data ? : null}
+ >
+ );
+}
+
+function Form({ data, isLoading }: { data: Response['/api/server/settings']; isLoading: boolean }) {
const navigate = useNavigate();
const form = useForm({
initialValues: {
- featuresImageCompression: true,
- featuresRobotsTxt: true,
- featuresHealthcheck: true,
- featuresUserRegistration: false,
- featuresOauthRegistration: true,
- featuresDeleteOnMaxViews: true,
- featuresThumbnailsEnabled: true,
- featuresThumbnailsNumberThreads: 4,
- featuresThumbnailsFormat: 'jpg',
- featuresThumbnailsInstantaneous: false,
- featuresMetricsEnabled: true,
- featuresMetricsAdminOnly: false,
- featuresMetricsShowUserSpecific: true,
- featuresVersionChecking: true,
- featuresVersionAPI: 'https://zipline-version.diced.sh/',
+ featuresImageCompression: data.settings.featuresImageCompression,
+ featuresRobotsTxt: data.settings.featuresRobotsTxt,
+ featuresHealthcheck: data.settings.featuresHealthcheck,
+ featuresUserRegistration: data.settings.featuresUserRegistration,
+ featuresOauthRegistration: data.settings.featuresOauthRegistration,
+ featuresDeleteOnMaxViews: data.settings.featuresDeleteOnMaxViews,
+
+ featuresThumbnailsEnabled: data.settings.featuresThumbnailsEnabled,
+ featuresThumbnailsNumberThreads: data.settings.featuresThumbnailsNumberThreads,
+ featuresThumbnailsFormat: data.settings.featuresThumbnailsFormat,
+ featuresThumbnailsInstantaneous: data.settings.featuresThumbnailsInstantaneous,
+
+ featuresMetricsEnabled: data.settings.featuresMetricsEnabled,
+ featuresMetricsAdminOnly: data.settings.featuresMetricsAdminOnly,
+ featuresMetricsShowUserSpecific: data.settings.featuresMetricsShowUserSpecific,
+
+ featuresVersionChecking: data.settings.featuresVersionChecking,
+ featuresVersionAPI: data.settings.featuresVersionAPI,
},
enhanceGetInputProps: (payload) => ({
- disabled: data?.tampered?.includes(payload.field) || false,
+ disabled: data.tampered.includes(payload.field) || false,
}),
});
const onSubmit = settingsOnSubmit(navigate, form);
- useEffect(() => {
- if (!data) return;
-
- form.setValues({
- featuresImageCompression: data.settings.featuresImageCompression ?? true,
- featuresRobotsTxt: data.settings.featuresRobotsTxt ?? true,
- featuresHealthcheck: data.settings.featuresHealthcheck ?? true,
- featuresUserRegistration: data.settings.featuresUserRegistration ?? false,
- featuresOauthRegistration: data.settings.featuresOauthRegistration ?? true,
- featuresDeleteOnMaxViews: data.settings.featuresDeleteOnMaxViews ?? true,
- featuresThumbnailsEnabled: data.settings.featuresThumbnailsEnabled ?? true,
- featuresThumbnailsNumberThreads: data.settings.featuresThumbnailsNumberThreads ?? 4,
- featuresThumbnailsFormat: data.settings.featuresThumbnailsFormat ?? 'jpg',
- featuresThumbnailsInstantaneous: data.settings.featuresThumbnailsInstantaneous ?? false,
- featuresMetricsEnabled: data.settings.featuresMetricsEnabled ?? true,
- featuresMetricsAdminOnly: data.settings.featuresMetricsAdminOnly ?? false,
- featuresMetricsShowUserSpecific: data.settings.featuresMetricsShowUserSpecific ?? true,
- featuresVersionChecking: data.settings.featuresVersionChecking ?? true,
- featuresVersionAPI: data.settings.featuresVersionAPI ?? 'https://zipline-version.diced.sh/',
- });
- }, [data]);
-
return (
- <>
-
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
-
+
-
+
-
+
-
+
+ The URL of the version checking server. The default is{' '}
+
+ https://zipline-version.diced.sh
+
+ . Visit the{' '}
+
+ GitHub
+ {' '}
+ to host your own version checking server.
+ >
+ }
+ placeholder='https://zipline-version.diced.sh/'
+ {...form.getInputProps('featuresVersionAPI')}
+ />
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- The URL of the version checking server. The default is{' '}
-
- https://zipline-version.diced.sh
-
- . Visit the{' '}
-
- GitHub
- {' '}
- to host your own version checking server.
- >
- }
- placeholder='https://zipline-version.diced.sh/'
- {...form.getInputProps('featuresVersionAPI')}
- />
-
-
- }>
- Save
-
-
- >
+ }>
+ Save
+
+
);
}
diff --git a/src/components/pages/serverSettings/parts/Files.tsx b/src/components/pages/serverSettings/parts/Files.tsx
index 9a77f6a7..c1cbd6ee 100644
--- a/src/components/pages/serverSettings/parts/Files.tsx
+++ b/src/components/pages/serverSettings/parts/Files.tsx
@@ -1,52 +1,44 @@
-import { Response } from '@/lib/api/response';
+import type { Response } from '@/lib/api/response';
import { Button, LoadingOverlay, NumberInput, Select, Stack, Switch, TextInput } from '@mantine/core';
import { useForm } from '@mantine/form';
import { IconDeviceFloppy } from '@tabler/icons-react';
-import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { settingsOnSubmit } from '../settingsOnSubmit';
+import useServerSettings from '../useServerSettings';
-export default function Files({
- swr: { data, isLoading },
-}: {
- swr: { data: Response['/api/server/settings'] | undefined; isLoading: boolean };
-}) {
+export default function Files() {
+ const { data, isLoading } = useServerSettings();
+
+ return (
+ <>
+
+ {data ? : null}
+ >
+ );
+}
+
+function Form({ data, isLoading }: { data: Response['/api/server/settings']; isLoading: boolean }) {
const navigate = useNavigate();
- const form = useForm<{
- filesRoute: string;
- filesLength: number;
- filesDefaultFormat: string;
- filesDisabledExtensions: string;
- filesMaxFileSize: string;
- filesDefaultExpiration: string | null;
- filesMaxExpiration: string | null;
- filesAssumeMimetypes: boolean;
- filesDefaultDateFormat: string;
- filesRemoveGpsMetadata: boolean;
- filesRandomWordsNumAdjectives: number;
- filesRandomWordsSeparator: string;
- filesDefaultCompressionFormat: string;
- filesMaxFilesPerUpload: number;
- }>({
+ const form = useForm({
initialValues: {
- filesRoute: '/u',
- filesLength: 6,
- filesDefaultFormat: 'random',
- filesDisabledExtensions: '',
- filesMaxFileSize: '100mb',
- filesDefaultExpiration: '',
- filesMaxExpiration: '',
- filesAssumeMimetypes: false,
- filesDefaultDateFormat: 'YYYY-MM-DD_HH:mm:ss',
- filesRemoveGpsMetadata: false,
- filesRandomWordsNumAdjectives: 3,
- filesRandomWordsSeparator: '-',
- filesDefaultCompressionFormat: 'jpg',
- filesMaxFilesPerUpload: 1000,
+ filesRoute: data.settings.filesRoute,
+ filesLength: data.settings.filesLength,
+ filesDefaultFormat: data.settings.filesDefaultFormat,
+ filesDisabledExtensions: data.settings.filesDisabledExtensions.join(', '),
+ filesMaxFileSize: data.settings.filesMaxFileSize,
+ filesDefaultExpiration: data.settings.filesDefaultExpiration,
+ filesMaxExpiration: data.settings.filesMaxExpiration,
+ filesAssumeMimetypes: data.settings.filesAssumeMimetypes,
+ filesDefaultDateFormat: data.settings.filesDefaultDateFormat,
+ filesRemoveGpsMetadata: data.settings.filesRemoveGpsMetadata,
+ filesRandomWordsNumAdjectives: data.settings.filesRandomWordsNumAdjectives,
+ filesRandomWordsSeparator: data.settings.filesRandomWordsSeparator,
+ filesDefaultCompressionFormat: data.settings.filesDefaultCompressionFormat,
+ filesMaxFilesPerUpload: data.settings.filesMaxFilesPerUpload,
},
enhanceGetInputProps: (payload) => ({
- disabled: data?.tampered?.includes(payload.field) || false,
+ disabled: data.tampered.includes(payload.field) || false,
}),
});
@@ -85,143 +77,118 @@ export default function Files({
return settingsOnSubmit(navigate, form)(values);
};
- useEffect(() => {
- if (!data) return;
-
- form.setValues({
- filesRoute: data.settings.filesRoute ?? '/u',
- filesLength: data.settings.filesLength ?? 6,
- filesDefaultFormat: data.settings.filesDefaultFormat ?? 'random',
- filesDisabledExtensions: data.settings.filesDisabledExtensions.join(', ') ?? '',
- filesMaxFileSize: data.settings.filesMaxFileSize ?? '100mb',
- filesDefaultExpiration: data.settings.filesDefaultExpiration ?? '',
- filesMaxExpiration: data.settings.filesMaxExpiration ?? '',
- filesAssumeMimetypes: data.settings.filesAssumeMimetypes ?? false,
- filesDefaultDateFormat: data.settings.filesDefaultDateFormat ?? 'YYYY-MM-DD_HH:mm:ss',
- filesRemoveGpsMetadata: data.settings.filesRemoveGpsMetadata ?? false,
- filesRandomWordsNumAdjectives: data.settings.filesRandomWordsNumAdjectives ?? 3,
- filesRandomWordsSeparator: data.settings.filesRandomWordsSeparator ?? '-',
- filesDefaultCompressionFormat: data.settings.filesDefaultCompressionFormat ?? 'jpg',
- filesMaxFilesPerUpload: data.settings.filesMaxFilesPerUpload ?? 1000,
- });
- }, [data]);
-
return (
- <>
-
+
+
+
-
-
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
+
-
-
-
- }>
- Save
-
-
- >
+ }>
+ Save
+
+
);
}
diff --git a/src/components/pages/serverSettings/parts/HttpWebhook.tsx b/src/components/pages/serverSettings/parts/HttpWebhook.tsx
index 20b4fd04..5d8f51d0 100644
--- a/src/components/pages/serverSettings/parts/HttpWebhook.tsx
+++ b/src/components/pages/serverSettings/parts/HttpWebhook.tsx
@@ -1,25 +1,32 @@
-import { Response } from '@/lib/api/response';
+import type { Response } from '@/lib/api/response';
import { Button, LoadingOverlay, Stack, TextInput } from '@mantine/core';
import { useForm } from '@mantine/form';
import { IconDeviceFloppy } from '@tabler/icons-react';
-import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { settingsOnSubmit } from '../settingsOnSubmit';
+import useServerSettings from '../useServerSettings';
-export default function HttpWebhook({
- swr: { data, isLoading },
-}: {
- swr: { data: Response['/api/server/settings'] | undefined; isLoading: boolean };
-}) {
+export default function HttpWebhook() {
+ const { data, isLoading } = useServerSettings();
+
+ return (
+ <>
+
+ {data ? : null}
+ >
+ );
+}
+
+function Form({ data, isLoading }: { data: Response['/api/server/settings']; isLoading: boolean }) {
const navigate = useNavigate();
const form = useForm({
initialValues: {
- httpWebhookOnUpload: '',
- httpWebhookOnShorten: '',
+ httpWebhookOnUpload: data.settings.httpWebhookOnUpload,
+ httpWebhookOnShorten: data.settings.httpWebhookOnShorten,
},
enhanceGetInputProps: (payload) => ({
- disabled: data?.tampered?.includes(payload.field) || false,
+ disabled: data.tampered.includes(payload.field) || false,
}),
});
@@ -37,40 +44,27 @@ export default function HttpWebhook({
return settingsOnSubmit(navigate, form)(values);
};
- useEffect(() => {
- if (!data) return;
-
- form.setValues({
- httpWebhookOnUpload: data.settings.httpWebhookOnUpload ?? '',
- httpWebhookOnShorten: data.settings.httpWebhookOnShorten ?? '',
- });
- }, [data]);
-
return (
- <>
-
+
+
+
-
-
-
+
+
-
-
-
- }>
- Save
-
-
- >
+ }>
+ Save
+
+
);
}
diff --git a/src/components/pages/serverSettings/parts/Invites.tsx b/src/components/pages/serverSettings/parts/Invites.tsx
index beb36764..63cc577c 100644
--- a/src/components/pages/serverSettings/parts/Invites.tsx
+++ b/src/components/pages/serverSettings/parts/Invites.tsx
@@ -1,26 +1,33 @@
-import { Response } from '@/lib/api/response';
+import type { Response } from '@/lib/api/response';
import { Button, LoadingOverlay, NumberInput, Stack, Switch } from '@mantine/core';
import { useForm } from '@mantine/form';
import { IconDeviceFloppy } from '@tabler/icons-react';
-import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { settingsOnSubmit } from '../settingsOnSubmit';
+import useServerSettings from '../useServerSettings';
-export default function Invites({
- swr: { data, isLoading },
-}: {
- swr: { data: Response['/api/server/settings'] | undefined; isLoading: boolean };
-}) {
+export default function Invites() {
+ const { data, isLoading } = useServerSettings();
+
+ return (
+ <>
+
+ {data ? : null}
+ >
+ );
+}
+
+function Form({ data, isLoading }: { data: Response['/api/server/settings']; isLoading: boolean }) {
const navigate = useNavigate();
const form = useForm({
initialValues: {
- invitesEnabled: true,
- invitesLength: 6,
+ invitesEnabled: data.settings.invitesEnabled,
+ invitesLength: data.settings.invitesLength,
},
enhanceGetInputProps: (payload: any): object => ({
disabled:
- data?.tampered?.includes(payload.field) ||
+ data.tampered.includes(payload.field) ||
(payload.field !== 'invitesEnabled' && !form.values.invitesEnabled) ||
false,
}),
@@ -28,41 +35,28 @@ export default function Invites({
const onSubmit = settingsOnSubmit(navigate, form);
- useEffect(() => {
- if (!data) return;
-
- form.setValues({
- invitesEnabled: data.settings.invitesEnabled ?? true,
- invitesLength: data.settings.invitesLength ?? 6,
- });
- }, [data]);
-
return (
- <>
-
+
+
+
-
-
-
+
+
-
-
-
- }>
- Save
-
-
- >
+ }>
+ Save
+
+
);
}
diff --git a/src/components/pages/serverSettings/parts/Mfa.tsx b/src/components/pages/serverSettings/parts/Mfa.tsx
index e74f8b74..54faa992 100644
--- a/src/components/pages/serverSettings/parts/Mfa.tsx
+++ b/src/components/pages/serverSettings/parts/Mfa.tsx
@@ -1,90 +1,81 @@
-import { Response } from '@/lib/api/response';
+import type { Response } from '@/lib/api/response';
import { Button, Divider, LoadingOverlay, Stack, Switch, TextInput } from '@mantine/core';
import { useForm } from '@mantine/form';
import { IconDeviceFloppy } from '@tabler/icons-react';
-import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { settingsOnSubmit } from '../settingsOnSubmit';
+import useServerSettings from '../useServerSettings';
-export default function Mfa({
- swr: { data, isLoading },
-}: {
- swr: { data: Response['/api/server/settings'] | undefined; isLoading: boolean };
-}) {
+export default function Mfa() {
+ const { data, isLoading } = useServerSettings();
+
+ return (
+ <>
+
+ {data ? : null}
+ >
+ );
+}
+
+function Form({ data, isLoading }: { data: Response['/api/server/settings']; isLoading: boolean }) {
const navigate = useNavigate();
const form = useForm({
initialValues: {
- mfaTotpEnabled: false,
- mfaTotpIssuer: 'Zipline',
- mfaPasskeysEnabled: false,
- mfaPasskeysRpID: '',
- mfaPasskeysOrigin: '',
+ mfaTotpEnabled: data.settings.mfaTotpEnabled,
+ mfaTotpIssuer: data.settings.mfaTotpIssuer,
+ mfaPasskeysEnabled: data.settings.mfaPasskeysEnabled,
+ mfaPasskeysRpID: data.settings.mfaPasskeysRpID,
+ mfaPasskeysOrigin: data.settings.mfaPasskeysOrigin,
},
enhanceGetInputProps: (payload) => ({
- disabled: data?.tampered?.includes(payload.field) || false,
+ disabled: data.tampered.includes(payload.field) || false,
}),
});
const onSubmit = settingsOnSubmit(navigate, form);
- useEffect(() => {
- if (!data) return;
-
- form.setValues({
- mfaTotpEnabled: data.settings.mfaTotpEnabled ?? false,
- mfaTotpIssuer: data.settings.mfaTotpIssuer ?? 'Zipline',
- mfaPasskeysEnabled: data.settings.mfaPasskeysEnabled ?? false,
- mfaPasskeysRpID: data.settings.mfaPasskeysRpID ?? '',
- mfaPasskeysOrigin: data.settings.mfaPasskeysOrigin ?? '',
- });
- }, [data]);
-
return (
- <>
-
+
+
+
-
-
-
+
-
+
-
+
-
+
+
+
-
-
-
-
- }>
- Save
-
-
- >
+ }>
+ Save
+
+
);
}
diff --git a/src/components/pages/serverSettings/parts/Oauth.tsx b/src/components/pages/serverSettings/parts/Oauth.tsx
index f9c7312a..4162a767 100644
--- a/src/components/pages/serverSettings/parts/Oauth.tsx
+++ b/src/components/pages/serverSettings/parts/Oauth.tsx
@@ -1,4 +1,4 @@
-import { Response } from '@/lib/api/response';
+import type { Response } from '@/lib/api/response';
import {
Anchor,
Button,
@@ -13,45 +13,52 @@ import {
} from '@mantine/core';
import { useForm } from '@mantine/form';
import { IconDeviceFloppy } from '@tabler/icons-react';
-import { useEffect } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { settingsOnSubmit } from '../settingsOnSubmit';
+import useServerSettings from '../useServerSettings';
-export default function Oauth({
- swr: { data, isLoading },
-}: {
- swr: { data: Response['/api/server/settings'] | undefined; isLoading: boolean };
-}) {
+export default function Oauth() {
+ const { data, isLoading } = useServerSettings();
+
+ return (
+ <>
+
+ {data ? : null}
+ >
+ );
+}
+
+function Form({ data, isLoading }: { data: Response['/api/server/settings']; isLoading: boolean }) {
const navigate = useNavigate();
const form = useForm({
initialValues: {
- oauthBypassLocalLogin: false,
- oauthLoginOnly: false,
+ oauthBypassLocalLogin: data.settings.oauthBypassLocalLogin,
+ oauthLoginOnly: data.settings.oauthLoginOnly,
- oauthDiscordClientId: '',
- oauthDiscordClientSecret: '',
- oauthDiscordRedirectUri: '',
- oauthDiscordAllowedIds: '',
- oauthDiscordDeniedIds: '',
+ oauthDiscordClientId: data.settings.oauthDiscordClientId,
+ oauthDiscordClientSecret: data.settings.oauthDiscordClientSecret,
+ oauthDiscordRedirectUri: data.settings.oauthDiscordRedirectUri,
+ oauthDiscordAllowedIds: data.settings.oauthDiscordAllowedIds.join(', '),
+ oauthDiscordDeniedIds: data.settings.oauthDiscordDeniedIds.join(', '),
- oauthGoogleClientId: '',
- oauthGoogleClientSecret: '',
- oauthGoogleRedirectUri: '',
+ oauthGoogleClientId: data.settings.oauthGoogleClientId,
+ oauthGoogleClientSecret: data.settings.oauthGoogleClientSecret,
+ oauthGoogleRedirectUri: data.settings.oauthGoogleRedirectUri,
- oauthGithubClientId: '',
- oauthGithubClientSecret: '',
- oauthGithubRedirectUri: '',
+ oauthGithubClientId: data.settings.oauthGithubClientId,
+ oauthGithubClientSecret: data.settings.oauthGithubClientSecret,
+ oauthGithubRedirectUri: data.settings.oauthGithubRedirectUri,
- oauthOidcClientId: '',
- oauthOidcClientSecret: '',
- oauthOidcAuthorizeUrl: '',
- oauthOidcTokenUrl: '',
- oauthOidcUserinfoUrl: '',
- oauthOidcRedirectUri: '',
+ oauthOidcClientId: data.settings.oauthOidcClientId,
+ oauthOidcClientSecret: data.settings.oauthOidcClientSecret,
+ oauthOidcAuthorizeUrl: data.settings.oauthOidcAuthorizeUrl,
+ oauthOidcTokenUrl: data.settings.oauthOidcTokenUrl,
+ oauthOidcUserinfoUrl: data.settings.oauthOidcUserinfoUrl,
+ oauthOidcRedirectUri: data.settings.oauthOidcRedirectUri,
},
enhanceGetInputProps: (payload) => ({
- disabled: data?.tampered?.includes(payload.field) || false,
+ disabled: data.tampered.includes(payload.field) || false,
}),
});
@@ -90,44 +97,8 @@ export default function Oauth({
return settingsOnSubmit(navigate, form)(values);
};
- useEffect(() => {
- if (!data) return;
-
- form.setValues({
- oauthBypassLocalLogin: data.settings.oauthBypassLocalLogin ?? false,
- oauthLoginOnly: data.settings.oauthLoginOnly ?? false,
-
- oauthDiscordClientId: data.settings.oauthDiscordClientId ?? '',
- oauthDiscordClientSecret: data.settings.oauthDiscordClientSecret ?? '',
- oauthDiscordRedirectUri: data.settings.oauthDiscordRedirectUri ?? '',
- oauthDiscordAllowedIds: data.settings.oauthDiscordAllowedIds
- ? data.settings.oauthDiscordAllowedIds.join(', ')
- : '',
- oauthDiscordDeniedIds: data.settings.oauthDiscordDeniedIds
- ? data.settings.oauthDiscordDeniedIds.join(', ')
- : '',
-
- oauthGoogleClientId: data.settings.oauthGoogleClientId ?? '',
- oauthGoogleClientSecret: data.settings.oauthGoogleClientSecret ?? '',
- oauthGoogleRedirectUri: data.settings.oauthGoogleRedirectUri ?? '',
-
- oauthGithubClientId: data.settings.oauthGithubClientId ?? '',
- oauthGithubClientSecret: data.settings.oauthGithubClientSecret ?? '',
- oauthGithubRedirectUri: data.settings.oauthGithubRedirectUri ?? '',
-
- oauthOidcClientId: data.settings.oauthOidcClientId ?? '',
- oauthOidcClientSecret: data.settings.oauthOidcClientSecret ?? '',
- oauthOidcAuthorizeUrl: data.settings.oauthOidcAuthorizeUrl ?? '',
- oauthOidcTokenUrl: data.settings.oauthOidcTokenUrl ?? '',
- oauthOidcUserinfoUrl: data.settings.oauthOidcUserinfoUrl ?? '',
- oauthOidcRedirectUri: data.settings.oauthOidcRedirectUri ?? '',
- });
- }, [data]);
-
return (
<>
-
-
For OAuth to work, the "OAuth Registration" setting must be enabled in the{' '}
diff --git a/src/components/pages/serverSettings/parts/PWA.tsx b/src/components/pages/serverSettings/parts/PWA.tsx
index c7253ce1..bcdbaed7 100644
--- a/src/components/pages/serverSettings/parts/PWA.tsx
+++ b/src/components/pages/serverSettings/parts/PWA.tsx
@@ -1,30 +1,37 @@
-import { Response } from '@/lib/api/response';
+import type { Response } from '@/lib/api/response';
import { Button, ColorInput, Group, LoadingOverlay, Stack, Switch, Text, TextInput } from '@mantine/core';
import { useForm } from '@mantine/form';
import { IconDeviceFloppy, IconRefresh } from '@tabler/icons-react';
-import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { settingsOnSubmit } from '../settingsOnSubmit';
+import useServerSettings from '../useServerSettings';
-export default function PWA({
- swr: { data, isLoading },
-}: {
- swr: { data: Response['/api/server/settings'] | undefined; isLoading: boolean };
-}) {
+export default function PWA() {
+ const { data, isLoading } = useServerSettings();
+
+ return (
+ <>
+
+ {data ? : null}
+ >
+ );
+}
+
+function Form({ data, isLoading }: { data: Response['/api/server/settings']; isLoading: boolean }) {
const navigate = useNavigate();
const form = useForm({
initialValues: {
- pwaEnabled: false,
- pwaTitle: '',
- pwaShortName: '',
- pwaDescription: '',
- pwaThemeColor: '',
- pwaBackgroundColor: '',
+ pwaEnabled: data.settings.pwaEnabled,
+ pwaTitle: data.settings.pwaTitle,
+ pwaShortName: data.settings.pwaShortName,
+ pwaDescription: data.settings.pwaDescription,
+ pwaThemeColor: data.settings.pwaThemeColor,
+ pwaBackgroundColor: data.settings.pwaBackgroundColor,
},
enhanceGetInputProps: (payload: any): object => ({
disabled:
- data?.tampered?.includes(payload.field) ||
+ data.tampered.includes(payload.field) ||
(payload.field !== 'pwaEnabled' && !form.values.pwaEnabled) ||
false,
}),
@@ -48,23 +55,8 @@ export default function PWA({
});
};
- useEffect(() => {
- if (!data) return;
-
- form.setValues({
- pwaEnabled: data.settings.pwaEnabled ?? false,
- pwaTitle: data.settings.pwaTitle ?? '',
- pwaShortName: data.settings.pwaShortName ?? '',
- pwaDescription: data.settings.pwaDescription ?? '',
- pwaThemeColor: data.settings.pwaThemeColor ?? '',
- pwaBackgroundColor: data.settings.pwaBackgroundColor ?? '',
- });
- }, [data]);
-
return (
<>
-
-
Refresh the page after enabling PWA to see any changes.
diff --git a/src/components/pages/serverSettings/parts/Ratelimit.tsx b/src/components/pages/serverSettings/parts/Ratelimit.tsx
index 6f8fecd3..5de00762 100644
--- a/src/components/pages/serverSettings/parts/Ratelimit.tsx
+++ b/src/components/pages/serverSettings/parts/Ratelimit.tsx
@@ -1,35 +1,42 @@
-import { Response } from '@/lib/api/response';
+import type { Response } from '@/lib/api/response';
import { Button, LoadingOverlay, NumberInput, Stack, Switch, Text, TextInput } from '@mantine/core';
import { useForm } from '@mantine/form';
import { IconDeviceFloppy } from '@tabler/icons-react';
-import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { settingsOnSubmit } from '../settingsOnSubmit';
+import useServerSettings from '../useServerSettings';
-export default function Ratelimit({
- swr: { data, isLoading },
-}: {
- swr: { data: Response['/api/server/settings'] | undefined; isLoading: boolean };
-}) {
+export default function Ratelimit() {
+ const { data, isLoading } = useServerSettings();
+
+ return (
+ <>
+
+ {data ? : null}
+ >
+ );
+}
+
+function Form({ data, isLoading }: { data: Response['/api/server/settings']; isLoading: boolean }) {
const navigate = useNavigate();
const form = useForm<{
ratelimitEnabled: boolean;
ratelimitMax: number;
- ratelimitWindow: number | '';
+ ratelimitWindow: number | '' | null;
ratelimitAdminBypass: boolean;
ratelimitAllowList: string;
}>({
initialValues: {
- ratelimitEnabled: true,
- ratelimitMax: 10,
- ratelimitWindow: '',
- ratelimitAdminBypass: false,
- ratelimitAllowList: '',
+ ratelimitEnabled: data.settings.ratelimitEnabled,
+ ratelimitMax: data.settings.ratelimitMax,
+ ratelimitWindow: data.settings.ratelimitWindow,
+ ratelimitAdminBypass: data.settings.ratelimitAdminBypass,
+ ratelimitAllowList: data.settings.ratelimitAllowList.join(', '),
},
enhanceGetInputProps: (payload: any): object => ({
disabled:
- data?.tampered?.includes(payload.field) ||
+ data.tampered.includes(payload.field) ||
(payload.field !== 'ratelimitEnabled' && !form.values.ratelimitEnabled) ||
false,
}),
@@ -55,22 +62,8 @@ export default function Ratelimit({
return settingsOnSubmit(navigate, form)(values);
};
- useEffect(() => {
- if (!data) return;
-
- form.setValues({
- ratelimitEnabled: data.settings.ratelimitEnabled ?? true,
- ratelimitMax: data.settings.ratelimitMax ?? 10,
- ratelimitWindow: data.settings.ratelimitWindow ?? '',
- ratelimitAdminBypass: data.settings.ratelimitAdminBypass ?? false,
- ratelimitAllowList: data.settings.ratelimitAllowList.join(', ') ?? '',
- });
- }, [data]);
-
return (
<>
-
-
All options require a restart to take effect.
diff --git a/src/components/pages/serverSettings/parts/Tasks.tsx b/src/components/pages/serverSettings/parts/Tasks.tsx
index db092911..f5e5f075 100644
--- a/src/components/pages/serverSettings/parts/Tasks.tsx
+++ b/src/components/pages/serverSettings/parts/Tasks.tsx
@@ -1,51 +1,43 @@
-import { Response } from '@/lib/api/response';
+import type { Response } from '@/lib/api/response';
import { Button, Code, LoadingOverlay, Stack, Text, TextInput } from '@mantine/core';
import { useForm } from '@mantine/form';
import { IconDeviceFloppy } from '@tabler/icons-react';
-import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { settingsOnSubmit } from '../settingsOnSubmit';
+import useServerSettings from '../useServerSettings';
-export default function Tasks({
- swr: { data, isLoading },
-}: {
- swr: { data: Response['/api/server/settings'] | undefined; isLoading: boolean };
-}) {
+export default function Tasks() {
+ const { data, isLoading } = useServerSettings();
+
+ return (
+ <>
+
+ {data ? : null}
+ >
+ );
+}
+
+function Form({ data, isLoading }: { data: Response['/api/server/settings']; isLoading: boolean }) {
const navigate = useNavigate();
const form = useForm({
initialValues: {
- tasksDeleteInterval: '30m',
- tasksClearInvitesInterval: '30m',
- tasksMaxViewsInterval: '30m',
- tasksThumbnailsInterval: '30m',
- tasksMetricsInterval: '30m',
- tasksCleanThumbnailsInterval: '1d',
+ tasksDeleteInterval: data.settings.tasksDeleteInterval,
+ tasksClearInvitesInterval: data.settings.tasksClearInvitesInterval,
+ tasksMaxViewsInterval: data.settings.tasksMaxViewsInterval,
+ tasksThumbnailsInterval: data.settings.tasksThumbnailsInterval,
+ tasksMetricsInterval: data.settings.tasksMetricsInterval,
+ tasksCleanThumbnailsInterval: data.settings.tasksCleanThumbnailsInterval,
},
enhanceGetInputProps: (payload) => ({
- disabled: data?.tampered?.includes(payload.field) || false,
+ disabled: data.tampered.includes(payload.field) || false,
}),
});
const onSubmit = settingsOnSubmit(navigate, form);
- useEffect(() => {
- if (!data) return;
-
- form.setValues({
- tasksDeleteInterval: data.settings.tasksDeleteInterval ?? '30m',
- tasksClearInvitesInterval: data.settings.tasksClearInvitesInterval ?? '30m',
- tasksMaxViewsInterval: data.settings.tasksMaxViewsInterval ?? '30m',
- tasksThumbnailsInterval: data.settings.tasksThumbnailsInterval ?? '30m',
- tasksMetricsInterval: data.settings.tasksMetricsInterval ?? '30m',
- tasksCleanThumbnailsInterval: data.settings.tasksCleanThumbnailsInterval ?? '1d',
- });
- }, [data]);
-
return (
<>
-
-
All options require a restart to take effect. Setting a value of 0 will disable the task.
diff --git a/src/components/pages/serverSettings/parts/Urls.tsx b/src/components/pages/serverSettings/parts/Urls.tsx
index 01eb823b..3b3bb680 100644
--- a/src/components/pages/serverSettings/parts/Urls.tsx
+++ b/src/components/pages/serverSettings/parts/Urls.tsx
@@ -1,66 +1,60 @@
-import { Response } from '@/lib/api/response';
+import type { Response } from '@/lib/api/response';
import { Button, LoadingOverlay, NumberInput, Stack, TextInput } from '@mantine/core';
import { useForm } from '@mantine/form';
import { IconDeviceFloppy } from '@tabler/icons-react';
-import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { settingsOnSubmit } from '../settingsOnSubmit';
+import useServerSettings from '../useServerSettings';
-export default function Urls({
- swr: { data, isLoading },
-}: {
- swr: { data: Response['/api/server/settings'] | undefined; isLoading: boolean };
-}) {
+export default function Urls() {
+ const { data, isLoading } = useServerSettings();
+
+ return (
+ <>
+
+ {data ? : null}
+ >
+ );
+}
+
+function Form({ data, isLoading }: { data: Response['/api/server/settings']; isLoading: boolean }) {
const navigate = useNavigate();
const form = useForm({
initialValues: {
- urlsRoute: '/go',
- urlsLength: 6,
+ urlsRoute: data.settings.urlsRoute,
+ urlsLength: data.settings.urlsLength,
},
enhanceGetInputProps: (payload) => ({
- disabled: data?.tampered?.includes(payload.field) || false,
+ disabled: data.tampered.includes(payload.field) || false,
}),
});
const onSubmit = settingsOnSubmit(navigate, form);
- useEffect(() => {
- if (!data) return;
-
- form.setValues({
- urlsRoute: data.settings.urlsRoute ?? '/go',
- urlsLength: data.settings.urlsLength ?? 6,
- });
- }, [data]);
-
return (
- <>
-
+
+
+
-
-
-
+
+
-
-
-
- }>
- Save
-
-
- >
+ }>
+ Save
+
+
);
}
diff --git a/src/components/pages/serverSettings/parts/Website.tsx b/src/components/pages/serverSettings/parts/Website.tsx
index 20065e2b..0fd15c14 100644
--- a/src/components/pages/serverSettings/parts/Website.tsx
+++ b/src/components/pages/serverSettings/parts/Website.tsx
@@ -1,45 +1,41 @@
-import { Response } from '@/lib/api/response';
+import type { Response } from '@/lib/api/response';
import { Button, JsonInput, LoadingOverlay, Stack, Switch, TextInput } from '@mantine/core';
import { useForm } from '@mantine/form';
import { IconDeviceFloppy } from '@tabler/icons-react';
-import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { settingsOnSubmit } from '../settingsOnSubmit';
+import useServerSettings from '../useServerSettings';
-const defaultExternalLinks = [
- {
- name: 'GitHub',
- url: 'https://github.com/diced/zipline',
- },
- {
- name: 'Documentation',
- url: 'https://zipline.diced.sh',
- },
-];
+export default function Website() {
+ const { data, isLoading } = useServerSettings();
-export default function Website({
- swr: { data, isLoading },
-}: {
- swr: { data: Response['/api/server/settings'] | undefined; isLoading: boolean };
-}) {
+ return (
+ <>
+
+ {data ? : null}
+ >
+ );
+}
+
+function Form({ data, isLoading }: { data: Response['/api/server/settings']; isLoading: boolean }) {
const navigate = useNavigate();
const form = useForm({
initialValues: {
- websiteTitle: 'Zipline',
- websiteTitleLogo: '',
- websiteExternalLinks: JSON.stringify(defaultExternalLinks),
- websiteLoginBackground: '',
- websiteLoginBackgroundBlur: true,
- websiteDefaultAvatar: '',
- websiteTos: '',
+ websiteTitle: data.settings.websiteTitle,
+ websiteTitleLogo: data.settings.websiteTitleLogo,
+ websiteExternalLinks: JSON.stringify(data.settings.websiteExternalLinks, null, 2),
+ websiteLoginBackground: data.settings.websiteLoginBackground,
+ websiteLoginBackgroundBlur: data.settings.websiteLoginBackgroundBlur,
+ websiteDefaultAvatar: data.settings.websiteDefaultAvatar,
+ websiteTos: data.settings.websiteTos,
- websiteThemeDefault: 'system',
- websiteThemeDark: 'builtin:dark_gray',
- websiteThemeLight: 'builtin:light_gray',
+ websiteThemeDefault: data.settings.websiteThemeDefault,
+ websiteThemeDark: data.settings.websiteThemeDark,
+ websiteThemeLight: data.settings.websiteThemeLight,
},
enhanceGetInputProps: (payload) => ({
- disabled: data?.tampered?.includes(payload.field) || false,
+ disabled: data.tampered.includes(payload.field) || false,
}),
});
@@ -59,12 +55,19 @@ export default function Website({
}
sendValues.websiteTitleLogo =
- values.websiteTitleLogo.trim() === '' ? null : values.websiteTitleLogo.trim();
+ values.websiteTitleLogo?.trim() === '' || !values.websiteTitleLogo?.trim()
+ ? null
+ : values.websiteTitleLogo.trim();
sendValues.websiteLoginBackground =
- values.websiteLoginBackground.trim() === '' ? null : values.websiteLoginBackground.trim();
+ values.websiteLoginBackground?.trim() === '' || !values.websiteLoginBackground?.trim()
+ ? null
+ : values.websiteLoginBackground.trim();
sendValues.websiteDefaultAvatar =
- values.websiteDefaultAvatar.trim() === '' ? null : values.websiteDefaultAvatar.trim();
- sendValues.websiteTos = values.websiteTos.trim() === '' ? null : values.websiteTos.trim();
+ values.websiteDefaultAvatar?.trim() === '' || !values.websiteDefaultAvatar?.trim()
+ ? null
+ : values.websiteDefaultAvatar.trim();
+ sendValues.websiteTos =
+ values.websiteTos?.trim() === '' || !values.websiteTos?.trim() ? null : values.websiteTos.trim();
sendValues.websiteThemeDefault = values.websiteThemeDefault.trim();
sendValues.websiteThemeDark = values.websiteThemeDark.trim();
@@ -76,110 +79,92 @@ export default function Website({
return settingsOnSubmit(navigate, form)(sendValues);
};
- useEffect(() => {
- if (!data) return;
-
- form.setValues({
- websiteTitle: data.settings.websiteTitle ?? 'Zipline',
- websiteTitleLogo: data.settings.websiteTitleLogo ?? '',
- websiteExternalLinks: JSON.stringify(
- data.settings.websiteExternalLinks ?? defaultExternalLinks,
- null,
- 2,
- ),
- websiteLoginBackground: data.settings.websiteLoginBackground ?? '',
- websiteLoginBackgroundBlur: data.settings.websiteLoginBackgroundBlur ?? true,
- websiteDefaultAvatar: data.settings.websiteDefaultAvatar ?? '',
- websiteTos: data.settings.websiteTos ?? '',
- websiteThemeDefault: data.settings.websiteThemeDefault ?? 'system',
- websiteThemeDark: data.settings.websiteThemeDark ?? 'builtin:dark_gray',
- websiteThemeLight: data.settings.websiteThemeLight ?? 'builtin:light_gray',
- });
- }, [data]);
-
return (
- <>
-
+
+
+
-
-
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
-
-
- }>
- Save
-
-
- >
+
+
+ }>
+ Save
+
+
);
}
diff --git a/src/components/pages/serverSettings/useServerSettings.ts b/src/components/pages/serverSettings/useServerSettings.ts
new file mode 100644
index 00000000..2b9dacb6
--- /dev/null
+++ b/src/components/pages/serverSettings/useServerSettings.ts
@@ -0,0 +1,6 @@
+import type { Response } from '@/lib/api/response';
+import useSWR from 'swr';
+
+export default function useServerSettings() {
+ return useSWR('/api/server/settings');
+}
diff --git a/src/components/pages/settings/parts/SettingsAvatar.tsx b/src/components/pages/settings/parts/SettingsAvatar.tsx
index 73308187..de15771d 100644
--- a/src/components/pages/settings/parts/SettingsAvatar.tsx
+++ b/src/components/pages/settings/parts/SettingsAvatar.tsx
@@ -25,7 +25,7 @@ import {
IconSettingsFilled,
IconX,
} from '@tabler/icons-react';
-import { useEffect, useState } from 'react';
+import { useState } from 'react';
export default function SettingsAvatar() {
const user = useUserStore((state) => state.user);
@@ -36,14 +36,16 @@ export default function SettingsAvatar() {
const [avatar, setAvatar] = useState(null);
const [avatarSrc, setAvatarSrc] = useState(null);
- useEffect(() => {
- (async () => {
- if (!avatar) return;
+ const onAvatarChange = async (file: File | null) => {
+ setAvatar(file);
- const base64url = await readToDataURL(avatar);
- setAvatarSrc(base64url);
- })();
- }, [avatar]);
+ if (!file) {
+ setAvatarSrc(null);
+ return;
+ }
+
+ setAvatarSrc(await readToDataURL(file));
+ };
const saveAvatar = async () => {
if (!avatar) return;
@@ -111,7 +113,7 @@ export default function SettingsAvatar() {
accept='image/*'
placeholder='Upload new avatar...'
value={avatar}
- onChange={(file) => setAvatar(file)}
+ onChange={onAvatarChange}
leftSection={}
/>
diff --git a/src/components/pages/settings/parts/SettingsFileView.tsx b/src/components/pages/settings/parts/SettingsFileView.tsx
index eddb39d4..7c9aae00 100644
--- a/src/components/pages/settings/parts/SettingsFileView.tsx
+++ b/src/components/pages/settings/parts/SettingsFileView.tsx
@@ -1,3 +1,4 @@
+import type { User } from '@/lib/db/models/user';
import { Response } from '@/lib/api/response';
import { fetchApi } from '@/lib/fetchApi';
import { useUserStore } from '@/lib/client/store/user';
@@ -27,7 +28,6 @@ import {
IconDeviceFloppy,
IconFileX,
} from '@tabler/icons-react';
-import { useEffect } from 'react';
import { mutate } from 'swr';
import { useShallow } from 'zustand/shallow';
@@ -40,19 +40,34 @@ const alignIcons: Record = {
export default function SettingsFileView() {
const [user, setUser] = useUserStore(useShallow((state) => [state.user, state.setUser]));
+ if (!user) {
+ return (
+
+ Viewing Files
+
+ Loading…
+
+
+ );
+ }
+
+ return ;
+}
+
+function Form({ user, setUser }: { user: User; setUser: (u: User) => void }) {
const form = useForm({
initialValues: {
- enabled: user?.view.enabled ?? false,
- content: user?.view.content ?? '',
- embed: user?.view.embed ?? false,
- embedTitle: user?.view.embedTitle ?? '',
- embedDescription: user?.view.embedDescription ?? '',
- embedSiteName: user?.view.embedSiteName ?? '',
- embedColor: user?.view.embedColor ?? '',
- align: user?.view.align ?? 'left',
- showMimetype: user?.view.showMimetype ?? false,
- showTags: user?.view.showTags ?? false,
- showFolder: user?.view.showFolder ?? false,
+ enabled: user.view.enabled || false,
+ content: user.view.content || '',
+ embed: user.view.embed || false,
+ embedTitle: user.view.embedTitle || '',
+ embedDescription: user.view.embedDescription || '',
+ embedSiteName: user.view.embedSiteName || '',
+ embedColor: user.view.embedColor || '',
+ align: user.view.align || 'left',
+ showMimetype: user.view.showMimetype || false,
+ showTags: user.view.showTags || false,
+ showFolder: user.view.showFolder || false,
},
});
@@ -95,24 +110,6 @@ export default function SettingsFileView() {
});
};
- useEffect(() => {
- if (user) {
- form.setValues({
- enabled: user.view.enabled || false,
- content: user.view.content || '',
- embed: user.view.embed || false,
- embedTitle: user.view.embedTitle || '',
- embedDescription: user.view.embedDescription || '',
- embedSiteName: user.view.embedSiteName || '',
- embedColor: user.view.embedColor || '',
- align: user.view.align || 'left',
- showMimetype: user.view.showMimetype || false,
- showTags: user.view.showTags || false,
- showFolder: user.view.showFolder || false,
- });
- }
- }, [user]);
-
return (
Viewing Files
diff --git a/src/components/pages/settings/parts/SettingsUser.tsx b/src/components/pages/settings/parts/SettingsUser.tsx
index 5918e188..417b2336 100644
--- a/src/components/pages/settings/parts/SettingsUser.tsx
+++ b/src/components/pages/settings/parts/SettingsUser.tsx
@@ -1,3 +1,4 @@
+import type { User } from '@/lib/db/models/user';
import { ApiError } from '@/lib/api/errors';
import { Response } from '@/lib/api/response';
import { fetchApi } from '@/lib/fetchApi';
@@ -25,29 +26,36 @@ import {
IconUser,
IconUserCancel,
} from '@tabler/icons-react';
-import { useEffect, useState } from 'react';
+import { useState } from 'react';
import { mutate } from 'swr';
+import useSWR from 'swr';
import { useShallow } from 'zustand/shallow';
export default function SettingsUser() {
const [user, setUser] = useUserStore(useShallow((state) => [state.user, state.setUser]));
+ const { data: tokenPayload } = useSWR('/api/user/token');
+
+ if (!user) {
+ return (
+
+ User
+
+ Loading…
+
+
+ );
+ }
+
+ return ;
+}
+
+function Form({ user, setUser, token }: { user: User; setUser: (u: User) => void; token: string }) {
const [tokenShown, setTokenShown] = useState(false);
- const [token, setToken] = useState('');
-
- useEffect(() => {
- (async () => {
- const { data } = await fetchApi('/api/user/token');
-
- if (data) {
- setToken(data.token || '');
- }
- })();
- }, []);
const form = useForm({
initialValues: {
- username: user?.username ?? '',
+ username: user.username,
password: '',
},
validate: {
@@ -61,7 +69,7 @@ export default function SettingsUser() {
password?: string;
} = {};
- if (values.username !== user?.username) send['username'] = values.username.trim();
+ if (values.username !== user.username) send['username'] = values.username.trim();
if (values.password) send['password'] = values.password.trim();
const { data, error } = await fetchApi('/api/user', 'PATCH', send);
@@ -84,6 +92,7 @@ export default function SettingsUser() {
if (!data?.user) return;
mutate('/api/user');
+ mutate('/api/user/token');
setUser(data.user);
notifications.show({
message: 'User updated',
@@ -96,7 +105,7 @@ export default function SettingsUser() {
User
- {user?.id}
+ {user.id}
@@ -134,7 +143,7 @@ export default function SettingsUser() {
leftSection={}
/>
- }>
+ }>
Save
diff --git a/src/lib/client/hooks/useUser.ts b/src/lib/client/hooks/useUser.ts
new file mode 100644
index 00000000..d413a229
--- /dev/null
+++ b/src/lib/client/hooks/useUser.ts
@@ -0,0 +1,24 @@
+import type { Response } from '@/lib/api/response';
+import useSWR from 'swr';
+
+async function fetcher(url: string): Promise {
+ const res = await fetch(url);
+ if (!res.ok) return null;
+
+ return res.json();
+}
+
+export default function useUser(): {
+ user: Response['/api/user']['user'] | undefined;
+ loading: boolean;
+} {
+ const { data, isLoading } = useSWR('/api/user', fetcher, {
+ revalidateOnFocus: false,
+ revalidateOnReconnect: false,
+ refreshWhenHidden: false,
+ revalidateIfStale: false,
+ shouldRetryOnError: false,
+ });
+
+ return { user: data?.user, loading: isLoading };
+}
diff --git a/src/lib/client/hooks/useVersion.ts b/src/lib/client/hooks/useVersion.ts
index 82260192..be721fee 100644
--- a/src/lib/client/hooks/useVersion.ts
+++ b/src/lib/client/hooks/useVersion.ts
@@ -1,15 +1,16 @@
import useSWR from 'swr';
import { Response } from '../../api/response';
-const f = async () => {
+
+async function fetcher() {
const res = await fetch('/api/version');
if (!res.ok) throw new Error('Failed to fetch version');
const r = await res.json();
return r;
-};
+}
export default function useVersion() {
- const { isLoading, data } = useSWR('/api/version', f, {
+ const { isLoading, data } = useSWR('/api/version', fetcher, {
refreshInterval: undefined,
revalidateOnFocus: false,
revalidateIfStale: false,