mirror of
https://github.com/Qortal/qortal-mobile.git
synced 2025-07-01 19:31:21 +00:00
Compare commits
27 Commits
v0.5.4-pre
...
feature/in
Author | SHA1 | Date | |
---|---|---|---|
93b8282a69 | |||
d2fc982852 | |||
9113267dc2 | |||
fe63d9c0ff | |||
7e34886d15 | |||
b2b7820017 | |||
74d064f735 | |||
1482a8ed60 | |||
d9db4e5c27 | |||
15c1373cb0 | |||
2fbc48d676 | |||
54a1bb636a | |||
a22c48667b | |||
4ad0fb7db3 | |||
cfa4afc506 | |||
44a2675c87 | |||
7be4a08a41 | |||
20c51e0806 | |||
0b9f32fd8c | |||
0cfdd5cbc9 | |||
21b3dd9d02 | |||
93305b8dc4 | |||
8c98fcbcdf | |||
3c00d40093 | |||
3e1e38838d | |||
24f133acf5 | |||
d2c79f52c4 |
@ -14,6 +14,7 @@ dependencies {
|
|||||||
implementation project(':capacitor-filesystem')
|
implementation project(':capacitor-filesystem')
|
||||||
implementation project(':capacitor-local-notifications')
|
implementation project(':capacitor-local-notifications')
|
||||||
implementation project(':capacitor-preferences')
|
implementation project(':capacitor-preferences')
|
||||||
|
implementation project(':capacitor-screen-orientation')
|
||||||
implementation project(':capacitor-splash-screen')
|
implementation project(':capacitor-splash-screen')
|
||||||
implementation project(':capawesome-capacitor-file-picker')
|
implementation project(':capawesome-capacitor-file-picker')
|
||||||
implementation project(':evva-capacitor-secure-storage-plugin')
|
implementation project(':evva-capacitor-secure-storage-plugin')
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
android:usesCleartextTraffic="true">
|
android:usesCleartextTraffic="true">
|
||||||
<activity
|
<activity
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
|
||||||
|
android:screenOrientation="unspecified"
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:label="@string/title_activity_main"
|
android:label="@string/title_activity_main"
|
||||||
android:theme="@style/AppTheme.NoActionBarLaunch"
|
android:theme="@style/AppTheme.NoActionBarLaunch"
|
||||||
|
@ -4,6 +4,8 @@ import com.getcapacitor.BridgeActivity;
|
|||||||
import com.github.Qortal.qortalMobile.NativeBcrypt;
|
import com.github.Qortal.qortalMobile.NativeBcrypt;
|
||||||
import com.github.Qortal.qortalMobile.NativePOW;
|
import com.github.Qortal.qortalMobile.NativePOW;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.webkit.WebSettings;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
|
||||||
public class MainActivity extends BridgeActivity {
|
public class MainActivity extends BridgeActivity {
|
||||||
@Override
|
@Override
|
||||||
@ -12,6 +14,9 @@ public class MainActivity extends BridgeActivity {
|
|||||||
registerPlugin(NativePOW.class);
|
registerPlugin(NativePOW.class);
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
// ✅ Enable mixed content mode for WebView
|
||||||
|
WebView webView = this.bridge.getWebView();
|
||||||
|
WebSettings webSettings = webView.getSettings();
|
||||||
|
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,9 @@ project(':capacitor-local-notifications').projectDir = new File('../node_modules
|
|||||||
include ':capacitor-preferences'
|
include ':capacitor-preferences'
|
||||||
project(':capacitor-preferences').projectDir = new File('../node_modules/@capacitor/preferences/android')
|
project(':capacitor-preferences').projectDir = new File('../node_modules/@capacitor/preferences/android')
|
||||||
|
|
||||||
|
include ':capacitor-screen-orientation'
|
||||||
|
project(':capacitor-screen-orientation').projectDir = new File('../node_modules/@capacitor/screen-orientation/android')
|
||||||
|
|
||||||
include ':capacitor-splash-screen'
|
include ':capacitor-splash-screen'
|
||||||
project(':capacitor-splash-screen').projectDir = new File('../node_modules/@capacitor/splash-screen/android')
|
project(':capacitor-splash-screen').projectDir = new File('../node_modules/@capacitor/splash-screen/android')
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ const config: CapacitorConfig = {
|
|||||||
"splashImmersive": true
|
"splashImmersive": true
|
||||||
},
|
},
|
||||||
CapacitorHttp: {
|
CapacitorHttp: {
|
||||||
enabled: true,
|
enabled: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
14
package-lock.json
generated
14
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "qortal-go",
|
"name": "qortal-go",
|
||||||
"version": "0.5.2",
|
"version": "0.5.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "qortal-go",
|
"name": "qortal-go",
|
||||||
"version": "0.5.2",
|
"version": "0.5.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capacitor/android": "^6.1.2",
|
"@capacitor/android": "^6.1.2",
|
||||||
"@capacitor/app": "^6.0.1",
|
"@capacitor/app": "^6.0.1",
|
||||||
@ -16,6 +16,7 @@
|
|||||||
"@capacitor/filesystem": "^6.0.1",
|
"@capacitor/filesystem": "^6.0.1",
|
||||||
"@capacitor/local-notifications": "^6.1.0",
|
"@capacitor/local-notifications": "^6.1.0",
|
||||||
"@capacitor/preferences": "^6.0.3",
|
"@capacitor/preferences": "^6.0.3",
|
||||||
|
"@capacitor/screen-orientation": "^6.0.3",
|
||||||
"@capacitor/splash-screen": "^6.0.2",
|
"@capacitor/splash-screen": "^6.0.2",
|
||||||
"@capawesome/capacitor-file-picker": "^6.1.0",
|
"@capawesome/capacitor-file-picker": "^6.1.0",
|
||||||
"@chatscope/chat-ui-kit-react": "^2.0.3",
|
"@chatscope/chat-ui-kit-react": "^2.0.3",
|
||||||
@ -1700,6 +1701,15 @@
|
|||||||
"@capacitor/core": "^6.0.0"
|
"@capacitor/core": "^6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@capacitor/screen-orientation": {
|
||||||
|
"version": "6.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@capacitor/screen-orientation/-/screen-orientation-6.0.3.tgz",
|
||||||
|
"integrity": "sha512-5R+tf+twRNnkZFGSWsQkEBz1MFyP1kzZDyqOA9rtXJlTQYNcFJWouSXEuNa+Ba6i6nEi4X83BuXVzEFJ7zDrgQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@capacitor/core": "^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@capacitor/splash-screen": {
|
"node_modules/@capacitor/splash-screen": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@capacitor/splash-screen/-/splash-screen-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@capacitor/splash-screen/-/splash-screen-6.0.2.tgz",
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
"@capacitor/filesystem": "^6.0.1",
|
"@capacitor/filesystem": "^6.0.1",
|
||||||
"@capacitor/local-notifications": "^6.1.0",
|
"@capacitor/local-notifications": "^6.1.0",
|
||||||
"@capacitor/preferences": "^6.0.3",
|
"@capacitor/preferences": "^6.0.3",
|
||||||
|
"@capacitor/screen-orientation": "^6.0.3",
|
||||||
"@capacitor/splash-screen": "^6.0.2",
|
"@capacitor/splash-screen": "^6.0.2",
|
||||||
"@capawesome/capacitor-file-picker": "^6.1.0",
|
"@capawesome/capacitor-file-picker": "^6.1.0",
|
||||||
"@chatscope/chat-ui-kit-react": "^2.0.3",
|
"@chatscope/chat-ui-kit-react": "^2.0.3",
|
||||||
|
183
src/App.tsx
183
src/App.tsx
@ -21,14 +21,18 @@ import {
|
|||||||
DialogContentText,
|
DialogContentText,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
Divider,
|
Divider,
|
||||||
|
FormControlLabel,
|
||||||
Input,
|
Input,
|
||||||
InputLabel,
|
InputLabel,
|
||||||
Popover,
|
Popover,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Typography,
|
Typography,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
import { ScreenOrientation } from '@capacitor/screen-orientation';
|
||||||
|
|
||||||
import { decryptStoredWallet } from "./utils/decryptWallet";
|
import { decryptStoredWallet } from "./utils/decryptWallet";
|
||||||
import AccountBalanceWalletIcon from '@mui/icons-material/AccountBalanceWallet';
|
import AccountBalanceWalletIcon from '@mui/icons-material/AccountBalanceWallet';
|
||||||
|
import PriorityHighIcon from '@mui/icons-material/PriorityHigh';
|
||||||
|
|
||||||
import { JsonView, allExpanded, darkStyles } from 'react-json-view-lite';
|
import { JsonView, allExpanded, darkStyles } from 'react-json-view-lite';
|
||||||
import 'react-json-view-lite/dist/index.css';
|
import 'react-json-view-lite/dist/index.css';
|
||||||
@ -127,6 +131,7 @@ import {
|
|||||||
isUsingImportExportSettingsAtom,
|
isUsingImportExportSettingsAtom,
|
||||||
lastEnteredGroupIdAtom,
|
lastEnteredGroupIdAtom,
|
||||||
mailsAtom,
|
mailsAtom,
|
||||||
|
myGroupsWhereIAmAdminAtom,
|
||||||
oldPinnedAppsAtom,
|
oldPinnedAppsAtom,
|
||||||
qMailLastEnteredTimestampAtom,
|
qMailLastEnteredTimestampAtom,
|
||||||
settingsLocalLastUpdatedAtom,
|
settingsLocalLastUpdatedAtom,
|
||||||
@ -153,6 +158,7 @@ import { BuyQortInformation } from "./components/BuyQortInformation";
|
|||||||
import { InstallPWA } from "./components/InstallPWA";
|
import { InstallPWA } from "./components/InstallPWA";
|
||||||
import { QortPayment } from "./components/QortPayment";
|
import { QortPayment } from "./components/QortPayment";
|
||||||
import { PdfViewer } from "./common/PdfViewer";
|
import { PdfViewer } from "./common/PdfViewer";
|
||||||
|
import { DownloadWallet } from "./components/Auth/DownloadWallet";
|
||||||
|
|
||||||
|
|
||||||
type extStates =
|
type extStates =
|
||||||
@ -439,7 +445,7 @@ function App() {
|
|||||||
const { isShow, onCancel, onOk, show, message } = useModal();
|
const { isShow, onCancel, onOk, show, message } = useModal();
|
||||||
const {isUserBlocked,
|
const {isUserBlocked,
|
||||||
addToBlockList,
|
addToBlockList,
|
||||||
removeBlockFromList, getAllBlockedUsers} = useBlockedAddresses()
|
removeBlockFromList, getAllBlockedUsers} = useBlockedAddresses(extState === 'authenticated')
|
||||||
const {
|
const {
|
||||||
isShow: isShowUnsavedChanges,
|
isShow: isShowUnsavedChanges,
|
||||||
onCancel: onCancelUnsavedChanges,
|
onCancel: onCancelUnsavedChanges,
|
||||||
@ -486,6 +492,8 @@ function App() {
|
|||||||
url: "http://127.0.0.1:12391",
|
url: "http://127.0.0.1:12391",
|
||||||
});
|
});
|
||||||
const [useLocalNode, setUseLocalNode] = useState(false);
|
const [useLocalNode, setUseLocalNode] = useState(false);
|
||||||
|
const [confirmRequestRead, setConfirmRequestRead] = useState(false);
|
||||||
|
|
||||||
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
||||||
const [showSeed, setShowSeed] = useState(false)
|
const [showSeed, setShowSeed] = useState(false)
|
||||||
const [creationStep, setCreationStep] = useState(1)
|
const [creationStep, setCreationStep] = useState(1)
|
||||||
@ -512,6 +520,15 @@ function App() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
ScreenOrientation.lock({ orientation: 'portrait' });
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
useEffect(()=> {
|
useEffect(()=> {
|
||||||
if(!shownTutorialsInitiated) return
|
if(!shownTutorialsInitiated) return
|
||||||
if(extState === 'not-authenticated'){
|
if(extState === 'not-authenticated'){
|
||||||
@ -558,6 +575,9 @@ function App() {
|
|||||||
const resetAtomQMailLastEnteredTimestampAtom = useResetRecoilState(qMailLastEnteredTimestampAtom)
|
const resetAtomQMailLastEnteredTimestampAtom = useResetRecoilState(qMailLastEnteredTimestampAtom)
|
||||||
const resetAtomMailsAtom = useResetRecoilState(mailsAtom)
|
const resetAtomMailsAtom = useResetRecoilState(mailsAtom)
|
||||||
const resetLastEnteredGroupIdAtom = useResetRecoilState(lastEnteredGroupIdAtom)
|
const resetLastEnteredGroupIdAtom = useResetRecoilState(lastEnteredGroupIdAtom)
|
||||||
|
const resetMyGroupsWhereIAmAdminAtom = useResetRecoilState(
|
||||||
|
myGroupsWhereIAmAdminAtom
|
||||||
|
);
|
||||||
const resetAllRecoil = () => {
|
const resetAllRecoil = () => {
|
||||||
resetAtomSortablePinnedAppsAtom();
|
resetAtomSortablePinnedAppsAtom();
|
||||||
resetAtomCanSaveSettingToQdnAtom();
|
resetAtomCanSaveSettingToQdnAtom();
|
||||||
@ -569,6 +589,7 @@ function App() {
|
|||||||
resetAtomMailsAtom()
|
resetAtomMailsAtom()
|
||||||
resetGroupPropertiesAtom()
|
resetGroupPropertiesAtom()
|
||||||
resetLastEnteredGroupIdAtom()
|
resetLastEnteredGroupIdAtom()
|
||||||
|
resetMyGroupsWhereIAmAdminAtom()
|
||||||
};
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isMobile) return;
|
if (!isMobile) return;
|
||||||
@ -850,6 +871,24 @@ function App() {
|
|||||||
});
|
});
|
||||||
balanceSetInterval()
|
balanceSetInterval()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const refetchUserInfo = () => {
|
||||||
|
window
|
||||||
|
.sendMessage('userInfo')
|
||||||
|
.then((response) => {
|
||||||
|
if (response && !response.error) {
|
||||||
|
setUserInfo(response);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Failed to get user info:', error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBalanceAndUserInfoFunc = () => {
|
||||||
|
getBalanceFunc();
|
||||||
|
refetchUserInfo();
|
||||||
|
};
|
||||||
const getLtcBalanceFunc = () => {
|
const getLtcBalanceFunc = () => {
|
||||||
setLtcBalanceLoading(true);
|
setLtcBalanceLoading(true);
|
||||||
window
|
window
|
||||||
@ -878,6 +917,8 @@ function App() {
|
|||||||
if(message?.payload?.checkbox1){
|
if(message?.payload?.checkbox1){
|
||||||
qortalRequestCheckbox1Ref.current = message?.payload?.checkbox1?.value || false
|
qortalRequestCheckbox1Ref.current = message?.payload?.checkbox1?.value || false
|
||||||
}
|
}
|
||||||
|
setConfirmRequestRead(false)
|
||||||
|
|
||||||
await showQortalRequestExtension(message?.payload);
|
await showQortalRequestExtension(message?.payload);
|
||||||
if (qortalRequestCheckbox1Ref.current) {
|
if (qortalRequestCheckbox1Ref.current) {
|
||||||
event.source.postMessage(
|
event.source.postMessage(
|
||||||
@ -1632,7 +1673,7 @@ function App() {
|
|||||||
{balance?.toFixed(2)} QORT
|
{balance?.toFixed(2)} QORT
|
||||||
</TextP>
|
</TextP>
|
||||||
<RefreshIcon
|
<RefreshIcon
|
||||||
onClick={getBalanceFunc}
|
onClick={getBalanceAndUserInfoFunc}
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: "16px",
|
fontSize: "16px",
|
||||||
color: "white",
|
color: "white",
|
||||||
@ -2578,87 +2619,14 @@ function App() {
|
|||||||
)}
|
)}
|
||||||
{extState === "download-wallet" && (
|
{extState === "download-wallet" && (
|
||||||
<>
|
<>
|
||||||
<Spacer height="22px" />
|
<DownloadWallet
|
||||||
<Box
|
returnToMain={returnToMain}
|
||||||
sx={{
|
setIsLoading={setIsLoading}
|
||||||
display: "flex",
|
showInfo={showInfo}
|
||||||
width: "100%",
|
rawWallet={rawWallet}
|
||||||
justifyContent: "flex-start",
|
setWalletToBeDownloaded={setWalletToBeDownloaded}
|
||||||
paddingLeft: "22px",
|
walletToBeDownloaded={walletToBeDownloaded}
|
||||||
boxSizing: "border-box",
|
/>
|
||||||
}}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
style={{
|
|
||||||
cursor: "pointer",
|
|
||||||
}}
|
|
||||||
onClick={returnToMain}
|
|
||||||
src={Return}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Spacer height="10px" />
|
|
||||||
<div
|
|
||||||
className="image-container"
|
|
||||||
style={{
|
|
||||||
width: "136px",
|
|
||||||
height: "154px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<img src={Logo1Dark} className="base-image" />
|
|
||||||
</div>
|
|
||||||
<Spacer height="35px" />
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
alignItems: "flex-start",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TextP
|
|
||||||
sx={{
|
|
||||||
textAlign: "start",
|
|
||||||
lineHeight: "24px",
|
|
||||||
fontSize: "20px",
|
|
||||||
fontWeight: 600,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Download Account
|
|
||||||
</TextP>
|
|
||||||
</Box>
|
|
||||||
<Spacer height="35px" />
|
|
||||||
{!walletToBeDownloaded && (
|
|
||||||
<>
|
|
||||||
<CustomLabel htmlFor="standard-adornment-password">
|
|
||||||
Confirm Wallet Password
|
|
||||||
</CustomLabel>
|
|
||||||
<Spacer height="5px" />
|
|
||||||
<PasswordField
|
|
||||||
id="standard-adornment-password"
|
|
||||||
value={walletToBeDownloadedPassword}
|
|
||||||
onChange={(e) =>
|
|
||||||
setWalletToBeDownloadedPassword(e.target.value)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Spacer height="20px" />
|
|
||||||
<CustomButton onClick={confirmPasswordToDownload}>
|
|
||||||
Confirm password
|
|
||||||
</CustomButton>
|
|
||||||
<ErrorText>{walletToBeDownloadedError}</ErrorText>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{walletToBeDownloaded && (
|
|
||||||
<>
|
|
||||||
<CustomButton onClick={async ()=> {
|
|
||||||
await saveFileToDiskFunc()
|
|
||||||
await showInfo({
|
|
||||||
message: isNative ? `Your account file was saved to internal storage, in the document folder. Keep that file secure.` : `Your account file was downloaded by your browser. Keep that file secure.` ,
|
|
||||||
})
|
|
||||||
}}>
|
|
||||||
Download account
|
|
||||||
</CustomButton>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{extState === "create-wallet" && (
|
{extState === "create-wallet" && (
|
||||||
@ -3110,7 +3078,7 @@ await showInfo({
|
|||||||
>
|
>
|
||||||
<CountdownCircleTimer
|
<CountdownCircleTimer
|
||||||
isPlaying
|
isPlaying
|
||||||
duration={30}
|
duration={60}
|
||||||
colors={["#004777", "#F7B801", "#A30000", "#A30000"]}
|
colors={["#004777", "#F7B801", "#A30000", "#A30000"]}
|
||||||
colorsTime={[7, 5, 2, 0]}
|
colorsTime={[7, 5, 2, 0]}
|
||||||
onComplete={() => {
|
onComplete={() => {
|
||||||
@ -3191,12 +3159,14 @@ await showInfo({
|
|||||||
>
|
>
|
||||||
{messageQortalRequestExtension?.text3}
|
{messageQortalRequestExtension?.text3}
|
||||||
</TextP>
|
</TextP>
|
||||||
<Spacer height="15px" />
|
|
||||||
</Box>
|
</Box>
|
||||||
|
<Spacer height="15px" />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{messageQortalRequestExtension?.text4 && (
|
{messageQortalRequestExtension?.text4 && (
|
||||||
|
<>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@ -3214,6 +3184,8 @@ await showInfo({
|
|||||||
{messageQortalRequestExtension?.text4}
|
{messageQortalRequestExtension?.text4}
|
||||||
</TextP>
|
</TextP>
|
||||||
</Box>
|
</Box>
|
||||||
|
<Spacer height="15px" />
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{messageQortalRequestExtension?.html && (
|
{messageQortalRequestExtension?.html && (
|
||||||
@ -3341,6 +3313,35 @@ await showInfo({
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
{messageQortalRequestExtension?.confirmCheckbox && (
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
onChange={(e) => setConfirmRequestRead(e.target.checked)}
|
||||||
|
checked={confirmRequestRead}
|
||||||
|
edge="start"
|
||||||
|
tabIndex={-1}
|
||||||
|
disableRipple
|
||||||
|
sx={{
|
||||||
|
"&.Mui-checked": {
|
||||||
|
color: "white",
|
||||||
|
},
|
||||||
|
"& .MuiSvgIcon-root": {
|
||||||
|
color: "white",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<Typography sx={{ fontSize: "14px" }}>
|
||||||
|
I have read this request
|
||||||
|
</Typography>
|
||||||
|
<PriorityHighIcon color="warning" />
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<Spacer height="29px" />
|
<Spacer height="29px" />
|
||||||
<Box
|
<Box
|
||||||
@ -3350,13 +3351,21 @@ await showInfo({
|
|||||||
gap: "14px",
|
gap: "14px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CustomButtonAccept
|
<CustomButtonAccept
|
||||||
color="black"
|
color="black"
|
||||||
bgColor="var(--green)"
|
bgColor="var(--green)"
|
||||||
sx={{
|
sx={{
|
||||||
minWidth: "102px",
|
minWidth: "102px",
|
||||||
|
opacity: messageQortalRequestExtension?.confirmCheckbox && !confirmRequestRead ? 0.1 : 0.7,
|
||||||
|
cursor: messageQortalRequestExtension?.confirmCheckbox && !confirmRequestRead ? 'default' : 'pointer',
|
||||||
|
"&:hover": {
|
||||||
|
opacity: messageQortalRequestExtension?.confirmCheckbox && !confirmRequestRead ? 0.1 : 1,
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
if(messageQortalRequestExtension?.confirmCheckbox && !confirmRequestRead) return
|
||||||
|
onOkQortalRequestExtension("accepted")
|
||||||
}}
|
}}
|
||||||
onClick={() => onOkQortalRequestExtension("accepted")}
|
|
||||||
>
|
>
|
||||||
accept
|
accept
|
||||||
</CustomButtonAccept>
|
</CustomButtonAccept>
|
||||||
|
@ -41,6 +41,14 @@ export const sortablePinnedAppsAtom = atom({
|
|||||||
{
|
{
|
||||||
name: 'Q-Wallets',
|
name: 'Q-Wallets',
|
||||||
service: 'APP'
|
service: 'APP'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Q-Search',
|
||||||
|
service: 'APP'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Q-Nodecontrol',
|
||||||
|
service: 'APP'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -180,3 +188,8 @@ export const lastPaymentSeenTimestampAtom = atom<null | number>({
|
|||||||
key: 'lastPaymentSeenTimestampAtom',
|
key: 'lastPaymentSeenTimestampAtom',
|
||||||
default: null,
|
default: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const isOpenBlockedModalAtom = atom({
|
||||||
|
key: 'isOpenBlockedModalAtom',
|
||||||
|
default: false,
|
||||||
|
});
|
@ -358,11 +358,12 @@ export async function sendCoinCase(request, event) {
|
|||||||
|
|
||||||
export async function inviteToGroupCase(request, event) {
|
export async function inviteToGroupCase(request, event) {
|
||||||
try {
|
try {
|
||||||
const { groupId, qortalAddress, inviteTime } = request.payload;
|
const { groupId, qortalAddress, inviteTime, txGroupId = 0 } = request.payload;
|
||||||
const response = await inviteToGroup({
|
const response = await inviteToGroup({
|
||||||
groupId,
|
groupId,
|
||||||
qortalAddress,
|
qortalAddress,
|
||||||
inviteTime,
|
inviteTime,
|
||||||
|
txGroupId
|
||||||
});
|
});
|
||||||
|
|
||||||
event.source.postMessage(
|
event.source.postMessage(
|
||||||
@ -483,8 +484,8 @@ export async function createGroupCase(request, event) {
|
|||||||
|
|
||||||
export async function cancelInvitationToGroupCase(request, event) {
|
export async function cancelInvitationToGroupCase(request, event) {
|
||||||
try {
|
try {
|
||||||
const { groupId, qortalAddress } = request.payload;
|
const { groupId, qortalAddress,txGroupId = 0 } = request.payload;
|
||||||
const response = await cancelInvitationToGroup({ groupId, qortalAddress });
|
const response = await cancelInvitationToGroup({ groupId, qortalAddress, txGroupId });
|
||||||
|
|
||||||
event.source.postMessage(
|
event.source.postMessage(
|
||||||
{
|
{
|
||||||
@ -564,11 +565,12 @@ export async function joinGroupCase(request, event) {
|
|||||||
|
|
||||||
export async function kickFromGroupCase(request, event) {
|
export async function kickFromGroupCase(request, event) {
|
||||||
try {
|
try {
|
||||||
const { groupId, qortalAddress, rBanReason } = request.payload;
|
const { groupId, qortalAddress, rBanReason, txGroupId = 0 } = request.payload;
|
||||||
const response = await kickFromGroup({
|
const response = await kickFromGroup({
|
||||||
groupId,
|
groupId,
|
||||||
qortalAddress,
|
qortalAddress,
|
||||||
rBanReason,
|
rBanReason,
|
||||||
|
txGroupId
|
||||||
});
|
});
|
||||||
|
|
||||||
event.source.postMessage(
|
event.source.postMessage(
|
||||||
@ -595,12 +597,13 @@ export async function kickFromGroupCase(request, event) {
|
|||||||
|
|
||||||
export async function banFromGroupCase(request, event) {
|
export async function banFromGroupCase(request, event) {
|
||||||
try {
|
try {
|
||||||
const { groupId, qortalAddress, rBanReason, rBanTime } = request.payload;
|
const { groupId, qortalAddress, rBanReason, rBanTime, txGroupId = 0 } = request.payload;
|
||||||
const response = await banFromGroup({
|
const response = await banFromGroup({
|
||||||
groupId,
|
groupId,
|
||||||
qortalAddress,
|
qortalAddress,
|
||||||
rBanReason,
|
rBanReason,
|
||||||
rBanTime,
|
rBanTime,
|
||||||
|
txGroupId
|
||||||
});
|
});
|
||||||
|
|
||||||
event.source.postMessage(
|
event.source.postMessage(
|
||||||
@ -734,8 +737,8 @@ export async function getUserSettingsCase(request, event) {
|
|||||||
|
|
||||||
export async function cancelBanCase(request, event) {
|
export async function cancelBanCase(request, event) {
|
||||||
try {
|
try {
|
||||||
const { groupId, qortalAddress } = request.payload;
|
const { groupId, qortalAddress, txGroupId = 0 } = request.payload;
|
||||||
const response = await cancelBan({ groupId, qortalAddress });
|
const response = await cancelBan({ groupId, qortalAddress, txGroupId });
|
||||||
|
|
||||||
event.source.postMessage(
|
event.source.postMessage(
|
||||||
{
|
{
|
||||||
@ -788,8 +791,8 @@ export async function registerNameCase(request, event) {
|
|||||||
|
|
||||||
export async function makeAdminCase(request, event) {
|
export async function makeAdminCase(request, event) {
|
||||||
try {
|
try {
|
||||||
const { groupId, qortalAddress } = request.payload;
|
const { groupId, qortalAddress,txGroupId = 0 } = request.payload;
|
||||||
const response = await makeAdmin({ groupId, qortalAddress });
|
const response = await makeAdmin({ groupId, qortalAddress, txGroupId });
|
||||||
|
|
||||||
event.source.postMessage(
|
event.source.postMessage(
|
||||||
{
|
{
|
||||||
@ -815,8 +818,8 @@ export async function makeAdminCase(request, event) {
|
|||||||
|
|
||||||
export async function removeAdminCase(request, event) {
|
export async function removeAdminCase(request, event) {
|
||||||
try {
|
try {
|
||||||
const { groupId, qortalAddress } = request.payload;
|
const { groupId, qortalAddress, txGroupId = 0 } = request.payload;
|
||||||
const response = await removeAdmin({ groupId, qortalAddress });
|
const response = await removeAdmin({ groupId, qortalAddress, txGroupId });
|
||||||
|
|
||||||
event.source.postMessage(
|
event.source.postMessage(
|
||||||
{
|
{
|
||||||
@ -1329,6 +1332,7 @@ export async function publishOnQDNCase(request, event) {
|
|||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
|
name = "",
|
||||||
identifier,
|
identifier,
|
||||||
service,
|
service,
|
||||||
title,
|
title,
|
||||||
@ -1346,6 +1350,7 @@ export async function publishOnQDNCase(request, event) {
|
|||||||
identifier,
|
identifier,
|
||||||
service,
|
service,
|
||||||
title,
|
title,
|
||||||
|
name,
|
||||||
description,
|
description,
|
||||||
category,
|
category,
|
||||||
tag1,
|
tag1,
|
||||||
|
@ -795,33 +795,35 @@ export async function getNameInfo() {
|
|||||||
const wallet = await getSaveWallet();
|
const wallet = await getSaveWallet();
|
||||||
const address = wallet.address0;
|
const address = wallet.address0;
|
||||||
const validApi = await getBaseApi();
|
const validApi = await getBaseApi();
|
||||||
const response = await fetch(validApi + "/names/address/" + address);
|
const response = await fetch(validApi + '/names/primary/' + address);
|
||||||
const nameData = await response.json();
|
const nameData = await response.json();
|
||||||
if (nameData?.length > 0) {
|
if (nameData?.name) {
|
||||||
return nameData[0].name;
|
return nameData.name;
|
||||||
} else {
|
} else {
|
||||||
return "";
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getNameInfoForOthers(address) {
|
export async function getNameInfoForOthers(address) {
|
||||||
|
if (!address) return '';
|
||||||
const validApi = await getBaseApi();
|
const validApi = await getBaseApi();
|
||||||
const response = await fetch(validApi + "/names/address/" + address);
|
const response = await fetch(validApi + '/names/primary/' + address);
|
||||||
const nameData = await response.json();
|
const nameData = await response.json();
|
||||||
if (nameData?.length > 0) {
|
if (nameData?.name) {
|
||||||
return nameData[0].name;
|
return nameData?.name;
|
||||||
} else {
|
} else {
|
||||||
return "";
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function getAddressInfo(address) {
|
export async function getAddressInfo(address) {
|
||||||
const validApi = await getBaseApi();
|
const validApi = await getBaseApi();
|
||||||
const response = await fetch(validApi + "/addresses/" + address);
|
const response = await fetch(validApi + "/addresses/" + address);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (!response?.ok && data?.error !== 124)
|
if (!response?.ok && data?.error !== 124)
|
||||||
throw new Error("Cannot fetch address info");
|
throw new Error("Cannot retrieve address info");
|
||||||
if (data?.error === 124) {
|
if (data?.error === 124) {
|
||||||
return {
|
return {
|
||||||
address,
|
address,
|
||||||
@ -928,6 +930,59 @@ export async function getBalanceInfo() {
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getAssetBalanceInfo(assetId: number) {
|
||||||
|
const wallet = await getSaveWallet();
|
||||||
|
const address = wallet.address0;
|
||||||
|
const validApi = await getBaseApi();
|
||||||
|
const response = await fetch(validApi + `/assets/balances?address=${address}&assetid=${assetId}&ordering=ASSET_BALANCE_ACCOUNT&limit=1`);
|
||||||
|
|
||||||
|
if (!response?.ok) throw new Error("Cannot fetch asset balance");
|
||||||
|
const data = await response.json();
|
||||||
|
return +data?.[0]?.balance
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAssetInfo(assetId: number) {
|
||||||
|
const validApi = await getBaseApi();
|
||||||
|
const response = await fetch(validApi + `/assets/info?assetId=${assetId}`);
|
||||||
|
|
||||||
|
if (!response?.ok) throw new Error("Cannot fetch asset info");
|
||||||
|
const data = await response.json();
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function transferAsset({
|
||||||
|
amount,
|
||||||
|
recipient,
|
||||||
|
assetId,
|
||||||
|
}) {
|
||||||
|
const lastReference = await getLastRef();
|
||||||
|
const resKeyPair = await getKeyPair();
|
||||||
|
const parsedData = resKeyPair;
|
||||||
|
const uint8PrivateKey = Base58.decode(parsedData.privateKey);
|
||||||
|
const uint8PublicKey = Base58.decode(parsedData.publicKey);
|
||||||
|
const keyPair = {
|
||||||
|
privateKey: uint8PrivateKey,
|
||||||
|
publicKey: uint8PublicKey,
|
||||||
|
};
|
||||||
|
const feeres = await getFee("TRANSFER_ASSET");
|
||||||
|
|
||||||
|
const tx = await createTransaction(12, keyPair, {
|
||||||
|
fee: feeres.fee,
|
||||||
|
recipient: recipient,
|
||||||
|
amount: amount,
|
||||||
|
assetId: assetId,
|
||||||
|
lastReference: lastReference,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const signedBytes = Base58.encode(tx.signedBytes);
|
||||||
|
|
||||||
|
const res = await processTransactionVersion2(signedBytes);
|
||||||
|
if (!res?.signature)
|
||||||
|
throw new Error(res?.message || "Transaction was not able to be processed");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
export async function getLTCBalance() {
|
export async function getLTCBalance() {
|
||||||
const wallet = await getSaveWallet();
|
const wallet = await getSaveWallet();
|
||||||
let _url = `${buyTradeNodeBaseUrl}/crosschain/ltc/walletbalance`;
|
let _url = `${buyTradeNodeBaseUrl}/crosschain/ltc/walletbalance`;
|
||||||
@ -1979,7 +2034,7 @@ export async function joinGroup({ groupId }) {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function cancelInvitationToGroup({ groupId, qortalAddress }) {
|
export async function cancelInvitationToGroup({ groupId, qortalAddress, txGroupId = 0 }) {
|
||||||
const lastReference = await getLastRef();
|
const lastReference = await getLastRef();
|
||||||
const resKeyPair = await getKeyPair();
|
const resKeyPair = await getKeyPair();
|
||||||
const parsedData = resKeyPair;
|
const parsedData = resKeyPair;
|
||||||
@ -1996,6 +2051,7 @@ export async function cancelInvitationToGroup({ groupId, qortalAddress }) {
|
|||||||
recipient: qortalAddress,
|
recipient: qortalAddress,
|
||||||
rGroupId: groupId,
|
rGroupId: groupId,
|
||||||
lastReference: lastReference,
|
lastReference: lastReference,
|
||||||
|
groupID: txGroupId
|
||||||
});
|
});
|
||||||
|
|
||||||
const signedBytes = Base58.encode(tx.signedBytes);
|
const signedBytes = Base58.encode(tx.signedBytes);
|
||||||
@ -2006,7 +2062,7 @@ export async function cancelInvitationToGroup({ groupId, qortalAddress }) {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function cancelBan({ groupId, qortalAddress }) {
|
export async function cancelBan({ groupId, qortalAddress, txGroupId = 0 }) {
|
||||||
const lastReference = await getLastRef();
|
const lastReference = await getLastRef();
|
||||||
const resKeyPair = await getKeyPair();
|
const resKeyPair = await getKeyPair();
|
||||||
const parsedData = resKeyPair;
|
const parsedData = resKeyPair;
|
||||||
@ -2023,6 +2079,7 @@ export async function cancelBan({ groupId, qortalAddress }) {
|
|||||||
recipient: qortalAddress,
|
recipient: qortalAddress,
|
||||||
rGroupId: groupId,
|
rGroupId: groupId,
|
||||||
lastReference: lastReference,
|
lastReference: lastReference,
|
||||||
|
groupID: txGroupId
|
||||||
});
|
});
|
||||||
|
|
||||||
const signedBytes = Base58.encode(tx.signedBytes);
|
const signedBytes = Base58.encode(tx.signedBytes);
|
||||||
@ -2087,7 +2144,7 @@ export async function updateName({ newName, oldName, description }) {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function makeAdmin({ groupId, qortalAddress }) {
|
export async function makeAdmin({ groupId, qortalAddress, txGroupId = 0 }) {
|
||||||
const lastReference = await getLastRef();
|
const lastReference = await getLastRef();
|
||||||
const resKeyPair = await getKeyPair();
|
const resKeyPair = await getKeyPair();
|
||||||
const parsedData = resKeyPair;
|
const parsedData = resKeyPair;
|
||||||
@ -2104,6 +2161,7 @@ export async function makeAdmin({ groupId, qortalAddress }) {
|
|||||||
recipient: qortalAddress,
|
recipient: qortalAddress,
|
||||||
rGroupId: groupId,
|
rGroupId: groupId,
|
||||||
lastReference: lastReference,
|
lastReference: lastReference,
|
||||||
|
groupID: txGroupId
|
||||||
});
|
});
|
||||||
|
|
||||||
const signedBytes = Base58.encode(tx.signedBytes);
|
const signedBytes = Base58.encode(tx.signedBytes);
|
||||||
@ -2114,7 +2172,7 @@ export async function makeAdmin({ groupId, qortalAddress }) {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removeAdmin({ groupId, qortalAddress }) {
|
export async function removeAdmin({ groupId, qortalAddress, txGroupId = 0 }) {
|
||||||
const lastReference = await getLastRef();
|
const lastReference = await getLastRef();
|
||||||
const resKeyPair = await getKeyPair();
|
const resKeyPair = await getKeyPair();
|
||||||
const parsedData = resKeyPair;
|
const parsedData = resKeyPair;
|
||||||
@ -2131,6 +2189,7 @@ export async function removeAdmin({ groupId, qortalAddress }) {
|
|||||||
recipient: qortalAddress,
|
recipient: qortalAddress,
|
||||||
rGroupId: groupId,
|
rGroupId: groupId,
|
||||||
lastReference: lastReference,
|
lastReference: lastReference,
|
||||||
|
groupID: txGroupId
|
||||||
});
|
});
|
||||||
|
|
||||||
const signedBytes = Base58.encode(tx.signedBytes);
|
const signedBytes = Base58.encode(tx.signedBytes);
|
||||||
@ -2146,6 +2205,7 @@ export async function banFromGroup({
|
|||||||
qortalAddress,
|
qortalAddress,
|
||||||
rBanReason = "",
|
rBanReason = "",
|
||||||
rBanTime,
|
rBanTime,
|
||||||
|
txGroupId = 0
|
||||||
}) {
|
}) {
|
||||||
const lastReference = await getLastRef();
|
const lastReference = await getLastRef();
|
||||||
const resKeyPair = await getKeyPair();
|
const resKeyPair = await getKeyPair();
|
||||||
@ -2165,6 +2225,7 @@ export async function banFromGroup({
|
|||||||
rBanReason: rBanReason,
|
rBanReason: rBanReason,
|
||||||
rBanTime,
|
rBanTime,
|
||||||
lastReference: lastReference,
|
lastReference: lastReference,
|
||||||
|
groupID: txGroupId
|
||||||
});
|
});
|
||||||
|
|
||||||
const signedBytes = Base58.encode(tx.signedBytes);
|
const signedBytes = Base58.encode(tx.signedBytes);
|
||||||
@ -2179,6 +2240,7 @@ export async function kickFromGroup({
|
|||||||
groupId,
|
groupId,
|
||||||
qortalAddress,
|
qortalAddress,
|
||||||
rBanReason = "",
|
rBanReason = "",
|
||||||
|
txGroupId = 0
|
||||||
}) {
|
}) {
|
||||||
const lastReference = await getLastRef();
|
const lastReference = await getLastRef();
|
||||||
const resKeyPair = await getKeyPair();
|
const resKeyPair = await getKeyPair();
|
||||||
@ -2197,6 +2259,7 @@ export async function kickFromGroup({
|
|||||||
rGroupId: groupId,
|
rGroupId: groupId,
|
||||||
rBanReason: rBanReason,
|
rBanReason: rBanReason,
|
||||||
lastReference: lastReference,
|
lastReference: lastReference,
|
||||||
|
groupID: txGroupId
|
||||||
});
|
});
|
||||||
|
|
||||||
const signedBytes = Base58.encode(tx.signedBytes);
|
const signedBytes = Base58.encode(tx.signedBytes);
|
||||||
@ -2247,7 +2310,153 @@ export async function createGroup({
|
|||||||
if (!res?.signature) throw new Error(res?.message || "Transaction was not able to be processed");
|
if (!res?.signature) throw new Error(res?.message || "Transaction was not able to be processed");
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
export async function inviteToGroup({ groupId, qortalAddress, inviteTime }) {
|
|
||||||
|
export async function sellName({
|
||||||
|
name,
|
||||||
|
sellPrice
|
||||||
|
}) {
|
||||||
|
const wallet = await getSaveWallet();
|
||||||
|
const address = wallet.address0;
|
||||||
|
if (!address) throw new Error("Cannot find user");
|
||||||
|
const lastReference = await getLastRef();
|
||||||
|
const feeres = await getFee("SELL_NAME");
|
||||||
|
const resKeyPair = await getKeyPair();
|
||||||
|
const parsedData = resKeyPair;
|
||||||
|
const uint8PrivateKey = Base58.decode(parsedData.privateKey);
|
||||||
|
const uint8PublicKey = Base58.decode(parsedData.publicKey);
|
||||||
|
const keyPair = {
|
||||||
|
privateKey: uint8PrivateKey,
|
||||||
|
publicKey: uint8PublicKey,
|
||||||
|
};
|
||||||
|
|
||||||
|
const tx = await createTransaction(5, keyPair, {
|
||||||
|
fee: feeres.fee,
|
||||||
|
name,
|
||||||
|
sellPrice: sellPrice,
|
||||||
|
lastReference: lastReference,
|
||||||
|
});
|
||||||
|
|
||||||
|
const signedBytes = Base58.encode(tx.signedBytes);
|
||||||
|
|
||||||
|
const res = await processTransactionVersion2(signedBytes);
|
||||||
|
if (!res?.signature)
|
||||||
|
throw new Error(res?.message || "Transaction was not able to be processed");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function cancelSellName({
|
||||||
|
name
|
||||||
|
}) {
|
||||||
|
const wallet = await getSaveWallet();
|
||||||
|
const address = wallet.address0;
|
||||||
|
if (!address) throw new Error("Cannot find user");
|
||||||
|
const lastReference = await getLastRef();
|
||||||
|
const feeres = await getFee("SELL_NAME");
|
||||||
|
const resKeyPair = await getKeyPair();
|
||||||
|
const parsedData = resKeyPair;
|
||||||
|
const uint8PrivateKey = Base58.decode(parsedData.privateKey);
|
||||||
|
const uint8PublicKey = Base58.decode(parsedData.publicKey);
|
||||||
|
const keyPair = {
|
||||||
|
privateKey: uint8PrivateKey,
|
||||||
|
publicKey: uint8PublicKey,
|
||||||
|
};
|
||||||
|
|
||||||
|
const tx = await createTransaction(6, keyPair, {
|
||||||
|
fee: feeres.fee,
|
||||||
|
name,
|
||||||
|
lastReference: lastReference,
|
||||||
|
});
|
||||||
|
|
||||||
|
const signedBytes = Base58.encode(tx.signedBytes);
|
||||||
|
|
||||||
|
const res = await processTransactionVersion2(signedBytes);
|
||||||
|
if (!res?.signature)
|
||||||
|
throw new Error(res?.message || "Transaction was not able to be processed");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function buyName({
|
||||||
|
name,
|
||||||
|
sellerAddress,
|
||||||
|
sellPrice
|
||||||
|
}) {
|
||||||
|
const wallet = await getSaveWallet();
|
||||||
|
const address = wallet.address0;
|
||||||
|
if (!address) throw new Error("Cannot find user");
|
||||||
|
const lastReference = await getLastRef();
|
||||||
|
const feeres = await getFee("BUY_NAME");
|
||||||
|
const resKeyPair = await getKeyPair();
|
||||||
|
const parsedData = resKeyPair;
|
||||||
|
const uint8PrivateKey = Base58.decode(parsedData.privateKey);
|
||||||
|
const uint8PublicKey = Base58.decode(parsedData.publicKey);
|
||||||
|
const keyPair = {
|
||||||
|
privateKey: uint8PrivateKey,
|
||||||
|
publicKey: uint8PublicKey,
|
||||||
|
};
|
||||||
|
|
||||||
|
const tx = await createTransaction(7, keyPair, {
|
||||||
|
fee: feeres.fee,
|
||||||
|
name,
|
||||||
|
sellPrice,
|
||||||
|
recipient: sellerAddress,
|
||||||
|
lastReference: lastReference,
|
||||||
|
});
|
||||||
|
|
||||||
|
const signedBytes = Base58.encode(tx.signedBytes);
|
||||||
|
|
||||||
|
const res = await processTransactionVersion2(signedBytes);
|
||||||
|
if (!res?.signature)
|
||||||
|
throw new Error(res?.message || "Transaction was not able to be processed");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateGroup({
|
||||||
|
groupId,
|
||||||
|
newOwner,
|
||||||
|
newIsOpen,
|
||||||
|
newDescription,
|
||||||
|
newApprovalThreshold,
|
||||||
|
newMinimumBlockDelay,
|
||||||
|
newMaximumBlockDelay,
|
||||||
|
txGroupId = 0
|
||||||
|
}) {
|
||||||
|
const wallet = await getSaveWallet();
|
||||||
|
const address = wallet.address0;
|
||||||
|
if (!address) throw new Error("Cannot find user");
|
||||||
|
const lastReference = await getLastRef();
|
||||||
|
const feeres = await getFee("UPDATE_GROUP");
|
||||||
|
const resKeyPair = await getKeyPair();
|
||||||
|
const parsedData = resKeyPair;
|
||||||
|
const uint8PrivateKey = Base58.decode(parsedData.privateKey);
|
||||||
|
const uint8PublicKey = Base58.decode(parsedData.publicKey);
|
||||||
|
const keyPair = {
|
||||||
|
privateKey: uint8PrivateKey,
|
||||||
|
publicKey: uint8PublicKey,
|
||||||
|
};
|
||||||
|
|
||||||
|
const tx = await createTransaction(23, keyPair, {
|
||||||
|
fee: feeres.fee,
|
||||||
|
_groupId: groupId,
|
||||||
|
newOwner,
|
||||||
|
newIsOpen,
|
||||||
|
newDescription,
|
||||||
|
newApprovalThreshold,
|
||||||
|
newMinimumBlockDelay,
|
||||||
|
newMaximumBlockDelay,
|
||||||
|
lastReference: lastReference,
|
||||||
|
groupID: txGroupId
|
||||||
|
});
|
||||||
|
|
||||||
|
const signedBytes = Base58.encode(tx.signedBytes);
|
||||||
|
|
||||||
|
const res = await processTransactionVersion2(signedBytes);
|
||||||
|
if (!res?.signature)
|
||||||
|
throw new Error(res?.message || "Transaction was not able to be processed");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function inviteToGroup({ groupId, qortalAddress, inviteTime, txGroupId = 0 }) {
|
||||||
const address = await getNameOrAddress(qortalAddress);
|
const address = await getNameOrAddress(qortalAddress);
|
||||||
if (!address) throw new Error("Cannot find user");
|
if (!address) throw new Error("Cannot find user");
|
||||||
const lastReference = await getLastRef();
|
const lastReference = await getLastRef();
|
||||||
@ -2267,13 +2476,14 @@ export async function inviteToGroup({ groupId, qortalAddress, inviteTime }) {
|
|||||||
rGroupId: groupId,
|
rGroupId: groupId,
|
||||||
rInviteTime: inviteTime,
|
rInviteTime: inviteTime,
|
||||||
lastReference: lastReference,
|
lastReference: lastReference,
|
||||||
|
groupID: txGroupId
|
||||||
});
|
});
|
||||||
|
|
||||||
const signedBytes = Base58.encode(tx.signedBytes);
|
const signedBytes = Base58.encode(tx.signedBytes);
|
||||||
|
|
||||||
const res = await processTransactionVersion2(signedBytes);
|
const res = await processTransactionVersion2(signedBytes);
|
||||||
if (!res?.signature)
|
if (!res?.signature)
|
||||||
throw new Error("Transaction was not able to be processed");
|
throw new Error(res?.message || "Transaction was not able to be processed");
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2988,6 +3198,7 @@ function setupMessageListener() {
|
|||||||
break;
|
break;
|
||||||
case "updateThreadActivity":
|
case "updateThreadActivity":
|
||||||
updateThreadActivityCase(request, event);
|
updateThreadActivityCase(request, event);
|
||||||
|
break;
|
||||||
case "decryptGroupEncryption":
|
case "decryptGroupEncryption":
|
||||||
decryptGroupEncryptionCase(request, event);
|
decryptGroupEncryptionCase(request, event);
|
||||||
break;
|
break;
|
||||||
|
@ -47,18 +47,29 @@ async function getSaveWallet() {
|
|||||||
throw new Error("No wallet saved");
|
throw new Error("No wallet saved");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function getNameInfo() {
|
export async function getNameInfo() {
|
||||||
const wallet = await getSaveWallet();
|
const wallet = await getSaveWallet();
|
||||||
const address = wallet.address0;
|
const address = wallet.address0;
|
||||||
const validApi = await getBaseApi()
|
const validApi = await getBaseApi();
|
||||||
const response = await fetch(validApi + "/names/address/" + address);
|
const response = await fetch(validApi + '/names/primary/' + address);
|
||||||
const nameData = await response.json();
|
const nameData = await response.json();
|
||||||
if (nameData?.length > 0) {
|
if (nameData?.name) {
|
||||||
return nameData[0].name;
|
return nameData?.name;
|
||||||
} else {
|
} else {
|
||||||
return "";
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function getAllUserNames() {
|
||||||
|
const wallet = await getSaveWallet();
|
||||||
|
const address = wallet.address0;
|
||||||
|
const validApi = await getBaseApi();
|
||||||
|
const response = await fetch(validApi + '/names/address/' + address);
|
||||||
|
const nameData = await response.json();
|
||||||
|
return nameData.map((item) => item.name);
|
||||||
|
}
|
||||||
|
|
||||||
async function getKeyPair() {
|
async function getKeyPair() {
|
||||||
const res = await getData<any>("keyPair").catch(() => null);
|
const res = await getData<any>("keyPair").catch(() => null);
|
||||||
if (res) {
|
if (res) {
|
||||||
@ -151,7 +162,7 @@ async function getKeyPair() {
|
|||||||
if(encryptedData){
|
if(encryptedData){
|
||||||
const registeredName = await getNameInfo()
|
const registeredName = await getNameInfo()
|
||||||
const data = await publishData({
|
const data = await publishData({
|
||||||
registeredName, file: encryptedData, service: 'DOCUMENT_PRIVATE', identifier: `admins-symmetric-qchat-group-${groupId}`, uploadType: 'file', isBase64: true, withFee: true
|
registeredName, data: encryptedData, service: 'DOCUMENT_PRIVATE', identifier: `admins-symmetric-qchat-group-${groupId}`, uploadType: 'base64', withFee: true
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
data,
|
data,
|
||||||
@ -202,7 +213,7 @@ export const encryptAndPublishSymmetricKeyGroupChat = async ({groupId, previousD
|
|||||||
if(encryptedData){
|
if(encryptedData){
|
||||||
const registeredName = await getNameInfo()
|
const registeredName = await getNameInfo()
|
||||||
const data = await publishData({
|
const data = await publishData({
|
||||||
registeredName, file: encryptedData, service: 'DOCUMENT_PRIVATE', identifier: `symmetric-qchat-group-${groupId}`, uploadType: 'file', isBase64: true, withFee: true
|
registeredName, data: encryptedData, service: 'DOCUMENT_PRIVATE', identifier: `symmetric-qchat-group-${groupId}`, uploadType: 'base64', withFee: true
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
data,
|
data,
|
||||||
@ -223,7 +234,7 @@ export const publishGroupEncryptedResource = async ({encryptedData, identifier})
|
|||||||
const registeredName = await getNameInfo()
|
const registeredName = await getNameInfo()
|
||||||
if(!registeredName) throw new Error('You need a name to publish')
|
if(!registeredName) throw new Error('You need a name to publish')
|
||||||
const data = await publishData({
|
const data = await publishData({
|
||||||
registeredName, file: encryptedData, service: 'DOCUMENT', identifier, uploadType: 'file', isBase64: true, withFee: true
|
registeredName, data: encryptedData, service: 'DOCUMENT', identifier, uploadType: 'base64', withFee: true
|
||||||
})
|
})
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@ -242,15 +253,16 @@ export const publishOnQDN = async ({data, identifier, service, title,
|
|||||||
tag3,
|
tag3,
|
||||||
tag4,
|
tag4,
|
||||||
tag5,
|
tag5,
|
||||||
|
name,
|
||||||
uploadType = 'file'
|
uploadType = 'file'
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
if(data && service){
|
if(data && service){
|
||||||
const registeredName = await getNameInfo()
|
const registeredName = name || await getNameInfo()
|
||||||
if(!registeredName) throw new Error('You need a name to publish')
|
if(!registeredName) throw new Error('You need a name to publish')
|
||||||
|
|
||||||
const res = await publishData({
|
const res = await publishData({
|
||||||
registeredName, file: data, service, identifier, uploadType, isBase64: true, withFee: true, title,
|
registeredName, data, service, identifier, uploadType, withFee: true, title,
|
||||||
description,
|
description,
|
||||||
category,
|
category,
|
||||||
tag1,
|
tag1,
|
||||||
|
@ -130,12 +130,17 @@ export const BoundedNumericTextField = ({
|
|||||||
...props?.InputProps,
|
...props?.InputProps,
|
||||||
endAdornment: addIconButtons ? (
|
endAdornment: addIconButtons ? (
|
||||||
<InputAdornment position="end">
|
<InputAdornment position="end">
|
||||||
<IconButton size="small" onClick={() => changeValueWithIncDecButton(1)}>
|
<IconButton size="small" onClick={() =>
|
||||||
|
changeValueWithIncDecButton(1)
|
||||||
|
|
||||||
|
} onTouchStart={(e)=> e.stopPropagation()}>
|
||||||
<AddIcon sx={{
|
<AddIcon sx={{
|
||||||
color: 'white'
|
color: 'white'
|
||||||
}} />{" "}
|
}} />{" "}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton size="small" onClick={() => changeValueWithIncDecButton(-1)}>
|
<IconButton onTouchStart={(e)=> e.stopPropagation()} size="small" onClick={() =>
|
||||||
|
changeValueWithIncDecButton(-1)
|
||||||
|
}>
|
||||||
<RemoveIcon sx={{
|
<RemoveIcon sx={{
|
||||||
color: 'white'
|
color: 'white'
|
||||||
}} />{" "}
|
}} />{" "}
|
||||||
|
@ -48,7 +48,7 @@ export const useModal = () => {
|
|||||||
const onCancel = () => {
|
const onCancel = () => {
|
||||||
const { reject } = promiseConfig.current;
|
const { reject } = promiseConfig.current;
|
||||||
hide();
|
hide();
|
||||||
reject();
|
reject('Declined');
|
||||||
setMessage({
|
setMessage({
|
||||||
publishFee: "",
|
publishFee: "",
|
||||||
message: ""
|
message: ""
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useContext, useEffect, useMemo, useState } from "react";
|
import React, { useCallback, useContext, useEffect, useMemo, useState } from "react";
|
||||||
import {
|
import {
|
||||||
AppCircle,
|
AppCircle,
|
||||||
AppCircleContainer,
|
AppCircleContainer,
|
||||||
@ -49,6 +49,7 @@ import { LoadingSnackbar } from "../Snackbar/LoadingSnackbar";
|
|||||||
import { CustomizedSnackbars } from "../Snackbar/Snackbar";
|
import { CustomizedSnackbars } from "../Snackbar/Snackbar";
|
||||||
import { getFee } from "../../background";
|
import { getFee } from "../../background";
|
||||||
import { fileToBase64 } from "../../utils/fileReading";
|
import { fileToBase64 } from "../../utils/fileReading";
|
||||||
|
import { useSortedMyNames } from "../../hooks/useSortedMyNames";
|
||||||
|
|
||||||
const CustomSelect = styled(Select)({
|
const CustomSelect = styled(Select)({
|
||||||
border: "0.5px solid var(--50-white, #FFFFFF80)",
|
border: "0.5px solid var(--50-white, #FFFFFF80)",
|
||||||
@ -82,7 +83,8 @@ const CustomMenuItem = styled(MenuItem)({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const AppPublish = ({ names, categories }) => {
|
export const AppPublish = ({ categories, myAddress, myName }) => {
|
||||||
|
const [names, setNames] = useState([]);
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
const [title, setTitle] = useState("");
|
const [title, setTitle] = useState("");
|
||||||
const [description, setDescription] = useState("");
|
const [description, setDescription] = useState("");
|
||||||
@ -99,6 +101,8 @@ export const AppPublish = ({ names, categories }) => {
|
|||||||
const [openSnack, setOpenSnack] = useState(false);
|
const [openSnack, setOpenSnack] = useState(false);
|
||||||
const [infoSnack, setInfoSnack] = useState(null);
|
const [infoSnack, setInfoSnack] = useState(null);
|
||||||
const [isLoading, setIsLoading] = useState("");
|
const [isLoading, setIsLoading] = useState("");
|
||||||
|
const mySortedNames = useSortedMyNames(names, myName);
|
||||||
|
|
||||||
const maxFileSize = appType === "APP" ? 50 * 1024 * 1024 : 400 * 1024 * 1024; // 50MB or 400MB
|
const maxFileSize = appType === "APP" ? 50 * 1024 * 1024 : 400 * 1024 * 1024; // 50MB or 400MB
|
||||||
const { getRootProps, getInputProps } = useDropzone({
|
const { getRootProps, getInputProps } = useDropzone({
|
||||||
accept: {
|
accept: {
|
||||||
@ -162,6 +166,25 @@ export const AppPublish = ({ names, categories }) => {
|
|||||||
getQapp(name, appType);
|
getQapp(name, appType);
|
||||||
}, [name, appType]);
|
}, [name, appType]);
|
||||||
|
|
||||||
|
const getNames = useCallback(async () => {
|
||||||
|
if (!myAddress) return;
|
||||||
|
try {
|
||||||
|
setIsLoading('Loading names');
|
||||||
|
const res = await fetch(
|
||||||
|
`${getBaseApiReact()}/names/address/${myAddress}?limit=0`
|
||||||
|
);
|
||||||
|
const data = await res.json();
|
||||||
|
setNames(data?.map((item) => item.name));
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading('');
|
||||||
|
}
|
||||||
|
}, [myAddress]);
|
||||||
|
useEffect(() => {
|
||||||
|
getNames();
|
||||||
|
}, [getNames]);
|
||||||
|
|
||||||
const publishApp = async () => {
|
const publishApp = async () => {
|
||||||
try {
|
try {
|
||||||
const data = {
|
const data = {
|
||||||
@ -199,10 +222,10 @@ export const AppPublish = ({ names, categories }) => {
|
|||||||
publishFee: fee.fee + " QORT",
|
publishFee: fee.fee + " QORT",
|
||||||
});
|
});
|
||||||
setIsLoading("Publishing... Please wait.");
|
setIsLoading("Publishing... Please wait.");
|
||||||
const fileBase64 = await fileToBase64(file);
|
|
||||||
await new Promise((res, rej) => {
|
await new Promise((res, rej) => {
|
||||||
window.sendMessage("publishOnQDN", {
|
window.sendMessage("publishOnQDN", {
|
||||||
data: fileBase64,
|
data: file,
|
||||||
service: appType,
|
service: appType,
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
@ -213,6 +236,7 @@ export const AppPublish = ({ names, categories }) => {
|
|||||||
tag4,
|
tag4,
|
||||||
tag5,
|
tag5,
|
||||||
uploadType: "zip",
|
uploadType: "zip",
|
||||||
|
name
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (!response?.error) {
|
if (!response?.error) {
|
||||||
@ -287,7 +311,7 @@ export const AppPublish = ({ names, categories }) => {
|
|||||||
</em>{" "}
|
</em>{" "}
|
||||||
{/* This is the placeholder item */}
|
{/* This is the placeholder item */}
|
||||||
</CustomMenuItem>
|
</CustomMenuItem>
|
||||||
{names.map((name) => {
|
{mySortedNames.map((name) => {
|
||||||
return <CustomMenuItem value={name}>{name}</CustomMenuItem>;
|
return <CustomMenuItem value={name}>{name}</CustomMenuItem>;
|
||||||
})}
|
})}
|
||||||
</CustomSelect>
|
</CustomSelect>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useContext, useEffect, useMemo, useState } from "react";
|
import React, { useCallback, useContext, useEffect, useMemo, useState } from "react";
|
||||||
|
|
||||||
import { Avatar, Box, } from "@mui/material";
|
import { Avatar, Box, } from "@mui/material";
|
||||||
import { Add } from "@mui/icons-material";
|
import { Add } from "@mui/icons-material";
|
||||||
@ -100,6 +100,57 @@ export const AppViewer = React.forwardRef(({ app , hide, isDevMode, skipAuth}, i
|
|||||||
};
|
};
|
||||||
}, [app, path]);
|
}, [app, path]);
|
||||||
|
|
||||||
|
const receiveChunksFunc = useCallback(
|
||||||
|
(e) => {
|
||||||
|
const iframe = iframeRef?.current;
|
||||||
|
if (!iframe || !iframe?.src) return;
|
||||||
|
if (app?.tabId !== e.detail?.tabId) return;
|
||||||
|
const publishLocation = e.detail?.publishLocation;
|
||||||
|
const chunksSubmitted = e.detail?.chunksSubmitted;
|
||||||
|
const totalChunks = e.detail?.totalChunks;
|
||||||
|
const retry = e.detail?.retry;
|
||||||
|
const filename = e.detail?.filename;
|
||||||
|
try {
|
||||||
|
if (publishLocation === undefined || publishLocation === null) return;
|
||||||
|
const dataToBeSent = {};
|
||||||
|
if (chunksSubmitted !== undefined && chunksSubmitted !== null) {
|
||||||
|
dataToBeSent.chunks = chunksSubmitted;
|
||||||
|
}
|
||||||
|
if (totalChunks !== undefined && totalChunks !== null) {
|
||||||
|
dataToBeSent.totalChunks = totalChunks;
|
||||||
|
}
|
||||||
|
if (retry !== undefined && retry !== null) {
|
||||||
|
dataToBeSent.retry = retry;
|
||||||
|
}
|
||||||
|
if (filename !== undefined && filename !== null) {
|
||||||
|
dataToBeSent.filename = filename;
|
||||||
|
}
|
||||||
|
const targetOrigin = new URL(iframe.src).origin;
|
||||||
|
iframe.contentWindow?.postMessage(
|
||||||
|
{
|
||||||
|
action: 'PUBLISH_STATUS',
|
||||||
|
publishLocation,
|
||||||
|
...dataToBeSent,
|
||||||
|
requestedHandler: 'UI',
|
||||||
|
processed: e.detail?.processed || false,
|
||||||
|
},
|
||||||
|
targetOrigin
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to send theme change to iframe:', err);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[iframeRef, app?.tabId]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
subscribeToEvent('receiveChunks', receiveChunksFunc);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unsubscribeFromEvent('receiveChunks', receiveChunksFunc);
|
||||||
|
};
|
||||||
|
}, [receiveChunksFunc]);
|
||||||
|
|
||||||
// Function to navigate back in iframe
|
// Function to navigate back in iframe
|
||||||
const navigateBackInIframe = async () => {
|
const navigateBackInIframe = async () => {
|
||||||
if (iframeRef.current && iframeRef.current.contentWindow && history?.currentIndex > 0) {
|
if (iframeRef.current && iframeRef.current.contentWindow && history?.currentIndex > 0) {
|
||||||
@ -194,7 +245,7 @@ export const AppViewer = React.forwardRef(({ app , hide, isDevMode, skipAuth}, i
|
|||||||
height: !isMobile ? '100vh' : `calc(${rootHeight} - 60px - 45px )`,
|
height: !isMobile ? '100vh' : `calc(${rootHeight} - 60px - 45px )`,
|
||||||
border: 'none',
|
border: 'none',
|
||||||
width: '100%'
|
width: '100%'
|
||||||
}} id="browser-iframe" src={defaultUrl} sandbox="allow-scripts allow-same-origin allow-forms allow-downloads allow-modals"
|
}} id="browser-iframe" src={defaultUrl} sandbox="allow-scripts allow-same-origin allow-forms allow-downloads allow-modals allow-orientation-lock"
|
||||||
allow="fullscreen; clipboard-read; clipboard-write">
|
allow="fullscreen; clipboard-read; clipboard-write">
|
||||||
|
|
||||||
</iframe>
|
</iframe>
|
||||||
|
@ -17,7 +17,7 @@ import { AppsLibrary } from "./AppsLibrary";
|
|||||||
|
|
||||||
const uid = new ShortUniqueId({ length: 8 });
|
const uid = new ShortUniqueId({ length: 8 });
|
||||||
|
|
||||||
export const Apps = ({ mode, setMode, show , myName}) => {
|
export const Apps = ({ mode, setMode, show , myName, myAddress}) => {
|
||||||
const [availableQapps, setAvailableQapps] = useState([]);
|
const [availableQapps, setAvailableQapps] = useState([]);
|
||||||
const [selectedAppInfo, setSelectedAppInfo] = useState(null);
|
const [selectedAppInfo, setSelectedAppInfo] = useState(null);
|
||||||
const [selectedCategory, setSelectedCategory] = useState(null)
|
const [selectedCategory, setSelectedCategory] = useState(null)
|
||||||
@ -298,7 +298,7 @@ export const Apps = ({ mode, setMode, show , myName}) => {
|
|||||||
>
|
>
|
||||||
{mode !== "viewer" && !selectedTab && <Spacer height="30px" />}
|
{mode !== "viewer" && !selectedTab && <Spacer height="30px" />}
|
||||||
{mode === "home" && (
|
{mode === "home" && (
|
||||||
<AppsHome myName={myName} availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} />
|
<AppsHome myName={myName} availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} myAddress={myAddress} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<AppsLibrary
|
<AppsLibrary
|
||||||
@ -314,7 +314,7 @@ export const Apps = ({ mode, setMode, show , myName}) => {
|
|||||||
{mode === "appInfo" && !selectedTab && <AppInfo app={selectedAppInfo} myName={myName} />}
|
{mode === "appInfo" && !selectedTab && <AppInfo app={selectedAppInfo} myName={myName} />}
|
||||||
{mode === "appInfo-from-category" && !selectedTab && <AppInfo app={selectedAppInfo} myName={myName} />}
|
{mode === "appInfo-from-category" && !selectedTab && <AppInfo app={selectedAppInfo} myName={myName} />}
|
||||||
<AppsCategory availableQapps={availableQapps} isShow={mode === 'category' && !selectedTab} category={selectedCategory} myName={myName} />
|
<AppsCategory availableQapps={availableQapps} isShow={mode === 'category' && !selectedTab} category={selectedCategory} myName={myName} />
|
||||||
{mode === "publish" && !selectedTab && <AppPublish names={myName ? [myName] : []} categories={categories} />}
|
{mode === "publish" && !selectedTab && <AppPublish categories={categories} myAddress={myAddress} myName={myName} />}
|
||||||
|
|
||||||
{tabs.map((tab) => {
|
{tabs.map((tab) => {
|
||||||
if (!iframeRefs.current[tab.tabId]) {
|
if (!iframeRefs.current[tab.tabId]) {
|
||||||
@ -335,7 +335,7 @@ export const Apps = ({ mode, setMode, show , myName}) => {
|
|||||||
{isNewTabWindow && mode === "viewer" && (
|
{isNewTabWindow && mode === "viewer" && (
|
||||||
<>
|
<>
|
||||||
<Spacer height="30px" />
|
<Spacer height="30px" />
|
||||||
<AppsHome myName={myName} availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} />
|
<AppsHome myName={myName} availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} myAddress={myAddress} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{mode !== "viewer" && !selectedTab && <Spacer height="180px" />}
|
{mode !== "viewer" && !selectedTab && <Spacer height="180px" />}
|
||||||
|
@ -41,7 +41,9 @@ const officialAppList = [
|
|||||||
"q-trade",
|
"q-trade",
|
||||||
"q-support",
|
"q-support",
|
||||||
"q-manager",
|
"q-manager",
|
||||||
"q-wallets"
|
"q-wallets",
|
||||||
|
"q-search",
|
||||||
|
"q-nodecontrol"
|
||||||
];
|
];
|
||||||
|
|
||||||
const ScrollerStyled = styled('div')({
|
const ScrollerStyled = styled('div')({
|
||||||
|
@ -47,7 +47,9 @@ const officialAppList = [
|
|||||||
"q-fund",
|
"q-fund",
|
||||||
"q-shop",
|
"q-shop",
|
||||||
"q-manager",
|
"q-manager",
|
||||||
"q-wallets"
|
"q-wallets",
|
||||||
|
"q-search",
|
||||||
|
"q-nodecontrol"
|
||||||
];
|
];
|
||||||
|
|
||||||
const ScrollerStyled = styled("div")({
|
const ScrollerStyled = styled("div")({
|
||||||
|
@ -20,7 +20,7 @@ import HelpIcon from '@mui/icons-material/Help';
|
|||||||
import { useHandleTutorials } from "../Tutorials/useHandleTutorials";
|
import { useHandleTutorials } from "../Tutorials/useHandleTutorials";
|
||||||
import { AppsPrivate } from "./AppsPrivate";
|
import { AppsPrivate } from "./AppsPrivate";
|
||||||
|
|
||||||
export const AppsHome = ({ setMode, myApp, myWebsite, availableQapps, myName }) => {
|
export const AppsHome = ({ setMode, myApp, myWebsite, availableQapps, myName, myAddress }) => {
|
||||||
const [qortalUrl, setQortalUrl] = useState('')
|
const [qortalUrl, setQortalUrl] = useState('')
|
||||||
const { showTutorial } = useContext(GlobalContext);
|
const { showTutorial } = useContext(GlobalContext);
|
||||||
|
|
||||||
@ -146,7 +146,7 @@ export const AppsHome = ({ setMode, myApp, myWebsite, availableQapps, myName }
|
|||||||
<AppCircleLabel>Library</AppCircleLabel>
|
<AppCircleLabel>Library</AppCircleLabel>
|
||||||
</AppCircleContainer>
|
</AppCircleContainer>
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
<AppsPrivate myName={myName} />
|
<AppsPrivate myName={myName} myAddress={myAddress} />
|
||||||
|
|
||||||
<SortablePinnedApps availableQapps={availableQapps} myWebsite={myWebsite} myApp={myApp} />
|
<SortablePinnedApps availableQapps={availableQapps} myWebsite={myWebsite} myApp={myApp} />
|
||||||
|
|
||||||
|
@ -45,7 +45,9 @@ const officialAppList = [
|
|||||||
"q-support",
|
"q-support",
|
||||||
"q-manager",
|
"q-manager",
|
||||||
"q-mintership",
|
"q-mintership",
|
||||||
"q-wallets"
|
"q-wallets",
|
||||||
|
"q-search",
|
||||||
|
"q-nodecontrol"
|
||||||
];
|
];
|
||||||
|
|
||||||
const ScrollerStyled = styled('div')({
|
const ScrollerStyled = styled('div')({
|
||||||
|
@ -56,7 +56,9 @@ const officialAppList = [
|
|||||||
"q-shop",
|
"q-shop",
|
||||||
"q-manager",
|
"q-manager",
|
||||||
"q-mintership",
|
"q-mintership",
|
||||||
"q-wallets"
|
"q-wallets",
|
||||||
|
"q-search",
|
||||||
|
"q-nodecontrol"
|
||||||
];
|
];
|
||||||
|
|
||||||
const ScrollerStyled = styled("div")({
|
const ScrollerStyled = styled("div")({
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useContext, useMemo, useState } from "react";
|
import React, { useCallback, useContext, useEffect, useMemo, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
Box,
|
Box,
|
||||||
@ -30,15 +30,18 @@ import {
|
|||||||
PublishQAppInfo,
|
PublishQAppInfo,
|
||||||
} from "./Apps-styles";
|
} from "./Apps-styles";
|
||||||
import ImageUploader from "../../common/ImageUploader";
|
import ImageUploader from "../../common/ImageUploader";
|
||||||
import { isMobile, MyContext } from "../../App";
|
import { getBaseApiReact, isMobile, MyContext } from "../../App";
|
||||||
import { fileToBase64 } from "../../utils/fileReading";
|
import { fileToBase64 } from "../../utils/fileReading";
|
||||||
import { objectToBase64 } from "../../qdn/encryption/group-encryption";
|
import { objectToBase64 } from "../../qdn/encryption/group-encryption";
|
||||||
import { getFee } from "../../background";
|
import { getFee } from "../../background";
|
||||||
|
import { useSortedMyNames } from "../../hooks/useSortedMyNames";
|
||||||
|
|
||||||
const maxFileSize = 50 * 1024 * 1024; // 50MB
|
const maxFileSize = 50 * 1024 * 1024; // 50MB
|
||||||
|
|
||||||
export const AppsPrivate = ({myName}) => {
|
export const AppsPrivate = ({myName, myAddress}) => {
|
||||||
const { openApp } = useHandlePrivateApps();
|
const { openApp } = useHandlePrivateApps();
|
||||||
|
const [names, setNames] = useState([]);
|
||||||
|
const [name, setName] = useState(0);
|
||||||
const [file, setFile] = useState(null);
|
const [file, setFile] = useState(null);
|
||||||
const [logo, setLogo] = useState(null);
|
const [logo, setLogo] = useState(null);
|
||||||
const [qortalUrl, setQortalUrl] = useState("");
|
const [qortalUrl, setQortalUrl] = useState("");
|
||||||
@ -48,6 +51,7 @@ export const AppsPrivate = ({myName}) => {
|
|||||||
const [myGroupsWhereIAmAdminFromGlobal] = useRecoilState(
|
const [myGroupsWhereIAmAdminFromGlobal] = useRecoilState(
|
||||||
myGroupsWhereIAmAdminAtom
|
myGroupsWhereIAmAdminAtom
|
||||||
);
|
);
|
||||||
|
const mySortedNames = useSortedMyNames(names, myName);
|
||||||
|
|
||||||
const myGroupsWhereIAmAdmin = useMemo(()=> {
|
const myGroupsWhereIAmAdmin = useMemo(()=> {
|
||||||
return myGroupsWhereIAmAdminFromGlobal?.filter((group)=> groupsProperties[group?.groupId]?.isOpen === false)
|
return myGroupsWhereIAmAdminFromGlobal?.filter((group)=> groupsProperties[group?.groupId]?.isOpen === false)
|
||||||
@ -165,6 +169,8 @@ export const AppsPrivate = ({myName}) => {
|
|||||||
data: decryptedData,
|
data: decryptedData,
|
||||||
identifier: newPrivateAppValues?.identifier,
|
identifier: newPrivateAppValues?.identifier,
|
||||||
service: newPrivateAppValues?.service,
|
service: newPrivateAppValues?.service,
|
||||||
|
uploadType: 'base64',
|
||||||
|
name,
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (!response?.error) {
|
if (!response?.error) {
|
||||||
@ -181,7 +187,7 @@ export const AppsPrivate = ({myName}) => {
|
|||||||
{
|
{
|
||||||
identifier: newPrivateAppValues?.identifier,
|
identifier: newPrivateAppValues?.identifier,
|
||||||
service: newPrivateAppValues?.service,
|
service: newPrivateAppValues?.service,
|
||||||
name: myName,
|
name,
|
||||||
groupId: selectedGroup,
|
groupId: selectedGroup,
|
||||||
},
|
},
|
||||||
true
|
true
|
||||||
@ -196,6 +202,24 @@ export const AppsPrivate = ({myName}) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getNames = useCallback(async () => {
|
||||||
|
if (!myAddress) return;
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
`${getBaseApiReact()}/names/address/${myAddress}?limit=0`
|
||||||
|
);
|
||||||
|
const data = await res.json();
|
||||||
|
setNames(data?.map((item) => item.name));
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}, [myAddress]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpenPrivateModal) {
|
||||||
|
getNames();
|
||||||
|
}
|
||||||
|
}, [getNames, isOpenPrivateModal]);
|
||||||
|
|
||||||
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
|
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
|
||||||
setValueTabPrivateApp(newValue);
|
setValueTabPrivateApp(newValue);
|
||||||
};
|
};
|
||||||
@ -432,6 +456,34 @@ export const AppsPrivate = ({myName}) => {
|
|||||||
{file ? "Change" : "Choose"} File
|
{file ? "Change" : "Choose"} File
|
||||||
</PublishQAppChoseFile>
|
</PublishQAppChoseFile>
|
||||||
<Spacer height="20px" />
|
<Spacer height="20px" />
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '5px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Label>Select a Qortal name</Label>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
labelId="demo-simple-select-label"
|
||||||
|
id="demo-simple-select"
|
||||||
|
value={name}
|
||||||
|
label="Groups where you are an admin"
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
>
|
||||||
|
<MenuItem value={0}>No name selected</MenuItem>
|
||||||
|
{mySortedNames.map((name) => {
|
||||||
|
return (
|
||||||
|
<MenuItem key={name} value={name}>
|
||||||
|
{name}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</Box>
|
||||||
|
<Spacer height="20px" />
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
@ -10,9 +10,99 @@ import { MyContext } from '../../App';
|
|||||||
import FileSaver from 'file-saver';
|
import FileSaver from 'file-saver';
|
||||||
|
|
||||||
import { Capacitor } from '@capacitor/core';
|
import { Capacitor } from '@capacitor/core';
|
||||||
|
import { createEndpoint } from '../../background';
|
||||||
|
import { uint8ArrayToBase64 } from '../../backgroundFunctions/encryption';
|
||||||
|
|
||||||
export const isNative = Capacitor.isNativePlatform();
|
export const isNative = Capacitor.isNativePlatform();
|
||||||
|
|
||||||
|
export const saveFileInChunksFromUrl = async (
|
||||||
|
location,
|
||||||
|
) => {
|
||||||
|
let fileName = location.filename
|
||||||
|
let locationUrl = `/arbitrary/${location.service}/${location.name}`;
|
||||||
|
if (location.identifier) {
|
||||||
|
locationUrl = locationUrl + `/${location.identifier}`;
|
||||||
|
}
|
||||||
|
const endpoint = await createEndpoint(
|
||||||
|
locationUrl +
|
||||||
|
`?attachment=true&attachmentFilename=${location?.filename}`
|
||||||
|
);
|
||||||
|
const response = await fetch(endpoint);
|
||||||
|
|
||||||
|
if (!response.ok || !response.body) {
|
||||||
|
throw new Error('Failed to fetch file or no readable stream');
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType = response.headers.get('Content-Type') || 'application/octet-stream';
|
||||||
|
const base64Prefix = `data:${contentType};base64,`;
|
||||||
|
|
||||||
|
const getExtensionFromFileName = (name: string): string => {
|
||||||
|
const lastDotIndex = name.lastIndexOf('.');
|
||||||
|
return lastDotIndex !== -1 ? name.substring(lastDotIndex) : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const existingExtension = getExtensionFromFileName(fileName);
|
||||||
|
|
||||||
|
if (existingExtension) {
|
||||||
|
fileName = fileName.substring(0, fileName.lastIndexOf('.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const mimeTypeToExtension = (mimeType: string): string => {
|
||||||
|
return mimeToExtensionMap[mimeType] || existingExtension || '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const extension = mimeTypeToExtension(contentType);
|
||||||
|
const fullFileName = `${fileName}_${Date.now()}${extension}`;
|
||||||
|
const reader = response.body.getReader();
|
||||||
|
let isFirstChunk = true;
|
||||||
|
let done = false;
|
||||||
|
|
||||||
|
let buffer = new Uint8Array(0);
|
||||||
|
const preferredChunkSize = 1024 * 1024; // 1MB
|
||||||
|
|
||||||
|
while (!done) {
|
||||||
|
const result = await reader.read();
|
||||||
|
done = result.done;
|
||||||
|
|
||||||
|
if (result.value) {
|
||||||
|
// Combine new value with existing buffer
|
||||||
|
const newBuffer = new Uint8Array(buffer.length + result.value.length);
|
||||||
|
newBuffer.set(buffer);
|
||||||
|
newBuffer.set(result.value, buffer.length);
|
||||||
|
buffer = newBuffer;
|
||||||
|
|
||||||
|
// While we have enough data, process 1MB chunks
|
||||||
|
while (buffer.length >= preferredChunkSize) {
|
||||||
|
const chunk = buffer.slice(0, preferredChunkSize);
|
||||||
|
buffer = buffer.slice(preferredChunkSize);
|
||||||
|
|
||||||
|
const base64Chunk = uint8ArrayToBase64(chunk);
|
||||||
|
await Filesystem.writeFile({
|
||||||
|
path: fullFileName,
|
||||||
|
data: isFirstChunk ? base64Prefix + base64Chunk : base64Chunk,
|
||||||
|
directory: Directory.Documents,
|
||||||
|
recursive: true,
|
||||||
|
append: !isFirstChunk,
|
||||||
|
});
|
||||||
|
|
||||||
|
isFirstChunk = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write remaining buffer (if any)
|
||||||
|
if (buffer.length > 0) {
|
||||||
|
const base64Chunk = uint8ArrayToBase64(buffer);
|
||||||
|
await Filesystem.writeFile({
|
||||||
|
path: fullFileName,
|
||||||
|
data: isFirstChunk ? base64Prefix + base64Chunk : base64Chunk,
|
||||||
|
directory: Directory.Documents,
|
||||||
|
recursive: true,
|
||||||
|
append: !isFirstChunk,
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const saveFileInChunks = async (
|
export const saveFileInChunks = async (
|
||||||
blob: Blob,
|
blob: Blob,
|
||||||
@ -255,7 +345,15 @@ export function openIndexedDB() {
|
|||||||
'GET_NODE_INFO',
|
'GET_NODE_INFO',
|
||||||
'GET_NODE_STATUS',
|
'GET_NODE_STATUS',
|
||||||
'GET_ARRR_SYNC_STATUS',
|
'GET_ARRR_SYNC_STATUS',
|
||||||
'SHOW_PDF_READER'
|
'SHOW_PDF_READER',
|
||||||
|
'UPDATE_GROUP',
|
||||||
|
'SELL_NAME',
|
||||||
|
'CANCEL_SELL_NAME',
|
||||||
|
'BUY_NAME', 'MULTI_ASSET_PAYMENT_WITH_PRIVATE_DATA',
|
||||||
|
'TRANSFER_ASSET',
|
||||||
|
'SIGN_FOREIGN_FEES',
|
||||||
|
'GET_PRIMARY_NAME',
|
||||||
|
'SCREEN-ORIENTATION'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -269,7 +367,13 @@ const UIQortalRequests = [
|
|||||||
'GET_SERVER_CONNECTION_HISTORY', 'SET_CURRENT_FOREIGN_SERVER',
|
'GET_SERVER_CONNECTION_HISTORY', 'SET_CURRENT_FOREIGN_SERVER',
|
||||||
'ADD_FOREIGN_SERVER', 'REMOVE_FOREIGN_SERVER', 'GET_DAY_SUMMARY', 'CREATE_TRADE_BUY_ORDER',
|
'ADD_FOREIGN_SERVER', 'REMOVE_FOREIGN_SERVER', 'GET_DAY_SUMMARY', 'CREATE_TRADE_BUY_ORDER',
|
||||||
'CREATE_TRADE_SELL_ORDER', 'CANCEL_TRADE_SELL_ORDER', 'IS_USING_PUBLIC_NODE', 'SIGN_TRANSACTION', 'ADMIN_ACTION', 'OPEN_NEW_TAB', 'CREATE_AND_COPY_EMBED_LINK', 'DECRYPT_QORTAL_GROUP_DATA', 'DECRYPT_DATA_WITH_SHARING_KEY', 'DELETE_HOSTED_DATA', 'GET_HOSTED_DATA', 'SHOW_ACTIONS', 'REGISTER_NAME', 'UPDATE_NAME', 'LEAVE_GROUP', 'INVITE_TO_GROUP', 'KICK_FROM_GROUP', 'BAN_FROM_GROUP', 'CANCEL_GROUP_BAN', 'ADD_GROUP_ADMIN', 'REMOVE_GROUP_ADMIN','DECRYPT_AESGCM', 'CANCEL_GROUP_INVITE', 'CREATE_GROUP', 'GET_USER_WALLET_TRANSACTIONS', 'GET_NODE_INFO',
|
'CREATE_TRADE_SELL_ORDER', 'CANCEL_TRADE_SELL_ORDER', 'IS_USING_PUBLIC_NODE', 'SIGN_TRANSACTION', 'ADMIN_ACTION', 'OPEN_NEW_TAB', 'CREATE_AND_COPY_EMBED_LINK', 'DECRYPT_QORTAL_GROUP_DATA', 'DECRYPT_DATA_WITH_SHARING_KEY', 'DELETE_HOSTED_DATA', 'GET_HOSTED_DATA', 'SHOW_ACTIONS', 'REGISTER_NAME', 'UPDATE_NAME', 'LEAVE_GROUP', 'INVITE_TO_GROUP', 'KICK_FROM_GROUP', 'BAN_FROM_GROUP', 'CANCEL_GROUP_BAN', 'ADD_GROUP_ADMIN', 'REMOVE_GROUP_ADMIN','DECRYPT_AESGCM', 'CANCEL_GROUP_INVITE', 'CREATE_GROUP', 'GET_USER_WALLET_TRANSACTIONS', 'GET_NODE_INFO',
|
||||||
'GET_NODE_STATUS', 'GET_ARRR_SYNC_STATUS', 'SHOW_PDF_READER'
|
'GET_NODE_STATUS', 'GET_ARRR_SYNC_STATUS', 'SHOW_PDF_READER', 'UPDATE_GROUP', 'SELL_NAME',
|
||||||
|
'CANCEL_SELL_NAME',
|
||||||
|
'BUY_NAME', 'MULTI_ASSET_PAYMENT_WITH_PRIVATE_DATA',
|
||||||
|
'TRANSFER_ASSET',
|
||||||
|
'SIGN_FOREIGN_FEES',
|
||||||
|
'GET_PRIMARY_NAME',
|
||||||
|
'SCREEN_ORIENTATION'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
@ -542,8 +646,18 @@ isDOMContentLoaded: false
|
|||||||
if (event?.data?.requestedHandler !== 'UI') return;
|
if (event?.data?.requestedHandler !== 'UI') return;
|
||||||
|
|
||||||
const sendMessageToRuntime = (message, eventPort) => {
|
const sendMessageToRuntime = (message, eventPort) => {
|
||||||
window.sendMessage(message.action, message.payload, 300000, message.isExtension, {
|
let timeout: number = 300000;
|
||||||
name: appName, service: appService
|
if (
|
||||||
|
message?.action === 'PUBLISH_MULTIPLE_QDN_RESOURCES' &&
|
||||||
|
message?.payload?.resources?.length > 0
|
||||||
|
) {
|
||||||
|
timeout = message?.payload?.resources?.length * 1200000;
|
||||||
|
} else if (message?.action === 'PUBLISH_QDN_RESOURCE') {
|
||||||
|
timeout = 1200000;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.sendMessage(message.action, message.payload, timeout, message.isExtension, {
|
||||||
|
name: appName, service: appService, tabId,
|
||||||
}, skipAuth)
|
}, skipAuth)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
@ -551,7 +665,7 @@ isDOMContentLoaded: false
|
|||||||
result: null,
|
result: null,
|
||||||
error: {
|
error: {
|
||||||
error: response.error,
|
error: response.error,
|
||||||
message: typeof response?.error === 'string' ? response.error : 'An error has occurred'
|
message: typeof response?.error === 'string' ? response?.error : typeof response?.message === 'string' ? response?.message : 'An error has occurred'
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -576,38 +690,26 @@ isDOMContentLoaded: false
|
|||||||
} else if(event?.data?.action === 'SAVE_FILE'
|
} else if(event?.data?.action === 'SAVE_FILE'
|
||||||
){
|
){
|
||||||
try {
|
try {
|
||||||
const res = await saveFile( event.data, null, true, {
|
await saveFile(event.data, null, true, {
|
||||||
openSnackGlobal,
|
openSnackGlobal,
|
||||||
setOpenSnackGlobal,
|
setOpenSnackGlobal,
|
||||||
infoSnackCustom,
|
infoSnackCustom,
|
||||||
setInfoSnackCustom
|
setInfoSnackCustom,
|
||||||
|
});
|
||||||
|
event.ports[0].postMessage({
|
||||||
|
result: true,
|
||||||
|
error: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
event.ports[0].postMessage({
|
||||||
|
result: null,
|
||||||
|
error: error?.message || 'Failed to save file',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
event?.data?.action === 'PUBLISH_MULTIPLE_QDN_RESOURCES' ||
|
|
||||||
event?.data?.action === 'PUBLISH_QDN_RESOURCE' ||
|
|
||||||
event?.data?.action === 'ENCRYPT_DATA' || event?.data?.action === 'ENCRYPT_DATA_WITH_SHARING_KEY' || event?.data?.action === 'ENCRYPT_QORTAL_GROUP_DATA'
|
event?.data?.action === 'ENCRYPT_DATA' || event?.data?.action === 'ENCRYPT_DATA_WITH_SHARING_KEY' || event?.data?.action === 'ENCRYPT_QORTAL_GROUP_DATA'
|
||||||
|
|
||||||
) {
|
) {
|
||||||
if (
|
|
||||||
event?.data?.action === 'PUBLISH_MULTIPLE_QDN_RESOURCES' ||
|
|
||||||
event?.data?.action === 'PUBLISH_QDN_RESOURCE'
|
|
||||||
|
|
||||||
){
|
|
||||||
try {
|
|
||||||
checkMobileSizeConstraints(event.data)
|
|
||||||
} catch (error) {
|
|
||||||
event.ports[0].postMessage({
|
|
||||||
result: null,
|
|
||||||
error: error?.message,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let data;
|
let data;
|
||||||
try {
|
try {
|
||||||
data = await storeFilesInIndexedDB(event.data);
|
data = await storeFilesInIndexedDB(event.data);
|
||||||
@ -630,6 +732,29 @@ isDOMContentLoaded: false
|
|||||||
error: 'Failed to prepare data for publishing',
|
error: 'Failed to prepare data for publishing',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} else if (
|
||||||
|
event?.data?.action === 'PUBLISH_MULTIPLE_QDN_RESOURCES' ||
|
||||||
|
event?.data?.action === 'PUBLISH_QDN_RESOURCE'
|
||||||
|
) {
|
||||||
|
|
||||||
|
const data = event.data;
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
sendMessageToRuntime(
|
||||||
|
{
|
||||||
|
action: event.data.action,
|
||||||
|
type: 'qortalRequest',
|
||||||
|
payload: data,
|
||||||
|
isExtension: true,
|
||||||
|
},
|
||||||
|
event.ports[0]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
event.ports[0].postMessage({
|
||||||
|
result: null,
|
||||||
|
error: 'Failed to prepare data for publishing',
|
||||||
|
});
|
||||||
|
}
|
||||||
} else if(event?.data?.action === 'LINK_TO_QDN_RESOURCE' ||
|
} else if(event?.data?.action === 'LINK_TO_QDN_RESOURCE' ||
|
||||||
event?.data?.action === 'QDN_RESOURCE_DISPLAYED'){
|
event?.data?.action === 'QDN_RESOURCE_DISPLAYED'){
|
||||||
const pathUrl = event?.data?.path != null ? (event?.data?.path.startsWith('/') ? '' : '/') + event?.data?.path : null
|
const pathUrl = event?.data?.path != null ? (event?.data?.path.startsWith('/') ? '' : '/') + event?.data?.path : null
|
||||||
@ -687,7 +812,7 @@ isDOMContentLoaded: false
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
}, [appName, appService]); // Empty dependency array to run once when the component mounts
|
}, [appName, appService, tabId]); // Empty dependency array to run once when the component mounts
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
248
src/components/Auth/DownloadWallet.tsx
Normal file
248
src/components/Auth/DownloadWallet.tsx
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Checkbox,
|
||||||
|
FormControlLabel,
|
||||||
|
Typography,
|
||||||
|
useTheme,
|
||||||
|
} from '@mui/material';
|
||||||
|
import { Spacer } from '../../common/Spacer';
|
||||||
|
import { PasswordField } from '../PasswordField/PasswordField';
|
||||||
|
import { ErrorText } from '../ErrorText/ErrorText';
|
||||||
|
import Logo1Dark from '../../assets/svgs/Logo1Dark.svg';
|
||||||
|
import { saveFileToDisk } from '../../utils/generateWallet/generateWallet';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { decryptStoredWallet } from '../../utils/decryptWallet';
|
||||||
|
import PhraseWallet from '../../utils/generateWallet/phrase-wallet';
|
||||||
|
import { crypto, walletVersion } from '../../constants/decryptWallet';
|
||||||
|
import Return from "../../assets/svgs/Return.svg";
|
||||||
|
import { CustomButton, CustomLabel, TextP } from '../../App-styles';
|
||||||
|
|
||||||
|
export const DownloadWallet = ({
|
||||||
|
returnToMain,
|
||||||
|
setIsLoading,
|
||||||
|
showInfo,
|
||||||
|
rawWallet,
|
||||||
|
setWalletToBeDownloaded,
|
||||||
|
walletToBeDownloaded,
|
||||||
|
}) => {
|
||||||
|
const [walletToBeDownloadedPassword, setWalletToBeDownloadedPassword] =
|
||||||
|
useState<string>('');
|
||||||
|
const [newPassword, setNewPassword] = useState<string>('');
|
||||||
|
const [keepCurrentPassword, setKeepCurrentPassword] = useState<boolean>(true);
|
||||||
|
const theme = useTheme();
|
||||||
|
const [walletToBeDownloadedError, setWalletToBeDownloadedError] =
|
||||||
|
useState<string>('');
|
||||||
|
|
||||||
|
|
||||||
|
const saveFileToDiskFunc = async () => {
|
||||||
|
try {
|
||||||
|
await saveFileToDisk(
|
||||||
|
walletToBeDownloaded.wallet,
|
||||||
|
walletToBeDownloaded.qortAddress
|
||||||
|
);
|
||||||
|
} catch (error: any) {
|
||||||
|
setWalletToBeDownloadedError(error?.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveWalletFunc = async (password: string, newPassword) => {
|
||||||
|
let wallet = structuredClone(rawWallet);
|
||||||
|
|
||||||
|
const res = await decryptStoredWallet(password, wallet);
|
||||||
|
const wallet2 = new PhraseWallet(res, wallet?.version || walletVersion);
|
||||||
|
const passwordToUse = newPassword || password;
|
||||||
|
wallet = await wallet2.generateSaveWalletData(
|
||||||
|
passwordToUse,
|
||||||
|
crypto.kdfThreads,
|
||||||
|
() => {}
|
||||||
|
);
|
||||||
|
|
||||||
|
setWalletToBeDownloaded({
|
||||||
|
wallet,
|
||||||
|
qortAddress: rawWallet.address0,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
wallet,
|
||||||
|
qortAddress: rawWallet.address0,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmPasswordToDownload = async () => {
|
||||||
|
try {
|
||||||
|
setWalletToBeDownloadedError('');
|
||||||
|
if (!keepCurrentPassword && !newPassword) {
|
||||||
|
setWalletToBeDownloadedError(
|
||||||
|
'Please enter a new password'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!walletToBeDownloadedPassword) {
|
||||||
|
setWalletToBeDownloadedError(
|
||||||
|
'Please enter your password'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setIsLoading(true);
|
||||||
|
await new Promise<void>((res) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
res();
|
||||||
|
}, 250);
|
||||||
|
});
|
||||||
|
const newPasswordForWallet = !keepCurrentPassword ? newPassword : null;
|
||||||
|
const res = await saveWalletFunc(
|
||||||
|
walletToBeDownloadedPassword,
|
||||||
|
newPasswordForWallet
|
||||||
|
);
|
||||||
|
} catch (error: any) {
|
||||||
|
setWalletToBeDownloadedError(error?.message);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Spacer height="22px" />
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
maxWidth: '700px',
|
||||||
|
paddingLeft: '22px',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
style={{
|
||||||
|
cursor: "pointer",
|
||||||
|
height: '24px'
|
||||||
|
}}
|
||||||
|
onClick={returnToMain}
|
||||||
|
src={Return}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Spacer height="10px" />
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="image-container"
|
||||||
|
style={{
|
||||||
|
width: '136px',
|
||||||
|
height: '154px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img src={Logo1Dark} className="base-image" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Spacer height="35px" />
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextP
|
||||||
|
sx={{
|
||||||
|
textAlign: 'start',
|
||||||
|
lineHeight: '24px',
|
||||||
|
fontSize: '20px',
|
||||||
|
fontWeight: 600,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Download account
|
||||||
|
</TextP>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Spacer height="35px" />
|
||||||
|
|
||||||
|
{!walletToBeDownloaded && (
|
||||||
|
<>
|
||||||
|
<CustomLabel htmlFor="standard-adornment-password">
|
||||||
|
Confirm password
|
||||||
|
</CustomLabel>
|
||||||
|
|
||||||
|
<Spacer height="5px" />
|
||||||
|
|
||||||
|
<PasswordField
|
||||||
|
id="standard-adornment-password"
|
||||||
|
value={walletToBeDownloadedPassword}
|
||||||
|
onChange={(e) => setWalletToBeDownloadedPassword(e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Spacer height="20px" />
|
||||||
|
|
||||||
|
<FormControlLabel
|
||||||
|
sx={{
|
||||||
|
margin: 0,
|
||||||
|
}}
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
onChange={(e) => setKeepCurrentPassword(e.target.checked)}
|
||||||
|
checked={keepCurrentPassword}
|
||||||
|
edge="start"
|
||||||
|
tabIndex={-1}
|
||||||
|
disableRipple
|
||||||
|
sx={{
|
||||||
|
'&.Mui-checked': {
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
},
|
||||||
|
'& .MuiSvgIcon-root': {
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<Typography sx={{ fontSize: '14px' }}>
|
||||||
|
Keep current password
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Spacer height="20px" />
|
||||||
|
{!keepCurrentPassword && (
|
||||||
|
<>
|
||||||
|
<CustomLabel htmlFor="standard-adornment-password">
|
||||||
|
New password
|
||||||
|
</CustomLabel>
|
||||||
|
|
||||||
|
<Spacer height="5px" />
|
||||||
|
<PasswordField
|
||||||
|
id="standard-adornment-password"
|
||||||
|
value={newPassword}
|
||||||
|
onChange={(e) => setNewPassword(e.target.value)}
|
||||||
|
/>
|
||||||
|
<Spacer height="20px" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<CustomButton onClick={confirmPasswordToDownload}>
|
||||||
|
Confirm wallet password
|
||||||
|
</CustomButton>
|
||||||
|
|
||||||
|
<ErrorText>{walletToBeDownloadedError}</ErrorText>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{walletToBeDownloaded && (
|
||||||
|
<>
|
||||||
|
<CustomButton
|
||||||
|
onClick={async () => {
|
||||||
|
await saveFileToDiskFunc();
|
||||||
|
await showInfo({
|
||||||
|
message: 'Keep your account file secure',
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Download account
|
||||||
|
</CustomButton>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -79,7 +79,7 @@ export const AdminSpaceInner = ({
|
|||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`${getBaseApiReact()}/arbitrary/DOCUMENT_PRIVATE/${
|
`${getBaseApiReact()}/arbitrary/DOCUMENT_PRIVATE/${
|
||||||
getLatestPublish.name
|
getLatestPublish.name
|
||||||
}/${getLatestPublish.identifier}?encoding=base64`
|
}/${getLatestPublish.identifier}?encoding=base64&rebuild=true`
|
||||||
);
|
);
|
||||||
data = await res.text();
|
data = await res.text();
|
||||||
|
|
||||||
|
@ -15,12 +15,12 @@ import { CustomizedSnackbars } from '../Snackbar/Snackbar'
|
|||||||
import { PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY } from '../../constants/codes'
|
import { PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY } from '../../constants/codes'
|
||||||
import { useMessageQueue } from '../../MessageQueueContext'
|
import { useMessageQueue } from '../../MessageQueueContext'
|
||||||
import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from '../../utils/events'
|
import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from '../../utils/events'
|
||||||
import { Box, ButtonBase, Divider, Typography } from '@mui/material'
|
import { Box, ButtonBase, Divider, IconButton, Tooltip, Typography } from '@mui/material'
|
||||||
import ShortUniqueId from "short-unique-id";
|
import ShortUniqueId from "short-unique-id";
|
||||||
import { ReplyPreview } from './MessageItem'
|
import { ReplyPreview } from './MessageItem'
|
||||||
import { ExitIcon } from '../../assets/Icons/ExitIcon'
|
import { ExitIcon } from '../../assets/Icons/ExitIcon'
|
||||||
import { RESOURCE_TYPE_NUMBER_GROUP_CHAT_REACTIONS } from '../../constants/resourceTypes'
|
import { RESOURCE_TYPE_NUMBER_GROUP_CHAT_REACTIONS } from '../../constants/resourceTypes'
|
||||||
import { isExtMsg } from '../../background'
|
import { getFee, isExtMsg } from '../../background'
|
||||||
import MentionList from './MentionList'
|
import MentionList from './MentionList'
|
||||||
import { ChatOptions } from './ChatOptions'
|
import { ChatOptions } from './ChatOptions'
|
||||||
import { isFocusedParentGroupAtom } from '../../atoms/global'
|
import { isFocusedParentGroupAtom } from '../../atoms/global'
|
||||||
@ -28,6 +28,10 @@ import { useRecoilState } from 'recoil'
|
|||||||
import AppViewerContainer from '../Apps/AppViewerContainer'
|
import AppViewerContainer from '../Apps/AppViewerContainer'
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
import { throttle } from 'lodash'
|
import { throttle } from 'lodash'
|
||||||
|
import ImageIcon from '@mui/icons-material/Image';
|
||||||
|
import { messageHasImage } from '../../utils/chat'
|
||||||
|
|
||||||
|
const uidImages = new ShortUniqueId({ length: 12 });
|
||||||
|
|
||||||
const uid = new ShortUniqueId({ length: 5 });
|
const uid = new ShortUniqueId({ length: 5 });
|
||||||
|
|
||||||
@ -55,8 +59,9 @@ export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey,
|
|||||||
const editorRef = useRef(null);
|
const editorRef = useRef(null);
|
||||||
const { queueChats, addToQueue, processWithNewMessages } = useMessageQueue();
|
const { queueChats, addToQueue, processWithNewMessages } = useMessageQueue();
|
||||||
const handleUpdateRef = useRef(null);
|
const handleUpdateRef = useRef(null);
|
||||||
const {isUserBlocked} = useContext(MyContext)
|
const {isUserBlocked, show} = useContext(MyContext)
|
||||||
|
const [chatImagesToSave, setChatImagesToSave] = useState([]);
|
||||||
|
const [isDeleteImage, setIsDeleteImage] = useState(false);
|
||||||
|
|
||||||
const lastReadTimestamp = useRef(null)
|
const lastReadTimestamp = useRef(null)
|
||||||
|
|
||||||
@ -624,6 +629,8 @@ if(isFocusedParent === false){
|
|||||||
setReplyMessage(null)
|
setReplyMessage(null)
|
||||||
setOnEditMessage(null)
|
setOnEditMessage(null)
|
||||||
clearEditorContent()
|
clearEditorContent()
|
||||||
|
setIsDeleteImage(false);
|
||||||
|
setChatImagesToSave([]);
|
||||||
}
|
}
|
||||||
}, [isFocusedParent])
|
}, [isFocusedParent])
|
||||||
const clearEditorContent = () => {
|
const clearEditorContent = () => {
|
||||||
@ -644,88 +651,193 @@ const clearEditorContent = () => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
const sendMessage = async ()=> {
|
const sendMessage = async () => {
|
||||||
try {
|
try {
|
||||||
if(messageSize > 4000) return
|
if (messageSize > 4000) return; // TODO magic number
|
||||||
if(isPrivate === null) throw new Error('Unable to determine if group is private')
|
if (isPrivate === null)
|
||||||
if(isSending) return
|
throw new Error(
|
||||||
if(+balance < 4) throw new Error('You need at least 4 QORT to send a message')
|
"Onable to determine if group is private"
|
||||||
pauseAllQueues()
|
);
|
||||||
|
if (isSending) return;
|
||||||
|
if (+balance < 4)
|
||||||
|
// TODO magic number
|
||||||
|
throw new Error(
|
||||||
|
"You need at least 4 QORT to send a message"
|
||||||
|
);
|
||||||
|
pauseAllQueues();
|
||||||
if (editorRef.current) {
|
if (editorRef.current) {
|
||||||
const htmlContent = editorRef.current.getHTML();
|
let htmlContent = editorRef.current.getHTML();
|
||||||
|
const deleteImage =
|
||||||
if(!htmlContent?.trim() || htmlContent?.trim() === '<p></p>') return
|
onEditMessage && isDeleteImage && messageHasImage(onEditMessage);
|
||||||
|
|
||||||
|
|
||||||
setIsSending(true)
|
const hasImage =
|
||||||
const message = isPrivate === false ? editorRef.current.getJSON() : htmlContent
|
chatImagesToSave?.length > 0 || onEditMessage?.images?.length > 0;
|
||||||
const secretKeyObject = await getSecretKey(false, true)
|
if (
|
||||||
|
(!htmlContent?.trim() || htmlContent?.trim() === '<p></p>') &&
|
||||||
|
!hasImage &&
|
||||||
|
!deleteImage
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
if (htmlContent?.trim() === '<p></p>') {
|
||||||
|
htmlContent = null;
|
||||||
|
}
|
||||||
|
setIsSending(true);
|
||||||
|
const message =
|
||||||
|
isPrivate === false
|
||||||
|
? !htmlContent
|
||||||
|
? '<p></p>'
|
||||||
|
: editorRef.current.getJSON()
|
||||||
|
: htmlContent;
|
||||||
|
const secretKeyObject = await getSecretKey(false, true);
|
||||||
|
|
||||||
let repliedTo = replyMessage?.signature
|
let repliedTo = replyMessage?.signature;
|
||||||
|
|
||||||
if (replyMessage?.chatReference) {
|
if (replyMessage?.chatReference) {
|
||||||
repliedTo = replyMessage?.chatReference
|
repliedTo = replyMessage?.chatReference;
|
||||||
}
|
}
|
||||||
let chatReference = onEditMessage?.signature
|
|
||||||
|
|
||||||
const publicData = isPrivate ? {} : {
|
const chatReference = onEditMessage?.signature;
|
||||||
isEdited : chatReference ? true : false,
|
|
||||||
}
|
|
||||||
const otherData = {
|
|
||||||
repliedTo,
|
|
||||||
...(onEditMessage?.decryptedData || {}),
|
|
||||||
type: chatReference ? 'edit' : '',
|
|
||||||
specialId: uid.rnd(),
|
|
||||||
...publicData
|
|
||||||
}
|
|
||||||
const objectMessage = {
|
|
||||||
...(otherData || {}),
|
|
||||||
[isPrivate ? 'message' : 'messageText']: message,
|
|
||||||
version: 3
|
|
||||||
}
|
|
||||||
const message64: any = await objectToBase64(objectMessage)
|
|
||||||
|
|
||||||
const encryptSingle = isPrivate === false ? JSON.stringify(objectMessage) : await encryptChatMessage(message64, secretKeyObject)
|
|
||||||
// const res = await sendChatGroup({groupId: selectedGroup,messageText: encryptSingle})
|
|
||||||
|
|
||||||
const sendMessageFunc = async () => {
|
|
||||||
return await sendChatGroup({groupId: selectedGroup,messageText: encryptSingle, chatReference})
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add the function to the queue
|
const publicData = isPrivate
|
||||||
const messageObj = {
|
? {}
|
||||||
message: {
|
: {
|
||||||
text: htmlContent,
|
isEdited: chatReference ? true : false,
|
||||||
timestamp: Date.now(),
|
};
|
||||||
senderName: myName,
|
|
||||||
sender: myAddress,
|
interface ImageToPublish {
|
||||||
...(otherData || {})
|
service: string;
|
||||||
},
|
identifier: string;
|
||||||
chatReference
|
name: string;
|
||||||
}
|
base64: string;
|
||||||
addToQueue(sendMessageFunc, messageObj, 'chat',
|
}
|
||||||
selectedGroup );
|
|
||||||
setTimeout(() => {
|
const imagesToPublish: ImageToPublish[] = [];
|
||||||
executeEvent("sent-new-message-group", {})
|
|
||||||
}, 150);
|
if (deleteImage) {
|
||||||
clearEditorContent()
|
const fee = await getFee('ARBITRARY');
|
||||||
setReplyMessage(null)
|
await show({
|
||||||
setOnEditMessage(null)
|
publishFee: fee.fee + ' QORT',
|
||||||
|
message: "Would you like to delete your previous chat image?",
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO magic string
|
||||||
|
await window.sendMessage('publishOnQDN', {
|
||||||
|
data: 'RA==',
|
||||||
|
identifier: onEditMessage?.images[0]?.identifier,
|
||||||
|
service: onEditMessage?.images[0]?.service,
|
||||||
|
uploadType: 'base64',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chatImagesToSave?.length > 0) {
|
||||||
|
const imageToSave = chatImagesToSave[0];
|
||||||
|
|
||||||
|
const base64ToSave = isPrivate
|
||||||
|
? await encryptChatMessage(imageToSave, secretKeyObject)
|
||||||
|
: imageToSave;
|
||||||
|
|
||||||
|
// 1 represents public group, 0 is private
|
||||||
|
const identifier = `grp-q-manager_${isPrivate ? 0 : 1}_group_${selectedGroup}_${uidImages.rnd()}`;
|
||||||
|
imagesToPublish.push({
|
||||||
|
service: 'IMAGE',
|
||||||
|
identifier,
|
||||||
|
name: myName,
|
||||||
|
base64: base64ToSave,
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await window.sendMessage(
|
||||||
|
'PUBLISH_MULTIPLE_QDN_RESOURCES',
|
||||||
|
{
|
||||||
|
resources: imagesToPublish,
|
||||||
|
},
|
||||||
|
240000,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
if (res?.error)
|
||||||
|
throw new Error(
|
||||||
|
"Unable to publish image"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const images =
|
||||||
|
imagesToPublish?.length > 0
|
||||||
|
? imagesToPublish.map((item) => {
|
||||||
|
return {
|
||||||
|
name: item.name,
|
||||||
|
identifier: item.identifier,
|
||||||
|
service: item.service,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
: chatReference
|
||||||
|
? isDeleteImage
|
||||||
|
? []
|
||||||
|
: onEditMessage?.images || []
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const otherData = {
|
||||||
|
repliedTo,
|
||||||
|
...(onEditMessage?.decryptedData || {}),
|
||||||
|
type: chatReference ? 'edit' : '',
|
||||||
|
specialId: uid.rnd(),
|
||||||
|
images: images,
|
||||||
|
...publicData,
|
||||||
|
};
|
||||||
|
const objectMessage = {
|
||||||
|
...(otherData || {}),
|
||||||
|
[isPrivate ? 'message' : 'messageText']: message,
|
||||||
|
version: 3,
|
||||||
|
};
|
||||||
|
const message64: any = await objectToBase64(objectMessage);
|
||||||
|
|
||||||
|
const encryptSingle =
|
||||||
|
isPrivate === false
|
||||||
|
? JSON.stringify(objectMessage)
|
||||||
|
: await encryptChatMessage(message64, secretKeyObject);
|
||||||
|
|
||||||
|
const sendMessageFunc = async () => {
|
||||||
|
return await sendChatGroup({
|
||||||
|
groupId: selectedGroup,
|
||||||
|
messageText: encryptSingle,
|
||||||
|
chatReference,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add the function to the queue
|
||||||
|
const messageObj = {
|
||||||
|
message: {
|
||||||
|
text: htmlContent,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
senderName: myName,
|
||||||
|
sender: myAddress,
|
||||||
|
...(otherData || {}),
|
||||||
|
},
|
||||||
|
chatReference,
|
||||||
|
};
|
||||||
|
addToQueue(sendMessageFunc, messageObj, 'chat', selectedGroup);
|
||||||
|
setTimeout(() => {
|
||||||
|
executeEvent('sent-new-message-group', {});
|
||||||
|
}, 150);
|
||||||
|
clearEditorContent();
|
||||||
|
setReplyMessage(null);
|
||||||
|
setOnEditMessage(null);
|
||||||
|
setIsDeleteImage(false);
|
||||||
|
setChatImagesToSave([]);
|
||||||
}
|
}
|
||||||
// send chat message
|
// send chat message
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMsg = error?.message || error
|
const errorMsg = error?.message || error;
|
||||||
setInfoSnack({
|
setInfoSnack({
|
||||||
type: "error",
|
type: 'error',
|
||||||
message: errorMsg,
|
message: errorMsg,
|
||||||
});
|
});
|
||||||
setOpenSnack(true);
|
setOpenSnack(true);
|
||||||
console.error(error)
|
console.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
setIsSending(false)
|
setIsSending(false);
|
||||||
resumeAllQueues()
|
resumeAllQueues();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (hide) {
|
if (hide) {
|
||||||
@ -742,7 +854,8 @@ const sendMessage = async ()=> {
|
|||||||
setReplyMessage(message)
|
setReplyMessage(message)
|
||||||
setOnEditMessage(null)
|
setOnEditMessage(null)
|
||||||
setIsFocusedParent(true);
|
setIsFocusedParent(true);
|
||||||
|
setIsDeleteImage(false);
|
||||||
|
setChatImagesToSave([]);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
editorRef?.current?.chain().focus()
|
editorRef?.current?.chain().focus()
|
||||||
|
|
||||||
@ -755,7 +868,7 @@ const sendMessage = async ()=> {
|
|||||||
setReplyMessage(null)
|
setReplyMessage(null)
|
||||||
setIsFocusedParent(true);
|
setIsFocusedParent(true);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
editorRef.current.chain().focus().setContent(message?.messageText || message?.text).run();
|
editorRef?.current?.chain().focus().setContent(message?.messageText || message?.text || '<p></p>').run();
|
||||||
}, 250);
|
}, 250);
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@ -824,6 +937,24 @@ const sendMessage = async ()=> {
|
|||||||
resumeAllQueues()
|
resumeAllQueues()
|
||||||
}
|
}
|
||||||
}, [isPrivate])
|
}, [isPrivate])
|
||||||
|
|
||||||
|
const insertImage = useCallback(
|
||||||
|
(img) => {
|
||||||
|
if (
|
||||||
|
chatImagesToSave?.length > 0 ||
|
||||||
|
(messageHasImage(onEditMessage) && !isDeleteImage)
|
||||||
|
) {
|
||||||
|
setInfoSnack({
|
||||||
|
type: 'error',
|
||||||
|
message: 'This message already has an image',
|
||||||
|
});
|
||||||
|
setOpenSnack(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setChatImagesToSave((prev) => [...prev, img]);
|
||||||
|
},
|
||||||
|
[chatImagesToSave, onEditMessage?.images, isDeleteImage]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
@ -864,6 +995,117 @@ const sendMessage = async ()=> {
|
|||||||
overflow: !isMobile && "auto",
|
overflow: !isMobile && "auto",
|
||||||
flexShrink: 0
|
flexShrink: 0
|
||||||
}}>
|
}}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
gap: '10px',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{!isDeleteImage &&
|
||||||
|
onEditMessage &&
|
||||||
|
messageHasImage(onEditMessage) &&
|
||||||
|
onEditMessage?.images?.map((_, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
height: '50px',
|
||||||
|
width: '50px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ImageIcon
|
||||||
|
|
||||||
|
sx={{
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
borderRadius: '3px',
|
||||||
|
color:'white'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Tooltip title="Delete image">
|
||||||
|
<IconButton
|
||||||
|
onClick={() => setIsDeleteImage(true)}
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
backgroundColor: (theme) =>
|
||||||
|
theme.palette.background.paper,
|
||||||
|
color: (theme) => theme.palette.text.primary,
|
||||||
|
borderRadius: '50%',
|
||||||
|
opacity: 0,
|
||||||
|
transition: 'opacity 0.2s',
|
||||||
|
boxShadow: (theme) => theme.shadows[2],
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: (theme) =>
|
||||||
|
theme.palette.background.default,
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
pointerEvents: 'auto',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{chatImagesToSave.map((imgBase64, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
height: '50px',
|
||||||
|
width: '50px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={`data:image/webp;base64,${imgBase64}`}
|
||||||
|
style={{
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: '3px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Tooltip title="Remove image">
|
||||||
|
<IconButton
|
||||||
|
onClick={() =>
|
||||||
|
setChatImagesToSave((prev) =>
|
||||||
|
prev.filter((_, i) => i !== index)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
backgroundColor: (theme) =>
|
||||||
|
theme.palette.background.paper,
|
||||||
|
color: (theme) => theme.palette.text.primary,
|
||||||
|
borderRadius: '50%',
|
||||||
|
opacity: 0,
|
||||||
|
transition: 'opacity 0.2s',
|
||||||
|
boxShadow: (theme) => theme.shadows[2],
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: (theme) =>
|
||||||
|
theme.palette.background.default,
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
pointerEvents: 'auto',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
{replyMessage && (
|
{replyMessage && (
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -895,7 +1137,7 @@ const sendMessage = async ()=> {
|
|||||||
}}>
|
}}>
|
||||||
|
|
||||||
|
|
||||||
<Tiptap isReply={onEditMessage || replyMessage} enableMentions setEditorRef={setEditorRef} onEnter={sendMessage} isChat disableEnter={isMobile ? true : false} isFocusedParent={isFocusedParent} setIsFocusedParent={setIsFocusedParent} membersWithNames={members} />
|
<Tiptap isReply={onEditMessage || replyMessage} enableMentions setEditorRef={setEditorRef} onEnter={sendMessage} isChat disableEnter={isMobile ? true : false} isFocusedParent={isFocusedParent} setIsFocusedParent={setIsFocusedParent} membersWithNames={members} insertImage={insertImage} />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -261,17 +261,18 @@ export const ChatList = ({ initialMessages, myAddress, tempMessages, chatId, onR
|
|||||||
if (chatReferences?.[message.signature]) {
|
if (chatReferences?.[message.signature]) {
|
||||||
reactions = chatReferences[message.signature]?.reactions || null;
|
reactions = chatReferences[message.signature]?.reactions || null;
|
||||||
|
|
||||||
if (chatReferences[message.signature]?.edit?.message && message?.text) {
|
if (chatReferences[message.signature]?.edit) {
|
||||||
message.text = chatReferences[message.signature]?.edit?.message;
|
message.text =
|
||||||
message.isEdit = true
|
chatReferences[message.signature]?.edit?.message;
|
||||||
message.editTimestamp = chatReferences[message.signature]?.edit?.timestamp
|
message.messageText =
|
||||||
}
|
chatReferences[message.signature]?.edit?.messageText;
|
||||||
if (chatReferences[message.signature]?.edit?.messageText && message?.messageText) {
|
message.images =
|
||||||
message.messageText = chatReferences[message.signature]?.edit?.messageText;
|
chatReferences[message.signature]?.edit?.images;
|
||||||
message.isEdit = true
|
|
||||||
message.editTimestamp = chatReferences[message.signature]?.edit?.timestamp
|
message.isEdit = true;
|
||||||
|
message.editTimestamp =
|
||||||
|
chatReferences[message.signature]?.edit?.timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if message is updating
|
// Check if message is updating
|
||||||
|
@ -39,6 +39,7 @@ import StarterKit from "@tiptap/starter-kit";
|
|||||||
import Underline from "@tiptap/extension-underline";
|
import Underline from "@tiptap/extension-underline";
|
||||||
import { generateHTML } from "@tiptap/react";
|
import { generateHTML } from "@tiptap/react";
|
||||||
import ErrorBoundary from "../../common/ErrorBoundary";
|
import ErrorBoundary from "../../common/ErrorBoundary";
|
||||||
|
import { isHtmlString } from "../../utils/chat";
|
||||||
|
|
||||||
const extractTextFromHTML = (htmlString = '') => {
|
const extractTextFromHTML = (htmlString = '') => {
|
||||||
return convert(htmlString, {
|
return convert(htmlString, {
|
||||||
@ -59,27 +60,30 @@ export const ChatOptions = ({ messages : untransformedMessages, goToMessage, mem
|
|||||||
const parentRefMentions = useRef();
|
const parentRefMentions = useRef();
|
||||||
const [lastMentionTimestamp, setLastMentionTimestamp] = useState(null)
|
const [lastMentionTimestamp, setLastMentionTimestamp] = useState(null)
|
||||||
const [debouncedValue, setDebouncedValue] = useState(""); // Debounced value
|
const [debouncedValue, setDebouncedValue] = useState(""); // Debounced value
|
||||||
const messages = useMemo(()=> {
|
const messages = useMemo(() => {
|
||||||
return untransformedMessages?.map((item)=> {
|
return untransformedMessages?.map((item) => {
|
||||||
if(item?.messageText){
|
if (item?.messageText) {
|
||||||
let transformedMessage = item?.messageText
|
let transformedMessage = item?.messageText;
|
||||||
|
const isHtml = isHtmlString(item?.messageText);
|
||||||
try {
|
try {
|
||||||
transformedMessage = generateHTML(item?.messageText, [
|
transformedMessage = isHtml
|
||||||
StarterKit,
|
? item?.messageText
|
||||||
Underline,
|
: generateHTML(item?.messageText, [
|
||||||
Highlight,
|
StarterKit,
|
||||||
Mention
|
Underline,
|
||||||
])
|
Highlight,
|
||||||
return {
|
Mention,
|
||||||
...item,
|
]);
|
||||||
messageText: transformedMessage
|
return {
|
||||||
}
|
...item,
|
||||||
|
messageText: transformedMessage,
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// error
|
console.log(error);
|
||||||
}
|
}
|
||||||
} else return item
|
} else return item;
|
||||||
})
|
});
|
||||||
}, [untransformedMessages])
|
}, [untransformedMessages]);
|
||||||
|
|
||||||
const getTimestampMention = async () => {
|
const getTimestampMention = async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -66,7 +66,7 @@ export const CreateCommonSecret = ({groupId, secretKey, isOwner, myAddress, sec
|
|||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`${getBaseApiReact()}/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${
|
`${getBaseApiReact()}/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${
|
||||||
publish.identifier
|
publish.identifier
|
||||||
}?encoding=base64`
|
}?encoding=base64&rebuild=true`
|
||||||
);
|
);
|
||||||
const data = await res.text();
|
const data = await res.text();
|
||||||
|
|
||||||
|
@ -33,6 +33,9 @@ import level7Img from "../../assets/badges/level-7.png"
|
|||||||
import level8Img from "../../assets/badges/level-8.png"
|
import level8Img from "../../assets/badges/level-8.png"
|
||||||
import level9Img from "../../assets/badges/level-9.png"
|
import level9Img from "../../assets/badges/level-9.png"
|
||||||
import level10Img from "../../assets/badges/level-10.png"
|
import level10Img from "../../assets/badges/level-10.png"
|
||||||
|
import { Embed } from "../Embeds/Embed";
|
||||||
|
import { buildImageEmbedLink, isHtmlString, messageHasImage } from "../../utils/chat";
|
||||||
|
import CommentsDisabledIcon from '@mui/icons-material/CommentsDisabled';
|
||||||
|
|
||||||
const getBadgeImg = (level)=> {
|
const getBadgeImg = (level)=> {
|
||||||
switch(level?.toString()){
|
switch(level?.toString()){
|
||||||
@ -102,35 +105,33 @@ useEffect(()=> {
|
|||||||
getInfo()
|
getInfo()
|
||||||
}, [message?.sender, getIndividualUserInfo])
|
}, [message?.sender, getIndividualUserInfo])
|
||||||
|
|
||||||
const htmlText = useMemo(()=> {
|
const htmlText = useMemo(() => {
|
||||||
|
if (message?.messageText) {
|
||||||
if(message?.messageText){
|
const isHtml = isHtmlString(message?.messageText);
|
||||||
|
if (isHtml) return message?.messageText;
|
||||||
return generateHTML(message?.messageText, [
|
return generateHTML(message?.messageText, [
|
||||||
StarterKit,
|
StarterKit,
|
||||||
Underline,
|
Underline,
|
||||||
Highlight,
|
Highlight,
|
||||||
Mention,
|
Mention,
|
||||||
TextStyle
|
TextStyle,
|
||||||
])
|
]);
|
||||||
}
|
}
|
||||||
|
}, [message?.editTimestamp]);
|
||||||
}, [message?.editTimestamp])
|
|
||||||
|
|
||||||
|
const htmlReply = useMemo(() => {
|
||||||
|
if (reply?.messageText) {
|
||||||
const htmlReply = useMemo(()=> {
|
const isHtml = isHtmlString(reply?.messageText);
|
||||||
|
if (isHtml) return reply?.messageText;
|
||||||
if(reply?.messageText){
|
|
||||||
return generateHTML(reply?.messageText, [
|
return generateHTML(reply?.messageText, [
|
||||||
StarterKit,
|
StarterKit,
|
||||||
Underline,
|
Underline,
|
||||||
Highlight,
|
Highlight,
|
||||||
Mention,
|
Mention,
|
||||||
TextStyle
|
TextStyle,
|
||||||
])
|
]);
|
||||||
}
|
}
|
||||||
|
}, [reply?.editTimestamp]);
|
||||||
}, [reply?.editTimestamp])
|
|
||||||
|
|
||||||
const userAvatarUrl = useMemo(()=> {
|
const userAvatarUrl = useMemo(()=> {
|
||||||
return message?.senderName ? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${
|
return message?.senderName ? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${
|
||||||
@ -142,6 +143,13 @@ const onSeenFunc = useCallback(()=> {
|
|||||||
onSeen(message.id);
|
onSeen(message.id);
|
||||||
}, [message?.id])
|
}, [message?.id])
|
||||||
|
|
||||||
|
const hasNoMessage =
|
||||||
|
(!message.decryptedData?.data?.message ||
|
||||||
|
message.decryptedData?.data?.message === '<p></p>') &&
|
||||||
|
(message?.images || [])?.length === 0 &&
|
||||||
|
(!message?.messageText || message?.messageText === '<p></p>') &&
|
||||||
|
(!message?.text || message?.text === '<p></p>');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MessageWragger lastMessage={lastSignature === message?.signature} isLast={isLast} onSeen={onSeenFunc}>
|
<MessageWragger lastMessage={lastSignature === message?.signature} isLast={isLast} onSeen={onSeenFunc}>
|
||||||
{message?.divide && (
|
{message?.divide && (
|
||||||
@ -335,7 +343,7 @@ const onSeenFunc = useCallback(()=> {
|
|||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{message?.messageText && (
|
{htmlText && !hasNoMessage && (
|
||||||
<MessageDisplay
|
<MessageDisplay
|
||||||
htmlContent={htmlText}
|
htmlContent={htmlText}
|
||||||
setMobileViewModeKeepOpen={setMobileViewModeKeepOpen}
|
setMobileViewModeKeepOpen={setMobileViewModeKeepOpen}
|
||||||
@ -343,9 +351,30 @@ const onSeenFunc = useCallback(()=> {
|
|||||||
)}
|
)}
|
||||||
{message?.decryptedData?.type === "notification" ? (
|
{message?.decryptedData?.type === "notification" ? (
|
||||||
<MessageDisplay htmlContent={message.decryptedData?.data?.message} />
|
<MessageDisplay htmlContent={message.decryptedData?.data?.message} />
|
||||||
) : (
|
) : hasNoMessage ? null : (
|
||||||
<MessageDisplay setMobileViewModeKeepOpen={setMobileViewModeKeepOpen} htmlContent={message.text} />
|
<MessageDisplay setMobileViewModeKeepOpen={setMobileViewModeKeepOpen} htmlContent={message.text} />
|
||||||
)}
|
)}
|
||||||
|
{hasNoMessage && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '10px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CommentsDisabledIcon sx={{
|
||||||
|
color: 'white'
|
||||||
|
}} />
|
||||||
|
<Typography sx={{
|
||||||
|
color: 'white'
|
||||||
|
}}>
|
||||||
|
No Message
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{message?.images && messageHasImage(message) && (
|
||||||
|
<Embed embedLink={buildImageEmbedLink(message.images[0])} />
|
||||||
|
)}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@ -519,6 +548,19 @@ const onSeenFunc = useCallback(()=> {
|
|||||||
|
|
||||||
export const ReplyPreview = ({message, isEdit})=> {
|
export const ReplyPreview = ({message, isEdit})=> {
|
||||||
|
|
||||||
|
const replyMessageText = useMemo(() => {
|
||||||
|
if (!message?.messageText) return null;
|
||||||
|
const isHtml = isHtmlString(message?.messageText);
|
||||||
|
if (isHtml) return message?.messageText;
|
||||||
|
return generateHTML(message?.messageText, [
|
||||||
|
StarterKit,
|
||||||
|
Underline,
|
||||||
|
Highlight,
|
||||||
|
Mention,
|
||||||
|
TextStyle,
|
||||||
|
]);
|
||||||
|
}, [message?.messageText]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -553,15 +595,9 @@ export const ReplyPreview = ({message, isEdit})=> {
|
|||||||
}}>Replied to {message?.senderName || message?.senderAddress}</Typography>
|
}}>Replied to {message?.senderName || message?.senderAddress}</Typography>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{message?.messageText && (
|
{replyMessageText && (
|
||||||
<MessageDisplay
|
<MessageDisplay
|
||||||
htmlContent={generateHTML(message?.messageText, [
|
htmlContent={replyMessageText}
|
||||||
StarterKit,
|
|
||||||
Underline,
|
|
||||||
Highlight,
|
|
||||||
Mention,
|
|
||||||
TextStyle
|
|
||||||
])}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{message?.decryptedData?.type === "notification" ? (
|
{message?.decryptedData?.type === "notification" ? (
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { EditorProvider, useCurrentEditor, useEditor } from "@tiptap/react";
|
import { EditorProvider, useCurrentEditor, useEditor } from "@tiptap/react";
|
||||||
import StarterKit from "@tiptap/starter-kit";
|
import StarterKit from "@tiptap/starter-kit";
|
||||||
import { Color } from "@tiptap/extension-color";
|
import { Color } from "@tiptap/extension-color";
|
||||||
@ -34,6 +34,7 @@ import ListItemButton from '@mui/material/ListItemButton';
|
|||||||
import ListItemText from '@mui/material/ListItemText';
|
import ListItemText from '@mui/material/ListItemText';
|
||||||
import { ReactRenderer } from '@tiptap/react'
|
import { ReactRenderer } from '@tiptap/react'
|
||||||
import MentionList from './MentionList.jsx'
|
import MentionList from './MentionList.jsx'
|
||||||
|
import { fileToBase64 } from "../../utils/fileReading/index.js";
|
||||||
|
|
||||||
function textMatcher(doc, from) {
|
function textMatcher(doc, from) {
|
||||||
const textBeforeCursor = doc.textBetween(0, from, ' ', ' ');
|
const textBeforeCursor = doc.textBetween(0, from, ' ', ' ');
|
||||||
@ -110,13 +111,13 @@ const MenuBar = ({ setEditorRef, isChat }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (editor) {
|
if (editor && !isChat) {
|
||||||
editor.view.dom.addEventListener("paste", handlePaste);
|
editor.view.dom.addEventListener("paste", handlePaste);
|
||||||
return () => {
|
return () => {
|
||||||
editor.view.dom.removeEventListener("paste", handlePaste);
|
editor.view.dom.removeEventListener("paste", handlePaste);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [editor]);
|
}, [editor, isChat]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="control-group">
|
<div className="control-group">
|
||||||
@ -299,7 +300,8 @@ export default ({
|
|||||||
customEditorHeight,
|
customEditorHeight,
|
||||||
membersWithNames,
|
membersWithNames,
|
||||||
enableMentions,
|
enableMentions,
|
||||||
isReply
|
isReply,
|
||||||
|
insertImage,
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
const extensionsFiltered = isChat
|
const extensionsFiltered = isChat
|
||||||
@ -329,7 +331,35 @@ export default ({
|
|||||||
}, [membersWithNames])
|
}, [membersWithNames])
|
||||||
|
|
||||||
|
|
||||||
|
const handleImageUpload = useCallback(async (file) => {
|
||||||
|
try {
|
||||||
|
if (!file.type.includes('image')) return;
|
||||||
|
let compressedFile = file;
|
||||||
|
if (file.type !== 'image/gif') {
|
||||||
|
await new Promise<void>((resolve) => {
|
||||||
|
new Compressor(file, {
|
||||||
|
quality: 0.6,
|
||||||
|
maxWidth: 1200,
|
||||||
|
mimeType: 'image/webp',
|
||||||
|
success(result) {
|
||||||
|
compressedFile = result;
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
error(err) {
|
||||||
|
console.error('Image compression error:', err);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (compressedFile) {
|
||||||
|
const toBase64 = await fileToBase64(compressedFile);
|
||||||
|
insertImage(toBase64);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}, [insertImage]);
|
||||||
|
|
||||||
|
|
||||||
const usersRef = useRef([]);
|
const usersRef = useRef([]);
|
||||||
@ -470,6 +500,25 @@ export default ({
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
handlePaste(view, event) {
|
||||||
|
if(!handleImageUpload) return
|
||||||
|
if (!isChat) return;
|
||||||
|
const items = event.clipboardData?.items;
|
||||||
|
if (!items) return false;
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
if (item.type.startsWith('image/')) {
|
||||||
|
const file = item.getAsFile();
|
||||||
|
if (file) {
|
||||||
|
event.preventDefault(); // Block the default paste
|
||||||
|
handleImageUpload(file); // Custom handler
|
||||||
|
return true; // Let ProseMirror know we handled it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false; // fallback to default behavior otherwise
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import React, { useCallback, useEffect, useRef } from "react";
|
import React, { useCallback, useEffect, useRef } from "react";
|
||||||
import { getBaseApiReact } from "../../App";
|
|
||||||
import { truncate } from "lodash";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const useBlockedAddresses = () => {
|
|
||||||
|
export const useBlockedAddresses = (isAuthenticated: boolean) => {
|
||||||
const userBlockedRef = useRef({})
|
const userBlockedRef = useRef({})
|
||||||
const userNamesBlockedRef = useRef({})
|
const userNamesBlockedRef = useRef({})
|
||||||
|
|
||||||
@ -19,7 +18,7 @@ export const useBlockedAddresses = () => {
|
|||||||
const isUserBlocked = useCallback((address, name)=> {
|
const isUserBlocked = useCallback((address, name)=> {
|
||||||
try {
|
try {
|
||||||
if(!address) return false
|
if(!address) return false
|
||||||
if(userBlockedRef.current[address] || userNamesBlockedRef.current[name]) return true
|
if(userBlockedRef.current[address]) return true
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
|
||||||
@ -29,6 +28,9 @@ export const useBlockedAddresses = () => {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(()=> {
|
useEffect(()=> {
|
||||||
|
if (!isAuthenticated) return;
|
||||||
|
userBlockedRef.current = {};
|
||||||
|
userNamesBlockedRef.current = {};
|
||||||
const fetchBlockedList = async ()=> {
|
const fetchBlockedList = async ()=> {
|
||||||
try {
|
try {
|
||||||
const response = await new Promise((res, rej) => {
|
const response = await new Promise((res, rej) => {
|
||||||
@ -87,46 +89,16 @@ export const useBlockedAddresses = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fetchBlockedList()
|
fetchBlockedList()
|
||||||
}, [])
|
}, [isAuthenticated])
|
||||||
|
|
||||||
const removeBlockFromList = useCallback(async (address, name)=> {
|
const removeBlockFromList = useCallback(async (address, name)=> {
|
||||||
await new Promise((res, rej) => {
|
if(name){
|
||||||
window.sendMessage("listActions", {
|
|
||||||
|
|
||||||
type: 'remove',
|
|
||||||
items: name ? [name] : [address],
|
|
||||||
listName: name ? 'blockedNames' : 'blockedAddresses'
|
|
||||||
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
if (response.error) {
|
|
||||||
rej(response?.message);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
if(!name){
|
|
||||||
const copyObject = {...userBlockedRef.current}
|
|
||||||
delete copyObject[address]
|
|
||||||
userBlockedRef.current = copyObject
|
|
||||||
} else {
|
|
||||||
const copyObject = {...userNamesBlockedRef.current}
|
|
||||||
delete copyObject[name]
|
|
||||||
userNamesBlockedRef.current = copyObject
|
|
||||||
}
|
|
||||||
|
|
||||||
res(response);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error("Failed qortalRequest", error);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
if(name && userBlockedRef.current[address]){
|
|
||||||
await new Promise((res, rej) => {
|
await new Promise((res, rej) => {
|
||||||
window.sendMessage("listActions", {
|
window.sendMessage("listActions", {
|
||||||
|
|
||||||
type: 'remove',
|
type: 'remove',
|
||||||
items: !name ? [name] : [address],
|
items: [name] ,
|
||||||
listName: !name ? 'blockedNames' : 'blockedAddresses'
|
listName: 'blockedNames'
|
||||||
|
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
@ -134,9 +106,12 @@ export const useBlockedAddresses = () => {
|
|||||||
rej(response?.message);
|
rej(response?.message);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
const copyObject = {...userBlockedRef.current}
|
|
||||||
delete copyObject[address]
|
const copyObject = {...userNamesBlockedRef.current}
|
||||||
userBlockedRef.current = copyObject
|
delete copyObject[name]
|
||||||
|
userNamesBlockedRef.current = copyObject
|
||||||
|
|
||||||
|
|
||||||
res(response);
|
res(response);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -145,42 +120,95 @@ export const useBlockedAddresses = () => {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(address){
|
||||||
|
await new Promise((res, rej) => {
|
||||||
|
window.sendMessage("listActions", {
|
||||||
|
|
||||||
|
type: 'remove',
|
||||||
|
items: [address],
|
||||||
|
listName: 'blockedAddresses'
|
||||||
|
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.error) {
|
||||||
|
rej(response?.message);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
const copyObject = {...userBlockedRef.current}
|
||||||
|
delete copyObject[address]
|
||||||
|
userBlockedRef.current = copyObject
|
||||||
|
|
||||||
|
|
||||||
|
res(response);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Failed qortalRequest", error);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const addToBlockList = useCallback(async (address, name)=> {
|
const addToBlockList = useCallback(async (address, name)=> {
|
||||||
await new Promise((res, rej) => {
|
if(name){
|
||||||
window.sendMessage("listActions", {
|
await new Promise((res, rej) => {
|
||||||
|
window.sendMessage("listActions", {
|
||||||
type: 'add',
|
|
||||||
items: name ? [name] : [address],
|
|
||||||
listName: name ? 'blockedNames' : 'blockedAddresses'
|
|
||||||
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
if (response.error) {
|
|
||||||
rej(response?.message);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
if(name){
|
|
||||||
|
|
||||||
const copyObject = {...userNamesBlockedRef.current}
|
|
||||||
copyObject[name] = true
|
|
||||||
userNamesBlockedRef.current = copyObject
|
|
||||||
}else {
|
|
||||||
const copyObject = {...userBlockedRef.current}
|
|
||||||
copyObject[address] = true
|
|
||||||
userBlockedRef.current = copyObject
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
res(response);
|
type: 'add',
|
||||||
}
|
items: [name],
|
||||||
|
listName: 'blockedNames'
|
||||||
|
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.error) {
|
||||||
|
rej(response?.message);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
const copyObject = {...userNamesBlockedRef.current}
|
||||||
|
copyObject[name] = true
|
||||||
|
userNamesBlockedRef.current = copyObject
|
||||||
|
|
||||||
|
|
||||||
|
res(response);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Failed qortalRequest", error);
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
}
|
||||||
console.error("Failed qortalRequest", error);
|
if(address){
|
||||||
});
|
await new Promise((res, rej) => {
|
||||||
})
|
window.sendMessage("listActions", {
|
||||||
|
|
||||||
|
type: 'add',
|
||||||
|
items: [address],
|
||||||
|
listName: 'blockedAddresses'
|
||||||
|
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.error) {
|
||||||
|
rej(response?.message);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
const copyObject = {...userBlockedRef.current}
|
||||||
|
copyObject[address] = true
|
||||||
|
userBlockedRef.current = copyObject
|
||||||
|
|
||||||
|
res(response);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Failed qortalRequest", error);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -52,6 +52,8 @@ export const ImageCard = ({
|
|||||||
backgroundColor: "#1F2023",
|
backgroundColor: "#1F2023",
|
||||||
height: height,
|
height: height,
|
||||||
transition: "height 0.6s ease-in-out",
|
transition: "height 0.6s ease-in-out",
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
@ -170,8 +172,18 @@ export const ImageCard = ({
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box
|
||||||
<CardContent>
|
sx={{
|
||||||
|
maxHeight: '100%',
|
||||||
|
flexGrow: 1,
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CardContent
|
||||||
|
sx={{
|
||||||
|
height: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<ImageViewer src={image} />
|
<ImageViewer src={image} />
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Box>
|
</Box>
|
||||||
@ -203,6 +215,7 @@ export const ImageCard = ({
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
|
height: '100%',
|
||||||
}}
|
}}
|
||||||
onClick={handleOpenFullscreen}
|
onClick={handleOpenFullscreen}
|
||||||
>
|
>
|
||||||
@ -239,6 +252,9 @@ export const ImageCard = ({
|
|||||||
position: "relative",
|
position: "relative",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
backgroundColor: "#000",
|
backgroundColor: "#000",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -6,19 +6,30 @@ import {
|
|||||||
DialogContent,
|
DialogContent,
|
||||||
DialogContentText,
|
DialogContentText,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
|
IconButton,
|
||||||
TextField,
|
TextField,
|
||||||
Typography,
|
Typography,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import React, { useContext, useEffect, useState } from "react";
|
import React, { useContext, useEffect, useState } from "react";
|
||||||
import { MyContext } from "../../App";
|
import { getBaseApiReact, MyContext } from "../../App";
|
||||||
import { Spacer } from "../../common/Spacer";
|
import { Spacer } from "../../common/Spacer";
|
||||||
import { executeEvent } from "../../utils/events";
|
import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from "../../utils/events";
|
||||||
|
import { validateAddress } from "../../utils/validateAddress";
|
||||||
|
import { getNameInfo, requestQueueMemberNames } from "./Group";
|
||||||
|
import { useModal } from "../../common/useModal";
|
||||||
|
import { useRecoilState } from "recoil";
|
||||||
|
import { isOpenBlockedModalAtom } from "../../atoms/global";
|
||||||
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
|
|
||||||
export const BlockedUsersModal = ({ close }) => {
|
import InfoIcon from '@mui/icons-material/Info';
|
||||||
|
export const BlockedUsersModal = () => {
|
||||||
|
const [isOpenBlockedModal, setIsOpenBlockedModal] = useRecoilState(isOpenBlockedModalAtom)
|
||||||
const [hasChanged, setHasChanged] = useState(false);
|
const [hasChanged, setHasChanged] = useState(false);
|
||||||
const [value, setValue] = useState("");
|
const [value, setValue] = useState("");
|
||||||
|
const [addressesWithNames, setAddressesWithNames] = useState({})
|
||||||
const { getAllBlockedUsers, removeBlockFromList, addToBlockList } = useContext(MyContext);
|
const { isShow, onCancel, onOk, show, message } = useModal();
|
||||||
|
const { getAllBlockedUsers, removeBlockFromList, addToBlockList, setOpenSnackGlobal, setInfoSnackCustom } =
|
||||||
|
useContext(MyContext);
|
||||||
const [blockedUsers, setBlockedUsers] = useState({
|
const [blockedUsers, setBlockedUsers] = useState({
|
||||||
addresses: {},
|
addresses: {},
|
||||||
names: {},
|
names: {},
|
||||||
@ -28,60 +39,162 @@ export const BlockedUsersModal = ({ close }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if(!isOpenBlockedModal) return
|
||||||
fetchBlockedUsers();
|
fetchBlockedUsers();
|
||||||
}, []);
|
}, [isOpenBlockedModal]);
|
||||||
|
|
||||||
|
const getNames = async () => {
|
||||||
|
// const validApi = await findUsableApi();
|
||||||
|
const addresses = Object.keys(blockedUsers?.addresses)
|
||||||
|
const addressNames = {}
|
||||||
|
|
||||||
|
|
||||||
|
const getMemNames = addresses.map(async (address) => {
|
||||||
|
const name = await requestQueueMemberNames.enqueue(() => {
|
||||||
|
return getNameInfo(address);
|
||||||
|
});
|
||||||
|
if (name) {
|
||||||
|
addressNames[address] = name
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(getMemNames);
|
||||||
|
|
||||||
|
setAddressesWithNames(addressNames)
|
||||||
|
};
|
||||||
|
|
||||||
|
const blockUser = async (e, user?: string) => {
|
||||||
|
try {
|
||||||
|
const valUser = user || value
|
||||||
|
if (!valUser) return;
|
||||||
|
const isAddress = validateAddress(valUser);
|
||||||
|
let userName = null;
|
||||||
|
let userAddress = null;
|
||||||
|
if (isAddress) {
|
||||||
|
userAddress = valUser;
|
||||||
|
const name = await getNameInfo(valUser);
|
||||||
|
if (name) {
|
||||||
|
userName = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isAddress) {
|
||||||
|
const response = await fetch(`${getBaseApiReact()}/names/${valUser}`);
|
||||||
|
const data = await response.json();
|
||||||
|
if (!data?.owner) throw new Error("Name does not exist");
|
||||||
|
if (data?.owner) {
|
||||||
|
userAddress = data.owner;
|
||||||
|
userName = valUser;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!userName){
|
||||||
|
await addToBlockList(userAddress, null);
|
||||||
|
fetchBlockedUsers();
|
||||||
|
setHasChanged(true);
|
||||||
|
executeEvent('updateChatMessagesWithBlocks', true)
|
||||||
|
setValue('')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const responseModal = await show({
|
||||||
|
userName,
|
||||||
|
userAddress,
|
||||||
|
});
|
||||||
|
if (responseModal === "both") {
|
||||||
|
await addToBlockList(userAddress, userName);
|
||||||
|
} else if (responseModal === "address") {
|
||||||
|
await addToBlockList(userAddress, null);
|
||||||
|
} else if (responseModal === "name") {
|
||||||
|
await addToBlockList(null, userName);
|
||||||
|
}
|
||||||
|
fetchBlockedUsers();
|
||||||
|
setHasChanged(true);
|
||||||
|
setValue('')
|
||||||
|
if(user){
|
||||||
|
setIsOpenBlockedModal(false)
|
||||||
|
}
|
||||||
|
if(responseModal === 'both' || responseModal === 'address'){
|
||||||
|
executeEvent('updateChatMessagesWithBlocks', true)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setOpenSnackGlobal(true);
|
||||||
|
|
||||||
|
setInfoSnackCustom({
|
||||||
|
type: "error",
|
||||||
|
message: error?.message || "Unable to block user",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const blockUserFromOutsideModalFunc = (e) => {
|
||||||
|
const user = e.detail?.user;
|
||||||
|
setIsOpenBlockedModal(true)
|
||||||
|
blockUser(null, user)
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
subscribeToEvent("blockUserFromOutside", blockUserFromOutsideModalFunc);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unsubscribeFromEvent("blockUserFromOutside", blockUserFromOutsideModalFunc);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
open={true}
|
open={isOpenBlockedModal}
|
||||||
aria-labelledby="alert-dialog-title"
|
aria-labelledby="alert-dialog-title"
|
||||||
aria-describedby="alert-dialog-description"
|
aria-describedby="alert-dialog-description"
|
||||||
>
|
>
|
||||||
<DialogTitle>Blocked Users</DialogTitle>
|
<DialogTitle>Blocked Users</DialogTitle>
|
||||||
<DialogContent sx={{
|
<DialogContent
|
||||||
padding: '20px'
|
sx={{
|
||||||
}}>
|
padding: "20px",
|
||||||
<Box
|
|
||||||
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: "10px",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TextField
|
<Box
|
||||||
placeholder="Name"
|
sx={{
|
||||||
value={value}
|
display: "flex",
|
||||||
onChange={(e) => {
|
alignItems: "center",
|
||||||
setValue(e.target.value);
|
gap: "10px",
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
<Button variant="contained" onClick={async ()=> {
|
<TextField
|
||||||
try {
|
placeholder="Name or address"
|
||||||
if(!value) return
|
value={value}
|
||||||
await addToBlockList(undefined, value)
|
onChange={(e) => {
|
||||||
fetchBlockedUsers()
|
setValue(e.target.value);
|
||||||
setHasChanged(true)
|
}}
|
||||||
} catch (error) {
|
/>
|
||||||
console.error(error)
|
<Button
|
||||||
}
|
sx={{
|
||||||
}}>Block</Button>
|
flexShrink: 0,
|
||||||
</Box>
|
}}
|
||||||
|
variant="contained"
|
||||||
|
onClick={blockUser}
|
||||||
|
>
|
||||||
|
Block
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
{Object.entries(blockedUsers?.addresses).length > 0 && (
|
{Object.entries(blockedUsers?.addresses).length > 0 && (
|
||||||
<>
|
<>
|
||||||
<Spacer height="20px" />
|
<Spacer height="20px" />
|
||||||
<DialogContentText id="alert-dialog-description">
|
<DialogContentText id="alert-dialog-description">
|
||||||
Blocked Users for Chat ( addresses )
|
Blocked addresses- blocks processing of txs
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
<Spacer height="10px" />
|
<Spacer height="10px" />
|
||||||
|
<Button variant="contained" size="small" onClick={getNames}>Fetch names</Button>
|
||||||
|
<Spacer height="10px" />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Box sx={{
|
<Box
|
||||||
display: 'flex',
|
sx={{
|
||||||
flexDirection: 'column',
|
display: "flex",
|
||||||
gap: '10px'
|
flexDirection: "column",
|
||||||
}}>
|
gap: "10px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
{Object.entries(blockedUsers?.addresses || {})?.map(
|
{Object.entries(blockedUsers?.addresses || {})?.map(
|
||||||
([key, value]) => {
|
([key, value]) => {
|
||||||
return (
|
return (
|
||||||
@ -90,18 +203,22 @@ export const BlockedUsersModal = ({ close }) => {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: "10px",
|
gap: "10px",
|
||||||
width: '100%',
|
width: "100%",
|
||||||
justifyContent: 'space-between'
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography>{key}</Typography>
|
<Typography>{addressesWithNames[key] || key}</Typography>
|
||||||
<Button
|
<Button
|
||||||
|
sx={{
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
size="small"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
try {
|
try {
|
||||||
await removeBlockFromList(key, undefined);
|
await removeBlockFromList(key, undefined);
|
||||||
setHasChanged(true);
|
setHasChanged(true);
|
||||||
setValue('')
|
setValue("");
|
||||||
fetchBlockedUsers();
|
fetchBlockedUsers();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -119,17 +236,19 @@ export const BlockedUsersModal = ({ close }) => {
|
|||||||
<>
|
<>
|
||||||
<Spacer height="20px" />
|
<Spacer height="20px" />
|
||||||
<DialogContentText id="alert-dialog-description">
|
<DialogContentText id="alert-dialog-description">
|
||||||
Blocked Users for QDN and Chat (names)
|
Blocked names for QDN
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
<Spacer height="10px" />
|
<Spacer height="10px" />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Box sx={{
|
<Box
|
||||||
display: 'flex',
|
sx={{
|
||||||
flexDirection: 'column',
|
display: "flex",
|
||||||
gap: '10px'
|
flexDirection: "column",
|
||||||
}}>
|
gap: "10px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
{Object.entries(blockedUsers?.names || {})?.map(([key, value]) => {
|
{Object.entries(blockedUsers?.names || {})?.map(([key, value]) => {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@ -137,12 +256,16 @@ export const BlockedUsersModal = ({ close }) => {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: "10px",
|
gap: "10px",
|
||||||
width: '100%',
|
width: "100%",
|
||||||
justifyContent: 'space-between'
|
justifyContent: "space-between",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography>{key}</Typography>
|
<Typography>{key}</Typography>
|
||||||
<Button
|
<Button
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
try {
|
try {
|
||||||
@ -175,16 +298,78 @@ export const BlockedUsersModal = ({ close }) => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={()=> {
|
onClick={() => {
|
||||||
if(hasChanged){
|
if (hasChanged) {
|
||||||
executeEvent('updateChatMessagesWithBlocks', true)
|
executeEvent("updateChatMessagesWithBlocks", true);
|
||||||
}
|
}
|
||||||
close()
|
setIsOpenBlockedModal(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
close
|
close
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
|
|
||||||
|
<Dialog
|
||||||
|
open={isShow}
|
||||||
|
aria-labelledby="alert-dialog-title"
|
||||||
|
aria-describedby="alert-dialog-description"
|
||||||
|
>
|
||||||
|
<DialogTitle id="alert-dialog-title">
|
||||||
|
{"Decide what to block"}
|
||||||
|
</DialogTitle>
|
||||||
|
<IconButton
|
||||||
|
onClick={onCancel}
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
right: 8,
|
||||||
|
top: 8,
|
||||||
|
color: 'white',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText id="alert-dialog-description">
|
||||||
|
Blocking {message?.userName || message?.userAddress}
|
||||||
|
</DialogContentText>
|
||||||
|
<Box sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '10px',
|
||||||
|
marginTop: '20px'
|
||||||
|
}}>
|
||||||
|
<InfoIcon sx={{
|
||||||
|
color: 'fff'
|
||||||
|
}}/> <Typography>Choose "block txs" or "all" to block chat messages </Typography>
|
||||||
|
</Box>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => {
|
||||||
|
onOk("address");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Block txs
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => {
|
||||||
|
onOk("name");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Block QDN data
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => {
|
||||||
|
onOk("both");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Block All
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -19,7 +19,8 @@ import React, {
|
|||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import BlockIcon from '@mui/icons-material/Block';
|
import PersonOffIcon from '@mui/icons-material/PersonOff';
|
||||||
|
|
||||||
import { WalletsAppWrapper } from "./WalletsAppWrapper";
|
import { WalletsAppWrapper } from "./WalletsAppWrapper";
|
||||||
|
|
||||||
import SettingsIcon from "@mui/icons-material/Settings";
|
import SettingsIcon from "@mui/icons-material/Settings";
|
||||||
@ -66,7 +67,7 @@ import HomeIcon from "@mui/icons-material/Home";
|
|||||||
import CloseIcon from "@mui/icons-material/Close";
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
|
|
||||||
import { ThingsToDoInitial } from "./ThingsToDoInitial";
|
import { ThingsToDoInitial } from "./ThingsToDoInitial";
|
||||||
import { GroupJoinRequests } from "./GroupJoinRequests";
|
import { GroupJoinRequests, requestQueueGroupJoinRequests } from "./GroupJoinRequests";
|
||||||
import { GroupForum } from "../Chat/GroupForum";
|
import { GroupForum } from "../Chat/GroupForum";
|
||||||
import { GroupInvites } from "./GroupInvites";
|
import { GroupInvites } from "./GroupInvites";
|
||||||
import {
|
import {
|
||||||
@ -99,7 +100,7 @@ import { formatEmailDate } from "./QMailMessages";
|
|||||||
import { useHandleMobileNativeBack } from "../../hooks/useHandleMobileNativeBack";
|
import { useHandleMobileNativeBack } from "../../hooks/useHandleMobileNativeBack";
|
||||||
import { AdminSpace } from "../Chat/AdminSpace";
|
import { AdminSpace } from "../Chat/AdminSpace";
|
||||||
import { useRecoilState, useSetRecoilState } from "recoil";
|
import { useRecoilState, useSetRecoilState } from "recoil";
|
||||||
import { addressInfoControllerAtom, groupsPropertiesAtom, lastEnteredGroupIdAtom, selectedGroupIdAtom } from "../../atoms/global";
|
import { addressInfoControllerAtom, groupsPropertiesAtom, isOpenBlockedModalAtom, lastEnteredGroupIdAtom, myGroupsWhereIAmAdminAtom, selectedGroupIdAtom } from "../../atoms/global";
|
||||||
import { sortArrayByTimestampAndGroupName } from "../../utils/time";
|
import { sortArrayByTimestampAndGroupName } from "../../utils/time";
|
||||||
import { BlockedUsersModal } from "./BlockedUsersModal";
|
import { BlockedUsersModal } from "./BlockedUsersModal";
|
||||||
import { GlobalTouchMenu } from "../GlobalTouchMenu";
|
import { GlobalTouchMenu } from "../GlobalTouchMenu";
|
||||||
@ -329,16 +330,17 @@ export const getDataPublishesFunc = async (groupId, type) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export async function getNameInfo(address: string) {
|
export async function getNameInfo(address: string) {
|
||||||
const response = await fetch(`${getBaseApiReact()}/names/address/` + address);
|
const response = await fetch(`${getBaseApiReact()}/names/primary/` + address);
|
||||||
const nameData = await response.json();
|
const nameData = await response.json();
|
||||||
|
|
||||||
if (nameData?.length > 0) {
|
if (nameData?.name) {
|
||||||
return nameData[0]?.name;
|
return nameData?.name;
|
||||||
} else {
|
} else {
|
||||||
return "";
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const getGroupAdmins = async (groupNumber: number) => {
|
export const getGroupAdmins = async (groupNumber: number) => {
|
||||||
// const validApi = await findUsableApi();
|
// const validApi = await findUsableApi();
|
||||||
|
|
||||||
@ -470,6 +472,9 @@ export const Group = ({
|
|||||||
const { setMemberGroups, memberGroups, rootHeight, isRunningPublicNode } = useContext(MyContext);
|
const { setMemberGroups, memberGroups, rootHeight, isRunningPublicNode } = useContext(MyContext);
|
||||||
const lastGroupNotification = useRef<null | number>(null);
|
const lastGroupNotification = useRef<null | number>(null);
|
||||||
const [timestampEnterData, setTimestampEnterData] = useState({});
|
const [timestampEnterData, setTimestampEnterData] = useState({});
|
||||||
|
const groupsPropertiesRef = useRef({});
|
||||||
|
const setMyGroupsWhereIAmAdmin = useSetRecoilState(myGroupsWhereIAmAdminAtom);
|
||||||
|
|
||||||
const [chatMode, setChatMode] = useState("groups");
|
const [chatMode, setChatMode] = useState("groups");
|
||||||
const [newChat, setNewChat] = useState(false);
|
const [newChat, setNewChat] = useState(false);
|
||||||
const [openSnack, setOpenSnack] = React.useState(false);
|
const [openSnack, setOpenSnack] = React.useState(false);
|
||||||
@ -483,6 +488,8 @@ export const Group = ({
|
|||||||
const [groupAnnouncements, setGroupAnnouncements] = React.useState({});
|
const [groupAnnouncements, setGroupAnnouncements] = React.useState({});
|
||||||
const [defaultThread, setDefaultThread] = React.useState(null);
|
const [defaultThread, setDefaultThread] = React.useState(null);
|
||||||
const [isOpenDrawer, setIsOpenDrawer] = React.useState(false);
|
const [isOpenDrawer, setIsOpenDrawer] = React.useState(false);
|
||||||
|
const setIsOpenBlockedUserModal = useSetRecoilState(isOpenBlockedModalAtom)
|
||||||
|
|
||||||
const [hideCommonKeyPopup, setHideCommonKeyPopup] = React.useState(false);
|
const [hideCommonKeyPopup, setHideCommonKeyPopup] = React.useState(false);
|
||||||
const [isLoadingGroupMessage, setIsLoadingGroupMessage] = React.useState("");
|
const [isLoadingGroupMessage, setIsLoadingGroupMessage] = React.useState("");
|
||||||
const [drawerMode, setDrawerMode] = React.useState("groups");
|
const [drawerMode, setDrawerMode] = React.useState("groups");
|
||||||
@ -507,7 +514,6 @@ export const Group = ({
|
|||||||
const [isForceShowCreationKeyPopup, setIsForceShowCreationKeyPopup] = useState(false)
|
const [isForceShowCreationKeyPopup, setIsForceShowCreationKeyPopup] = useState(false)
|
||||||
const [groupsProperties, setGroupsProperties] = useRecoilState(groupsPropertiesAtom)
|
const [groupsProperties, setGroupsProperties] = useRecoilState(groupsPropertiesAtom)
|
||||||
const setUserInfoForLevels = useSetRecoilState(addressInfoControllerAtom);
|
const setUserInfoForLevels = useSetRecoilState(addressInfoControllerAtom);
|
||||||
const [isOpenBlockedUserModal, setIsOpenBlockedUserModal] = React.useState(false);
|
|
||||||
const setLastEnteredGroupIdAtom = useSetRecoilState(lastEnteredGroupIdAtom)
|
const setLastEnteredGroupIdAtom = useSetRecoilState(lastEnteredGroupIdAtom)
|
||||||
const isPrivate = useMemo(()=> {
|
const isPrivate = useMemo(()=> {
|
||||||
if(selectedGroup?.groupId === '0') return false
|
if(selectedGroup?.groupId === '0') return false
|
||||||
@ -533,6 +539,9 @@ export const Group = ({
|
|||||||
useEffect(()=> {
|
useEffect(()=> {
|
||||||
timestampEnterDataRef.current = timestampEnterData
|
timestampEnterDataRef.current = timestampEnterData
|
||||||
}, [timestampEnterData])
|
}, [timestampEnterData])
|
||||||
|
useEffect(() => {
|
||||||
|
groupsPropertiesRef.current = groupsProperties;
|
||||||
|
}, [groupsProperties]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
isFocusedRef.current = isFocused;
|
isFocusedRef.current = isFocused;
|
||||||
@ -572,7 +581,7 @@ export const Group = ({
|
|||||||
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("error", error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -839,7 +848,7 @@ export const Group = ({
|
|||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`${getBaseApiReact()}/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${
|
`${getBaseApiReact()}/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${
|
||||||
publish.identifier
|
publish.identifier
|
||||||
}?encoding=base64`
|
}?encoding=base64&rebuild=true`
|
||||||
);
|
);
|
||||||
data = await res.text();
|
data = await res.text();
|
||||||
}
|
}
|
||||||
@ -985,15 +994,50 @@ export const Group = ({
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const getGroupsWhereIAmAMember = useCallback(async (groups) => {
|
||||||
|
try {
|
||||||
|
let groupsAsAdmin = [];
|
||||||
|
const getAllGroupsAsAdmin = groups
|
||||||
|
.filter((item) => item.groupId !== '0')
|
||||||
|
.map(async (group) => {
|
||||||
|
const isAdminResponse = await requestQueueGroupJoinRequests.enqueue(
|
||||||
|
() => {
|
||||||
|
return fetch(
|
||||||
|
`${getBaseApiReact()}/groups/members/${group.groupId}?limit=0&onlyAdmins=true`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const isAdminData = await isAdminResponse.json();
|
||||||
|
|
||||||
useEffect(()=> {
|
const findMyself = isAdminData?.members?.find(
|
||||||
if(!myAddress) return
|
(member) => member.member === myAddress
|
||||||
if(areKeysEqual(groups?.map((grp)=> grp?.groupId), Object.keys(groupsProperties))){
|
);
|
||||||
} else {
|
|
||||||
getGroupsProperties(myAddress)
|
if (findMyself) {
|
||||||
|
groupsAsAdmin.push(group);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(getAllGroupsAsAdmin);
|
||||||
|
setMyGroupsWhereIAmAdmin(groupsAsAdmin);
|
||||||
|
} catch (error) {
|
||||||
|
console.error();
|
||||||
}
|
}
|
||||||
}, [groups, myAddress])
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!myAddress) return;
|
||||||
|
if (
|
||||||
|
!areKeysEqual(
|
||||||
|
groups?.map((grp) => grp?.groupId),
|
||||||
|
Object.keys(groupsPropertiesRef.current)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
getGroupsProperties(myAddress);
|
||||||
|
getGroupsWhereIAmAMember(groups);
|
||||||
|
}
|
||||||
|
}, [groups, myAddress]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Handler function for incoming messages
|
// Handler function for incoming messages
|
||||||
@ -1905,6 +1949,7 @@ export const Group = ({
|
|||||||
width: "100%",
|
width: "100%",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
padding: "10px",
|
padding: "10px",
|
||||||
|
gap: '10px'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CustomButton
|
<CustomButton
|
||||||
@ -1922,6 +1967,23 @@ export const Group = ({
|
|||||||
/>
|
/>
|
||||||
New Chat
|
New Chat
|
||||||
</CustomButton>
|
</CustomButton>
|
||||||
|
{!isRunningPublicNode && (
|
||||||
|
<CustomButton
|
||||||
|
onClick={() => {
|
||||||
|
setIsOpenBlockedUserModal(true);
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
minWidth: 'unset',
|
||||||
|
padding: '10px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PersonOffIcon
|
||||||
|
sx={{
|
||||||
|
color: 'white',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</CustomButton>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -2159,7 +2221,7 @@ export const Group = ({
|
|||||||
padding: '10px'
|
padding: '10px'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<BlockIcon
|
<PersonOffIcon
|
||||||
sx={{
|
sx={{
|
||||||
color: "white",
|
color: "white",
|
||||||
}}
|
}}
|
||||||
@ -2656,11 +2718,9 @@ export const Group = ({
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{isOpenBlockedUserModal && (
|
|
||||||
<BlockedUsersModal close={()=> {
|
<BlockedUsersModal />
|
||||||
setIsOpenBlockedUserModal(false)
|
|
||||||
}} />
|
|
||||||
)}
|
|
||||||
{selectedDirect && !newChat && (
|
{selectedDirect && !newChat && (
|
||||||
<>
|
<>
|
||||||
<Box
|
<Box
|
||||||
@ -2756,7 +2816,7 @@ export const Group = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
<Apps mode={appsMode} setMode={setAppsMode} show={mobileViewMode === "apps"} myName={userInfo?.name} />
|
<Apps mode={appsMode} setMode={setAppsMode} show={mobileViewMode === "apps"} myName={userInfo?.name} myAddress={userInfo?.address} />
|
||||||
)}
|
)}
|
||||||
{!isMobile && (
|
{!isMobile && (
|
||||||
<AppsDesktop toggleSideViewGroups={toggleSideViewGroups} toggleSideViewDirects={toggleSideViewDirects} goToHome={goToHome} mode={appsMode} setMode={setAppsMode} setDesktopSideView={setDesktopSideView} hasUnreadDirects={directChatHasUnread} show={desktopViewMode === "apps"} myName={userInfo?.name} isGroups={isOpenSideViewGroups}
|
<AppsDesktop toggleSideViewGroups={toggleSideViewGroups} toggleSideViewDirects={toggleSideViewDirects} goToHome={goToHome} mode={appsMode} setMode={setAppsMode} setDesktopSideView={setDesktopSideView} hasUnreadDirects={directChatHasUnread} show={desktopViewMode === "apps"} myName={userInfo?.name} isGroups={isOpenSideViewGroups}
|
||||||
|
@ -17,7 +17,7 @@ import { CustomLoader } from "../../common/CustomLoader";
|
|||||||
import { getBaseApi } from "../../background";
|
import { getBaseApi } from "../../background";
|
||||||
import { MyContext, getBaseApiReact, isMobile } from "../../App";
|
import { MyContext, getBaseApiReact, isMobile } from "../../App";
|
||||||
import { myGroupsWhereIAmAdminAtom } from "../../atoms/global";
|
import { myGroupsWhereIAmAdminAtom } from "../../atoms/global";
|
||||||
import { useSetRecoilState } from "recoil";
|
import { useRecoilState, useSetRecoilState } from "recoil";
|
||||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||||
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
|
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
|
||||||
export const requestQueueGroupJoinRequests = new RequestQueueWithPromise(2)
|
export const requestQueueGroupJoinRequests = new RequestQueueWithPromise(2)
|
||||||
@ -28,66 +28,44 @@ export const GroupJoinRequests = ({ myAddress, groups, setOpenManageMembers, get
|
|||||||
const [groupsWithJoinRequests, setGroupsWithJoinRequests] = React.useState([])
|
const [groupsWithJoinRequests, setGroupsWithJoinRequests] = React.useState([])
|
||||||
const [loading, setLoading] = React.useState(true)
|
const [loading, setLoading] = React.useState(true)
|
||||||
const {txList, setTxList} = React.useContext(MyContext)
|
const {txList, setTxList} = React.useContext(MyContext)
|
||||||
const setMyGroupsWhereIAmAdmin = useSetRecoilState(
|
const [myGroupsWhereIAmAdmin] = useRecoilState(myGroupsWhereIAmAdminAtom);
|
||||||
myGroupsWhereIAmAdminAtom
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
const getJoinRequests = async ()=> {
|
|
||||||
|
const getJoinRequests = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true)
|
setLoading(true);
|
||||||
|
const res = await Promise.all(
|
||||||
let groupsAsAdmin = []
|
myGroupsWhereIAmAdmin.map(async (group) => {
|
||||||
const getAllGroupsAsAdmin = groups.filter((item)=> item.groupId !== '0').map(async (group)=> {
|
const joinRequestResponse =
|
||||||
|
await requestQueueGroupJoinRequests.enqueue(() => {
|
||||||
const isAdminResponse = await requestQueueGroupJoinRequests.enqueue(()=> {
|
return fetch(
|
||||||
return fetch(
|
`${getBaseApiReact()}/groups/joinrequests/${group.groupId}`
|
||||||
`${getBaseApiReact()}/groups/members/${group.groupId}?limit=0&onlyAdmins=true`
|
);
|
||||||
);
|
});
|
||||||
})
|
|
||||||
const isAdminData = await isAdminResponse.json()
|
|
||||||
|
|
||||||
|
|
||||||
const findMyself = isAdminData?.members?.find((member)=> member.member === myAddress)
|
const joinRequestData = await joinRequestResponse.json();
|
||||||
|
return {
|
||||||
if(findMyself){
|
group,
|
||||||
groupsAsAdmin.push(group)
|
data: joinRequestData,
|
||||||
}
|
};
|
||||||
return true
|
})
|
||||||
})
|
);
|
||||||
|
setGroupsWithJoinRequests(res);
|
||||||
|
|
||||||
await Promise.all(getAllGroupsAsAdmin)
|
|
||||||
setMyGroupsWhereIAmAdmin(groupsAsAdmin)
|
|
||||||
const res = await Promise.all(groupsAsAdmin.map(async (group)=> {
|
|
||||||
|
|
||||||
const joinRequestResponse = await requestQueueGroupJoinRequests.enqueue(()=> {
|
|
||||||
return fetch(
|
|
||||||
`${getBaseApiReact()}/groups/joinrequests/${group.groupId}`
|
|
||||||
);
|
|
||||||
})
|
|
||||||
|
|
||||||
const joinRequestData = await joinRequestResponse.json()
|
|
||||||
return {
|
|
||||||
group,
|
|
||||||
data: joinRequestData
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
setGroupsWithJoinRequests(res)
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (myAddress && groups.length > 0) {
|
if (myAddress && myGroupsWhereIAmAdmin.length > 0) {
|
||||||
getJoinRequests()
|
getJoinRequests();
|
||||||
} else {
|
} else {
|
||||||
setLoading(false)
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [myAddress, groups]);
|
}, [myAddress, myGroupsWhereIAmAdmin]);
|
||||||
|
|
||||||
const filteredJoinRequests = React.useMemo(()=> {
|
const filteredJoinRequests = React.useMemo(()=> {
|
||||||
return groupsWithJoinRequests.map((group)=> {
|
return groupsWithJoinRequests.map((group)=> {
|
||||||
|
@ -226,6 +226,7 @@ export const ListOfGroupPromotions = () => {
|
|||||||
data: data,
|
data: data,
|
||||||
identifier: identifier,
|
identifier: identifier,
|
||||||
service: "DOCUMENT",
|
service: "DOCUMENT",
|
||||||
|
uploadType: 'base64',
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (!response?.error) {
|
if (!response?.error) {
|
||||||
|
@ -54,35 +54,19 @@ export const NewUsersCTA = ({ balance }) => {
|
|||||||
textDecoration: "underline",
|
textDecoration: "underline",
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (chrome && chrome.tabs) {
|
window.open("https://link.qortal.dev/support", '_system')
|
||||||
chrome.tabs.create({ url: "https://link.qortal.dev/telegram-invite" }, (tab) => {
|
|
||||||
if (chrome.runtime.lastError) {
|
|
||||||
console.error("Error opening tab:", chrome.runtime.lastError);
|
|
||||||
} else {
|
|
||||||
console.log("Tab opened successfully:", tab);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Telegram
|
Nextcloud
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
<ButtonBase
|
<ButtonBase
|
||||||
sx={{
|
sx={{
|
||||||
textDecoration: "underline",
|
textDecoration: "underline",
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (chrome && chrome.tabs) {
|
window.open("https://link.qortal.dev/discord-invite", '_system')
|
||||||
chrome.tabs.create({ url: "https://link.qortal.dev/discord-invite" }, (tab) => {
|
|
||||||
if (chrome.runtime.lastError) {
|
|
||||||
console.error("Error opening tab:", chrome.runtime.lastError);
|
|
||||||
} else {
|
|
||||||
console.log("Tab opened successfully:", tab);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
|
|
||||||
>
|
>
|
||||||
Discord
|
Discord
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
|
@ -67,6 +67,7 @@ const [isLoading, setIsLoading] = useState(false)
|
|||||||
data: avatarBase64,
|
data: avatarBase64,
|
||||||
identifier: "qortal_avatar",
|
identifier: "qortal_avatar",
|
||||||
service: "THUMBNAIL",
|
service: "THUMBNAIL",
|
||||||
|
uploadType: 'base64',
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (!response?.error) {
|
if (!response?.error) {
|
||||||
|
@ -89,14 +89,14 @@ export const Minting = ({
|
|||||||
const getName = async (address) => {
|
const getName = async (address) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${getBaseApiReact()}/names/address/${address}`
|
`${getBaseApiReact()}/names/primary/${address}`
|
||||||
);
|
);
|
||||||
const nameData = await response.json();
|
const nameData = await response.json();
|
||||||
if (nameData?.length > 0) {
|
if (nameData?.name) {
|
||||||
setNames((prev) => {
|
setNames((prev) => {
|
||||||
return {
|
return {
|
||||||
...prev,
|
...prev,
|
||||||
[address]: nameData[0].name,
|
[address]: nameData?.name,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -108,7 +108,7 @@ export const Minting = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// error
|
console.log(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -155,6 +155,7 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
|
|||||||
data: encryptData,
|
data: encryptData,
|
||||||
identifier: "ext_saved_settings",
|
identifier: "ext_saved_settings",
|
||||||
service: "DOCUMENT_PRIVATE",
|
service: "DOCUMENT_PRIVATE",
|
||||||
|
uploadType: 'base64',
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (!response?.error) {
|
if (!response?.error) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { DrawerUserLookup } from "../Drawer/DrawerUserLookup";
|
import { DrawerUserLookup } from "../Drawer/DrawerUserLookup";
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
@ -16,6 +16,7 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
Table,
|
Table,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
|
Autocomplete,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { getAddressInfo, getNameOrAddress } from "../../background";
|
import { getAddressInfo, getNameOrAddress } from "../../background";
|
||||||
import { getBaseApiReact } from "../../App";
|
import { getBaseApiReact } from "../../App";
|
||||||
@ -26,6 +27,8 @@ import { formatTimestamp } from "../../utils/time";
|
|||||||
import CloseFullscreenIcon from '@mui/icons-material/CloseFullscreen';
|
import CloseFullscreenIcon from '@mui/icons-material/CloseFullscreen';
|
||||||
import SearchIcon from '@mui/icons-material/Search';
|
import SearchIcon from '@mui/icons-material/Search';
|
||||||
import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from "../../utils/events";
|
import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from "../../utils/events";
|
||||||
|
import { useNameSearch } from "../../hooks/useNameSearch";
|
||||||
|
import { validateAddress } from "../../utils/validateAddress";
|
||||||
|
|
||||||
function formatAddress(str) {
|
function formatAddress(str) {
|
||||||
if (str.length <= 12) return str;
|
if (str.length <= 12) return str;
|
||||||
@ -38,6 +41,13 @@ function formatAddress(str) {
|
|||||||
|
|
||||||
export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => {
|
export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => {
|
||||||
const [nameOrAddress, setNameOrAddress] = useState("");
|
const [nameOrAddress, setNameOrAddress] = useState("");
|
||||||
|
const [inputValue, setInputValue] = useState('');
|
||||||
|
const { results, isLoading } = useNameSearch(inputValue);
|
||||||
|
const options = useMemo(() => {
|
||||||
|
const isAddress = validateAddress(inputValue);
|
||||||
|
if (isAddress) return [inputValue];
|
||||||
|
return results?.map((item) => item.name);
|
||||||
|
}, [results, inputValue]);
|
||||||
const [errorMessage, setErrorMessage] = useState("");
|
const [errorMessage, setErrorMessage] = useState("");
|
||||||
const [addressInfo, setAddressInfo] = useState(null);
|
const [addressInfo, setAddressInfo] = useState(null);
|
||||||
const [isLoadingUser, setIsLoadingUser] = useState(false);
|
const [isLoadingUser, setIsLoadingUser] = useState(false);
|
||||||
@ -58,7 +68,10 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => {
|
|||||||
if (!addressInfoRes?.publicKey) {
|
if (!addressInfoRes?.publicKey) {
|
||||||
throw new Error("Address does not exist on blockchain");
|
throw new Error("Address does not exist on blockchain");
|
||||||
}
|
}
|
||||||
const name = await getNameInfo(owner);
|
const isAddress = validateAddress(messageAddressOrName);
|
||||||
|
const name = !isAddress
|
||||||
|
? messageAddressOrName
|
||||||
|
: await getNameInfo(owner);
|
||||||
const balanceRes = await fetch(
|
const balanceRes = await fetch(
|
||||||
`${getBaseApiReact()}/addresses/balance/${owner}`
|
`${getBaseApiReact()}/addresses/balance/${owner}`
|
||||||
);
|
);
|
||||||
@ -106,6 +119,7 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => {
|
|||||||
setIsOpenDrawerLookup(false)
|
setIsOpenDrawerLookup(false)
|
||||||
setNameOrAddress('')
|
setNameOrAddress('')
|
||||||
setErrorMessage('')
|
setErrorMessage('')
|
||||||
|
setInputValue('');
|
||||||
setPayments([])
|
setPayments([])
|
||||||
setIsLoadingUser(false)
|
setIsLoadingUser(false)
|
||||||
setIsLoadingPayments(false)
|
setIsLoadingPayments(false)
|
||||||
@ -134,27 +148,66 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => {
|
|||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TextField
|
<Autocomplete
|
||||||
autoFocus
|
|
||||||
value={nameOrAddress}
|
value={nameOrAddress}
|
||||||
onChange={(e) => setNameOrAddress(e.target.value)}
|
onChange={(event: any, newValue: string | null) => {
|
||||||
size="small"
|
if (!newValue) {
|
||||||
placeholder="Address or Name"
|
setNameOrAddress('');
|
||||||
autoComplete="off"
|
return;
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === "Enter" && nameOrAddress) {
|
|
||||||
lookupFunc();
|
|
||||||
}
|
}
|
||||||
|
setNameOrAddress(newValue);
|
||||||
|
lookupFunc(newValue);
|
||||||
}}
|
}}
|
||||||
|
inputValue={inputValue}
|
||||||
|
onInputChange={(event, newInputValue) => {
|
||||||
|
setInputValue(newInputValue);
|
||||||
|
}}
|
||||||
|
id="controllable-states-demo"
|
||||||
|
loading={isLoading}
|
||||||
|
options={options}
|
||||||
|
sx={{ width: 300 }}
|
||||||
|
size="small"
|
||||||
|
renderInput={(params) => (
|
||||||
|
<TextField
|
||||||
|
autoFocus
|
||||||
|
autoComplete="off"
|
||||||
|
{...params}
|
||||||
|
label="Address or Name"
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' && inputValue) {
|
||||||
|
lookupFunc(inputValue);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
|
||||||
|
sx={{
|
||||||
|
'& .MuiOutlinedInput-root': {
|
||||||
|
'& fieldset': {
|
||||||
|
borderColor: 'white',
|
||||||
|
},
|
||||||
|
'&:hover fieldset': {
|
||||||
|
borderColor: 'white',
|
||||||
|
},
|
||||||
|
'&.Mui-focused fieldset': {
|
||||||
|
borderColor: 'white',
|
||||||
|
},
|
||||||
|
'& input': {
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'& .MuiInputLabel-root': {
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
'& .MuiInputLabel-root.Mui-focused': {
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
'& .MuiAutocomplete-endAdornment svg': {
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
<ButtonBase onClick={()=> {
|
|
||||||
lookupFunc();
|
|
||||||
}} >
|
|
||||||
<SearchIcon sx={{
|
|
||||||
color: 'white',
|
|
||||||
marginRight: '20px'
|
|
||||||
}} />
|
|
||||||
</ButtonBase>
|
|
||||||
<ButtonBase sx={{
|
<ButtonBase sx={{
|
||||||
marginLeft: 'auto',
|
marginLeft: 'auto',
|
||||||
|
|
||||||
|
@ -167,12 +167,9 @@ useEffect(()=> {
|
|||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
if(isAlreadyBlocked === true){
|
executeEvent("blockUserFromOutside", {
|
||||||
await removeBlockFromList(address, name)
|
user: address
|
||||||
} else if(isAlreadyBlocked === false) {
|
})
|
||||||
await addToBlockList(address, name)
|
|
||||||
}
|
|
||||||
executeEvent('updateChatMessagesWithBlocks', true)
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -173,3 +173,6 @@ const STATIC_BCRYPT_SALT = `$${BCRYPT_VERSION}$${BCRYPT_ROUNDS}$IxVE941tXVUD4cW0
|
|||||||
const KDF_THREADS = 16
|
const KDF_THREADS = 16
|
||||||
|
|
||||||
export { TX_TYPES, ERROR_CODES, QORT_DECIMALS, PROXY_URL, STATIC_SALT, ADDRESS_VERSION, KDF_THREADS, STATIC_BCRYPT_SALT, CHAT_REFERENCE_FEATURE_TRIGGER_TIMESTAMP, DYNAMIC_FEE_TIMESTAMP }
|
export { TX_TYPES, ERROR_CODES, QORT_DECIMALS, PROXY_URL, STATIC_SALT, ADDRESS_VERSION, KDF_THREADS, STATIC_BCRYPT_SALT, CHAT_REFERENCE_FEATURE_TRIGGER_TIMESTAMP, DYNAMIC_FEE_TIMESTAMP }
|
||||||
|
|
||||||
|
export const MAX_SIZE_PUBLIC_NODE = 500 * 1024 * 1024; // 500mb
|
||||||
|
export const MAX_SIZE_PUBLISH = 2000 * 1024 * 1024; // 2GB
|
55
src/hooks/useNameSearch.tsx
Normal file
55
src/hooks/useNameSearch.tsx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { getBaseApiReact } from '../App';
|
||||||
|
|
||||||
|
interface NameListItem {
|
||||||
|
name: string;
|
||||||
|
address: string;
|
||||||
|
}
|
||||||
|
export const useNameSearch = (value: string, limit = 20) => {
|
||||||
|
const [nameList, setNameList] = useState<NameListItem[]>([]);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const checkIfNameExisits = useCallback(
|
||||||
|
async (name: string, listLimit: number) => {
|
||||||
|
try {
|
||||||
|
if (!name) {
|
||||||
|
setNameList([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch(
|
||||||
|
`${getBaseApiReact()}/names/search?query=${name}&prefix=true&limit=${listLimit}`
|
||||||
|
);
|
||||||
|
const data = await res.json();
|
||||||
|
setNameList(
|
||||||
|
data?.map((item: any) => {
|
||||||
|
return {
|
||||||
|
name: item.name,
|
||||||
|
address: item.owner,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
// Debounce logic
|
||||||
|
useEffect(() => {
|
||||||
|
setIsLoading(true);
|
||||||
|
const handler = setTimeout(() => {
|
||||||
|
checkIfNameExisits(value, limit);
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
// Cleanup timeout if searchValue changes before the timeout completes
|
||||||
|
return () => {
|
||||||
|
clearTimeout(handler);
|
||||||
|
};
|
||||||
|
}, [value, limit, checkIfNameExisits]);
|
||||||
|
return {
|
||||||
|
isLoading,
|
||||||
|
results: nameList,
|
||||||
|
};
|
||||||
|
};
|
11
src/hooks/useSortedMyNames.tsx
Normal file
11
src/hooks/useSortedMyNames.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
export function useSortedMyNames(names, myName) {
|
||||||
|
return useMemo(() => {
|
||||||
|
return [...names].sort((a, b) => {
|
||||||
|
if (a === myName) return -1;
|
||||||
|
if (b === myName) return 1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}, [names, myName]);
|
||||||
|
}
|
@ -24,7 +24,7 @@ window.addEventListener("message", (event) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const sendMessageBackground = (action, data = {}, timeout = 180000, isExtension, appInfo, skipAuth) => {
|
export const sendMessageBackground = (action, data = {}, timeout = 600000, isExtension, appInfo, skipAuth) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const requestId = generateRequestId(); // Unique ID for each request
|
const requestId = generateRequestId(); // Unique ID for each request
|
||||||
callbackMap.set(requestId, { resolve, reject }); // Store both resolve and reject callbacks
|
callbackMap.set(requestId, { resolve, reject }); // Store both resolve and reject callbacks
|
||||||
|
@ -1,265 +1,474 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
|
|
||||||
import { Buffer } from "buffer"
|
import { Buffer } from 'buffer';
|
||||||
import Base58 from "../../deps/Base58"
|
import Base58 from '../../deps/Base58';
|
||||||
import nacl from "../../deps/nacl-fast"
|
import nacl from '../../deps/nacl-fast';
|
||||||
import utils from "../../utils/utils"
|
import utils from '../../utils/utils';
|
||||||
import { createEndpoint, getBaseApi } from "../../background";
|
import { createEndpoint, getBaseApi } from '../../background';
|
||||||
import { getData } from "../../utils/chromeStorage";
|
import { getData } from '../../utils/chromeStorage';
|
||||||
|
import { executeEvent } from '../../utils/events';
|
||||||
|
|
||||||
export async function reusableGet(endpoint){
|
export async function reusableGet(endpoint) {
|
||||||
const validApi = await getBaseApi();
|
const validApi = await getBaseApi();
|
||||||
|
|
||||||
const response = await fetch(validApi + endpoint);
|
const response = await fetch(validApi + endpoint);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
return data
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function reusablePost(endpoint, _body){
|
async function reusablePost(endpoint, _body) {
|
||||||
// const validApi = await findUsableApi();
|
// const validApi = await findUsableApi();
|
||||||
const url = await createEndpoint(endpoint)
|
const url = await createEndpoint(endpoint);
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: _body
|
body: _body,
|
||||||
});
|
});
|
||||||
let data
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
throw new Error(errorText);
|
||||||
|
}
|
||||||
|
let data;
|
||||||
try {
|
try {
|
||||||
data = await response.clone().json()
|
data = await response.clone().json();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
data = await response.text()
|
data = await response.text();
|
||||||
}
|
}
|
||||||
return data
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reusablePostStream(endpoint, _body) {
|
||||||
|
const url = await createEndpoint(endpoint);
|
||||||
|
|
||||||
|
const headers = {};
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers,
|
||||||
|
body: _body,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response; // return the actual response so calling code can use response.ok
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadChunkWithRetry(endpoint, formData, index, maxRetries = 3) {
|
||||||
|
let attempt = 0;
|
||||||
|
while (attempt < maxRetries) {
|
||||||
|
try {
|
||||||
|
const response = await reusablePostStream(endpoint, formData);
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
throw new Error(errorText);
|
||||||
|
}
|
||||||
|
return; // Success
|
||||||
|
} catch (err) {
|
||||||
|
attempt++;
|
||||||
|
console.warn(
|
||||||
|
`Chunk ${index} failed (attempt ${attempt}): ${err.message}`
|
||||||
|
);
|
||||||
|
if (attempt >= maxRetries) {
|
||||||
|
throw new Error(`Chunk ${index} failed after ${maxRetries} attempts`);
|
||||||
|
}
|
||||||
|
// Wait 10 seconds before next retry
|
||||||
|
await new Promise((res) => setTimeout(res, 25_000));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resuablePostRetry(
|
||||||
|
endpoint,
|
||||||
|
body,
|
||||||
|
maxRetries = 3,
|
||||||
|
appInfo,
|
||||||
|
resourceInfo
|
||||||
|
) {
|
||||||
|
let attempt = 0;
|
||||||
|
while (attempt < maxRetries) {
|
||||||
|
try {
|
||||||
|
const response = await reusablePost(endpoint, body);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (err) {
|
||||||
|
attempt++;
|
||||||
|
if (attempt >= maxRetries) {
|
||||||
|
throw new Error(
|
||||||
|
err instanceof Error
|
||||||
|
? err?.message || `Failed to make request`
|
||||||
|
: `Failed to make request`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (appInfo?.tabId && resourceInfo) {
|
||||||
|
executeEvent('receiveChunks', {
|
||||||
|
tabId: appInfo.tabId,
|
||||||
|
publishLocation: {
|
||||||
|
name: resourceInfo?.name,
|
||||||
|
identifier: resourceInfo?.identifier,
|
||||||
|
service: resourceInfo?.service,
|
||||||
|
},
|
||||||
|
retry: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Wait 10 seconds before next retry
|
||||||
|
await new Promise((res) => setTimeout(res, 25_000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function getKeyPair() {
|
async function getKeyPair() {
|
||||||
const res = await getData<any>("keyPair").catch(() => null);
|
const res = await getData<any>('keyPair').catch(() => null);
|
||||||
if (res) {
|
if (res) {
|
||||||
return res
|
return res;
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Wallet not authenticated");
|
throw new Error('Wallet not authenticated');
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const publishData = async ({
|
export const publishData = async ({
|
||||||
registeredName,
|
registeredName,
|
||||||
file,
|
data,
|
||||||
service,
|
service,
|
||||||
identifier,
|
identifier,
|
||||||
uploadType,
|
uploadType,
|
||||||
isBase64,
|
filename,
|
||||||
filename,
|
withFee,
|
||||||
withFee,
|
title,
|
||||||
title,
|
description,
|
||||||
description,
|
category,
|
||||||
category,
|
tag1,
|
||||||
tag1,
|
tag2,
|
||||||
tag2,
|
tag3,
|
||||||
tag3,
|
tag4,
|
||||||
tag4,
|
tag5,
|
||||||
tag5,
|
feeAmount,
|
||||||
feeAmount
|
appInfo
|
||||||
}: any) => {
|
}: any) => {
|
||||||
|
const validateName = async (receiverName: string) => {
|
||||||
const validateName = async (receiverName: string) => {
|
return await reusableGet(`/names/${receiverName}`);
|
||||||
return await reusableGet(`/names/${receiverName}`)
|
};
|
||||||
}
|
|
||||||
|
|
||||||
const convertBytesForSigning = async (transactionBytesBase58: string) => {
|
const convertBytesForSigning = async (transactionBytesBase58: string) => {
|
||||||
return await reusablePost('/transactions/convert', transactionBytesBase58)
|
return await resuablePostRetry(
|
||||||
}
|
'/transactions/convert',
|
||||||
|
transactionBytesBase58,
|
||||||
|
3,
|
||||||
|
appInfo,
|
||||||
|
{ identifier, name: registeredName, service }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const getArbitraryFee = async () => {
|
const getArbitraryFee = async () => {
|
||||||
const timestamp = Date.now()
|
const timestamp = Date.now();
|
||||||
|
|
||||||
let fee = await reusableGet(`/transactions/unitfee?txType=ARBITRARY×tamp=${timestamp}`)
|
let fee = await reusableGet(
|
||||||
|
`/transactions/unitfee?txType=ARBITRARY×tamp=${timestamp}`
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
timestamp,
|
timestamp,
|
||||||
fee: Number(fee),
|
fee: Number(fee),
|
||||||
feeToShow: (Number(fee) / 1e8).toFixed(8)
|
feeToShow: (Number(fee) / 1e8).toFixed(8),
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const signArbitraryWithFee = (arbitraryBytesBase58, arbitraryBytesForSigningBase58, keyPair) => {
|
const signArbitraryWithFee = (
|
||||||
if (!arbitraryBytesBase58) {
|
arbitraryBytesBase58,
|
||||||
throw new Error('ArbitraryBytesBase58 not defined')
|
arbitraryBytesForSigningBase58,
|
||||||
}
|
keyPair
|
||||||
|
) => {
|
||||||
if (!keyPair) {
|
if (!arbitraryBytesBase58) {
|
||||||
throw new Error('keyPair not defined')
|
throw new Error('ArbitraryBytesBase58 not defined');
|
||||||
}
|
|
||||||
|
|
||||||
const arbitraryBytes = Base58.decode(arbitraryBytesBase58)
|
|
||||||
const _arbitraryBytesBuffer = Object.keys(arbitraryBytes).map(function (key) { return arbitraryBytes[key]; })
|
|
||||||
const arbitraryBytesBuffer = new Uint8Array(_arbitraryBytesBuffer)
|
|
||||||
const arbitraryBytesForSigning = Base58.decode(arbitraryBytesForSigningBase58)
|
|
||||||
const _arbitraryBytesForSigningBuffer = Object.keys(arbitraryBytesForSigning).map(function (key) { return arbitraryBytesForSigning[key]; })
|
|
||||||
const arbitraryBytesForSigningBuffer = new Uint8Array(_arbitraryBytesForSigningBuffer)
|
|
||||||
const signature = nacl.sign.detached(arbitraryBytesForSigningBuffer, keyPair.privateKey)
|
|
||||||
|
|
||||||
return utils.appendBuffer(arbitraryBytesBuffer, signature)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const processTransactionVersion2 = async (bytes) => {
|
if (!keyPair) {
|
||||||
|
throw new Error('keyPair not defined');
|
||||||
|
}
|
||||||
|
|
||||||
return await reusablePost('/transactions/process?apiVersion=2', Base58.encode(bytes))
|
const arbitraryBytes = Base58.decode(arbitraryBytesBase58);
|
||||||
}
|
const _arbitraryBytesBuffer = Object.keys(arbitraryBytes).map(
|
||||||
|
function (key) {
|
||||||
|
return arbitraryBytes[key];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const arbitraryBytesBuffer = new Uint8Array(_arbitraryBytesBuffer);
|
||||||
|
const arbitraryBytesForSigning = Base58.decode(
|
||||||
|
arbitraryBytesForSigningBase58
|
||||||
|
);
|
||||||
|
const _arbitraryBytesForSigningBuffer = Object.keys(
|
||||||
|
arbitraryBytesForSigning
|
||||||
|
).map(function (key) {
|
||||||
|
return arbitraryBytesForSigning[key];
|
||||||
|
});
|
||||||
|
const arbitraryBytesForSigningBuffer = new Uint8Array(
|
||||||
|
_arbitraryBytesForSigningBuffer
|
||||||
|
);
|
||||||
|
const signature = nacl.sign.detached(
|
||||||
|
arbitraryBytesForSigningBuffer,
|
||||||
|
keyPair.privateKey
|
||||||
|
);
|
||||||
|
|
||||||
const signAndProcessWithFee = async (transactionBytesBase58: string) => {
|
return utils.appendBuffer(arbitraryBytesBuffer, signature);
|
||||||
let convertedBytesBase58 = await convertBytesForSigning(
|
};
|
||||||
transactionBytesBase58
|
|
||||||
)
|
|
||||||
|
|
||||||
if (convertedBytesBase58.error) {
|
const processTransactionVersion2 = async (bytes) => {
|
||||||
throw new Error('Error when signing')
|
return await resuablePostRetry(
|
||||||
}
|
'/transactions/process?apiVersion=2',
|
||||||
|
Base58.encode(bytes),
|
||||||
|
3,
|
||||||
|
appInfo,
|
||||||
|
{ identifier, name: registeredName, service }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const signAndProcessWithFee = async (transactionBytesBase58: string) => {
|
||||||
|
let convertedBytesBase58 = await convertBytesForSigning(
|
||||||
|
transactionBytesBase58
|
||||||
|
);
|
||||||
|
|
||||||
const resKeyPair = await getKeyPair()
|
if (convertedBytesBase58.error) {
|
||||||
const parsedData = resKeyPair
|
throw new Error('Error when signing');
|
||||||
const uint8PrivateKey = Base58.decode(parsedData.privateKey);
|
}
|
||||||
const uint8PublicKey = Base58.decode(parsedData.publicKey);
|
|
||||||
const keyPair = {
|
|
||||||
privateKey: uint8PrivateKey,
|
|
||||||
publicKey: uint8PublicKey
|
|
||||||
};
|
|
||||||
|
|
||||||
let signedArbitraryBytes = signArbitraryWithFee(transactionBytesBase58, convertedBytesBase58, keyPair)
|
const resKeyPair = await getKeyPair();
|
||||||
const response = await processTransactionVersion2(signedArbitraryBytes)
|
const parsedData = resKeyPair;
|
||||||
|
const uint8PrivateKey = Base58.decode(parsedData.privateKey);
|
||||||
|
const uint8PublicKey = Base58.decode(parsedData.publicKey);
|
||||||
|
const keyPair = {
|
||||||
|
privateKey: uint8PrivateKey,
|
||||||
|
publicKey: uint8PublicKey,
|
||||||
|
};
|
||||||
|
|
||||||
let myResponse = { error: '' }
|
let signedArbitraryBytes = signArbitraryWithFee(
|
||||||
|
transactionBytesBase58,
|
||||||
|
convertedBytesBase58,
|
||||||
|
keyPair
|
||||||
|
);
|
||||||
|
const response = await processTransactionVersion2(signedArbitraryBytes);
|
||||||
|
|
||||||
if (response === false) {
|
let myResponse = { error: '' };
|
||||||
throw new Error('Error when signing')
|
|
||||||
} else {
|
|
||||||
myResponse = response
|
|
||||||
}
|
|
||||||
|
|
||||||
return myResponse
|
if (response === false) {
|
||||||
}
|
throw new Error('Error when signing');
|
||||||
|
} else {
|
||||||
|
myResponse = response;
|
||||||
|
}
|
||||||
|
if (appInfo?.tabId) {
|
||||||
|
executeEvent('receiveChunks', {
|
||||||
|
tabId: appInfo.tabId,
|
||||||
|
publishLocation: {
|
||||||
|
name: registeredName,
|
||||||
|
identifier,
|
||||||
|
service,
|
||||||
|
},
|
||||||
|
processed: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return myResponse;
|
||||||
|
};
|
||||||
|
|
||||||
const validate = async () => {
|
const validate = async () => {
|
||||||
let validNameRes = await validateName(registeredName)
|
let validNameRes = await validateName(registeredName);
|
||||||
|
|
||||||
if (validNameRes.error) {
|
if (validNameRes.error) {
|
||||||
throw new Error('Name not found')
|
throw new Error('Name not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
let fee = null
|
let fee = null;
|
||||||
|
|
||||||
if (withFee && feeAmount) {
|
if (withFee && feeAmount) {
|
||||||
fee = feeAmount
|
fee = feeAmount;
|
||||||
} else if (withFee) {
|
} else if (withFee) {
|
||||||
const res = await getArbitraryFee()
|
const res = await getArbitraryFee();
|
||||||
if (res.fee) {
|
if (res.fee) {
|
||||||
fee = res.fee
|
fee = res.fee;
|
||||||
} else {
|
} else {
|
||||||
throw new Error('unable to get fee')
|
throw new Error('unable to get fee');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let transactionBytes = await uploadData(registeredName, file, fee)
|
|
||||||
if (!transactionBytes || transactionBytes.error) {
|
|
||||||
throw new Error(transactionBytes?.message || 'Error when uploading')
|
|
||||||
} else if (transactionBytes.includes('Error 500 Internal Server Error')) {
|
|
||||||
throw new Error('Error when uploading')
|
|
||||||
}
|
|
||||||
|
|
||||||
let signAndProcessRes
|
let transactionBytes = await uploadData(registeredName, data, fee);
|
||||||
|
if (!transactionBytes || transactionBytes.error) {
|
||||||
|
throw new Error(transactionBytes?.message || 'Error when uploading');
|
||||||
|
} else if (transactionBytes.includes('Error 500 Internal Server Error')) {
|
||||||
|
throw new Error('Error when uploading');
|
||||||
|
}
|
||||||
|
|
||||||
if (withFee) {
|
let signAndProcessRes;
|
||||||
signAndProcessRes = await signAndProcessWithFee(transactionBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (signAndProcessRes?.error) {
|
if (withFee) {
|
||||||
throw new Error('Error when signing')
|
signAndProcessRes = await signAndProcessWithFee(transactionBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
return signAndProcessRes
|
if (signAndProcessRes?.error) {
|
||||||
}
|
throw new Error('Error when signing');
|
||||||
|
}
|
||||||
|
|
||||||
const uploadData = async (registeredName: string, file:any, fee: number) => {
|
return signAndProcessRes;
|
||||||
|
};
|
||||||
|
|
||||||
let postBody = ''
|
const uploadData = async (registeredName: string, data: any, fee: number) => {
|
||||||
let urlSuffix = ''
|
let postBody = '';
|
||||||
|
let urlSuffix = '';
|
||||||
|
|
||||||
if (file != null) {
|
if (data != null) {
|
||||||
// If we're sending zipped data, make sure to use the /zip version of the POST /arbitrary/* API
|
if (uploadType === 'base64') {
|
||||||
if (uploadType === 'zip') {
|
urlSuffix = '/base64';
|
||||||
urlSuffix = '/zip'
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// If we're sending file data, use the /base64 version of the POST /arbitrary/* API
|
if (uploadType === 'base64') {
|
||||||
else if (uploadType === 'file') {
|
postBody = data;
|
||||||
urlSuffix = '/base64'
|
}
|
||||||
}
|
} else {
|
||||||
|
throw new Error('No data provided');
|
||||||
|
}
|
||||||
|
|
||||||
// Base64 encode the file to work around compatibility issues between javascript and java byte arrays
|
let uploadDataUrl = `/arbitrary/${service}/${registeredName}`;
|
||||||
if (isBase64) {
|
let paramQueries = '';
|
||||||
postBody = file
|
if (identifier?.trim().length > 0) {
|
||||||
}
|
uploadDataUrl = `/arbitrary/${service}/${registeredName}/${identifier}`;
|
||||||
|
}
|
||||||
|
|
||||||
if (!isBase64) {
|
paramQueries = paramQueries + `?fee=${fee}`;
|
||||||
let fileBuffer = new Uint8Array(await file.arrayBuffer())
|
|
||||||
postBody = Buffer.from(fileBuffer).toString("base64")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
if (filename != null && filename != 'undefined') {
|
||||||
|
paramQueries = paramQueries + '&filename=' + encodeURIComponent(filename);
|
||||||
let uploadDataUrl = `/arbitrary/${service}/${registeredName}${urlSuffix}`
|
}
|
||||||
if (identifier?.trim().length > 0) {
|
|
||||||
uploadDataUrl = `/arbitrary/${service}/${registeredName}/${identifier}${urlSuffix}`
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadDataUrl = uploadDataUrl + `?fee=${fee}`
|
|
||||||
|
|
||||||
|
|
||||||
if (filename != null && filename != 'undefined') {
|
if (title != null && title != 'undefined') {
|
||||||
uploadDataUrl = uploadDataUrl + '&filename=' + encodeURIComponent(filename)
|
paramQueries = paramQueries + '&title=' + encodeURIComponent(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (title != null && title != 'undefined') {
|
if (description != null && description != 'undefined') {
|
||||||
uploadDataUrl = uploadDataUrl + '&title=' + encodeURIComponent(title)
|
paramQueries =
|
||||||
}
|
paramQueries + '&description=' + encodeURIComponent(description);
|
||||||
|
}
|
||||||
|
|
||||||
if (description != null && description != 'undefined') {
|
if (category != null && category != 'undefined') {
|
||||||
uploadDataUrl = uploadDataUrl + '&description=' + encodeURIComponent(description)
|
paramQueries = paramQueries + '&category=' + encodeURIComponent(category);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (category != null && category != 'undefined') {
|
if (tag1 != null && tag1 != 'undefined') {
|
||||||
uploadDataUrl = uploadDataUrl + '&category=' + encodeURIComponent(category)
|
paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag1 != null && tag1 != 'undefined') {
|
if (tag2 != null && tag2 != 'undefined') {
|
||||||
uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag1)
|
paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag2 != null && tag2 != 'undefined') {
|
if (tag3 != null && tag3 != 'undefined') {
|
||||||
uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag2)
|
paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag3);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag3 != null && tag3 != 'undefined') {
|
if (tag4 != null && tag4 != 'undefined') {
|
||||||
uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag3)
|
paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag4);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag4 != null && tag4 != 'undefined') {
|
if (tag5 != null && tag5 != 'undefined') {
|
||||||
uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag4)
|
paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag5);
|
||||||
}
|
}
|
||||||
|
if (uploadType === 'zip') {
|
||||||
|
paramQueries = paramQueries + '&isZip=' + true;
|
||||||
|
}
|
||||||
|
|
||||||
if (tag5 != null && tag5 != 'undefined') {
|
if (uploadType === 'base64') {
|
||||||
uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag5)
|
if (urlSuffix) {
|
||||||
}
|
uploadDataUrl = uploadDataUrl + urlSuffix;
|
||||||
|
}
|
||||||
|
uploadDataUrl = uploadDataUrl + paramQueries;
|
||||||
|
if (appInfo?.tabId) {
|
||||||
|
executeEvent('receiveChunks', {
|
||||||
|
tabId: appInfo.tabId,
|
||||||
|
publishLocation: {
|
||||||
|
name: registeredName,
|
||||||
|
identifier,
|
||||||
|
service,
|
||||||
|
},
|
||||||
|
chunksSubmitted: 1,
|
||||||
|
totalChunks: 1,
|
||||||
|
processed: false,
|
||||||
|
filename: filename || title || `${service}-${identifier || ''}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return await resuablePostRetry(uploadDataUrl, postBody, 3, appInfo, {
|
||||||
|
identifier,
|
||||||
|
name: registeredName,
|
||||||
|
service,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return await reusablePost(uploadDataUrl, postBody)
|
const file = data;
|
||||||
|
const urlCheck = `/arbitrary/check/tmp?totalSize=${file.size}`;
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
const checkEndpoint = await createEndpoint(urlCheck);
|
||||||
return await validate()
|
const checkRes = await fetch(checkEndpoint);
|
||||||
} catch (error: any) {
|
if (!checkRes.ok) {
|
||||||
throw new Error(error?.message)
|
throw new Error('Not enough space on your hard drive');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
const chunkUrl = uploadDataUrl + `/chunk`;
|
||||||
|
const chunkSize = 5 * 1024 * 1024; // 5MB
|
||||||
|
|
||||||
|
const totalChunks = Math.ceil(file.size / chunkSize);
|
||||||
|
if (appInfo?.tabId) {
|
||||||
|
executeEvent('receiveChunks', {
|
||||||
|
tabId: appInfo.tabId,
|
||||||
|
publishLocation: {
|
||||||
|
name: registeredName,
|
||||||
|
identifier,
|
||||||
|
service,
|
||||||
|
},
|
||||||
|
chunksSubmitted: 0,
|
||||||
|
totalChunks,
|
||||||
|
processed: false,
|
||||||
|
filename:
|
||||||
|
file?.name || filename || title || `${service}-${identifier || ''}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (let index = 0; index < totalChunks; index++) {
|
||||||
|
const start = index * chunkSize;
|
||||||
|
const end = Math.min(start + chunkSize, file.size);
|
||||||
|
const chunk = file.slice(start, end);
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('chunk', chunk, file.name); // Optional: include filename
|
||||||
|
formData.append('index', index);
|
||||||
|
|
||||||
|
await uploadChunkWithRetry(chunkUrl, formData, index);
|
||||||
|
if (appInfo?.tabId) {
|
||||||
|
executeEvent('receiveChunks', {
|
||||||
|
tabId: appInfo.tabId,
|
||||||
|
publishLocation: {
|
||||||
|
name: registeredName,
|
||||||
|
identifier,
|
||||||
|
service,
|
||||||
|
},
|
||||||
|
chunksSubmitted: index + 1,
|
||||||
|
totalChunks,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
const finalizeUrl = uploadDataUrl + `/finalize` + paramQueries;
|
||||||
|
|
||||||
|
const finalizeEndpoint = await createEndpoint(finalizeUrl);
|
||||||
|
|
||||||
|
const response = await fetch(finalizeEndpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response?.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
throw new Error(`Finalize failed: ${errorText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.text(); // Base58-encoded unsigned transaction
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await validate();
|
||||||
|
} catch (error: any) {
|
||||||
|
throw new Error(error?.message);
|
||||||
|
}
|
||||||
|
};
|
@ -1,9 +1,10 @@
|
|||||||
import { gateways, getApiKeyFromStorage } from "./background";
|
import { gateways, getApiKeyFromStorage, getNameInfoForOthers } from "./background";
|
||||||
import { listOfAllQortalRequests } from "./components/Apps/useQortalMessageListener";
|
import { listOfAllQortalRequests } from "./components/Apps/useQortalMessageListener";
|
||||||
import { addForeignServer, addGroupAdminRequest, addListItems, adminAction, banFromGroupRequest, cancelGroupBanRequest, cancelGroupInviteRequest, cancelSellOrder, createAndCopyEmbedLink, createBuyOrder, createGroupRequest, createPoll, decryptAESGCMRequest, decryptData, decryptDataWithSharingKey, decryptQortalGroupData, deleteHostedData, deleteListItems, deployAt, encryptData, encryptDataWithSharingKey, encryptQortalGroupData, getArrrSyncStatus, getCrossChainServerInfo, getDaySummary, getForeignFee, getHostedData, getListItems, getNodeInfo, getNodeStatus, getServerConnectionHistory, getTxActivitySummary, getUserAccount, getUserWallet, getUserWalletInfo, getUserWalletTransactions, getWalletBalance, inviteToGroupRequest, joinGroup, kickFromGroupRequest, leaveGroupRequest, openNewTab, publishMultipleQDNResources, publishQDNResource, registerNameRequest, removeForeignServer, removeGroupAdminRequest, saveFile, sendChatMessage, sendCoin, setCurrentForeignServer, signTransaction, updateForeignFee, updateNameRequest, voteOnPoll } from "./qortalRequests/get";
|
import { addForeignServer, addGroupAdminRequest, addListItems, adminAction, banFromGroupRequest, buyNameRequest, cancelGroupBanRequest, cancelGroupInviteRequest, cancelSellNameRequest, cancelSellOrder, createAndCopyEmbedLink, createBuyOrder, createGroupRequest, createPoll, createSellOrder, decryptAESGCMRequest, decryptData, decryptDataWithSharingKey, decryptQortalGroupData, deleteHostedData, deleteListItems, deployAt, encryptData, encryptDataWithSharingKey, encryptQortalGroupData, getArrrSyncStatus, getCrossChainServerInfo, getDaySummary, getForeignFee, getHostedData, getListItems, getNodeInfo, getNodeStatus, getServerConnectionHistory, getTxActivitySummary, getUserAccount, getUserWallet, getUserWalletInfo, getUserWalletTransactions, getWalletBalance, inviteToGroupRequest, joinGroup, kickFromGroupRequest, leaveGroupRequest, multiPaymentWithPrivateData, openNewTab, publishMultipleQDNResources, publishQDNResource, registerNameRequest, removeForeignServer, removeGroupAdminRequest, saveFile, sellNameRequest, sendChatMessage, sendCoin, setCurrentForeignServer, signForeignFees, signTransaction, transferAssetRequest, updateForeignFee, updateGroupRequest, updateNameRequest, voteOnPoll } from "./qortalRequests/get";
|
||||||
import { getData, storeData } from "./utils/chromeStorage";
|
import { getData, storeData } from "./utils/chromeStorage";
|
||||||
import { executeEvent } from "./utils/events";
|
import { executeEvent } from "./utils/events";
|
||||||
|
|
||||||
|
import { ScreenOrientation } from '@capacitor/screen-orientation';
|
||||||
|
|
||||||
|
|
||||||
function getLocalStorage(key) {
|
function getLocalStorage(key) {
|
||||||
@ -200,7 +201,7 @@ export const isRunningGateway = async ()=> {
|
|||||||
|
|
||||||
case "PUBLISH_QDN_RESOURCE": {
|
case "PUBLISH_QDN_RESOURCE": {
|
||||||
try {
|
try {
|
||||||
const res = await publishQDNResource(request.payload, event.source, isFromExtension);
|
const res = await publishQDNResource(request.payload, event.source, isFromExtension, appInfo);
|
||||||
event.source.postMessage({
|
event.source.postMessage({
|
||||||
requestId: request.requestId,
|
requestId: request.requestId,
|
||||||
action: request.action,
|
action: request.action,
|
||||||
@ -220,7 +221,7 @@ export const isRunningGateway = async ()=> {
|
|||||||
|
|
||||||
case "PUBLISH_MULTIPLE_QDN_RESOURCES": {
|
case "PUBLISH_MULTIPLE_QDN_RESOURCES": {
|
||||||
try {
|
try {
|
||||||
const res = await publishMultipleQDNResources(request.payload, event.source, isFromExtension);
|
const res = await publishMultipleQDNResources(request.payload, event.source, isFromExtension, appInfo);
|
||||||
event.source.postMessage({
|
event.source.postMessage({
|
||||||
requestId: request.requestId,
|
requestId: request.requestId,
|
||||||
action: request.action,
|
action: request.action,
|
||||||
@ -462,7 +463,7 @@ export const isRunningGateway = async ()=> {
|
|||||||
|
|
||||||
case "UPDATE_FOREIGN_FEE": {
|
case "UPDATE_FOREIGN_FEE": {
|
||||||
try {
|
try {
|
||||||
const res = await updateForeignFee(request.payload);
|
const res = await updateForeignFee(request.payload, isFromExtension);
|
||||||
event.source.postMessage({
|
event.source.postMessage({
|
||||||
requestId: request.requestId,
|
requestId: request.requestId,
|
||||||
action: request.action,
|
action: request.action,
|
||||||
@ -502,7 +503,7 @@ export const isRunningGateway = async ()=> {
|
|||||||
|
|
||||||
case "SET_CURRENT_FOREIGN_SERVER": {
|
case "SET_CURRENT_FOREIGN_SERVER": {
|
||||||
try {
|
try {
|
||||||
const res = await setCurrentForeignServer(request.payload);
|
const res = await setCurrentForeignServer(request.payload, isFromExtension);
|
||||||
event.source.postMessage({
|
event.source.postMessage({
|
||||||
requestId: request.requestId,
|
requestId: request.requestId,
|
||||||
action: request.action,
|
action: request.action,
|
||||||
@ -522,7 +523,7 @@ export const isRunningGateway = async ()=> {
|
|||||||
|
|
||||||
case "ADD_FOREIGN_SERVER": {
|
case "ADD_FOREIGN_SERVER": {
|
||||||
try {
|
try {
|
||||||
const res = await addForeignServer(request.payload);
|
const res = await addForeignServer(request.payload, isFromExtension);
|
||||||
event.source.postMessage({
|
event.source.postMessage({
|
||||||
requestId: request.requestId,
|
requestId: request.requestId,
|
||||||
action: request.action,
|
action: request.action,
|
||||||
@ -542,7 +543,7 @@ export const isRunningGateway = async ()=> {
|
|||||||
|
|
||||||
case "REMOVE_FOREIGN_SERVER": {
|
case "REMOVE_FOREIGN_SERVER": {
|
||||||
try {
|
try {
|
||||||
const res = await removeForeignServer(request.payload);
|
const res = await removeForeignServer(request.payload, isFromExtension);
|
||||||
event.source.postMessage({
|
event.source.postMessage({
|
||||||
requestId: request.requestId,
|
requestId: request.requestId,
|
||||||
action: request.action,
|
action: request.action,
|
||||||
@ -620,6 +621,32 @@ export const isRunningGateway = async ()=> {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'CREATE_TRADE_SELL_ORDER': {
|
||||||
|
try {
|
||||||
|
const res = await createSellOrder(request.payload, isFromExtension);
|
||||||
|
event.source.postMessage(
|
||||||
|
{
|
||||||
|
requestId: request.requestId,
|
||||||
|
action: request.action,
|
||||||
|
payload: res,
|
||||||
|
type: 'backgroundMessageResponse',
|
||||||
|
},
|
||||||
|
event.origin
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
event.source.postMessage(
|
||||||
|
{
|
||||||
|
requestId: request.requestId,
|
||||||
|
action: request.action,
|
||||||
|
error: error.message,
|
||||||
|
type: 'backgroundMessageResponse',
|
||||||
|
},
|
||||||
|
event.origin
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "CANCEL_TRADE_SELL_ORDER": {
|
case "CANCEL_TRADE_SELL_ORDER": {
|
||||||
try {
|
try {
|
||||||
const res = await cancelSellOrder(request.payload, isFromExtension);
|
const res = await cancelSellOrder(request.payload, isFromExtension);
|
||||||
@ -1206,6 +1233,206 @@ export const isRunningGateway = async ()=> {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "UPDATE_GROUP" : {
|
||||||
|
try {
|
||||||
|
const res = await updateGroupRequest(request.payload, isFromExtension)
|
||||||
|
event.source.postMessage({
|
||||||
|
requestId: request.requestId,
|
||||||
|
action: request.action,
|
||||||
|
payload: res,
|
||||||
|
type: "backgroundMessageResponse",
|
||||||
|
}, event.origin);
|
||||||
|
} catch (error) {
|
||||||
|
event.source.postMessage({
|
||||||
|
requestId: request.requestId,
|
||||||
|
action: request.action,
|
||||||
|
error: error?.message,
|
||||||
|
type: "backgroundMessageResponse",
|
||||||
|
}, event.origin);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "BUY_NAME": {
|
||||||
|
try {
|
||||||
|
const res = await buyNameRequest(request.payload, isFromExtension);
|
||||||
|
event.source.postMessage({
|
||||||
|
requestId: request.requestId,
|
||||||
|
action: request.action,
|
||||||
|
payload: res,
|
||||||
|
type: "backgroundMessageResponse",
|
||||||
|
}, event.origin);
|
||||||
|
} catch (error) {
|
||||||
|
event.source.postMessage({
|
||||||
|
requestId: request.requestId,
|
||||||
|
action: request.action,
|
||||||
|
error: error.message,
|
||||||
|
type: "backgroundMessageResponse",
|
||||||
|
}, event.origin);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "SELL_NAME": {
|
||||||
|
try {
|
||||||
|
const res = await sellNameRequest(request.payload, isFromExtension);
|
||||||
|
event.source.postMessage({
|
||||||
|
requestId: request.requestId,
|
||||||
|
action: request.action,
|
||||||
|
payload: res,
|
||||||
|
type: "backgroundMessageResponse",
|
||||||
|
}, event.origin);
|
||||||
|
} catch (error) {
|
||||||
|
event.source.postMessage({
|
||||||
|
requestId: request.requestId,
|
||||||
|
action: request.action,
|
||||||
|
error: error.message,
|
||||||
|
type: "backgroundMessageResponse",
|
||||||
|
}, event.origin);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CANCEL_SELL_NAME": {
|
||||||
|
try {
|
||||||
|
const res = await cancelSellNameRequest(request.payload, isFromExtension);
|
||||||
|
event.source.postMessage({
|
||||||
|
requestId: request.requestId,
|
||||||
|
action: request.action,
|
||||||
|
payload: res,
|
||||||
|
type: "backgroundMessageResponse",
|
||||||
|
}, event.origin);
|
||||||
|
} catch (error) {
|
||||||
|
event.source.postMessage({
|
||||||
|
requestId: request.requestId,
|
||||||
|
action: request.action,
|
||||||
|
error: error.message,
|
||||||
|
type: "backgroundMessageResponse",
|
||||||
|
}, event.origin);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "MULTI_ASSET_PAYMENT_WITH_PRIVATE_DATA" : {
|
||||||
|
try {
|
||||||
|
const res = await multiPaymentWithPrivateData(request.payload, isFromExtension)
|
||||||
|
event.source.postMessage({
|
||||||
|
requestId: request.requestId,
|
||||||
|
action: request.action,
|
||||||
|
payload: res,
|
||||||
|
type: "backgroundMessageResponse",
|
||||||
|
}, event.origin);
|
||||||
|
} catch (error) {
|
||||||
|
event.source.postMessage({
|
||||||
|
requestId: request.requestId,
|
||||||
|
action: request.action,
|
||||||
|
error: error?.message,
|
||||||
|
type: "backgroundMessageResponse",
|
||||||
|
}, event.origin);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "TRANSFER_ASSET" : {
|
||||||
|
try {
|
||||||
|
const res = await transferAssetRequest(request.payload, isFromExtension)
|
||||||
|
event.source.postMessage({
|
||||||
|
requestId: request.requestId,
|
||||||
|
action: request.action,
|
||||||
|
payload: res,
|
||||||
|
type: "backgroundMessageResponse",
|
||||||
|
}, event.origin);
|
||||||
|
} catch (error) {
|
||||||
|
event.source.postMessage({
|
||||||
|
requestId: request.requestId,
|
||||||
|
action: request.action,
|
||||||
|
error: error?.message,
|
||||||
|
type: "backgroundMessageResponse",
|
||||||
|
}, event.origin);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'SIGN_FOREIGN_FEES': {
|
||||||
|
try {
|
||||||
|
const res = await signForeignFees(request.payload, isFromExtension);
|
||||||
|
event.source.postMessage(
|
||||||
|
{
|
||||||
|
requestId: request.requestId,
|
||||||
|
action: request.action,
|
||||||
|
payload: res,
|
||||||
|
type: 'backgroundMessageResponse',
|
||||||
|
},
|
||||||
|
event.origin
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
event.source.postMessage(
|
||||||
|
{
|
||||||
|
requestId: request.requestId,
|
||||||
|
action: request.action,
|
||||||
|
error: error.message,
|
||||||
|
type: 'backgroundMessageResponse',
|
||||||
|
},
|
||||||
|
event.origin
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'GET_PRIMARY_NAME': {
|
||||||
|
try {
|
||||||
|
const res = await getNameInfoForOthers(request.payload?.address);
|
||||||
|
const resData = res ? res : "";
|
||||||
|
event.source.postMessage(
|
||||||
|
{
|
||||||
|
requestId: request.requestId,
|
||||||
|
action: request.action,
|
||||||
|
payload: resData,
|
||||||
|
type: 'backgroundMessageResponse',
|
||||||
|
},
|
||||||
|
event.origin
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
event.source.postMessage(
|
||||||
|
{
|
||||||
|
requestId: request.requestId,
|
||||||
|
action: request.action,
|
||||||
|
error: error.message,
|
||||||
|
type: 'backgroundMessageResponse',
|
||||||
|
},
|
||||||
|
event.origin
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'SCREEN_ORIENTATION': {
|
||||||
|
try {
|
||||||
|
const mode = request.payload?.mode
|
||||||
|
if(mode === 'unlock'){
|
||||||
|
await ScreenOrientation.unlock();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
await ScreenOrientation.lock({ orientation: mode });
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
event.source.postMessage(
|
||||||
|
{
|
||||||
|
requestId: request.requestId,
|
||||||
|
action: request.action,
|
||||||
|
payload: true,
|
||||||
|
type: 'backgroundMessageResponse',
|
||||||
|
},
|
||||||
|
event.origin
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
event.source.postMessage(
|
||||||
|
{
|
||||||
|
requestId: request.requestId,
|
||||||
|
action: request.action,
|
||||||
|
error: error.message,
|
||||||
|
type: 'backgroundMessageResponse',
|
||||||
|
},
|
||||||
|
event.origin
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
45
src/transactions/BuyNameTransacion.ts
Normal file
45
src/transactions/BuyNameTransacion.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { QORT_DECIMALS } from "../constants/constants"
|
||||||
|
import TransactionBase from "./TransactionBase"
|
||||||
|
|
||||||
|
|
||||||
|
export default class BuyNameTransacion extends TransactionBase {
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
this.type = 7
|
||||||
|
}
|
||||||
|
|
||||||
|
set fee(fee) {
|
||||||
|
this._fee = fee * QORT_DECIMALS
|
||||||
|
this._feeBytes = this.constructor.utils.int64ToBytes(this._fee)
|
||||||
|
}
|
||||||
|
|
||||||
|
set name(name) {
|
||||||
|
this.nameText = name
|
||||||
|
this._nameBytes = this.constructor.utils.stringtoUTF8Array(name)
|
||||||
|
this._nameLength = this.constructor.utils.int32ToBytes(this._nameBytes.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
set sellPrice(sellPrice) {
|
||||||
|
this._sellPrice = sellPrice * QORT_DECIMALS
|
||||||
|
this._sellPriceBytes = this.constructor.utils.int64ToBytes(this._sellPrice)
|
||||||
|
}
|
||||||
|
|
||||||
|
set recipient(recipient) {
|
||||||
|
this._recipient = recipient instanceof Uint8Array ? recipient : this.constructor.Base58.decode(recipient)
|
||||||
|
this.theRecipient = recipient
|
||||||
|
}
|
||||||
|
|
||||||
|
get params() {
|
||||||
|
const params = super.params
|
||||||
|
params.push(
|
||||||
|
this._nameLength,
|
||||||
|
this._nameBytes,
|
||||||
|
this._sellPriceBytes,
|
||||||
|
this._recipient,
|
||||||
|
this._feeBytes
|
||||||
|
)
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
}
|
33
src/transactions/CancelSellNameTransacion.ts
Normal file
33
src/transactions/CancelSellNameTransacion.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { QORT_DECIMALS } from "../constants/constants"
|
||||||
|
import TransactionBase from "./TransactionBase"
|
||||||
|
|
||||||
|
|
||||||
|
export default class CancelSellNameTransacion extends TransactionBase {
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
this.type = 6
|
||||||
|
}
|
||||||
|
|
||||||
|
set fee(fee) {
|
||||||
|
this._fee = fee * QORT_DECIMALS
|
||||||
|
this._feeBytes = this.constructor.utils.int64ToBytes(this._fee)
|
||||||
|
}
|
||||||
|
|
||||||
|
set name(name) {
|
||||||
|
this.nameText = name
|
||||||
|
this._nameBytes = this.constructor.utils.stringtoUTF8Array(name)
|
||||||
|
this._nameLength = this.constructor.utils.int32ToBytes(this._nameBytes.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
get params() {
|
||||||
|
const params = super.params
|
||||||
|
params.push(
|
||||||
|
this._nameLength,
|
||||||
|
this._nameBytes,
|
||||||
|
this._feeBytes
|
||||||
|
)
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
}
|
40
src/transactions/SellNameTransacion.ts
Normal file
40
src/transactions/SellNameTransacion.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { QORT_DECIMALS } from "../constants/constants"
|
||||||
|
import TransactionBase from "./TransactionBase"
|
||||||
|
|
||||||
|
|
||||||
|
export default class SellNameTransacion extends TransactionBase {
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
this.type = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
set fee(fee) {
|
||||||
|
this._fee = fee * QORT_DECIMALS
|
||||||
|
this._feeBytes = this.constructor.utils.int64ToBytes(this._fee)
|
||||||
|
}
|
||||||
|
|
||||||
|
set name(name) {
|
||||||
|
this.nameText = name
|
||||||
|
this._nameBytes = this.constructor.utils.stringtoUTF8Array(name)
|
||||||
|
this._nameLength = this.constructor.utils.int32ToBytes(this._nameBytes.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
set sellPrice(sellPrice) {
|
||||||
|
this.showSellPrice = sellPrice
|
||||||
|
this._sellPrice = sellPrice * QORT_DECIMALS
|
||||||
|
this._sellPriceBytes = this.constructor.utils.int64ToBytes(this._sellPrice)
|
||||||
|
}
|
||||||
|
|
||||||
|
get params() {
|
||||||
|
const params = super.params
|
||||||
|
params.push(
|
||||||
|
this._nameLength,
|
||||||
|
this._nameBytes,
|
||||||
|
this._sellPriceBytes,
|
||||||
|
this._feeBytes
|
||||||
|
)
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
}
|
35
src/transactions/TransferAssetTransaction.ts
Normal file
35
src/transactions/TransferAssetTransaction.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { QORT_DECIMALS } from '../constants/constants'
|
||||||
|
import TransactionBase from './TransactionBase'
|
||||||
|
|
||||||
|
export default class TransferAssetTransaction extends TransactionBase {
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
this.type = 12
|
||||||
|
}
|
||||||
|
|
||||||
|
set recipient(recipient) {
|
||||||
|
this._recipient = recipient instanceof Uint8Array ? recipient : this.constructor.Base58.decode(recipient)
|
||||||
|
}
|
||||||
|
|
||||||
|
set amount(amount) {
|
||||||
|
this._amount = Math.round(amount * QORT_DECIMALS)
|
||||||
|
this._amountBytes = this.constructor.utils.int64ToBytes(this._amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
set assetId(assetId) {
|
||||||
|
this._assetId = this.constructor.utils.int64ToBytes(assetId)
|
||||||
|
}
|
||||||
|
|
||||||
|
get params() {
|
||||||
|
const params = super.params
|
||||||
|
params.push(
|
||||||
|
this._recipient,
|
||||||
|
this._assetId,
|
||||||
|
this._amountBytes,
|
||||||
|
this._feeBytes
|
||||||
|
)
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
}
|
62
src/transactions/UpdateGroupTransaction.ts
Normal file
62
src/transactions/UpdateGroupTransaction.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
|
||||||
|
import { QORT_DECIMALS } from "../constants/constants";
|
||||||
|
import TransactionBase from "./TransactionBase";
|
||||||
|
|
||||||
|
export default class UpdateGroupTransaction extends TransactionBase {
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
this.type = 23
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
set fee(fee) {
|
||||||
|
this._fee = fee * QORT_DECIMALS
|
||||||
|
this._feeBytes = this.constructor.utils.int64ToBytes(this._fee)
|
||||||
|
}
|
||||||
|
set newOwner(newOwner) {
|
||||||
|
this._newOwner = newOwner instanceof Uint8Array ? newOwner : this.constructor.Base58.decode(newOwner)
|
||||||
|
}
|
||||||
|
set newIsOpen(newIsOpen) {
|
||||||
|
|
||||||
|
this._rGroupType = new Uint8Array(1)
|
||||||
|
this._rGroupType[0] = newIsOpen
|
||||||
|
}
|
||||||
|
set newDescription(newDescription) {
|
||||||
|
this._rGroupDescBytes = this.constructor.utils.stringtoUTF8Array(newDescription.toLocaleLowerCase())
|
||||||
|
this._rGroupDescLength = this.constructor.utils.int32ToBytes(this._rGroupDescBytes.length)
|
||||||
|
}
|
||||||
|
set newApprovalThreshold(newApprovalThreshold) {
|
||||||
|
this._rGroupApprovalThreshold = new Uint8Array(1)
|
||||||
|
this._rGroupApprovalThreshold[0] = newApprovalThreshold;
|
||||||
|
}
|
||||||
|
set newMinimumBlockDelay(newMinimumBlockDelay) {
|
||||||
|
this._rGroupMinimumBlockDelayBytes = this.constructor.utils.int32ToBytes(newMinimumBlockDelay)
|
||||||
|
}
|
||||||
|
set newMaximumBlockDelay(newMaximumBlockDelay) {
|
||||||
|
|
||||||
|
this._rGroupMaximumBlockDelayBytes = this.constructor.utils.int32ToBytes(newMaximumBlockDelay)
|
||||||
|
}
|
||||||
|
|
||||||
|
set _groupId(_groupId){
|
||||||
|
this._groupBytes = this.constructor.utils.int32ToBytes(_groupId)
|
||||||
|
}
|
||||||
|
get params() {
|
||||||
|
const params = super.params
|
||||||
|
params.push(
|
||||||
|
this._groupBytes,
|
||||||
|
this._newOwner,
|
||||||
|
this._rGroupDescLength,
|
||||||
|
this._rGroupDescBytes,
|
||||||
|
this._rGroupType,
|
||||||
|
this._rGroupApprovalThreshold,
|
||||||
|
this._rGroupMinimumBlockDelayBytes,
|
||||||
|
this._rGroupMaximumBlockDelayBytes,
|
||||||
|
this._feeBytes
|
||||||
|
)
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
}
|
@ -20,17 +20,27 @@ import DeployAtTransaction from './DeployAtTransaction.js'
|
|||||||
import RewardShareTransaction from './RewardShareTransaction.js'
|
import RewardShareTransaction from './RewardShareTransaction.js'
|
||||||
import RemoveRewardShareTransaction from './RemoveRewardShareTransaction.js'
|
import RemoveRewardShareTransaction from './RemoveRewardShareTransaction.js'
|
||||||
import UpdateNameTransaction from './UpdateNameTransaction.js'
|
import UpdateNameTransaction from './UpdateNameTransaction.js'
|
||||||
|
import UpdateGroupTransaction from './UpdateGroupTransaction.js'
|
||||||
|
import SellNameTransacion from './SellNameTransacion.js'
|
||||||
|
import CancelSellNameTransacion from './CancelSellNameTransacion.js'
|
||||||
|
import BuyNameTransacion from './BuyNameTransacion.js'
|
||||||
|
import TransferAssetTransaction from './TransferAssetTransaction.js'
|
||||||
|
|
||||||
export const transactionTypes = {
|
export const transactionTypes = {
|
||||||
3: RegisterNameTransaction,
|
3: RegisterNameTransaction,
|
||||||
4: UpdateNameTransaction,
|
4: UpdateNameTransaction,
|
||||||
2: PaymentTransaction,
|
2: PaymentTransaction,
|
||||||
|
5: SellNameTransacion,
|
||||||
|
6: CancelSellNameTransacion,
|
||||||
|
7: BuyNameTransacion,
|
||||||
8: CreatePollTransaction,
|
8: CreatePollTransaction,
|
||||||
9: VoteOnPollTransaction,
|
9: VoteOnPollTransaction,
|
||||||
|
12: TransferAssetTransaction,
|
||||||
16: DeployAtTransaction,
|
16: DeployAtTransaction,
|
||||||
18: ChatTransaction,
|
18: ChatTransaction,
|
||||||
181: GroupChatTransaction,
|
181: GroupChatTransaction,
|
||||||
22: CreateGroupTransaction,
|
22: CreateGroupTransaction,
|
||||||
|
23: UpdateGroupTransaction,
|
||||||
24: AddGroupAdminTransaction,
|
24: AddGroupAdminTransaction,
|
||||||
25: RemoveGroupAdminTransaction,
|
25: RemoveGroupAdminTransaction,
|
||||||
26: GroupBanTransaction,
|
26: GroupBanTransaction,
|
||||||
|
26
src/utils/chat.ts
Normal file
26
src/utils/chat.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
export function buildImageEmbedLink(image?: {
|
||||||
|
name?: string;
|
||||||
|
identifier?: string;
|
||||||
|
service?: string;
|
||||||
|
timestamp?: number;
|
||||||
|
}): string | null {
|
||||||
|
if (!image?.name || !image.identifier || !image.service) return null;
|
||||||
|
|
||||||
|
const base = `qortal://use-embed/IMAGE?name=${image.name}&identifier=${image.identifier}&service=${image.service}&mimeType=image%2Fpng×tamp=${image?.timestamp || ''}`;
|
||||||
|
|
||||||
|
const isEncrypted = image.identifier.startsWith('grp-q-manager_0');
|
||||||
|
return isEncrypted ? `${base}&encryptionType=group` : base;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const messageHasImage = (message) => {
|
||||||
|
return (
|
||||||
|
Array.isArray(message?.images) &&
|
||||||
|
message.images[0]?.identifier &&
|
||||||
|
message.images[0]?.name &&
|
||||||
|
message.images[0]?.service
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export function isHtmlString(value) {
|
||||||
|
return typeof value === 'string' && /<[^>]+>/.test(value.trim());
|
||||||
|
}
|
@ -13,4 +13,19 @@ export function decodeIfEncoded(input) {
|
|||||||
|
|
||||||
// Return input as-is if not URI-encoded
|
// Return input as-is if not URI-encoded
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const isValidBase64 = (str: string): boolean => {
|
||||||
|
if (typeof str !== "string" || str.length % 4 !== 0) return false;
|
||||||
|
|
||||||
|
const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
|
||||||
|
return base64Regex.test(str);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isValidBase64WithDecode = (str: string): boolean => {
|
||||||
|
try {
|
||||||
|
return isValidBase64(str) && Boolean(atob(str));
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
@ -43,14 +43,15 @@ export function formatTimestamp(timestamp: number): string {
|
|||||||
// Both have timestamp, sort by timestamp descending
|
// Both have timestamp, sort by timestamp descending
|
||||||
return b.timestamp - a.timestamp;
|
return b.timestamp - a.timestamp;
|
||||||
} else if (a.timestamp) {
|
} else if (a.timestamp) {
|
||||||
// Only `a` has timestamp, it comes first
|
|
||||||
return -1;
|
return -1;
|
||||||
} else if (b.timestamp) {
|
} else if (b.timestamp) {
|
||||||
// Only `b` has timestamp, it comes first
|
|
||||||
return 1;
|
return 1;
|
||||||
} else {
|
} else {
|
||||||
// Neither has timestamp, sort alphabetically by groupName
|
// Neither has timestamp, sort alphabetically by groupName (with fallback)
|
||||||
return a.groupName.localeCompare(b.groupName);
|
const nameA = a.groupName || '';
|
||||||
|
const nameB = b.groupName || '';
|
||||||
|
return nameA.localeCompare(nameB);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user