Merge pull request #25 from nbenaglia/feature/i18n-groups

I18N: add group namespace
This commit is contained in:
nico.benaz
2025-04-26 17:14:20 +02:00
committed by GitHub
39 changed files with 995 additions and 507 deletions

View File

@@ -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 />
</>
);

View File

@@ -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 (

View File

@@ -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>
);

View File

@@ -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>
);
};

View File

@@ -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>

View File

@@ -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',

View File

@@ -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>

View File

@@ -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) {

View File

@@ -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" />

View File

@@ -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>

View File

@@ -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 />

View File

@@ -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>

View File

@@ -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) => {

View File

@@ -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>
);

View File

@@ -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',

View File

@@ -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>
);
};

View File

@@ -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>

View File

@@ -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={{

View File

@@ -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'

View File

@@ -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);

View File

@@ -22,7 +22,7 @@ export const useHandleUserInfo = () => {
};
return data?.level;
} catch (error) {
//error
console.log(error);
}
}, []);

View File

@@ -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',

View 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;

View File

@@ -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',

View File

@@ -62,6 +62,7 @@ export const QMailStatus = () => {
color: theme.palette.text.primary,
fontSize: '14px',
fontWeight: 700,
textTransform: 'uppercase',
}}
>
{t('core:q_mail', {

View File

@@ -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>

View File

@@ -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',
}}
>

View File

@@ -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>