update #1

Merged
crowetic merged 5 commits from :update into main 2025-09-11 22:35:25 +00:00
11 changed files with 150 additions and 48 deletions
+2
View File
@@ -1,5 +1,6 @@
import { useEffect, useState } from "react";
import { Route, Routes } from "react-router-dom";
import { useIframe } from './hooks/useIframe'
import { ThemeProvider } from "@mui/material/styles";
import { CssBaseline } from "@mui/material";
import { darkTheme, lightTheme } from "./styles/theme";
@@ -16,6 +17,7 @@ import { fetchFeesRedux } from "./constants/PublishFees/FeePricePublish/FeePrice
function App() {
// const themeColor = window._qdnTheme
const [theme, setTheme] = useState("dark");
useIframe()
useEffect(() => {
fetchFeesRedux();
@@ -130,9 +130,10 @@ export const CommentEditor = ({
const address = user?.address;
const name = user?.name || "";
let errorMsg = "";
const safePostName = encodeURIComponent(postName);
const notificationMessage = `This is an automated Q-Support notification indicating that someone has commented on your issue here:
qortal://APP/Q-Support/issue/${postName}/${postId}`;
qortal://APP/Q-Support/issue/${safePostName}/${postId}`;
if (useTestIdentifiers) await sendQchatDM(postName, notificationMessage);
if (!address) {
+15
View File
@@ -40,6 +40,8 @@ interface Props {
userAvatar: string;
authenticate: () => void;
setTheme: (val: string) => void;
accountNames: { name: string }[];
setActiveName: (name: string) => void;
}
const NavBar: React.FC<Props> = ({
@@ -48,6 +50,8 @@ const NavBar: React.FC<Props> = ({
userAvatar,
authenticate,
setTheme,
accountNames,
setActiveName,
}) => {
const windowSize = useWindowSize();
const searchValRef = useRef("");
@@ -303,6 +307,17 @@ const NavBar: React.FC<Props> = ({
horizontal: "left",
}}
>
{accountNames.map(n => (
<DropdownContainer
key={n.name}
onClick={() => {
setActiveName(n.name);
handleCloseUserDropdown();
}}
>
<DropdownText>{n.name || "(nameless address)"}</DropdownText>
</DropdownContainer>
))}
<DropdownContainer
onClick={() => {
setIsOpenBlockedNamesModal(true);
@@ -1,8 +1,7 @@
import { Issue } from "../../state/features/fileSlice.ts";
import { getUserAccountNames } from "../../utils/qortalRequests.ts";
import { PublishFeeData } from "./SendFeeFunctions.ts";
export type AccountName = { name: string; owner: string };
export interface GetRequestData {
limit?: number;
offset?: number;
@@ -24,35 +23,6 @@ export interface getTransactionBySignatureResponse {
amount: string;
}
export const stringIsEmpty = (value: string) => {
return value === "";
};
export const getAccountNames = async (
address: string,
params?: GetRequestData
) => {
const names = (await qortalRequest({
action: "GET_ACCOUNT_NAMES",
address: address,
...params,
})) as AccountName[];
const namelessAddress = { name: "", owner: address };
const emptyNamesFilled = names.map(({ name, owner }) => {
return stringIsEmpty(name) ? namelessAddress : { name, owner };
});
const returnValue =
emptyNamesFilled.length > 0 ? emptyNamesFilled : [namelessAddress];
return returnValue as AccountName[];
};
export const getUserAccountNames = async () => {
const account = await getUserAccount();
return await getAccountNames(account.address);
};
export const userHasName = async (name: string) => {
const userAccountNames = await getUserAccountNames();
const userNames = userAccountNames.map(userName => userName.name);
+1 -1
View File
@@ -1,6 +1,5 @@
import { feeDestinationName, maxFeePublishTimeDiff } from "./FeeData.tsx";
import {
getAccountNames,
getTransactionBySignatureResponse,
objectHasNullValues,
objectToPublishFeeData,
@@ -8,6 +7,7 @@ import {
import { verifyFeeAmount } from "./FeePricePublish/FeePricePublish.ts";
import { getNameData, PublishFeeData } from "./SendFeeFunctions.ts";
import { Issue } from "../../state/features/fileSlice.ts";
import { getAccountNames } from "../../utils/qortalRequests.ts";
const getSignature = async (signature: string) => {
const url = "/transactions/signature/" + signature;
+27
View File
@@ -0,0 +1,27 @@
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
export const useIframe = () => {
const navigate = useNavigate();
useEffect(() => {
function handleNavigation(event: MessageEvent) {
if (event.data?.action === "NAVIGATE_TO_PATH" && event.data.path) {
console.log("Navigating to path within React app:", event.data.path);
navigate(event.data.path); // Navigate directly to the specified path
// Send a response back to the parent window after navigation is handled
window.parent.postMessage(
{ action: "NAVIGATION_SUCCESS", path: event.data.path },
"*"
);
}
}
window.addEventListener("message", handleNavigation);
return () => {
window.removeEventListener("message", handleNavigation);
};
}, [navigate]);
return { navigate };
};
+1 -1
View File
@@ -50,7 +50,7 @@ export const IssueListComponentLevel = ({ mode }: VideoListProps) => {
try {
const offset = issues.length;
// `/arbitrary/resources/search?mode=ALL&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true&offset=${offset}&limit=${videoLimit}`;
const url = `/arbitrary/resources/search?mode=ALL&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true&offset=${offset}&limit=50&service=DOCUMENT&query=${QSUPPORT_FILE_BASE}_&name=${paramName}`;
const url = `/arbitrary/resources/search?mode=ALL&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true&offset=${offset}&limit=50&service=DOCUMENT&query=${QSUPPORT_FILE_BASE}&name=${paramName}`;
const response = await fetch(url, {
method: "GET",
headers: {
+38
View File
@@ -1,4 +1,6 @@
import DownloadIcon from "@mui/icons-material/Download";
import ContentCopyIcon from "@mui/icons-material/ContentCopy";
import { IconButton, Tooltip } from "@mui/material";
import { Avatar, Box, Typography, useTheme } from "@mui/material";
import React, { useEffect, useMemo, useRef, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
@@ -110,6 +112,12 @@ export const IssueContent = () => {
}, [issueData]);
const dispatch = useDispatch();
const ticketNumber = useMemo(() => {
if (!id) return "";
const match = id.match(/_([^_]+)_metadata$/);
return match ? match[1] : "";
}, [id]);
const getIssueData = React.useCallback(async (name: string, id: string) => {
try {
if (!name || !id) return;
@@ -313,6 +321,36 @@ export const IssueContent = () => {
<IssueIcon iconSrc={QORTicon} style={{ marginLeft: "10px" }} />
)}
</div>
{ticketNumber && (
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "6px",
}}
>
<Typography
variant="subtitle1"
sx={{ fontWeight: "bold", userSelect: "text" }}
>
Ticket&nbsp;#{ticketNumber}
</Typography>
<Tooltip title="Copy issue URL">
<IconButton
size="small"
onClick={() =>
navigator.clipboard.writeText(
`qortal://APP/Q-Support/issue/${name}/${id}`
)
}
aria-label="Copy issue URL"
>
<ContentCopyIcon fontSize="inherit" />
</IconButton>
</Tooltip>
</Box>
)}
{issueData?.created && (
<Typography
variant="h4"
+13 -5
View File
@@ -1,13 +1,21 @@
import { createSlice } from '@reduxjs/toolkit';
export interface NameRecord {
name: string;
owner: string;
}
interface User {
address: string;
publicKey: string;
name?: string;
names?: NameRecord[];
}
interface AuthState {
user: {
address: string;
publicKey: string;
name?: string;
} | null;
user: User | null;
}
const initialState: AuthState = {
user: null
};
+32 -1
View File
@@ -2,8 +2,8 @@ import moment from "moment";
import { CoinType } from "../constants/PublishFees/FeePricePublish/FeePricePublish.ts";
import { NameData } from "../constants/PublishFees/SendFeeFunctions.ts";
import {
GetRequestData,
getUserAccount,
getUserAccountNames,
} from "../constants/PublishFees/VerifyPayment-Functions.ts";
import { Issue } from "../state/features/fileSlice.ts";
import { isNumber } from "./utilFunctions.ts";
@@ -15,6 +15,37 @@ export const getNameData = async (name: string) => {
})) as NameData;
};
export type AccountName = { name: string; owner: string };
export const stringIsEmpty = (value: string) => {
return value === "";
};
export const getAccountNames = async (
address: string,
params?: GetRequestData
) => {
const names = (await qortalRequest({
action: "GET_ACCOUNT_NAMES",
address: address,
...params,
})) as AccountName[];
const namelessAddress = { name: "", owner: address };
const emptyNamesFilled = names.map(({ name, owner }) => {
return stringIsEmpty(name) ? namelessAddress : { name, owner };
});
const returnValue =
emptyNamesFilled.length > 0 ? emptyNamesFilled : [namelessAddress];
return returnValue as AccountName[];
};
export const getUserAccountNames = async () => {
const account = await getUserAccount();
return await getAccountNames(account.address);
};
export const sendQchatDM = async (
recipientName: string,
message: string,
+18 -8
View File
@@ -74,12 +74,11 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
const { isLoadingGlobal } = useSelector((state: RootState) => state.global);
async function getNameInfo(address: string) {
return await qortalRequest({
action: "GET_PRIMARY_NAME",
address: address,
});
}
const getAccountNames = async (address: string) =>
(await qortalRequest({
action: "GET_ACCOUNT_NAMES",
address,
})) as { name: string; owner: string }[];
const askForAccountInformation = React.useCallback(async () => {
try {
@@ -87,8 +86,15 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
action: "GET_USER_ACCOUNT",
});
const name = await getNameInfo(account.address);
dispatch(addUser({ ...account, name }));
const names = await getAccountNames(account.address);
const [primary] = names;
dispatch(
addUser({
...account,
name: primary?.name ?? "",
names,
})
);
} catch (error) {
console.error(error);
}
@@ -128,6 +134,10 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
setTheme={(val: string) => setTheme(val)}
isAuthenticated={!!user?.name}
userName={user?.name || ""}
accountNames={user?.names || []}
setActiveName={(name: string) =>
dispatch(addUser({ ...user, name }))
}
userAvatar={userAvatar}
authenticate={askForAccountInformation}
/>