mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-12-17 09:34:16 -05:00
feat(gui): Display developer responses to feedback (#302)
This commit is contained in:
parent
f1e5cdfbfe
commit
53a994e6dc
19 changed files with 1216 additions and 45 deletions
|
|
@ -3,7 +3,7 @@ import { ThemeProvider } from "@material-ui/core/styles";
|
|||
import "@tauri-apps/plugin-shell";
|
||||
import { Route, MemoryRouter as Router, Routes } from "react-router-dom";
|
||||
import Navigation, { drawerWidth } from "./navigation/Navigation";
|
||||
import HelpPage from "./pages/help/HelpPage";
|
||||
import SettingsPage from "./pages/help/SettingsPage";
|
||||
import HistoryPage from "./pages/history/HistoryPage";
|
||||
import SwapPage from "./pages/swap/SwapPage";
|
||||
import WalletPage from "./pages/wallet/WalletPage";
|
||||
|
|
@ -14,6 +14,7 @@ import { themes } from "./theme";
|
|||
import { useEffect } from "react";
|
||||
import { setupBackgroundTasks } from "renderer/background";
|
||||
import "@fontsource/roboto";
|
||||
import FeedbackPage from "./pages/feedback/FeedbackPage";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
innerContent: {
|
||||
|
|
@ -54,7 +55,8 @@ function InnerContent() {
|
|||
<Route path="/swap" element={<SwapPage />} />
|
||||
<Route path="/history" element={<HistoryPage />} />
|
||||
<Route path="/wallet" element={<WalletPage />} />
|
||||
<Route path="/help" element={<HelpPage />} />
|
||||
<Route path="/settings" element={<SettingsPage />} />
|
||||
<Route path="/feedback" element={<FeedbackPage />} />
|
||||
<Route path="/" element={<SwapPage />} />
|
||||
</Routes>
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import TruncatedText from "renderer/components/other/TruncatedText";
|
|||
import { store } from "renderer/store/storeRenderer";
|
||||
import { useActiveSwapInfo, useAppSelector } from "store/hooks";
|
||||
import { logsToRawString, parseDateString } from "utils/parseUtils";
|
||||
import { submitFeedbackViaHttp } from "../../../api";
|
||||
import { submitFeedbackViaHttp, AttachmentInput } from "../../../api";
|
||||
import LoadingButton from "../../other/LoadingButton";
|
||||
import { PiconeroAmount } from "../../other/Units";
|
||||
import { getLogsOfSwap, redactLogs } from "renderer/rpc";
|
||||
|
|
@ -31,26 +31,58 @@ import logger from "utils/logger";
|
|||
import { Label, Visibility } from "@material-ui/icons";
|
||||
import CliLogsBox from "renderer/components/other/RenderedCliLog";
|
||||
import { CliLog, parseCliLogString } from "models/cliModel";
|
||||
import { addFeedbackId } from "store/features/conversationsSlice";
|
||||
|
||||
async function submitFeedback(body: string, swapId: string | null, swapLogs: string | null, daemonLogs: string | null) {
|
||||
let attachedBody = "";
|
||||
const attachments: AttachmentInput[] = [];
|
||||
|
||||
if (swapId !== null) {
|
||||
const swapInfo = store.getState().rpc.state.swapInfos[swapId];
|
||||
|
||||
if (swapInfo === undefined) {
|
||||
throw new Error(`Swap with id ${swapId} not found`);
|
||||
if (swapInfo) {
|
||||
// Add swap info as an attachment
|
||||
attachments.push({
|
||||
key: `swap_info_${swapId}.json`,
|
||||
content: JSON.stringify(swapInfo, null, 2), // Pretty print JSON
|
||||
});
|
||||
// Retrieve and add logs for the specific swap
|
||||
try {
|
||||
const logs = await getLogsOfSwap(swapId, false);
|
||||
attachments.push({
|
||||
key: `swap_logs_${swapId}.txt`,
|
||||
content: logs.logs.map((l) => JSON.stringify(l)).join("\n"),
|
||||
});
|
||||
} catch (logError) {
|
||||
logger.error(logError, "Failed to get logs for swap", { swapId });
|
||||
// Optionally add an attachment indicating log retrieval failure
|
||||
attachments.push({ key: `swap_logs_${swapId}.error`, content: "Failed to retrieve swap logs." });
|
||||
}
|
||||
} else {
|
||||
logger.warn("Selected swap info not found in state", { swapId });
|
||||
attachments.push({ key: `swap_info_${swapId}.error`, content: "Swap info not found." });
|
||||
}
|
||||
|
||||
attachedBody = `${JSON.stringify(swapInfo, null, 4)}\n\nLogs: ${swapLogs ?? ""}`;
|
||||
// Add swap logs as an attachment
|
||||
if (swapLogs) {
|
||||
attachments.push({
|
||||
key: `swap_logs_${swapId}.txt`,
|
||||
content: swapLogs,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Handle daemon logs
|
||||
if (daemonLogs !== null) {
|
||||
attachedBody += `\n\nDaemon Logs: ${daemonLogs ?? ""}`;
|
||||
attachments.push({
|
||||
key: "daemon_logs.txt",
|
||||
content: daemonLogs,
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`Sending feedback with attachement: \`\n${attachedBody}\``)
|
||||
await submitFeedbackViaHttp(body, attachedBody);
|
||||
// Call the updated API function
|
||||
const feedbackId = await submitFeedbackViaHttp(body, attachments);
|
||||
|
||||
// Dispatch only the ID
|
||||
store.dispatch(addFeedbackId(feedbackId));
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -152,7 +184,12 @@ export default function FeedbackDialog({
|
|||
|
||||
try {
|
||||
setPending(true);
|
||||
await submitFeedback(bodyText, selectedSwap, logsToRawString(swapLogs), logsToRawString(daemonLogs));
|
||||
await submitFeedback(
|
||||
bodyText,
|
||||
selectedSwap,
|
||||
logsToRawString(swapLogs ?? []),
|
||||
logsToRawString(daemonLogs ?? [])
|
||||
);
|
||||
enqueueSnackbar("Feedback submitted successfully!", {
|
||||
variant: "success",
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,12 +1,16 @@
|
|||
import { Box, List } from "@material-ui/core";
|
||||
import { Box, List, Badge } from "@material-ui/core";
|
||||
import AccountBalanceWalletIcon from "@material-ui/icons/AccountBalanceWallet";
|
||||
import HelpOutlineIcon from "@material-ui/icons/HelpOutline";
|
||||
import HistoryOutlinedIcon from "@material-ui/icons/HistoryOutlined";
|
||||
import SwapHorizOutlinedIcon from "@material-ui/icons/SwapHorizOutlined";
|
||||
import FeedbackOutlinedIcon from '@material-ui/icons/FeedbackOutlined';
|
||||
import RouteListItemIconButton from "./RouteListItemIconButton";
|
||||
import UnfinishedSwapsBadge from "./UnfinishedSwapsCountBadge";
|
||||
import { useTotalUnreadMessagesCount } from "store/hooks";
|
||||
import SettingsIcon from '@material-ui/icons/Settings';
|
||||
|
||||
export default function NavigationHeader() {
|
||||
const totalUnreadCount = useTotalUnreadMessagesCount();
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<List>
|
||||
|
|
@ -21,8 +25,18 @@ export default function NavigationHeader() {
|
|||
<RouteListItemIconButton name="Wallet" route="/wallet">
|
||||
<AccountBalanceWalletIcon />
|
||||
</RouteListItemIconButton>
|
||||
<RouteListItemIconButton name="Help & Settings" route="/help">
|
||||
<HelpOutlineIcon />
|
||||
<RouteListItemIconButton name="Feedback" route="/feedback">
|
||||
<Badge
|
||||
badgeContent={totalUnreadCount}
|
||||
color="primary"
|
||||
overlap="rectangular"
|
||||
invisible={totalUnreadCount === 0}
|
||||
>
|
||||
<FeedbackOutlinedIcon />
|
||||
</Badge>
|
||||
</RouteListItemIconButton>
|
||||
<RouteListItemIconButton name="Settings" route="/settings">
|
||||
<SettingsIcon />
|
||||
</RouteListItemIconButton>
|
||||
</List>
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
import { Box, makeStyles } from "@material-ui/core";
|
||||
import FeedbackInfoBox from "../help/FeedbackInfoBox";
|
||||
import ConversationsBox from "../help/ConversationsBox";
|
||||
import ContactInfoBox from "../help/ContactInfoBox";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
outer: {
|
||||
display: "flex",
|
||||
gap: theme.spacing(2),
|
||||
flexDirection: "column",
|
||||
paddingBottom: theme.spacing(2),
|
||||
},
|
||||
}));
|
||||
|
||||
export default function FeedbackPage() {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<Box className={classes.outer}>
|
||||
<FeedbackInfoBox />
|
||||
<ConversationsBox />
|
||||
<ContactInfoBox />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
346
src-gui/src/renderer/components/pages/help/ConversationsBox.tsx
Normal file
346
src-gui/src/renderer/components/pages/help/ConversationsBox.tsx
Normal file
|
|
@ -0,0 +1,346 @@
|
|||
import { useState, useEffect, useMemo } from "react";
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
makeStyles,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
IconButton,
|
||||
TableContainer,
|
||||
Table,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableBody,
|
||||
Paper,
|
||||
Badge,
|
||||
TextField,
|
||||
CircularProgress,
|
||||
InputAdornment,
|
||||
Tooltip,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemIcon,
|
||||
Link,
|
||||
} from "@material-ui/core";
|
||||
import ChatIcon from '@material-ui/icons/Chat';
|
||||
import SendIcon from '@material-ui/icons/Send';
|
||||
import InfoBox from "renderer/components/modal/swap/InfoBox";
|
||||
import TruncatedText from "renderer/components/other/TruncatedText";
|
||||
import clsx from 'clsx';
|
||||
import { useAppSelector, useAppDispatch, useUnreadMessagesCount } from "store/hooks";
|
||||
import { markMessagesAsSeen } from "store/features/conversationsSlice";
|
||||
import { appendFeedbackMessageViaHttp, fetchAllConversations } from "renderer/api";
|
||||
import { useSnackbar } from "notistack";
|
||||
import logger from "utils/logger";
|
||||
import AttachmentIcon from '@material-ui/icons/Attachment';
|
||||
import { Message, PrimitiveDateTimeString } from "models/apiModel";
|
||||
import { formatDateTime } from "utils/conversionUtils";
|
||||
|
||||
// Styles
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
content: { display: "flex", flexDirection: "column", alignItems: "flex-start", gap: theme.spacing(2) },
|
||||
tableContainer: { maxHeight: 300 },
|
||||
dialogContent: { display: 'flex', flexDirection: 'column' },
|
||||
messagesContainer: { flexGrow: 1, overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: theme.spacing(1), maxHeight: 400, padding: theme.spacing(1) },
|
||||
messageRow: { display: 'flex', marginTop: theme.spacing(1) },
|
||||
staffRow: { justifyContent: 'flex-start' },
|
||||
userRow: { justifyContent: 'flex-end' },
|
||||
messageBubble: { padding: theme.spacing(1.5), borderRadius: theme.shape.borderRadius * 2, maxWidth: '75%', wordBreak: 'break-word', boxShadow: theme.shadows[1] },
|
||||
staffBubble: { border: `1px solid ${theme.palette.divider}`, color: theme.palette.text.primary, borderRadius: theme.spacing(2) },
|
||||
userBubble: { backgroundColor: theme.palette.primary.main, color: theme.palette.primary.contrastText, borderRadius: theme.spacing(2) },
|
||||
timestamp: { marginTop: theme.spacing(0.5), fontSize: '0.75rem', opacity: 0.7, textAlign: 'right' },
|
||||
inputArea: { flexShrink: 0, marginTop: theme.spacing(2) },
|
||||
attachmentList: {
|
||||
marginTop: theme.spacing(1),
|
||||
marginBottom: theme.spacing(1),
|
||||
paddingLeft: theme.spacing(2),
|
||||
},
|
||||
attachmentItem: {
|
||||
paddingTop: theme.spacing(0.5),
|
||||
paddingBottom: theme.spacing(0.5),
|
||||
}
|
||||
}));
|
||||
|
||||
// Hook: sorted feedback IDs by latest activity, then unread
|
||||
function useSortedFeedbackIds() {
|
||||
const ids = useAppSelector((s) => s.conversations.knownFeedbackIds || []);
|
||||
const conv = useAppSelector((s) => s.conversations.conversations);
|
||||
const seen = useAppSelector((s) => new Set(s.conversations.seenMessages));
|
||||
return useMemo(() => {
|
||||
const arr = ids.map((id) => {
|
||||
const msgs = conv[id] || [];
|
||||
const unread = msgs.filter((m) => m.is_from_staff && !seen.has(m.id.toString())).length;
|
||||
const latest = msgs.reduce((d, m) => {
|
||||
try {
|
||||
const formattedDate = formatDateTime(m.created_at);
|
||||
if (formattedDate.startsWith("Invalid")) return d;
|
||||
const t = new Date(formattedDate).getTime();
|
||||
return isNaN(t) ? d : Math.max(d, t);
|
||||
} catch(e) { return d; }
|
||||
}, 0);
|
||||
return { id, unread, latest };
|
||||
});
|
||||
arr.sort((a, b) => b.latest - a.latest || (b.unread > 0 ? 1 : 0) - (a.unread > 0 ? 1 : 0));
|
||||
return arr.map((x) => x.id);
|
||||
}, [ids, conv, seen]);
|
||||
}
|
||||
|
||||
// Main component
|
||||
export default function ConversationsBox() {
|
||||
const classes = useStyles();
|
||||
const sortedIds = useSortedFeedbackIds();
|
||||
const [openId, setOpenId] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch conversations via API function (handles its own dispatch)
|
||||
fetchAllConversations();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<InfoBox
|
||||
title="Developer Responses"
|
||||
icon={null}
|
||||
loading={false}
|
||||
mainContent={
|
||||
<Box className={classes.content}>
|
||||
<Typography variant="subtitle2">
|
||||
View your past feedback submissions and any replies from the development team.
|
||||
</Typography>
|
||||
{sortedIds.length === 0 ? (
|
||||
<Typography variant="body2">No feedback submitted yet.</Typography>
|
||||
) : (
|
||||
<TableContainer component={Paper} className={classes.tableContainer}>
|
||||
<Table stickyHeader size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell style={{ width: '25%' }}>Last Message</TableCell>
|
||||
<TableCell style={{ width: '60%' }}>Preview</TableCell>
|
||||
<TableCell align="right" style={{ width: '15%' }} />
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{sortedIds.map((id) => (
|
||||
<ConversationRow key={id} feedbackId={id} onOpen={setOpenId} />
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)}
|
||||
</Box>
|
||||
}
|
||||
additionalContent={
|
||||
openId && (
|
||||
<ConversationModal
|
||||
open={!!openId}
|
||||
onClose={() => setOpenId(null)}
|
||||
feedbackId={openId}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// Single row
|
||||
function ConversationRow({ feedbackId, onOpen }: { feedbackId: string, onOpen: (id: string) => void }) {
|
||||
const msgs = useAppSelector((s) => s.conversations.conversations[feedbackId] || []);
|
||||
const unread = useUnreadMessagesCount(feedbackId);
|
||||
const sorted = useMemo(
|
||||
() =>
|
||||
[...msgs].sort((a, b) => {
|
||||
try {
|
||||
const formattedDateA = formatDateTime(a.created_at);
|
||||
const formattedDateB = formatDateTime(b.created_at);
|
||||
if (formattedDateA.startsWith("Invalid")) return 1;
|
||||
if (formattedDateB.startsWith("Invalid")) return -1;
|
||||
const dateA = new Date(formattedDateA).getTime();
|
||||
const dateB = new Date(formattedDateB).getTime();
|
||||
if (isNaN(dateA)) return 1;
|
||||
if (isNaN(dateB)) return -1;
|
||||
return dateB - dateA;
|
||||
} catch (e) { return 0; }
|
||||
}),
|
||||
[msgs]
|
||||
);
|
||||
const lastMsg = sorted[0];
|
||||
const time = lastMsg ? formatDateTime(lastMsg.created_at) : '-';
|
||||
const content = lastMsg ? lastMsg.content : 'No messages yet';
|
||||
const preview = (() => {
|
||||
return content;
|
||||
})();
|
||||
const hasStaff = useMemo(() => msgs.some((m) => m.is_from_staff), [msgs]);
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell style={{ width: '25%' }}>{time}</TableCell>
|
||||
<TableCell style={{ width: '60%' }}>
|
||||
"<TruncatedText limit={30}>{preview}</TruncatedText>"
|
||||
</TableCell>
|
||||
<TableCell align="right" style={{ width: '15%' }}>
|
||||
<Badge badgeContent={unread} color="primary" overlap="rectangular">
|
||||
<Tooltip title={hasStaff ? 'Open Conversation' : 'No developer has responded'} arrow>
|
||||
<span>
|
||||
<IconButton size="small" onClick={() => onOpen(feedbackId)} disabled={!hasStaff}>
|
||||
<ChatIcon />
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Badge>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
// Modal
|
||||
function ConversationModal({ open, onClose, feedbackId }: { open: boolean, onClose: () => void, feedbackId: string }) {
|
||||
const classes = useStyles();
|
||||
const dispatch = useAppDispatch();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const msgs = useAppSelector((s): Message[] => s.conversations.conversations[feedbackId] || []);
|
||||
const seen = useAppSelector((s) => new Set(s.conversations.seenMessages));
|
||||
const [text, setText] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
const unseen = msgs.filter((m) => !seen.has(m.id.toString()));
|
||||
if (unseen.length) dispatch(markMessagesAsSeen(unseen));
|
||||
}
|
||||
}, [open, msgs, seen, dispatch]);
|
||||
|
||||
const sorted = useMemo(
|
||||
() =>
|
||||
[...msgs].sort((a, b) => {
|
||||
try {
|
||||
const formattedDateA = formatDateTime(a.created_at);
|
||||
const formattedDateB = formatDateTime(b.created_at);
|
||||
if (formattedDateA.startsWith("Invalid")) return 1;
|
||||
if (formattedDateB.startsWith("Invalid")) return -1;
|
||||
const dateA = new Date(formattedDateA).getTime();
|
||||
const dateB = new Date(formattedDateB).getTime();
|
||||
if (isNaN(dateA)) return 1;
|
||||
if (isNaN(dateB)) return -1;
|
||||
return dateB - dateA;
|
||||
} catch(e) { return 0; }
|
||||
}),
|
||||
[msgs]
|
||||
);
|
||||
|
||||
const sendMessage = async () => {
|
||||
if (!text.trim()) return;
|
||||
setLoading(true);
|
||||
try {
|
||||
await appendFeedbackMessageViaHttp(feedbackId, text);
|
||||
setText('');
|
||||
enqueueSnackbar('Message sent successfully!', { variant: 'success' });
|
||||
fetchAllConversations();
|
||||
} catch (e) {
|
||||
logger.error(e, 'Send failed');
|
||||
enqueueSnackbar('Failed to send message. Please try again.', { variant: 'error' });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth scroll="paper">
|
||||
<DialogTitle>Conversation <TruncatedText limit={8} truncateMiddle>{feedbackId}</TruncatedText></DialogTitle>
|
||||
<DialogContent dividers className={classes.dialogContent}>
|
||||
{sorted.length === 0 ? (
|
||||
<Typography variant="body2">No messages in this conversation.</Typography>
|
||||
) : (
|
||||
<Box className={classes.messagesContainer}>
|
||||
{sorted.map((m: Message) => {
|
||||
const raw = m.content;
|
||||
return (
|
||||
<Box
|
||||
key={m.id}
|
||||
className={clsx(
|
||||
classes.messageRow,
|
||||
m.is_from_staff ? classes.staffRow : classes.userRow
|
||||
)}
|
||||
>
|
||||
<Box className={clsx(
|
||||
classes.messageBubble,
|
||||
m.is_from_staff ? classes.staffBubble : classes.userBubble
|
||||
)}
|
||||
>
|
||||
<Typography variant="body1" style={{ whiteSpace: 'pre-wrap' }}>
|
||||
{raw}
|
||||
</Typography>
|
||||
{m.attachments && m.attachments.length > 0 && (
|
||||
<List dense disablePadding className={classes.attachmentList}>
|
||||
{m.attachments.map((att) => (
|
||||
<ListItem key={att.id} className={classes.attachmentItem}>
|
||||
<ListItemIcon style={{ minWidth: 'auto', marginRight: '8px' }}>
|
||||
<AttachmentIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<Link
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
alert(`Attachment Key: ${att.key}\n\nContent:\n${att.content}`);
|
||||
}}
|
||||
variant="body2"
|
||||
color="inherit"
|
||||
underline="none"
|
||||
>
|
||||
{att.key}
|
||||
</Link>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
)}
|
||||
<Typography variant="caption" className={classes.timestamp}>
|
||||
{m.is_from_staff ? 'Developer' : 'You'} · {formatDateTime(m.created_at)}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
)}
|
||||
<Box className={classes.inputArea}>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
placeholder="Type your message..."
|
||||
value={text}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
disabled={loading}
|
||||
multiline
|
||||
rowsMax={4}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
sendMessage();
|
||||
}
|
||||
}}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={sendMessage}
|
||||
disabled={!text.trim() || loading}
|
||||
>
|
||||
{loading ? <CircularProgress size={24} /> : <SendIcon />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} variant="outlined">
|
||||
Close
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
import { Box, makeStyles } from "@material-ui/core";
|
||||
import ContactInfoBox from "./ContactInfoBox";
|
||||
import DonateInfoBox from "./DonateInfoBox";
|
||||
import FeedbackInfoBox from "./FeedbackInfoBox";
|
||||
import DaemonControlBox from "./DaemonControlBox";
|
||||
import SettingsBox from "./SettingsBox";
|
||||
import ExportDataBox from "./ExportDataBox";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { useEffect } from "react";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
outer: {
|
||||
display: "flex",
|
||||
|
|
@ -16,7 +16,7 @@ const useStyles = makeStyles((theme) => ({
|
|||
},
|
||||
}));
|
||||
|
||||
export default function HelpPage() {
|
||||
export default function SettingsPage() {
|
||||
const classes = useStyles();
|
||||
const location = useLocation();
|
||||
|
||||
|
|
@ -29,11 +29,9 @@ export default function HelpPage() {
|
|||
|
||||
return (
|
||||
<Box className={classes.outer}>
|
||||
<FeedbackInfoBox />
|
||||
<SettingsBox />
|
||||
<ExportDataBox />
|
||||
<DaemonControlBox />
|
||||
<ContactInfoBox />
|
||||
<DonateInfoBox />
|
||||
</Box>
|
||||
);
|
||||
Loading…
Add table
Add a link
Reference in a new issue