mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-02-20 07:37:26 -05:00
completed frontend for devices in settings
This commit is contained in:
@@ -185,3 +185,12 @@ export function translateKindName(kindName: string): string {
|
||||
return kindName;
|
||||
}
|
||||
}
|
||||
|
||||
export function fetchAccessToken(): string {
|
||||
const accessToken: string =
|
||||
JSON.parse(window.localStorage.getItem('frontendCookies') ?? '[]')
|
||||
.find((cookie: string) => cookie.startsWith('st-access-token'))
|
||||
?.split('=')[1]
|
||||
.split(';')[0] || '';
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Key } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { HardwareModel, useBridgeQuery, useLibraryQuery } from '@sd/client';
|
||||
import { useLocale, useOperatingSystem } from '~/hooks';
|
||||
import { useAccessToken, useLocale, useOperatingSystem } from '~/hooks';
|
||||
import { useRouteTitle } from '~/hooks/useRouteTitle';
|
||||
import { hardwareModelToIcon } from '~/util/hardware';
|
||||
|
||||
@@ -28,16 +28,14 @@ export const Component = () => {
|
||||
const os = useOperatingSystem();
|
||||
|
||||
const { t } = useLocale();
|
||||
const accessToken = useAccessToken();
|
||||
|
||||
const locationsQuery = useLibraryQuery(['locations.list'], { keepPreviousData: true });
|
||||
const locations = locationsQuery.data ?? [];
|
||||
|
||||
// not sure if we'll need the node state in the future, as it should be returned with the cloud.devices.list query
|
||||
// const { data: node } = useBridgeQuery(['nodeState']);
|
||||
const cloudDevicesList = useBridgeQuery(['cloud.devices.list'], {
|
||||
suspense: true,
|
||||
retry: false
|
||||
});
|
||||
const cloudDevicesList = useBridgeQuery(['cloud.devices.list', { access_token: accessToken }]);
|
||||
|
||||
const search = useSearchFromSearchParams({ defaultTarget: 'paths' });
|
||||
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { HardwareModel, useBridgeMutation, useBridgeQuery, useZodForm } from '@sd/client';
|
||||
import { Dialog, ErrorMessage, useDialog, UseDialogProps } from '@sd/ui';
|
||||
import { Icon } from '~/components';
|
||||
import { useAccessToken, useLocale } from '~/hooks';
|
||||
import { hardwareModelToIcon } from '~/util/hardware';
|
||||
import { usePlatform } from '~/util/Platform';
|
||||
|
||||
interface Props extends UseDialogProps {
|
||||
pubId: string;
|
||||
name: string;
|
||||
device_model: string;
|
||||
}
|
||||
|
||||
interface CorePubId {
|
||||
Uuid: string;
|
||||
}
|
||||
|
||||
export default function DeleteLibraryDialog(props: Props) {
|
||||
const { t } = useLocale();
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const platform = usePlatform();
|
||||
const navigate = useNavigate();
|
||||
const accessToken = useAccessToken();
|
||||
const { data: node } = useBridgeQuery(['nodeState']);
|
||||
const deleteDevice = useBridgeMutation('cloud.devices.delete');
|
||||
const deviceAmount = useBridgeQuery(['cloud.devices.list', { access_token: accessToken }]).data
|
||||
?.length;
|
||||
|
||||
const form = useZodForm();
|
||||
|
||||
// Check if the current device matches the UUID or if it's the only device
|
||||
useEffect(() => {
|
||||
if (deviceAmount === 1) {
|
||||
form.setError('pubId', {
|
||||
type: 'manual',
|
||||
message: t('error_only_device')
|
||||
});
|
||||
} else if ((node?.id as CorePubId).Uuid === props.pubId) {
|
||||
form.setError('pubId', {
|
||||
type: 'manual',
|
||||
message: t('error_current_device')
|
||||
});
|
||||
}
|
||||
}, [form, node, props.pubId, deviceAmount, t]);
|
||||
|
||||
const onSubmit = form.handleSubmit(async () => {
|
||||
try {
|
||||
// Check for form errors before proceeding
|
||||
if (form.formState.errors.pubId) {
|
||||
return;
|
||||
}
|
||||
|
||||
await deleteDevice.mutateAsync({
|
||||
access_token: accessToken,
|
||||
pub_id: props.pubId
|
||||
});
|
||||
queryClient.invalidateQueries(['library.list']);
|
||||
|
||||
platform.refreshMenuBar && platform.refreshMenuBar();
|
||||
navigate('/');
|
||||
} catch (e) {
|
||||
alert(`Failed to delete device: ${e}`);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
form={form}
|
||||
onSubmit={onSubmit}
|
||||
dialog={useDialog(props)}
|
||||
title={t('delete_device')}
|
||||
closeLabel={t('close')}
|
||||
description={t('delete_device_description')}
|
||||
ctaDanger
|
||||
ctaLabel={t('delete')}
|
||||
>
|
||||
<div className="mt-5 flex flex-col items-center justify-center gap-2">
|
||||
<Icon
|
||||
// once backend endpoint is populated need to check if this is working correctly i.e fetching correct icons for devices
|
||||
name={hardwareModelToIcon(props.device_model as HardwareModel)}
|
||||
alt="Device icon"
|
||||
size={56}
|
||||
className="mr-2"
|
||||
/>
|
||||
<p className="text-medium mb-4 rounded-full text-sm font-medium">{props.name}</p>
|
||||
<ErrorMessage name="pubId" className="mb-4" />
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -2,11 +2,13 @@ import { Trash } from '@phosphor-icons/react';
|
||||
import { iconNames } from '@sd/assets/util';
|
||||
import { Key } from 'react';
|
||||
import { HardwareModel, humanizeSize } from '@sd/client';
|
||||
import { Button, Card, Tooltip } from '@sd/ui';
|
||||
import { Button, Card, dialogManager, Tooltip } from '@sd/ui';
|
||||
import { Icon } from '~/components';
|
||||
import { useLocale } from '~/hooks';
|
||||
import { useAccessToken, useLocale } from '~/hooks';
|
||||
import { hardwareModelToIcon } from '~/util/hardware';
|
||||
|
||||
import DeleteDeviceDialog from './DeleteDeviceDialog';
|
||||
|
||||
interface DeviceItemProps {
|
||||
pub_id: Key | null | undefined;
|
||||
name: string;
|
||||
@@ -46,7 +48,19 @@ export default (props: DeviceItemProps) => {
|
||||
}}
|
||||
>
|
||||
<Tooltip label={t('Delete device')}>
|
||||
<Trash className="size-4" />
|
||||
<Trash
|
||||
onClick={() => {
|
||||
dialogManager.create((dp) => (
|
||||
<DeleteDeviceDialog
|
||||
name={props.name}
|
||||
device_model={props.device_model}
|
||||
pubId={String(props.pub_id)}
|
||||
{...dp}
|
||||
/>
|
||||
));
|
||||
}}
|
||||
className="size-4"
|
||||
/>
|
||||
</Tooltip>
|
||||
</Button>
|
||||
</Card>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Key, useState } from 'react';
|
||||
import { LibraryConfigWrapped, useBridgeQuery } from '@sd/client';
|
||||
import { Button, ButtonLink, Card, dialogManager, Tooltip } from '@sd/ui';
|
||||
import { Icon } from '~/components';
|
||||
import { useLocale } from '~/hooks';
|
||||
import { useAccessToken, useLocale } from '~/hooks';
|
||||
|
||||
import DeleteDialog from './DeleteDialog';
|
||||
import DeviceItem from './DeviceItem';
|
||||
@@ -18,12 +18,8 @@ export default (props: Props) => {
|
||||
const { t } = useLocale();
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
|
||||
const cloudDevicesList = useBridgeQuery(['cloud.devices.list'], {
|
||||
suspense: true,
|
||||
retry: false
|
||||
});
|
||||
console.log(cloudDevicesList);
|
||||
|
||||
const accessToken = useAccessToken();
|
||||
const cloudDevicesList = useBridgeQuery(['cloud.devices.list', { access_token: accessToken }]);
|
||||
const toggleExpansion = () => {
|
||||
setIsExpanded((prev) => !prev);
|
||||
};
|
||||
|
||||
@@ -32,3 +32,4 @@ export * from './useZodParams';
|
||||
export * from './useZodRouteParams';
|
||||
export * from './useZodSearchParams';
|
||||
export * from './useDeeplinkEventHandler';
|
||||
export * from './useAccessToken';
|
||||
|
||||
8
interface/hooks/useAccessToken.ts
Normal file
8
interface/hooks/useAccessToken.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export function useAccessToken(): string {
|
||||
const accessToken: string =
|
||||
JSON.parse(window.localStorage.getItem('frontendCookies') ?? '[]')
|
||||
.find((cookie: string) => cookie.startsWith('st-access-token'))
|
||||
?.split('=')[1]
|
||||
.split(';')[0] || '';
|
||||
return accessToken.trim();
|
||||
}
|
||||
@@ -168,11 +168,13 @@
|
||||
"default": "Default",
|
||||
"default_settings": "Default Settings",
|
||||
"delete": "Delete",
|
||||
"delete_device": "Delete device",
|
||||
"delete_device_description": "This is permanent! This device will lose access to its the corresponding library and be removed. ",
|
||||
"delete_dialog_title": "Delete {{prefix}} {{type}}",
|
||||
"delete_forever": "Delete Forever",
|
||||
"delete_info": "This will not delete the actual folder on disk. Preview media will be deleted.",
|
||||
"delete_library": "Delete Library",
|
||||
"delete_library_description": "This is permanent! Original files will not be deleted, only the Spacedrive library.",
|
||||
"delete_library_description": "This is permanent! Only the Spacedrive library will be deleted, and original files will remain untouched.",
|
||||
"delete_location": "Delete Location",
|
||||
"delete_location_description": "Deleting a location will also remove all files associated with it from the Spacedrive database, the files themselves will not be deleted.",
|
||||
"delete_object": "Delete object",
|
||||
@@ -191,7 +193,6 @@
|
||||
"dialog": "Dialog",
|
||||
"dialog_shortcut_description": "To perform actions and operations",
|
||||
"direction": "Direction",
|
||||
"drop_files_here_to_send_with": "Drop files here to send with Spacedrop",
|
||||
"directory_one": "directory",
|
||||
"directory_other": "directories",
|
||||
"disabled": "Disabled",
|
||||
@@ -212,6 +213,7 @@
|
||||
"download": "Download",
|
||||
"downloading_update": "Downloading Update",
|
||||
"drag_to_resize": "Drag to resize",
|
||||
"drop_files_here_to_send_with": "Drop files here to send with Spacedrop",
|
||||
"duplicate": "Duplicate",
|
||||
"duplicate_object": "Duplicate object",
|
||||
"duplicate_success": "Items duplicated",
|
||||
@@ -242,8 +244,10 @@
|
||||
"erase_a_file": "Erase a file",
|
||||
"erase_a_file_description": "Configure your erasure settings.",
|
||||
"error": "Error",
|
||||
"error_current_device": "You are currently on this device, and cannot delete the device from the library. Please use another device if you'd like to remove this device.",
|
||||
"error_loading_original_file": "Error loading original file",
|
||||
"error_message": "Error: {{error}}.",
|
||||
"error_only_device": "You cannot delete this device as it is the only device that belongs to this library.",
|
||||
"error_unknown": "An unknown error occurred.",
|
||||
"executable": "Executable",
|
||||
"executable_one": "Executable",
|
||||
|
||||
@@ -50,7 +50,7 @@ export const Form = <T extends FieldValues>({
|
||||
};
|
||||
|
||||
export const errorStyles = cva(
|
||||
'flex justify-center gap-2 break-all rounded border border-red-500/40 bg-red-800/40 px-3 py-2 text-white',
|
||||
'flex justify-center gap-2 whitespace-normal break-words rounded border border-red-500/40 bg-red-800/40 px-3 py-2 text-white',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
@@ -89,7 +89,7 @@ export const ErrorMessage = ({ name, variant, className }: ErrorMessageProps) =>
|
||||
return typeof message === 'string' ? (
|
||||
<animated.div style={styles} className={errorStyles({ variant, className })}>
|
||||
<Warning className="size-4" />
|
||||
<p>{message}</p>
|
||||
<p className="whitespace-normal">{message}</p>
|
||||
</animated.div>
|
||||
) : null;
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user