mirror of
https://github.com/Qortal/Qortal-Hub.git
synced 2025-07-23 04:36:52 +00:00
Merge pull request #25 from nbenaglia/feature/i18n-groups
I18N: add group namespace
This commit is contained in:
118
src/App.tsx
118
src/App.tsx
@@ -135,6 +135,7 @@ import { GeneralNotifications } from './components/GeneralNotifications';
|
||||
import { PdfViewer } from './common/PdfViewer';
|
||||
import ThemeSelector from './components/Theme/ThemeSelector.tsx';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import LanguageSelector from './components/Language/LanguageSelector.tsx';
|
||||
import { DownloadWallet } from './components/Auth/DownloadWallet.tsx';
|
||||
|
||||
type extStates =
|
||||
@@ -255,14 +256,7 @@ export const getBaseApiReact = (customApi?: string) => {
|
||||
return groupApi;
|
||||
}
|
||||
};
|
||||
// export const getArbitraryEndpointReact = () => {
|
||||
|
||||
// if (globalApiKey) {
|
||||
// return `/arbitrary/resources/search`;
|
||||
// } else {
|
||||
// return `/arbitrary/resources/searchsimple`;
|
||||
// }
|
||||
// };
|
||||
export const getArbitraryEndpointReact = () => {
|
||||
if (globalApiKey) {
|
||||
return `/arbitrary/resources/searchsimple`;
|
||||
@@ -571,26 +565,6 @@ function App() {
|
||||
isFocusedRef.current = isFocused;
|
||||
}, [isFocused]);
|
||||
|
||||
// const checkIfUserHasLocalNode = useCallback(async () => {
|
||||
// try {
|
||||
// const url = `http://127.0.0.1:12391/admin/status`;
|
||||
// const response = await fetch(url, {
|
||||
// method: "GET",
|
||||
// headers: {
|
||||
// "Content-Type": "application/json",
|
||||
// },
|
||||
// });
|
||||
// const data = await response.json();
|
||||
// if (data?.isSynchronizing === false && data?.syncPercent === 100) {
|
||||
// setHasLocalNode(true);
|
||||
// }
|
||||
// } catch (error) {}
|
||||
// }, []);
|
||||
|
||||
// useEffect(() => {
|
||||
// checkIfUserHasLocalNode();
|
||||
// }, [extState]);
|
||||
|
||||
const address = useMemo(() => {
|
||||
if (!rawWallet?.address0) return '';
|
||||
return rawWallet.address0;
|
||||
@@ -1007,7 +981,7 @@ function App() {
|
||||
await showUnsavedChanges({
|
||||
message:
|
||||
'Your settings have changed. If you logout you will lose your changes. Click on the save button in the header to keep your changed settings.',
|
||||
});
|
||||
}); // TODO translate
|
||||
} else if (extState === 'authenticated') {
|
||||
await showUnsavedChanges({
|
||||
message: 'Are you sure you would like to logout?',
|
||||
@@ -1311,19 +1285,24 @@ function App() {
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Spacer height="48px" />
|
||||
|
||||
{authenticatedMode === 'ltc' ? (
|
||||
<>
|
||||
<img src={ltcLogo} />
|
||||
|
||||
<Spacer height="32px" />
|
||||
|
||||
<CopyToClipboard text={rawWallet?.ltcAddress}>
|
||||
<AddressBox>
|
||||
{rawWallet?.ltcAddress?.slice(0, 6)}...
|
||||
{rawWallet?.ltcAddress?.slice(-4)} <img src={Copy} />
|
||||
</AddressBox>
|
||||
</CopyToClipboard>
|
||||
|
||||
<Spacer height="10px" />
|
||||
|
||||
{ltcBalanceLoading && (
|
||||
<CircularProgress color="success" size={16} />
|
||||
)}
|
||||
@@ -1345,6 +1324,7 @@ function App() {
|
||||
>
|
||||
{ltcBalance} LTC
|
||||
</TextP>
|
||||
|
||||
<RefreshIcon
|
||||
onClick={getLtcBalanceFunc}
|
||||
sx={{
|
||||
@@ -1364,7 +1344,9 @@ function App() {
|
||||
myName={userInfo?.name}
|
||||
balance={balance}
|
||||
/>
|
||||
|
||||
<Spacer height="32px" />
|
||||
|
||||
<TextP
|
||||
sx={{
|
||||
textAlign: 'center',
|
||||
@@ -1374,7 +1356,9 @@ function App() {
|
||||
>
|
||||
{userInfo?.name}
|
||||
</TextP>
|
||||
|
||||
<Spacer height="10px" />
|
||||
|
||||
<CopyToClipboard text={rawWallet?.address0}>
|
||||
<AddressBox>
|
||||
{rawWallet?.address0?.slice(0, 6)}...
|
||||
@@ -1514,7 +1498,7 @@ function App() {
|
||||
textTransform: 'uppercase',
|
||||
}}
|
||||
>
|
||||
{t('core:logout')}
|
||||
{t('core:action.logout')}
|
||||
</span>
|
||||
}
|
||||
placement="left"
|
||||
@@ -1869,7 +1853,7 @@ function App() {
|
||||
textTransform: 'uppercase',
|
||||
}}
|
||||
>
|
||||
{t('core:backup_wallet')}
|
||||
{t('core:action.backup_wallet')}
|
||||
</span>
|
||||
}
|
||||
placement="left"
|
||||
@@ -1903,10 +1887,6 @@ function App() {
|
||||
<AppContainer
|
||||
sx={{
|
||||
height: '100vh',
|
||||
// backgroundImage: desktopViewMode === "apps" && 'url("appsBg.svg")',
|
||||
// backgroundSize: desktopViewMode === "apps" && "cover",
|
||||
// backgroundPosition: desktopViewMode === "apps" && "center",
|
||||
// backgroundRepeat: desktopViewMode === "apps" && "no-repeat",
|
||||
}}
|
||||
>
|
||||
<PdfViewer />
|
||||
@@ -2036,7 +2016,6 @@ function App() {
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{isShowQortalRequest && !isMainWindow && (
|
||||
<>
|
||||
<Spacer height="120px" />
|
||||
@@ -2319,7 +2298,6 @@ function App() {
|
||||
<ErrorText>{sendPaymentError}</ErrorText>
|
||||
</>
|
||||
)}
|
||||
|
||||
{extState === 'web-app-request-payment' && !isMainWindow && (
|
||||
<>
|
||||
<Spacer height="100px" />
|
||||
@@ -2953,7 +2931,9 @@ function App() {
|
||||
});
|
||||
}}
|
||||
>
|
||||
Backup Account
|
||||
{t('core:action.backup_account', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</CustomButton>
|
||||
</>
|
||||
)}
|
||||
@@ -2981,7 +2961,9 @@ function App() {
|
||||
lineHeight: '15px',
|
||||
}}
|
||||
>
|
||||
The transfer was succesful!
|
||||
{t('core:message.success.transfer', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</TextP>
|
||||
<Spacer height="100px" />
|
||||
<CustomButton
|
||||
@@ -2989,7 +2971,7 @@ function App() {
|
||||
returnToMain();
|
||||
}}
|
||||
>
|
||||
Continue
|
||||
{t('core:action.continue', { postProcess: 'capitalize' })}
|
||||
</CustomButton>
|
||||
</Box>
|
||||
)}
|
||||
@@ -3004,7 +2986,9 @@ function App() {
|
||||
lineHeight: '15px',
|
||||
}}
|
||||
>
|
||||
The transfer was succesful!
|
||||
{t('core:message.success.transfer', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</TextP>
|
||||
<Spacer height="100px" />
|
||||
<CustomButton
|
||||
@@ -3012,7 +2996,7 @@ function App() {
|
||||
window.close();
|
||||
}}
|
||||
>
|
||||
Continue
|
||||
{t('core:action.continue', { postProcess: 'capitalize' })}
|
||||
</CustomButton>
|
||||
</>
|
||||
)}
|
||||
@@ -3027,7 +3011,9 @@ function App() {
|
||||
lineHeight: '15px',
|
||||
}}
|
||||
>
|
||||
Your buy order was submitted
|
||||
{t('core:message.success.order_submitted', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</TextP>
|
||||
<Spacer height="100px" />
|
||||
<CustomButton
|
||||
@@ -3035,10 +3021,11 @@ function App() {
|
||||
window.close();
|
||||
}}
|
||||
>
|
||||
Close
|
||||
{t('core:action.close', { postProcess: 'capitalize' })}
|
||||
</CustomButton>
|
||||
</>
|
||||
)}
|
||||
|
||||
{countdown && (
|
||||
<Box
|
||||
style={{
|
||||
@@ -3082,12 +3069,18 @@ function App() {
|
||||
</DialogContentText>
|
||||
{message?.paymentFee && (
|
||||
<DialogContentText id="alert-dialog-description2">
|
||||
payment fee: {message.paymentFee}
|
||||
{t('core:fee.payment', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
: {message.paymentFee}
|
||||
</DialogContentText>
|
||||
)}
|
||||
{message?.publishFee && (
|
||||
<DialogContentText id="alert-dialog-description2">
|
||||
publish fee: {message.publishFee}
|
||||
{t('core:fee.publish', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
: {message.publishFee}
|
||||
</DialogContentText>
|
||||
)}
|
||||
</DialogContent>
|
||||
@@ -3108,7 +3101,9 @@ function App() {
|
||||
onClick={onOk}
|
||||
autoFocus
|
||||
>
|
||||
accept
|
||||
{t('core:action.accept', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Button>
|
||||
<Button
|
||||
sx={{
|
||||
@@ -3125,7 +3120,9 @@ function App() {
|
||||
variant="contained"
|
||||
onClick={onCancel}
|
||||
>
|
||||
decline
|
||||
{t('core:action.decline', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
@@ -3146,7 +3143,9 @@ function App() {
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button variant="contained" onClick={onOkInfo} autoFocus>
|
||||
Close
|
||||
{t('core:action.close', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
@@ -3165,14 +3164,18 @@ function App() {
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button variant="contained" onClick={onCancelUnsavedChanges}>
|
||||
Cancel
|
||||
{t('core:action.cancel', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={onOkUnsavedChanges}
|
||||
autoFocus
|
||||
>
|
||||
Continue to Logout
|
||||
{t('core:action.decline', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
@@ -3443,7 +3446,9 @@ function App() {
|
||||
label={
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Typography sx={{ fontSize: '14px' }}>
|
||||
I have read this request
|
||||
{t('core:message.success.request_read', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Typography>
|
||||
<PriorityHighIcon color="warning" />
|
||||
</Box>
|
||||
@@ -3454,8 +3459,8 @@ function App() {
|
||||
<Spacer height="29px" />
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
gap: '14px',
|
||||
}}
|
||||
>
|
||||
@@ -3491,7 +3496,9 @@ function App() {
|
||||
onOkQortalRequestExtension('accepted');
|
||||
}}
|
||||
>
|
||||
accept
|
||||
{t('core:action.accept', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</CustomButtonAccept>
|
||||
<CustomButtonAccept
|
||||
color="black"
|
||||
@@ -3501,7 +3508,9 @@ function App() {
|
||||
}}
|
||||
onClick={() => onCancelQortalRequestExtension()}
|
||||
>
|
||||
decline
|
||||
{t('core:action.decline', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</CustomButtonAccept>
|
||||
</Box>
|
||||
<ErrorText>{sendPaymentError}</ErrorText>
|
||||
@@ -3566,6 +3575,7 @@ function App() {
|
||||
/>
|
||||
)}
|
||||
|
||||
<LanguageSelector />
|
||||
<ThemeSelector />
|
||||
</AppContainer>
|
||||
);
|
||||
|
@@ -31,6 +31,7 @@ import { GlobalContext } from '../App';
|
||||
import Tooltip, { TooltipProps, tooltipClasses } from '@mui/material/Tooltip';
|
||||
import ThemeSelector from '../components/Theme/ThemeSelector';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import LanguageSelector from '../components/Language/LanguageSelector';
|
||||
|
||||
const manifestData = {
|
||||
version: '0.5.3',
|
||||
@@ -510,14 +511,8 @@ export const NotAuthenticated = ({
|
||||
fontSize: '16px',
|
||||
}}
|
||||
>
|
||||
Your wallet is like your digital ID on Qortal, and is how you
|
||||
will login to the Qortal User Interface. It holds your public
|
||||
address and the Qortal name you will eventually choose. Every
|
||||
transaction you make is linked to your ID, and this is where you
|
||||
manage all your QORT and other tradeable cryptocurrencies on
|
||||
Qortal.
|
||||
</Typography>{' '}
|
||||
// TODO translate
|
||||
{t('auth:tips.digital_id', { postProcess: 'capitalize' })}
|
||||
</Typography>
|
||||
</React.Fragment>
|
||||
}
|
||||
>
|
||||
@@ -547,9 +542,8 @@ export const NotAuthenticated = ({
|
||||
fontSize: '18px',
|
||||
}}
|
||||
>
|
||||
New users start here!
|
||||
</Typography>{' '}
|
||||
// TODO translate
|
||||
{t('auth:tips.new_users', { postProcess: 'capitalize' })}
|
||||
</Typography>
|
||||
<Spacer height="10px" />
|
||||
<Typography
|
||||
color="inherit"
|
||||
@@ -557,12 +551,8 @@ export const NotAuthenticated = ({
|
||||
fontSize: '16px',
|
||||
}}
|
||||
>
|
||||
Creating an account means creating a new wallet and digital ID
|
||||
to start using Qortal. Once you have made your account, you can
|
||||
start doing things like obtaining some QORT, buying a name and
|
||||
avatar, publishing videos and blogs, and much more.
|
||||
</Typography>{' '}
|
||||
// TODO translate
|
||||
{t('auth:tips.new_account', { postProcess: 'capitalize' })}
|
||||
</Typography>
|
||||
</React.Fragment>
|
||||
}
|
||||
>
|
||||
@@ -816,7 +806,7 @@ export const NotAuthenticated = ({
|
||||
}}
|
||||
variant="contained"
|
||||
>
|
||||
{t('core:choose', { postProcess: 'capitalize' })}
|
||||
{t('core:action.choose', { postProcess: 'capitalize' })}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -875,7 +865,9 @@ export const NotAuthenticated = ({
|
||||
}}
|
||||
variant="contained"
|
||||
>
|
||||
{t('core:choose', { postProcess: 'capitalize' })}
|
||||
{t('core:action.choose', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@@ -888,7 +880,9 @@ export const NotAuthenticated = ({
|
||||
}}
|
||||
variant="contained"
|
||||
>
|
||||
{t('core:edit', { postProcess: 'capitalize' })}
|
||||
{t('core:action.edit', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@@ -940,7 +934,7 @@ export const NotAuthenticated = ({
|
||||
<DialogActions>
|
||||
{mode === 'list' && (
|
||||
<Button variant="contained" onClick={addCustomNode}>
|
||||
{t('core:add', { postProcess: 'capitalize' })}
|
||||
{t('core:action.add', { postProcess: 'capitalize' })}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -953,7 +947,7 @@ export const NotAuthenticated = ({
|
||||
}}
|
||||
autoFocus
|
||||
>
|
||||
{t('core:close', { postProcess: 'capitalize' })}
|
||||
{t('core:action.close', { postProcess: 'capitalize' })}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
@@ -1075,7 +1069,7 @@ export const NotAuthenticated = ({
|
||||
setShowSelectApiKey(false);
|
||||
}}
|
||||
>
|
||||
{t('core:close', { postProcess: 'capitalize' })}
|
||||
{t('core:action.close', { postProcess: 'capitalize' })}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
@@ -1097,6 +1091,7 @@ export const NotAuthenticated = ({
|
||||
/>
|
||||
</ButtonBase>
|
||||
|
||||
<LanguageSelector />
|
||||
<ThemeSelector />
|
||||
</>
|
||||
);
|
||||
|
@@ -15,6 +15,7 @@ import { extractComponents } from '../Chat/MessageDisplay';
|
||||
import ArrowOutwardIcon from '@mui/icons-material/ArrowOutward';
|
||||
import { AppsPrivate } from './AppsPrivate';
|
||||
import ThemeSelector from '../Theme/ThemeSelector';
|
||||
import LanguageSelector from '../Language/LanguageSelector';
|
||||
|
||||
export const AppsHomeDesktop = ({
|
||||
setMode,
|
||||
@@ -157,6 +158,7 @@ export const AppsHomeDesktop = ({
|
||||
/>
|
||||
</AppsContainer>
|
||||
|
||||
<LanguageSelector />
|
||||
<ThemeSelector />
|
||||
</>
|
||||
);
|
||||
|
@@ -79,21 +79,21 @@ export const CoreSyncStatus = () => {
|
||||
|
||||
if (isMintingPossible && !isUsingGateway) {
|
||||
imagePath = syncedMintingImg;
|
||||
message = `${t(`core:result.status.${isSynchronizing ? 'synchronizing' : 'synchronized'}`, { postProcess: 'capitalize' })} ${t('core:result.status.minting')}`;
|
||||
message = `${t(`core:message.status.${isSynchronizing ? 'synchronizing' : 'synchronized'}`, { postProcess: 'capitalize' })} ${t('core:message.status.minting')}`;
|
||||
} else if (isSynchronizing === true && syncPercent === 99) {
|
||||
imagePath = syncingImg;
|
||||
} else if (isSynchronizing && !isMintingPossible && syncPercent === 100) {
|
||||
imagePath = syncingImg;
|
||||
message = `${t('core:result.status.synchronizing', { postProcess: 'capitalize' })} ${!isUsingGateway ? t('core:result.status.not_minting') : ''}`;
|
||||
message = `${t('core:message.status.synchronizing', { postProcess: 'capitalize' })} ${!isUsingGateway ? t('core:message.status.not_minting') : ''}`;
|
||||
} else if (!isSynchronizing && !isMintingPossible && syncPercent === 100) {
|
||||
imagePath = syncedImg;
|
||||
message = `${t('core:result.status.synchronized', { postProcess: 'capitalize' })} ${!isUsingGateway ? t('core:result.status.not_minting') : ''}`;
|
||||
message = `${t('core:message.status.synchronized', { postProcess: 'capitalize' })} ${!isUsingGateway ? t('core:message.status.not_minting') : ''}`;
|
||||
} else if (isSynchronizing && isMintingPossible && syncPercent === 100) {
|
||||
imagePath = syncingImg;
|
||||
message = `${t('core:result.status.synchronizing', { postProcess: 'capitalize' })} ${!isUsingGateway ? t('core:result.status.minting') : ''}`;
|
||||
message = `${t('core:message.status.synchronizing', { postProcess: 'capitalize' })} ${!isUsingGateway ? t('core:message.status.minting') : ''}`;
|
||||
} else if (!isSynchronizing && isMintingPossible && syncPercent === 100) {
|
||||
imagePath = syncedMintingImg;
|
||||
message = `${t('core:result.status.synchronized', { postProcess: 'capitalize' })} ${!isUsingGateway ? t('core:result.status.minting') : ''}`;
|
||||
message = `${t('core:message.status.synchronized', { postProcess: 'capitalize' })} ${!isUsingGateway ? t('core:message.status.minting') : ''}`;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@@ -8,6 +8,7 @@ import { enabledDevModeAtom } from '../atoms/global';
|
||||
import { AppsIcon } from '../assets/Icons/AppsIcon';
|
||||
import ThemeSelector from './Theme/ThemeSelector';
|
||||
import { CoreSyncStatus } from './CoreSyncStatus';
|
||||
import LanguageSelector from './Language/LanguageSelector';
|
||||
|
||||
export const DesktopSideBar = ({
|
||||
goToHome,
|
||||
@@ -143,6 +144,7 @@ export const DesktopSideBar = ({
|
||||
</ButtonBase>
|
||||
)}
|
||||
|
||||
<LanguageSelector />
|
||||
<ThemeSelector />
|
||||
</Box>
|
||||
);
|
||||
|
@@ -1,4 +1,13 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
forwardRef,
|
||||
Fragment,
|
||||
ReactElement,
|
||||
Ref,
|
||||
SyntheticEvent,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import Button from '@mui/material/Button';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import AppBar from '@mui/material/AppBar';
|
||||
@@ -28,6 +37,7 @@ import { CustomizedSnackbars } from '../Snackbar/Snackbar';
|
||||
import { getFee } from '../../background';
|
||||
import { MyContext } from '../../App';
|
||||
import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const Label = styled('label')`
|
||||
display: block;
|
||||
@@ -37,30 +47,29 @@ export const Label = styled('label')`
|
||||
margin-bottom: 4px;
|
||||
`;
|
||||
|
||||
const Transition = React.forwardRef(function Transition(
|
||||
const Transition = forwardRef(function Transition(
|
||||
props: TransitionProps & {
|
||||
children: React.ReactElement;
|
||||
children: ReactElement;
|
||||
},
|
||||
ref: React.Ref<unknown>
|
||||
ref: Ref<unknown>
|
||||
) {
|
||||
return <Slide direction="up" ref={ref} {...props} />;
|
||||
});
|
||||
|
||||
export const AddGroup = ({ address, open, setOpen }) => {
|
||||
const { show, setTxList } = React.useContext(MyContext);
|
||||
const [tab, setTab] = React.useState('create');
|
||||
const [openAdvance, setOpenAdvance] = React.useState(false);
|
||||
const [name, setName] = React.useState('');
|
||||
const [description, setDescription] = React.useState('');
|
||||
const [groupType, setGroupType] = React.useState('1');
|
||||
const [approvalThreshold, setApprovalThreshold] = React.useState('40');
|
||||
const [minBlock, setMinBlock] = React.useState('5');
|
||||
const [maxBlock, setMaxBlock] = React.useState('21600');
|
||||
const [value, setValue] = React.useState(0);
|
||||
const [openSnack, setOpenSnack] = React.useState(false);
|
||||
const [infoSnack, setInfoSnack] = React.useState(null);
|
||||
const { show, setTxList } = useContext(MyContext);
|
||||
const [openAdvance, setOpenAdvance] = useState(false);
|
||||
const [name, setName] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
const [groupType, setGroupType] = useState('1');
|
||||
const [approvalThreshold, setApprovalThreshold] = useState('40');
|
||||
const [minBlock, setMinBlock] = useState('5');
|
||||
const [maxBlock, setMaxBlock] = useState('21600');
|
||||
const [value, setValue] = useState(0);
|
||||
const [openSnack, setOpenSnack] = useState(false);
|
||||
const [infoSnack, setInfoSnack] = useState(null);
|
||||
|
||||
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
|
||||
const handleChange = (event: SyntheticEvent, newValue: number) => {
|
||||
setValue(newValue);
|
||||
};
|
||||
|
||||
@@ -84,16 +93,30 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
setMaxBlock(event.target.value as string);
|
||||
};
|
||||
|
||||
const { t } = useTranslation(['core', 'group']);
|
||||
const theme = useTheme();
|
||||
|
||||
const handleCreateGroup = async () => {
|
||||
try {
|
||||
if (!name) throw new Error('Please provide a name');
|
||||
if (!description) throw new Error('Please provide a description');
|
||||
if (!name)
|
||||
throw new Error(
|
||||
t('group:message.error.name_required', {
|
||||
postProcess: 'capitalize',
|
||||
})
|
||||
);
|
||||
if (!description)
|
||||
throw new Error(
|
||||
t('group:message.error.description_required', {
|
||||
postProcess: 'capitalize',
|
||||
})
|
||||
);
|
||||
|
||||
const fee = await getFee('CREATE_GROUP');
|
||||
|
||||
const fee = await getFee('CREATE_GROUP'); // TODO translate
|
||||
await show({
|
||||
message: 'Would you like to perform an CREATE_GROUP transaction?',
|
||||
message: t('group:question.create_group', {
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
publishFee: fee.fee + ' QORT',
|
||||
});
|
||||
|
||||
@@ -111,16 +134,23 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
if (!response?.error) {
|
||||
setInfoSnack({
|
||||
type: 'success',
|
||||
message:
|
||||
'Successfully created group. It may take a couple of minutes for the changes to propagate',
|
||||
message: t('group:message.success.group_creation', {
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
});
|
||||
setOpenSnack(true);
|
||||
setTxList((prev) => [
|
||||
{
|
||||
...response,
|
||||
type: 'created-group',
|
||||
label: `Created group ${name}: awaiting confirmation`,
|
||||
labelDone: `Created group ${name}: success!`,
|
||||
label: t('group:message.success.group_creation_name', {
|
||||
group_name: name,
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
labelDone: t('group:message.success.group_creation_label', {
|
||||
group_name: name,
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
done: false,
|
||||
},
|
||||
...prev,
|
||||
@@ -131,7 +161,11 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
rej({ message: response.error });
|
||||
})
|
||||
.catch((error) => {
|
||||
rej({ message: error.message || 'An error occurred' });
|
||||
rej({
|
||||
message:
|
||||
error.message ||
|
||||
t('core:message.error.generic', { postProcess: 'capitalize' }),
|
||||
});
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -143,22 +177,6 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
}
|
||||
};
|
||||
|
||||
function CustomTabPanel(props: TabPanelProps) {
|
||||
const { children, value, index, ...other } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
role="tabpanel"
|
||||
hidden={value !== index}
|
||||
id={`simple-tabpanel-${index}`}
|
||||
aria-labelledby={`simple-tab-${index}`}
|
||||
{...other}
|
||||
>
|
||||
{value === index && <Box sx={{ p: 3 }}>{children}</Box>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function a11yProps(index: number) {
|
||||
return {
|
||||
id: `simple-tab-${index}`,
|
||||
@@ -170,7 +188,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
setValue(2);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
subscribeToEvent('openGroupInvitesRequest', openGroupInvitesRequestFunc);
|
||||
|
||||
return () => {
|
||||
@@ -182,7 +200,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Fragment>
|
||||
<Dialog
|
||||
fullScreen
|
||||
open={open}
|
||||
@@ -197,7 +215,7 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
>
|
||||
<Toolbar>
|
||||
<Typography sx={{ ml: 2, flex: 1 }} variant="h4" component="div">
|
||||
Group Management
|
||||
{t('group:group.management', { postProcess: 'capitalize' })}
|
||||
</Typography>
|
||||
|
||||
<IconButton
|
||||
@@ -208,12 +226,9 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
|
||||
{/* <Button autoFocus color="inherit" onClick={handleClose}>
|
||||
save
|
||||
</Button> */}
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
bgcolor: theme.palette.background.default,
|
||||
@@ -241,7 +256,9 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
}}
|
||||
>
|
||||
<Tab
|
||||
label="Create Group"
|
||||
label={t('group:action.create_group', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
{...a11yProps(0)}
|
||||
sx={{
|
||||
'&.Mui-selected': {
|
||||
@@ -251,7 +268,9 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
}}
|
||||
/>
|
||||
<Tab
|
||||
label="Find Group"
|
||||
label={t('group:action.find_group', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
{...a11yProps(1)}
|
||||
sx={{
|
||||
'&.Mui-selected': {
|
||||
@@ -261,7 +280,9 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
}}
|
||||
/>
|
||||
<Tab
|
||||
label="Group Invites"
|
||||
label={t('group:group.invites', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
{...a11yProps(2)}
|
||||
sx={{
|
||||
'&.Mui-selected': {
|
||||
@@ -295,9 +316,15 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
gap: '5px',
|
||||
}}
|
||||
>
|
||||
<Label>Name of group</Label>
|
||||
<Label>
|
||||
{t('group:group.name', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Label>
|
||||
<Input
|
||||
placeholder="Name of group"
|
||||
placeholder={t('group:group.name', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
@@ -309,14 +336,21 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
gap: '5px',
|
||||
}}
|
||||
>
|
||||
<Label>Description of group</Label>
|
||||
<Label>
|
||||
{t('group:group.description', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Label>
|
||||
|
||||
<Input
|
||||
placeholder="Description of group"
|
||||
placeholder={t('group:group.description', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
@@ -324,7 +358,13 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
gap: '5px',
|
||||
}}
|
||||
>
|
||||
<Label>Group type</Label>
|
||||
<Label>
|
||||
{' '}
|
||||
{t('group:group.type', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Label>
|
||||
|
||||
<Select
|
||||
labelId="demo-simple-select-label"
|
||||
id="demo-simple-select"
|
||||
@@ -332,12 +372,19 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
label="Group Type"
|
||||
onChange={handleChangeGroupType}
|
||||
>
|
||||
<MenuItem value={1}>Open (public)</MenuItem>
|
||||
<MenuItem value={1}>
|
||||
{t('group:group.open', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</MenuItem>
|
||||
<MenuItem value={0}>
|
||||
Closed (private) - users need permission to join
|
||||
{t('group:group.closed', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</MenuItem>
|
||||
</Select>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
@@ -347,10 +394,15 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
}}
|
||||
onClick={() => setOpenAdvance((prev) => !prev)}
|
||||
>
|
||||
<Typography>Advanced options</Typography>
|
||||
<Typography>
|
||||
{t('group:advanced_options', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Typography>
|
||||
|
||||
{openAdvance ? <ExpandLess /> : <ExpandMore />}
|
||||
</Box>
|
||||
|
||||
<Collapse in={openAdvance} timeout="auto" unmountOnExit>
|
||||
<Box
|
||||
sx={{
|
||||
@@ -360,8 +412,9 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
}}
|
||||
>
|
||||
<Label>
|
||||
Group Approval Threshold (number / percentage of Admins
|
||||
that must approve a transaction)
|
||||
{t('group:approval_threshold', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Label>
|
||||
<Select
|
||||
labelId="demo-simple-select-label"
|
||||
@@ -370,14 +423,21 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
label="Group Approval Threshold"
|
||||
onChange={handleChangeApprovalThreshold}
|
||||
>
|
||||
<MenuItem value={0}>NONE</MenuItem>
|
||||
<MenuItem value={1}>ONE </MenuItem>
|
||||
|
||||
<MenuItem value={20}>20% </MenuItem>
|
||||
<MenuItem value={40}>40% </MenuItem>
|
||||
<MenuItem value={60}>60% </MenuItem>
|
||||
<MenuItem value={80}>80% </MenuItem>
|
||||
<MenuItem value={100}>100% </MenuItem>
|
||||
<MenuItem value={0}>
|
||||
{t('core.count.none', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</MenuItem>
|
||||
<MenuItem value={1}>
|
||||
{t('core.count.one', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</MenuItem>
|
||||
<MenuItem value={20}>20%</MenuItem>
|
||||
<MenuItem value={40}>40%</MenuItem>
|
||||
<MenuItem value={60}>60%</MenuItem>
|
||||
<MenuItem value={80}>80%</MenuItem>
|
||||
<MenuItem value={100}>100%</MenuItem>
|
||||
</Select>
|
||||
</Box>
|
||||
<Box
|
||||
@@ -388,7 +448,9 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
}}
|
||||
>
|
||||
<Label>
|
||||
Minimum Block delay for Group Transaction Approvals
|
||||
{t('group.block_delay.minimum', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Label>
|
||||
<Select
|
||||
labelId="demo-simple-select-label"
|
||||
@@ -397,18 +459,42 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
label="Minimum Block delay"
|
||||
onChange={handleChangeMinBlock}
|
||||
>
|
||||
<MenuItem value={5}>5 minutes</MenuItem>
|
||||
<MenuItem value={10}>10 minutes</MenuItem>
|
||||
<MenuItem value={30}>30 minutes</MenuItem>
|
||||
<MenuItem value={60}>1 hour</MenuItem>
|
||||
<MenuItem value={180}>3 hours</MenuItem>
|
||||
<MenuItem value={300}>5 hours</MenuItem>
|
||||
<MenuItem value={420}>7 hours</MenuItem>
|
||||
<MenuItem value={720}>12 hours</MenuItem>
|
||||
<MenuItem value={1440}>1 day</MenuItem>
|
||||
<MenuItem value={4320}>3 days</MenuItem>
|
||||
<MenuItem value={7200}>5 days</MenuItem>
|
||||
<MenuItem value={10080}>7 days</MenuItem>
|
||||
<MenuItem value={5}>
|
||||
{t('core.time.minute', { count: 5 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={10}>
|
||||
{t('core.time.minute', { count: 10 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={30}>
|
||||
{t('core.time.minute', { count: 30 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={60}>
|
||||
{t('core.time.hour', { count: 1 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={180}>
|
||||
{t('core.time.hour', { count: 3 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={300}>
|
||||
{t('core.time.hour', { count: 5 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={420}>
|
||||
{t('core.time.hour', { count: 7 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={720}>
|
||||
{t('core.time.hour', { count: 12 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={1440}>
|
||||
{t('core.time.day', { count: 1 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={4320}>
|
||||
{t('core.time.day', { count: 3 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={7200}>
|
||||
{t('core.time.day', { count: 5 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={10080}>
|
||||
{t('core.time.day', { count: 7 })}
|
||||
</MenuItem>
|
||||
</Select>
|
||||
</Box>
|
||||
<Box
|
||||
@@ -419,7 +505,9 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
}}
|
||||
>
|
||||
<Label>
|
||||
Maximum Block delay for Group Transaction Approvals
|
||||
{t('group.block_delay.maximum', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Label>
|
||||
<Select
|
||||
labelId="demo-simple-select-label"
|
||||
@@ -428,17 +516,39 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
label="Maximum Block delay"
|
||||
onChange={handleChangeMaxBlock}
|
||||
>
|
||||
<MenuItem value={60}>1 hour</MenuItem>
|
||||
<MenuItem value={180}>3 hours</MenuItem>
|
||||
<MenuItem value={300}>5 hours</MenuItem>
|
||||
<MenuItem value={420}>7 hours</MenuItem>
|
||||
<MenuItem value={720}>12 hours</MenuItem>
|
||||
<MenuItem value={1440}>1 day</MenuItem>
|
||||
<MenuItem value={4320}>3 days</MenuItem>
|
||||
<MenuItem value={7200}>5 days</MenuItem>
|
||||
<MenuItem value={10080}>7 days</MenuItem>
|
||||
<MenuItem value={14400}>10 days</MenuItem>
|
||||
<MenuItem value={21600}>15 days</MenuItem>
|
||||
<MenuItem value={60}>
|
||||
{t('core.time.hour', { count: 1 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={180}>
|
||||
3{t('core.time.hour', { count: 3 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={300}>
|
||||
{t('core.time.hour', { count: 5 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={420}>
|
||||
{t('core.time.hour', { count: 7 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={720}>
|
||||
{t('core.time.hour', { count: 12 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={1440}>
|
||||
{t('core.time.day', { count: 1 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={4320}>
|
||||
{t('core.time.day', { count: 3 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={7200}>
|
||||
{t('core.time.day', { count: 5 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={10080}>
|
||||
{t('core.time.day', { count: 7 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={14400}>
|
||||
{t('core.time.day', { count: 10 })}
|
||||
</MenuItem>
|
||||
<MenuItem value={21600}>
|
||||
{t('core.time.day', { count: 15 })}
|
||||
</MenuItem>
|
||||
</Select>
|
||||
</Box>
|
||||
</Collapse>
|
||||
@@ -454,7 +564,9 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
color="primary"
|
||||
onClick={handleCreateGroup}
|
||||
>
|
||||
Create Group
|
||||
{t('group.action.create', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -503,6 +615,6 @@ export const AddGroup = ({ address, open, setOpen }) => {
|
||||
setInfo={setInfoSnack}
|
||||
/>
|
||||
</Dialog>
|
||||
</React.Fragment>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
ListItemText,
|
||||
@@ -8,7 +7,7 @@ import {
|
||||
TextField,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import React, {
|
||||
import {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
@@ -25,10 +24,12 @@ import {
|
||||
import _ from 'lodash';
|
||||
import { MyContext, getBaseApiReact } from '../../App';
|
||||
import { LoadingButton } from '@mui/lab';
|
||||
import { getBaseApi, getFee } from '../../background';
|
||||
import { getFee } from '../../background';
|
||||
import LockIcon from '@mui/icons-material/Lock';
|
||||
import NoEncryptionGmailerrorredIcon from '@mui/icons-material/NoEncryptionGmailerrorred';
|
||||
import { Spacer } from '../../common/Spacer';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const cache = new CellMeasurerCache({
|
||||
fixedWidth: true,
|
||||
defaultHeight: 50,
|
||||
@@ -36,7 +37,7 @@ const cache = new CellMeasurerCache({
|
||||
|
||||
export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
|
||||
const { memberGroups, show, setTxList } = useContext(MyContext);
|
||||
|
||||
const { t } = useTranslation(['core', 'group']);
|
||||
const [groups, setGroups] = useState([]);
|
||||
const [popoverAnchor, setPopoverAnchor] = useState(null); // Track which list item the popover is anchored to
|
||||
const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open
|
||||
@@ -101,12 +102,17 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
|
||||
const handleJoinGroup = async (group, isOpen) => {
|
||||
try {
|
||||
const groupId = group.groupId;
|
||||
const fee = await getFee('JOIN_GROUP'); // TODO translate
|
||||
|
||||
const fee = await getFee('JOIN_GROUP');
|
||||
|
||||
await show({
|
||||
message: 'Would you like to perform an JOIN_GROUP transaction?',
|
||||
message: t('group:question.join_group', {
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
publishFee: fee.fee + ' QORT',
|
||||
});
|
||||
setIsLoading(true);
|
||||
|
||||
await new Promise((res, rej) => {
|
||||
window
|
||||
.sendMessage('joinGroup', {
|
||||
@@ -116,8 +122,9 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
|
||||
if (!response?.error) {
|
||||
setInfoSnack({
|
||||
type: 'success',
|
||||
message:
|
||||
'Successfully requested to join group. It may take a couple of minutes for the changes to propagate',
|
||||
message: t('group:message.success.join_group', {
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
});
|
||||
|
||||
if (isOpen) {
|
||||
@@ -125,8 +132,14 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
|
||||
{
|
||||
...response,
|
||||
type: 'joined-group',
|
||||
label: `Joined Group ${group?.groupName}: awaiting confirmation`,
|
||||
labelDone: `Joined Group ${group?.groupName}: success!`,
|
||||
label: t('group:message.success.group_join_label', {
|
||||
group_name: group?.groupName,
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
labelDone: t('group:message.success.group_join_label', {
|
||||
group_name: group?.groupName,
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
done: false,
|
||||
groupId,
|
||||
},
|
||||
@@ -215,7 +228,10 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
|
||||
padding: '10px',
|
||||
}}
|
||||
>
|
||||
<Typography>Join {group?.groupName}</Typography>
|
||||
<Typography>
|
||||
{t('core:action.join', { postProcess: 'capitalize' })}{' '}
|
||||
{group?.groupName}
|
||||
</Typography>
|
||||
<Typography>
|
||||
{group?.isOpen === false &&
|
||||
'This is a closed/private group, so you will need to wait until an admin accepts your request'}
|
||||
@@ -226,7 +242,9 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => {
|
||||
variant="contained"
|
||||
onClick={() => handleJoinGroup(group, group?.isOpen)}
|
||||
>
|
||||
Join group
|
||||
{t('group:action.join_group', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</LoadingButton>
|
||||
</Box>
|
||||
</Popover>
|
||||
|
@@ -129,7 +129,7 @@ export const BlockedUsersModal = () => {
|
||||
executeEvent('updateChatMessagesWithBlocks', true);
|
||||
}
|
||||
} catch (error) {
|
||||
setOpenSnackGlobal(true); // TODO translate
|
||||
setOpenSnackGlobal(true);
|
||||
setInfoSnackCustom({
|
||||
type: 'error',
|
||||
message: error?.message || 'Unable to block user',
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import React, {
|
||||
FC,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
@@ -17,7 +16,6 @@ import {
|
||||
ComposeIcon,
|
||||
ComposeP,
|
||||
GroupContainer,
|
||||
GroupNameP,
|
||||
InstanceFooter,
|
||||
InstanceListContainer,
|
||||
InstanceListContainerRow,
|
||||
@@ -58,10 +56,12 @@ import { executeEvent } from '../../../utils/events';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import { getArbitraryEndpointReact, getBaseApiReact } from '../../../App';
|
||||
import { addDataPublishesFunc, getDataPublishesFunc } from '../Group';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const filterOptions = ['Recently active', 'Newest', 'Oldest'];
|
||||
|
||||
export const threadIdentifier = 'DOCUMENT';
|
||||
|
||||
export const GroupMail = ({
|
||||
selectedGroup,
|
||||
userInfo,
|
||||
@@ -82,6 +82,7 @@ export const GroupMail = ({
|
||||
const anchorElInstanceFilter = useRef<any>(null);
|
||||
const [tempPublishedList, setTempPublishedList] = useState([]);
|
||||
const dataPublishes = useRef({});
|
||||
const { t } = useTranslation(['core']);
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const groupIdRef = useRef<any>(null);
|
||||
@@ -120,7 +121,9 @@ export const GroupMail = ({
|
||||
});
|
||||
setTempPublishedList(tempData);
|
||||
}
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const getEncryptedResource = async (
|
||||
@@ -627,9 +630,9 @@ export const GroupMail = ({
|
||||
<ThreadContainer>
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<NewThread
|
||||
@@ -667,8 +670,8 @@ export const GroupMail = ({
|
||||
<Spacer height="30px" />
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
@@ -682,6 +685,7 @@ export const GroupMail = ({
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Spacer height="30px" />
|
||||
|
||||
{combinedListTempAndReal.map((thread) => {
|
||||
@@ -754,8 +758,8 @@ export const GroupMail = ({
|
||||
{filterMode === 'Recently active' && (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
}}
|
||||
>
|
||||
<ThreadSingleLastMessageP>
|
||||
@@ -776,16 +780,16 @@ export const GroupMail = ({
|
||||
}, 300);
|
||||
}}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
bottom: '2px',
|
||||
right: '2px',
|
||||
borderRadius: '5px',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#27282c',
|
||||
borderRadius: '5px',
|
||||
bottom: '2px',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
gap: '10px',
|
||||
alignItems: 'center',
|
||||
padding: '5px',
|
||||
cursor: 'pointer',
|
||||
position: 'absolute',
|
||||
right: '2px',
|
||||
'&:hover': {
|
||||
background: 'rgba(255, 255, 255, 0.60)',
|
||||
},
|
||||
@@ -795,9 +799,11 @@ export const GroupMail = ({
|
||||
sx={{
|
||||
color: 'white',
|
||||
fontSize: '12px',
|
||||
}} // TODO translate
|
||||
}}
|
||||
>
|
||||
Last page
|
||||
{t('core:page.last', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Typography>
|
||||
<ArrowForwardIosIcon
|
||||
sx={{
|
||||
@@ -828,7 +834,9 @@ export const GroupMail = ({
|
||||
<LoadingSnackbar
|
||||
open={isLoading}
|
||||
info={{
|
||||
message: 'Loading threads... please wait.',
|
||||
message: t('group:message.success.loading_threads', {
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
</GroupContainer>
|
||||
|
@@ -1,11 +1,9 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Box, CircularProgress, Input } from '@mui/material';
|
||||
import ShortUniqueId from 'short-unique-id';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import ModalCloseSVG from '../../../assets/svgs/ModalClose.svg';
|
||||
import ComposeIconSVG from '../../../assets/svgs/ComposeIcon.svg';
|
||||
import {
|
||||
AttachmentContainer,
|
||||
CloseContainer,
|
||||
ComposeContainer,
|
||||
ComposeIcon,
|
||||
@@ -30,6 +28,7 @@ import TipTap from '../../Chat/TipTap';
|
||||
import { MessageDisplay } from '../../Chat/MessageDisplay';
|
||||
import { CustomizedSnackbars } from '../../Snackbar/Snackbar';
|
||||
import { saveTempPublish } from '../../Chat/GroupAnnouncements';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const uid = new ShortUniqueId({ length: 8 });
|
||||
|
||||
@@ -129,6 +128,7 @@ export const encryptSingleFunc = async (data: string, secretKeyObject: any) => {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const NewThread = ({
|
||||
groupInfo,
|
||||
members,
|
||||
@@ -143,8 +143,8 @@ export const NewThread = ({
|
||||
setPostReply,
|
||||
isPrivate,
|
||||
}: NewMessageProps) => {
|
||||
const { t } = useTranslation(['core', 'group']);
|
||||
const { show } = React.useContext(MyContext);
|
||||
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
const [value, setValue] = useState('');
|
||||
const [isSending, setIsSending] = useState(false);
|
||||
@@ -183,21 +183,28 @@ export const NewThread = ({
|
||||
const missingFields: string[] = [];
|
||||
|
||||
if (!isMessage && !threadTitle) {
|
||||
errorMsg = 'Please provide a thread title';
|
||||
errorMsg = t('group:question.provide_thread', {
|
||||
postProcess: 'capitalize',
|
||||
});
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
errorMsg = 'Cannot send a message without a access to your name';
|
||||
errorMsg = t('group:message.error.access_name', {
|
||||
postProcess: 'capitalize',
|
||||
});
|
||||
}
|
||||
|
||||
if (!groupInfo) {
|
||||
errorMsg = 'Cannot access group information';
|
||||
} // TODO translate
|
||||
errorMsg = t('group:message.error.group_info', {
|
||||
postProcess: 'capitalize',
|
||||
});
|
||||
}
|
||||
|
||||
// if (!description) missingFields.push('subject')
|
||||
if (missingFields.length > 0) {
|
||||
const missingFieldsString = missingFields.join(', ');
|
||||
const errMsg = `Missing: ${missingFieldsString}`;
|
||||
errorMsg = errMsg;
|
||||
errorMsg = errMsg; // TODO translate
|
||||
}
|
||||
|
||||
if (errorMsg) {
|
||||
|
@@ -3,7 +3,6 @@ import { Avatar, Box, IconButton } from '@mui/material';
|
||||
import DOMPurify from 'dompurify';
|
||||
import FormatQuoteIcon from '@mui/icons-material/FormatQuote';
|
||||
import MoreSVG from '../../../assets/svgs/More.svg';
|
||||
|
||||
import {
|
||||
MoreImg,
|
||||
MoreP,
|
||||
@@ -38,16 +37,16 @@ export const ShowMessage = ({ message, openNewPostWithQuote, myName }: any) => {
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'flex-start',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
display: 'flex',
|
||||
gap: '10px',
|
||||
}}
|
||||
>
|
||||
@@ -67,6 +66,7 @@ export const ShowMessage = ({ message, openNewPostWithQuote, myName }: any) => {
|
||||
{message?.name?.charAt(0)}
|
||||
</Avatar>
|
||||
</WrapperUserAction>
|
||||
|
||||
<ThreadInfoColumn>
|
||||
<WrapperUserAction
|
||||
disabled={myName === message?.name}
|
||||
@@ -75,6 +75,7 @@ export const ShowMessage = ({ message, openNewPostWithQuote, myName }: any) => {
|
||||
>
|
||||
<ThreadInfoColumnNameP>{message?.name}</ThreadInfoColumnNameP>
|
||||
</WrapperUserAction>
|
||||
|
||||
<ThreadInfoColumnTime>
|
||||
{formatTimestampForum(message?.created)}
|
||||
</ThreadInfoColumnTime>
|
||||
@@ -205,6 +206,7 @@ export const ShowMessage = ({ message, openNewPostWithQuote, myName }: any) => {
|
||||
>
|
||||
{message?.reply?.name?.charAt(0)}
|
||||
</Avatar>
|
||||
|
||||
<ThreadInfoColumn>
|
||||
<ThreadInfoColumnNameP
|
||||
sx={{
|
||||
@@ -215,6 +217,7 @@ export const ShowMessage = ({ message, openNewPostWithQuote, myName }: any) => {
|
||||
</ThreadInfoColumnNameP>
|
||||
</ThreadInfoColumn>
|
||||
</Box>
|
||||
|
||||
<MessageDisplay htmlContent={message?.reply?.textContentV2} />
|
||||
</Box>
|
||||
<Spacer height="20px" />
|
||||
|
@@ -1,20 +1,11 @@
|
||||
import React, {
|
||||
FC,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
Button,
|
||||
ButtonBase,
|
||||
IconButton,
|
||||
Skeleton,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { Avatar, Box, Button, ButtonBase, Typography } from '@mui/material';
|
||||
import { ShowMessage } from './ShowMessageWithoutModal';
|
||||
import {
|
||||
ComposeP,
|
||||
@@ -51,8 +42,12 @@ import { RequestQueueWithPromise } from '../../../utils/queue/queue';
|
||||
import { CustomLoader } from '../../../common/CustomLoader';
|
||||
import { WrapperUserAction } from '../../WrapperUserAction';
|
||||
import { formatTimestampForum } from '../../../utils/time';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const requestQueueSaveToLocal = new RequestQueueWithPromise(1);
|
||||
|
||||
const requestQueueDownloadPost = new RequestQueueWithPromise(3);
|
||||
|
||||
interface ThreadProps {
|
||||
currentThread: any;
|
||||
groupInfo: any;
|
||||
@@ -120,6 +115,7 @@ export const Thread = ({
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [postReply, setPostReply] = useState(null);
|
||||
const [hasLastPage, setHasLastPage] = useState(false);
|
||||
const { t } = useTranslation(['core']);
|
||||
|
||||
// Update: Use a new ref for the scrollable container
|
||||
const threadContainerRef = useRef(null);
|
||||
@@ -251,6 +247,7 @@ export const Thread = ({
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
const responseData = await response.json();
|
||||
|
||||
let fullArrayMsg = [...responseData];
|
||||
@@ -431,6 +428,7 @@ export const Thread = ({
|
||||
}
|
||||
const newArray = responseData.slice(0, findMessage).reverse();
|
||||
let fullArrayMsg = [...messages];
|
||||
|
||||
for (const message of newArray) {
|
||||
try {
|
||||
const responseDataMessage = await getEncryptedResource({
|
||||
@@ -468,7 +466,6 @@ export const Thread = ({
|
||||
setMessages(fullArrayMsg);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
}
|
||||
},
|
||||
[messages]
|
||||
@@ -565,20 +562,20 @@ export const Thread = ({
|
||||
return (
|
||||
<GroupContainer
|
||||
sx={{
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
}}
|
||||
// Removed the ref from here since the scrollable area has changed
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexShrink: 0, // Corrected property name
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
<NewThread
|
||||
@@ -598,9 +595,9 @@ export const Thread = ({
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
gap: '35px',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<ShowMessageReturnButton
|
||||
@@ -610,7 +607,11 @@ export const Thread = ({
|
||||
}}
|
||||
>
|
||||
<MailIconImg src={ReturnSVG} />
|
||||
<ComposeP>Return to Threads</ComposeP>
|
||||
<ComposeP>
|
||||
{t('group:action.return_to_thread', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</ComposeP>
|
||||
</ShowMessageReturnButton>
|
||||
{/* Conditionally render the scroll buttons */}
|
||||
{showScrollButton &&
|
||||
@@ -658,6 +659,7 @@ export const Thread = ({
|
||||
>
|
||||
<GroupNameP>{currentThread?.threadData?.title}</GroupNameP>
|
||||
</Box>
|
||||
|
||||
<Spacer height={'15px'} />
|
||||
|
||||
<Box
|
||||
@@ -685,8 +687,9 @@ export const Thread = ({
|
||||
disabled={!hasFirstPage}
|
||||
variant="contained"
|
||||
>
|
||||
First
|
||||
{t('core:page.first', { postProcess: 'capitalize' })}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
sx={{
|
||||
textTransformation: 'capitalize',
|
||||
@@ -701,9 +704,9 @@ export const Thread = ({
|
||||
);
|
||||
}}
|
||||
disabled={!hasPreviousPage}
|
||||
variant="contained" // TODO translate
|
||||
variant="contained"
|
||||
>
|
||||
Previous
|
||||
{t('core:page.previous', { postProcess: 'capitalize' })}
|
||||
</Button>
|
||||
<Button
|
||||
sx={{
|
||||
@@ -721,7 +724,7 @@ export const Thread = ({
|
||||
disabled={!hasNextPage}
|
||||
variant="contained"
|
||||
>
|
||||
Next
|
||||
{t('core:page.next', { postProcess: 'capitalize' })}
|
||||
</Button>
|
||||
<Button
|
||||
sx={{
|
||||
@@ -739,7 +742,7 @@ export const Thread = ({
|
||||
disabled={!hasLastPage}
|
||||
variant="contained"
|
||||
>
|
||||
Last
|
||||
{t('core:page.last', { postProcess: 'capitalize' })}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
@@ -925,7 +928,7 @@ export const Thread = ({
|
||||
color: 'white',
|
||||
}}
|
||||
>
|
||||
Downloading from QDN
|
||||
{t('core:downloading_qdn', { postProcess: 'capitalize' })}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -959,7 +962,9 @@ export const Thread = ({
|
||||
color: 'white',
|
||||
}}
|
||||
>
|
||||
Refetch page
|
||||
{t('group:action.refetch_page', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
@@ -997,7 +1002,7 @@ export const Thread = ({
|
||||
disabled={!hasFirstPage}
|
||||
variant="contained"
|
||||
>
|
||||
First
|
||||
{t('core:page.first', { postProcess: 'capitalize' })}
|
||||
</Button>
|
||||
<Button
|
||||
sx={{
|
||||
@@ -1015,7 +1020,7 @@ export const Thread = ({
|
||||
disabled={!hasPreviousPage}
|
||||
variant="contained"
|
||||
>
|
||||
Previous
|
||||
{t('core:page.previous', { postProcess: 'capitalize' })}
|
||||
</Button>
|
||||
<Button
|
||||
sx={{
|
||||
@@ -1033,7 +1038,7 @@ export const Thread = ({
|
||||
disabled={!hasNextPage}
|
||||
variant="contained"
|
||||
>
|
||||
Next
|
||||
{t('core:page.next', { postProcess: 'capitalize' })}
|
||||
</Button>
|
||||
<Button
|
||||
sx={{
|
||||
@@ -1051,7 +1056,7 @@ export const Thread = ({
|
||||
disabled={!hasLastPage}
|
||||
variant="contained"
|
||||
>
|
||||
Last
|
||||
{t('core:page.last', { postProcess: 'capitalize' })}
|
||||
</Button>
|
||||
</Box>
|
||||
<Spacer height="30px" />
|
||||
@@ -1063,7 +1068,7 @@ export const Thread = ({
|
||||
<LoadingSnackbar
|
||||
open={isLoading}
|
||||
info={{
|
||||
message: 'Loading posts... please wait.',
|
||||
message: t('core:loading_posts', { postProcess: 'capitalize' }),
|
||||
}}
|
||||
/>
|
||||
</GroupContainer>
|
||||
|
@@ -55,8 +55,6 @@ import { RequestQueueWithPromise } from '../../utils/queue/queue';
|
||||
import { WebSocketActive } from './WebsocketActive';
|
||||
import { useMessageQueue } from '../../MessageQueueContext';
|
||||
import { ContextMenu } from '../ContextMenu';
|
||||
import { ReturnIcon } from '../../assets/Icons/ReturnIcon';
|
||||
import { ExitIcon } from '../../assets/Icons/ExitIcon';
|
||||
import { HomeDesktop } from './HomeDesktop';
|
||||
import { IconWrapper } from '../Desktop/DesktopFooter';
|
||||
import { DesktopHeader } from '../Desktop/DesktopHeader';
|
||||
@@ -80,6 +78,7 @@ import LockIcon from '@mui/icons-material/Lock';
|
||||
import NoEncryptionGmailerrorredIcon from '@mui/icons-material/NoEncryptionGmailerrorred';
|
||||
import { BlockedUsersModal } from './BlockedUsersModal';
|
||||
import { WalletsAppWrapper } from './WalletsAppWrapper';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const getPublishesFromAdmins = async (admins: string[], groupId) => {
|
||||
const queryString = admins.map((name) => `name=${name}`).join('&');
|
||||
@@ -450,6 +449,7 @@ export const Group = ({
|
||||
const [isOpenSideViewGroups, setIsOpenSideViewGroups] = useState(false);
|
||||
const [isForceShowCreationKeyPopup, setIsForceShowCreationKeyPopup] =
|
||||
useState(false);
|
||||
const { t } = useTranslation(['core', 'group']);
|
||||
|
||||
const [groupsProperties, setGroupsProperties] =
|
||||
useRecoilState(groupsPropertiesAtom);
|
||||
@@ -2219,9 +2219,10 @@ export const Group = ({
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
>
|
||||
No group selected
|
||||
</Typography>{' '}
|
||||
// TODO translate
|
||||
{t('group:message.generic.no_selection', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -2317,9 +2318,9 @@ export const Group = ({
|
||||
>
|
||||
{' '}
|
||||
<Typography>
|
||||
The group's first common encryption key is in the process
|
||||
of creation. Please wait a few minutes for it to be
|
||||
retrieved by the network. Checking every 2 minutes...
|
||||
{t('group:message.generic.encryption_key', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Typography>
|
||||
</div>
|
||||
)}
|
||||
@@ -2343,18 +2344,23 @@ export const Group = ({
|
||||
>
|
||||
{' '}
|
||||
<Typography>
|
||||
You are not part of the encrypted group of members. Wait
|
||||
until an admin re-encrypts the keys.
|
||||
{t('group:message.generic.not_part_group', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Typography>
|
||||
<Spacer height="25px" />
|
||||
<Typography>
|
||||
<strong>
|
||||
Only unencrypted messages will be displayed.
|
||||
{t('group:message.generic.only_encrypted', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</strong>
|
||||
</Typography>
|
||||
<Spacer height="25px" />
|
||||
<Typography>
|
||||
Try notifying an admin from the list of admins below:
|
||||
{t('group:message.generic.notify_admins', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Typography>
|
||||
<Spacer height="25px" />
|
||||
{adminsWithNames.map((admin) => {
|
||||
@@ -2374,7 +2380,9 @@ export const Group = ({
|
||||
variant="contained"
|
||||
onClick={() => notifyAdmin(admin)}
|
||||
>
|
||||
Notify
|
||||
{t('core:action.notify', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</LoadingButton>
|
||||
</Box>
|
||||
);
|
||||
@@ -2594,14 +2602,19 @@ export const Group = ({
|
||||
open={isLoadingGroup}
|
||||
info={{
|
||||
message:
|
||||
isLoadingGroupMessage || 'Setting up group... please wait.',
|
||||
isLoadingGroupMessage ||
|
||||
t('group:message.generic.setting_group', {
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
|
||||
<LoadingSnackbar
|
||||
open={isLoadingGroups}
|
||||
info={{
|
||||
message: 'Setting up groups... please wait.',
|
||||
message: t('group:message.generic.setting_group', {
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
<WalletsAppWrapper />
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import * as React from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import List from '@mui/material/List';
|
||||
import ListItem from '@mui/material/ListItem';
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
@@ -12,14 +12,13 @@ import { CustomLoader } from '../../common/CustomLoader';
|
||||
import { getBaseApiReact } from '../../App';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
|
||||
const [groupsWithJoinRequests, setGroupsWithJoinRequests] = React.useState(
|
||||
[]
|
||||
);
|
||||
const [isExpanded, setIsExpanded] = React.useState(false);
|
||||
const [groupsWithJoinRequests, setGroupsWithJoinRequests] = useState([]);
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const getJoinRequests = async () => {
|
||||
try {
|
||||
@@ -38,9 +37,10 @@ export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const { t } = useTranslation(['core', 'group']);
|
||||
const theme = useTheme();
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (myAddress) {
|
||||
getJoinRequests();
|
||||
}
|
||||
@@ -69,9 +69,9 @@ export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '1rem',
|
||||
}} // TODO translate
|
||||
}}
|
||||
>
|
||||
Group Invites{' '}
|
||||
{t('group:group_invites', { postProcess: 'capitalize' })}{' '}
|
||||
{groupsWithJoinRequests?.length > 0 &&
|
||||
` (${groupsWithJoinRequests?.length})`}
|
||||
</Typography>
|
||||
@@ -130,7 +130,9 @@ export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
|
||||
fontWeight: 400,
|
||||
}}
|
||||
>
|
||||
Nothing to display
|
||||
{t('group:message.generic.no_display', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
@@ -177,7 +179,10 @@ export const GroupInvites = ({ myAddress, setOpenAddGroup }) => {
|
||||
fontWeight: 400,
|
||||
},
|
||||
}}
|
||||
primary={`${group?.groupName} has invited you`}
|
||||
primary={t('group:message.generic.group_invited_you', {
|
||||
group: group?.groupName,
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
/>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
|
@@ -14,6 +14,7 @@ import { myGroupsWhereIAmAdminAtom } from '../../atoms/global';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
export const requestQueueGroupJoinRequests = new RequestQueueWithPromise(2);
|
||||
|
||||
export const GroupJoinRequests = ({
|
||||
@@ -27,7 +28,7 @@ export const GroupJoinRequests = ({
|
||||
setDesktopViewMode,
|
||||
}) => {
|
||||
const [isExpanded, setIsExpanded] = React.useState(false);
|
||||
|
||||
const { t } = useTranslation(['core', 'group']);
|
||||
const [groupsWithJoinRequests, setGroupsWithJoinRequests] = React.useState(
|
||||
[]
|
||||
);
|
||||
@@ -139,9 +140,9 @@ export const GroupJoinRequests = ({
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '1rem',
|
||||
}} // TODO translate
|
||||
}}
|
||||
>
|
||||
Join Requests{' '}
|
||||
{t('group:join_requests', { postProcess: 'capitalize' })}{' '}
|
||||
{filteredJoinRequests?.filter((group) => group?.data?.length > 0)
|
||||
?.length > 0 &&
|
||||
` (${filteredJoinRequests?.filter((group) => group?.data?.length > 0)?.length})`}
|
||||
@@ -163,14 +164,13 @@ export const GroupJoinRequests = ({
|
||||
<Collapse in={isExpanded} timeout="auto" unmountOnExit>
|
||||
<Box
|
||||
sx={{
|
||||
width: '322px',
|
||||
height: '250px',
|
||||
|
||||
bgcolor: 'background.paper',
|
||||
borderRadius: '19px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
bgcolor: 'background.paper',
|
||||
height: '250px',
|
||||
padding: '20px',
|
||||
borderRadius: '19px',
|
||||
width: '322px',
|
||||
}}
|
||||
>
|
||||
{loading && filteredJoinRequests.length === 0 && (
|
||||
@@ -204,18 +204,20 @@ export const GroupJoinRequests = ({
|
||||
color: 'rgba(255, 255, 255, 0.2)',
|
||||
}}
|
||||
>
|
||||
Nothing to display
|
||||
{t('group:message.generic.no_display', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
<List
|
||||
className="scrollable-container"
|
||||
sx={{
|
||||
width: '100%',
|
||||
maxWidth: 360,
|
||||
bgcolor: 'background.paper',
|
||||
maxHeight: '300px',
|
||||
maxWidth: 360,
|
||||
overflow: 'auto',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
{filteredJoinRequests?.map((group) => {
|
||||
|
@@ -4,16 +4,21 @@ import { useState } from 'react';
|
||||
import { Spacer } from '../../common/Spacer';
|
||||
import { Label } from './AddGroup';
|
||||
import { getFee } from '../../background';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
|
||||
const [value, setValue] = useState('');
|
||||
const [expiryTime, setExpiryTime] = useState<string>('259200');
|
||||
const [isLoadingInvite, setIsLoadingInvite] = useState(false);
|
||||
const { t } = useTranslation(['core', 'group']);
|
||||
|
||||
const inviteMember = async () => {
|
||||
try {
|
||||
const fee = await getFee('GROUP_INVITE');
|
||||
await show({
|
||||
message: 'Would you like to perform a GROUP_INVITE transaction?',
|
||||
message: t('group:question.group_invite', {
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
publishFee: fee.fee + ' QORT',
|
||||
});
|
||||
setIsLoadingInvite(true);
|
||||
@@ -27,10 +32,12 @@ export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response?.error) {
|
||||
// TODO translate
|
||||
setInfoSnack({
|
||||
type: 'success',
|
||||
message: `Successfully invited ${value}. It may take a couple of minutes for the changes to propagate`,
|
||||
message: t('group:message.success.group_invite', {
|
||||
value: value,
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
});
|
||||
setOpenSnack(true);
|
||||
res(response);
|
||||
@@ -72,7 +79,7 @@ export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
Invite member
|
||||
{t('group:action.invite_member', { postProcess: 'capitalize' })}
|
||||
<Spacer height="20px" />
|
||||
<Input
|
||||
value={value}
|
||||
@@ -80,24 +87,26 @@ export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
/>
|
||||
<Spacer height="20px" />
|
||||
<Label>Invitation Expiry Time</Label>
|
||||
<Label>
|
||||
{t('group:invitation_expiry', { postProcess: 'capitalize' })}
|
||||
</Label>
|
||||
<Select
|
||||
labelId="demo-simple-select-label"
|
||||
id="demo-simple-select"
|
||||
value={expiryTime}
|
||||
label="Invitation Expiry Time"
|
||||
label={t('group:invitation_expiry', { postProcess: 'capitalize' })}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<MenuItem value={10800}>3 hours</MenuItem>
|
||||
<MenuItem value={21600}>6 hours</MenuItem>
|
||||
<MenuItem value={43200}>12 hours</MenuItem>
|
||||
<MenuItem value={86400}>1 day</MenuItem>
|
||||
<MenuItem value={259200}>3 days</MenuItem>
|
||||
<MenuItem value={432000}>5 days</MenuItem>
|
||||
<MenuItem value={604800}>7 days</MenuItem>
|
||||
<MenuItem value={864000}>10 days</MenuItem>
|
||||
<MenuItem value={1296000}>15 days</MenuItem>
|
||||
<MenuItem value={2592000}>30 days</MenuItem>
|
||||
<MenuItem value={10800}>{t('core.time.hour', { count: 3 })}</MenuItem>
|
||||
<MenuItem value={21600}>{t('core.time.hour', { count: 6 })}</MenuItem>
|
||||
<MenuItem value={43200}>{t('core.time.hour', { count: 12 })}</MenuItem>
|
||||
<MenuItem value={86400}>{t('core.time.day', { count: 1 })}</MenuItem>
|
||||
<MenuItem value={259200}>{t('core.time.day', { count: 3 })}</MenuItem>
|
||||
<MenuItem value={432000}>{t('core.time.day', { count: 5 })}</MenuItem>
|
||||
<MenuItem value={604800}>{t('core.time.day', { count: 7 })}</MenuItem>
|
||||
<MenuItem value={864000}>{t('core.time.day', { count: 10 })}</MenuItem>
|
||||
<MenuItem value={1296000}>{t('core.time.day', { count: 15 })}</MenuItem>
|
||||
<MenuItem value={2592000}>{t('core.time.day', { count: 30 })}</MenuItem>
|
||||
</Select>
|
||||
<Spacer height="20px" />
|
||||
<LoadingButton
|
||||
@@ -106,7 +115,7 @@ export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
|
||||
loading={isLoadingInvite}
|
||||
onClick={inviteMember}
|
||||
>
|
||||
Invite
|
||||
{t('core:action.invite', { postProcess: 'capitalize' })}
|
||||
</LoadingButton>
|
||||
</Box>
|
||||
);
|
||||
|
@@ -18,6 +18,7 @@ import { getNameInfo } from './Group';
|
||||
import { getFee } from '../../background';
|
||||
import { LoadingButton } from '@mui/lab';
|
||||
import { getBaseApiReact } from '../../App';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const getMemberInvites = async (groupNumber) => {
|
||||
const response = await fetch(
|
||||
@@ -55,6 +56,7 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
|
||||
const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open
|
||||
const listRef = useRef();
|
||||
const [isLoadingUnban, setIsLoadingUnban] = useState(false);
|
||||
const { t } = useTranslation(['core', 'group']);
|
||||
|
||||
const getInvites = async (groupId) => {
|
||||
try {
|
||||
@@ -84,10 +86,9 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
|
||||
|
||||
const handleCancelBan = async (address) => {
|
||||
try {
|
||||
// TODO translate
|
||||
const fee = await getFee('CANCEL_GROUP_BAN');
|
||||
await show({
|
||||
message: 'Would you like to perform a CANCEL_GROUP_BAN transaction?',
|
||||
message: t('group:question.cancel_ban', { postProcess: 'capitalize' }),
|
||||
publishFee: fee.fee + ' QORT',
|
||||
});
|
||||
setIsLoadingUnban(true);
|
||||
@@ -103,8 +104,9 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
|
||||
setIsLoadingUnban(false);
|
||||
setInfoSnack({
|
||||
type: 'success',
|
||||
message:
|
||||
'Successfully unbanned user. It may take a couple of minutes for the changes to propagate',
|
||||
message: t('group:message.success.unbanned_user', {
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
});
|
||||
handlePopoverClose();
|
||||
setOpenSnack(true);
|
||||
@@ -127,6 +129,7 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsLoadingUnban(false);
|
||||
}
|
||||
@@ -177,10 +180,13 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
|
||||
variant="contained"
|
||||
onClick={() => handleCancelBan(member?.offender)}
|
||||
>
|
||||
Cancel Ban
|
||||
{t('group:action.cancel_ban', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</LoadingButton>
|
||||
</Box>
|
||||
</Popover>
|
||||
|
||||
<ListItemButton
|
||||
onClick={(event) => handlePopoverOpen(event, index)}
|
||||
>
|
||||
@@ -205,7 +211,7 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>Ban list</p>
|
||||
<p>{t('group:ban_list', { postProcess: 'capitalize' })}</p>
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import * as React from 'react';
|
||||
import { forwardRef, Fragment, ReactElement, Ref, useEffect } from 'react';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import AppBar from '@mui/material/AppBar';
|
||||
import Toolbar from '@mui/material/Toolbar';
|
||||
@@ -11,13 +11,6 @@ import { Box, FormControlLabel, Switch, styled, useTheme } from '@mui/material';
|
||||
import { enabledDevModeAtom } from '../../atoms/global';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
function a11yProps(index: number) {
|
||||
return {
|
||||
id: `simple-tab-${index}`,
|
||||
'aria-controls': `simple-tabpanel-${index}`,
|
||||
};
|
||||
}
|
||||
|
||||
const LocalNodeSwitch = styled(Switch)(({ theme }) => ({
|
||||
padding: 8,
|
||||
'& .MuiSwitch-track': {
|
||||
@@ -51,11 +44,11 @@ const LocalNodeSwitch = styled(Switch)(({ theme }) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const Transition = React.forwardRef(function Transition(
|
||||
const Transition = forwardRef(function Transition(
|
||||
props: TransitionProps & {
|
||||
children: React.ReactElement;
|
||||
children: ReactElement;
|
||||
},
|
||||
ref: React.Ref<unknown>
|
||||
ref: Ref<unknown>
|
||||
) {
|
||||
return <Slide direction="up" ref={ref} {...props} />;
|
||||
});
|
||||
@@ -118,12 +111,12 @@ export const Settings = ({ address, open, setOpen }) => {
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
getUserSettings();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Fragment>
|
||||
<Dialog
|
||||
fullScreen
|
||||
open={open}
|
||||
@@ -192,6 +185,6 @@ export const Settings = ({ address, open, setOpen }) => {
|
||||
)}
|
||||
</Box>
|
||||
</Dialog>
|
||||
</React.Fragment>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
@@ -141,21 +141,6 @@ export const ThingsToDoInitial = ({
|
||||
outline: '1px solid rgba(9, 182, 232, 1)',
|
||||
}}
|
||||
/>
|
||||
{/* <Checkbox
|
||||
edge="start"
|
||||
checked={checked1}
|
||||
tabIndex={-1}
|
||||
disableRipple
|
||||
disabled={true}
|
||||
sx={{
|
||||
"&.Mui-checked": {
|
||||
color: "white", // Customize the color when checked
|
||||
},
|
||||
"& .MuiSvgIcon-root": {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
/> */}
|
||||
</ListItemIcon>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
@@ -163,15 +148,6 @@ export const ThingsToDoInitial = ({
|
||||
sx={{
|
||||
marginBottom: '20px',
|
||||
}}
|
||||
// secondaryAction={
|
||||
// <IconButton edge="end" aria-label="comments">
|
||||
// <InfoIcon
|
||||
// sx={{
|
||||
// color: "white",
|
||||
// }}
|
||||
// />
|
||||
// </IconButton>
|
||||
// }
|
||||
disablePadding
|
||||
>
|
||||
<ListItemButton
|
||||
@@ -215,34 +191,6 @@ export const ThingsToDoInitial = ({
|
||||
</ListItemIcon>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
{/* <ListItem
|
||||
disablePadding
|
||||
>
|
||||
<ListItemButton sx={{
|
||||
padding: "0px",
|
||||
}} disableRipple role={undefined} dense>
|
||||
|
||||
<ListItemText sx={{
|
||||
"& .MuiTypography-root": {
|
||||
fontSize: "13px",
|
||||
fontWeight: 400,
|
||||
},
|
||||
}} primary={`Join a group`} />
|
||||
<ListItemIcon sx={{
|
||||
justifyContent: "flex-end",
|
||||
}}>
|
||||
<Box
|
||||
sx={{
|
||||
height: "18px",
|
||||
width: "18px",
|
||||
borderRadius: "50%",
|
||||
backgroundColor: checked3 ? "rgba(9, 182, 232, 1)" : "transparent",
|
||||
outline: "1px solid rgba(9, 182, 232, 1)",
|
||||
}}
|
||||
/>
|
||||
</ListItemIcon>
|
||||
</ListItemButton>
|
||||
</ListItem> */}
|
||||
</List>
|
||||
)}
|
||||
</Box>
|
||||
|
@@ -88,7 +88,7 @@ export const WalletsAppWrapper = () => {
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
<Typography>Q-Wallets</Typography> // TODO translate
|
||||
<Typography>Q-Wallets</Typography>
|
||||
<ButtonBase onClick={handleClose}>
|
||||
<CloseIcon
|
||||
sx={{
|
||||
|
@@ -107,7 +107,6 @@ export const WebSocketActive = ({ myAddress, setIsLoadingGroups }) => {
|
||||
directs: sortedDirects,
|
||||
})
|
||||
.catch((error) => {
|
||||
// TODO translate
|
||||
console.error(
|
||||
'Failed to handle active group data from socket:',
|
||||
error.message || 'An error occurred'
|
||||
|
@@ -17,7 +17,7 @@ export const useBlockedAddresses = () => {
|
||||
if (userBlockedRef.current[address]) return true;
|
||||
return false;
|
||||
} catch (error) {
|
||||
//error
|
||||
console.log(error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -42,10 +42,13 @@ export const useBlockedAddresses = () => {
|
||||
console.error('Failed qortalRequest', error);
|
||||
});
|
||||
});
|
||||
|
||||
const blockedUsers = {};
|
||||
|
||||
response?.forEach((item) => {
|
||||
blockedUsers[item] = true;
|
||||
});
|
||||
|
||||
userBlockedRef.current = blockedUsers;
|
||||
|
||||
const response2 = await new Promise((res, rej) => {
|
||||
@@ -66,10 +69,13 @@ export const useBlockedAddresses = () => {
|
||||
console.error('Failed qortalRequest', error);
|
||||
});
|
||||
});
|
||||
|
||||
const blockedUsers2 = {};
|
||||
|
||||
response2?.forEach((item) => {
|
||||
blockedUsers2[item] = true;
|
||||
});
|
||||
|
||||
userNamesBlockedRef.current = blockedUsers2;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
@@ -22,7 +22,7 @@ export const useHandleUserInfo = () => {
|
||||
};
|
||||
return data?.level;
|
||||
} catch (error) {
|
||||
//error
|
||||
console.log(error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
@@ -1,46 +1,52 @@
|
||||
import { Box, ButtonBase, Typography } from '@mui/material';
|
||||
import { Spacer } from '../../common/Spacer';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const NewUsersCTA = ({ balance }) => {
|
||||
const { t } = useTranslation(['core']);
|
||||
|
||||
if (balance === undefined || +balance > 0) return null;
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Spacer height="40px" />
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
width: '320px',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
padding: '15px',
|
||||
outline: '1px solid gray',
|
||||
borderRadius: '4px',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
outline: '1px solid gray',
|
||||
padding: '15px',
|
||||
width: '320px',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
textAlign: 'center',
|
||||
fontSize: '1.2rem',
|
||||
fontWeight: 'bold',
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
Are you a new user?
|
||||
</Typography>{' '}
|
||||
// TODO translate
|
||||
<Spacer height="20px" />
|
||||
<Typography>
|
||||
Please message us on Telegram or Discord if you need 4 QORT to start
|
||||
chatting without any limitations
|
||||
{t('core:new_user', { postProcess: 'capitalize' })}
|
||||
</Typography>
|
||||
|
||||
<Spacer height="20px" />
|
||||
|
||||
<Typography>
|
||||
{t('core:message_us', { postProcess: 'capitalize' })}
|
||||
</Typography>
|
||||
|
||||
<Spacer height="20px" />
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
@@ -68,6 +74,7 @@ export const NewUsersCTA = ({ balance }) => {
|
||||
>
|
||||
Telegram
|
||||
</ButtonBase>
|
||||
|
||||
<ButtonBase
|
||||
sx={{
|
||||
textDecoration: 'underline',
|
||||
|
92
src/components/Language/LanguageSelector.tsx
Normal file
92
src/components/Language/LanguageSelector.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { supportedLanguages } from '../../../i18n';
|
||||
import { Tooltip, useTheme } from '@mui/material';
|
||||
|
||||
const LanguageSelector = () => {
|
||||
const { i18n, t } = useTranslation(['core']);
|
||||
const [showSelect, setShowSelect] = useState(false);
|
||||
const theme = useTheme();
|
||||
const selectorRef = useRef(null);
|
||||
|
||||
const handleChange = (e) => {
|
||||
const newLang = e.target.value;
|
||||
i18n.changeLanguage(newLang);
|
||||
setShowSelect(false);
|
||||
};
|
||||
|
||||
const currentLang = i18n.language;
|
||||
const { name, flag } =
|
||||
supportedLanguages[currentLang] || supportedLanguages['en'];
|
||||
|
||||
// Detect clicks outside the component
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
if (selectorRef.current && !selectorRef.current.contains(event.target)) {
|
||||
setShowSelect(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={selectorRef}
|
||||
style={{
|
||||
bottom: '5%',
|
||||
display: 'flex',
|
||||
gap: '12px',
|
||||
left: '1.5vh',
|
||||
position: 'absolute',
|
||||
}}
|
||||
>
|
||||
<Tooltip
|
||||
title={t('core:action.change_language', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
>
|
||||
{showSelect ? (
|
||||
<select
|
||||
style={{
|
||||
fontSize: '1rem',
|
||||
border: '2px',
|
||||
background: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
cursor: 'pointer',
|
||||
position: 'relative',
|
||||
bottom: '7px',
|
||||
}}
|
||||
value={currentLang}
|
||||
onChange={handleChange}
|
||||
autoFocus
|
||||
>
|
||||
{Object.entries(supportedLanguages).map(([code, { name }]) => (
|
||||
<option key={code} value={code}>
|
||||
{code.toUpperCase()} - {name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => setShowSelect(true)}
|
||||
style={{
|
||||
fontSize: '1.5rem',
|
||||
border: 'none',
|
||||
background: 'none',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
aria-label={`Current language: ${name}`}
|
||||
>
|
||||
{showSelect ? undefined : flag}
|
||||
</button>
|
||||
)}
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LanguageSelector;
|
@@ -265,7 +265,7 @@ export const Minting = ({
|
||||
rej({ message: response.error });
|
||||
})
|
||||
.catch((error) => {
|
||||
rej({ message: error.message || 'An error occurred' }); //TODO translate
|
||||
rej({ message: error.message || 'An error occurred' });
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -280,7 +280,7 @@ export const Minting = ({
|
||||
}, []);
|
||||
|
||||
const createRewardShare = useCallback(async (publicKey, recipient) => {
|
||||
const fee = await getFee('REWARD_SHARE');
|
||||
const fee = await getFee('REWARD_SHARE'); // TODO translate
|
||||
await show({
|
||||
message: 'Would you like to perform an REWARD_SHARE transaction?',
|
||||
publishFee: fee.fee + ' QORT',
|
||||
|
@@ -62,6 +62,7 @@ export const QMailStatus = () => {
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: '14px',
|
||||
fontWeight: 700,
|
||||
textTransform: 'uppercase',
|
||||
}}
|
||||
>
|
||||
{t('core:q_mail', {
|
||||
|
@@ -176,7 +176,7 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
|
||||
.catch((error) => {
|
||||
rej(
|
||||
error.message ||
|
||||
t('core:result.error.generic', { postProcess: 'capitalize' })
|
||||
t('core:message.error.generic', { postProcess: 'capitalize' })
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -185,7 +185,7 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
|
||||
setSettingsQdnLastUpdated(Date.now());
|
||||
setInfoSnack({
|
||||
type: 'success',
|
||||
message: t('core:result.success.publish_qdn', {
|
||||
message: t('core:message.success.publish_qdn', {
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
});
|
||||
@@ -198,7 +198,7 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
|
||||
type: 'error',
|
||||
message:
|
||||
error?.message ||
|
||||
t('core:result.error.save_qdn', {
|
||||
t('core:message.error.save_qdn', {
|
||||
postProcess: 'capitalize',
|
||||
}),
|
||||
});
|
||||
@@ -591,7 +591,7 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t('core:import', {
|
||||
{t('core:action.import', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</ButtonBase>
|
||||
@@ -616,7 +616,7 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t('core:export', {
|
||||
{t('core:action.export', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
</ButtonBase>
|
||||
|
@@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
const ThemeSelector = () => {
|
||||
const { t } = useTranslation(['core']);
|
||||
|
||||
const { themeMode, toggleTheme } = useThemeContext();
|
||||
|
||||
return (
|
||||
@@ -14,7 +15,7 @@ const ThemeSelector = () => {
|
||||
bottom: '1%',
|
||||
display: 'flex',
|
||||
gap: '12px',
|
||||
left: '1.5vh',
|
||||
left: '1.2vh',
|
||||
position: 'absolute',
|
||||
}}
|
||||
>
|
||||
|
@@ -91,7 +91,7 @@ export const Tutorials = () => {
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button variant="contained" onClick={handleClose}>
|
||||
{t('core:close', { postProcess: 'capitalize' })}
|
||||
{t('core:action.close', { postProcess: 'capitalize' })}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
@@ -138,7 +138,7 @@ export const Tutorials = () => {
|
||||
|
||||
<DialogActions>
|
||||
<Button variant="contained" onClick={handleClose}>
|
||||
{t('core:close', { postProcess: 'capitalize' })}
|
||||
{t('core:action.close', { postProcess: 'capitalize' })}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||
import {
|
||||
canSaveSettingToQdnAtom,
|
||||
@@ -46,6 +46,7 @@ const getPublishRecord = async (myName) => {
|
||||
|
||||
return { hasPublishRecord: false };
|
||||
};
|
||||
|
||||
const getPublish = async (myName) => {
|
||||
try {
|
||||
let data;
|
||||
@@ -57,7 +58,6 @@ const getPublish = async (myName) => {
|
||||
if (!data) throw new Error('Unable to fetch publish');
|
||||
|
||||
const decryptedKey: any = await decryptResource(data);
|
||||
|
||||
const dataint8Array = base64ToUint8Array(decryptedKey.data);
|
||||
const decryptedKeyToObject = uint8ArrayToObject(dataint8Array);
|
||||
return decryptedKeyToObject;
|
||||
@@ -112,6 +112,7 @@ export const useQortalGetSaveSettings = (myName, isAuthenticated) => {
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!myName ||
|
||||
|
@@ -1,55 +1,69 @@
|
||||
import React, { useCallback, useEffect } from 'react'
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { isUsingImportExportSettingsAtom, oldPinnedAppsAtom, settingsLocalLastUpdatedAtom, settingsQDNLastUpdatedAtom, sortablePinnedAppsAtom } from './atoms/global';
|
||||
import {
|
||||
isUsingImportExportSettingsAtom,
|
||||
oldPinnedAppsAtom,
|
||||
settingsLocalLastUpdatedAtom,
|
||||
settingsQDNLastUpdatedAtom,
|
||||
sortablePinnedAppsAtom,
|
||||
} from './atoms/global';
|
||||
|
||||
function fetchFromLocalStorage(key) {
|
||||
try {
|
||||
const serializedValue = localStorage.getItem(key);
|
||||
if (serializedValue === null) {
|
||||
return null;
|
||||
}
|
||||
return JSON.parse(serializedValue);
|
||||
} catch (error) {
|
||||
console.error('Error fetching from localStorage:', error);
|
||||
return null;
|
||||
try {
|
||||
const serializedValue = localStorage.getItem(key);
|
||||
if (serializedValue === null) {
|
||||
return null;
|
||||
}
|
||||
return JSON.parse(serializedValue);
|
||||
} catch (error) {
|
||||
console.error('Error fetching from localStorage:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export const useRetrieveDataLocalStorage = (address) => {
|
||||
const setSortablePinnedApps = useSetRecoilState(sortablePinnedAppsAtom);
|
||||
const setSettingsLocalLastUpdated = useSetRecoilState(settingsLocalLastUpdatedAtom);
|
||||
const setIsUsingImportExportSettings = useSetRecoilState(isUsingImportExportSettingsAtom)
|
||||
const setSettingsQDNLastUpdated = useSetRecoilState(settingsQDNLastUpdatedAtom);
|
||||
const setOldPinnedApps = useSetRecoilState(oldPinnedAppsAtom)
|
||||
const setSortablePinnedApps = useSetRecoilState(sortablePinnedAppsAtom);
|
||||
|
||||
const getSortablePinnedApps = useCallback(()=> {
|
||||
const pinnedAppsLocal = fetchFromLocalStorage('ext_saved_settings')
|
||||
if(pinnedAppsLocal?.sortablePinnedApps){
|
||||
setSortablePinnedApps(pinnedAppsLocal?.sortablePinnedApps)
|
||||
setSettingsLocalLastUpdated(pinnedAppsLocal?.timestamp || -1)
|
||||
} else {
|
||||
setSettingsLocalLastUpdated(-1)
|
||||
}
|
||||
|
||||
}, [])
|
||||
const getSortablePinnedAppsImportExport = useCallback(()=> {
|
||||
const pinnedAppsLocal = fetchFromLocalStorage('ext_saved_settings_import_export')
|
||||
if(pinnedAppsLocal?.sortablePinnedApps){
|
||||
setOldPinnedApps(pinnedAppsLocal?.sortablePinnedApps)
|
||||
|
||||
|
||||
setIsUsingImportExportSettings(true)
|
||||
setSettingsQDNLastUpdated(pinnedAppsLocal?.timestamp || 0)
|
||||
|
||||
} else {
|
||||
setIsUsingImportExportSettings(false)
|
||||
}
|
||||
|
||||
}, [])
|
||||
useEffect(()=> {
|
||||
|
||||
getSortablePinnedApps()
|
||||
getSortablePinnedAppsImportExport()
|
||||
}, [getSortablePinnedApps, address])
|
||||
|
||||
}
|
||||
const setSettingsLocalLastUpdated = useSetRecoilState(
|
||||
settingsLocalLastUpdatedAtom
|
||||
);
|
||||
|
||||
const setIsUsingImportExportSettings = useSetRecoilState(
|
||||
isUsingImportExportSettingsAtom
|
||||
);
|
||||
|
||||
const setSettingsQDNLastUpdated = useSetRecoilState(
|
||||
settingsQDNLastUpdatedAtom
|
||||
);
|
||||
|
||||
const setOldPinnedApps = useSetRecoilState(oldPinnedAppsAtom);
|
||||
|
||||
const getSortablePinnedApps = useCallback(() => {
|
||||
const pinnedAppsLocal = fetchFromLocalStorage('ext_saved_settings');
|
||||
|
||||
if (pinnedAppsLocal?.sortablePinnedApps) {
|
||||
setSortablePinnedApps(pinnedAppsLocal?.sortablePinnedApps);
|
||||
setSettingsLocalLastUpdated(pinnedAppsLocal?.timestamp || -1);
|
||||
} else {
|
||||
setSettingsLocalLastUpdated(-1);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const getSortablePinnedAppsImportExport = useCallback(() => {
|
||||
const pinnedAppsLocal = fetchFromLocalStorage(
|
||||
'ext_saved_settings_import_export'
|
||||
);
|
||||
if (pinnedAppsLocal?.sortablePinnedApps) {
|
||||
setOldPinnedApps(pinnedAppsLocal?.sortablePinnedApps);
|
||||
setIsUsingImportExportSettings(true);
|
||||
setSettingsQDNLastUpdated(pinnedAppsLocal?.timestamp || 0);
|
||||
} else {
|
||||
setIsUsingImportExportSettings(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
getSortablePinnedApps();
|
||||
getSortablePinnedAppsImportExport();
|
||||
}, [getSortablePinnedApps, address]);
|
||||
};
|
||||
|
Reference in New Issue
Block a user