mirror of
https://github.com/binwiederhier/ntfy.git
synced 2026-10-16 04:26:37 +02:00
PWA reload icon, lazy loading: EmojiPicker and MarkdownContent
This commit is contained in:
1 parent
8022401728
commit
65e409e9e4
19 files changed
+155
-88
No files matched your search
@@ -11,11 +11,20 @@ if [ -z "$1" ]; then
|
||||
echo "Example:"
|
||||
echo " $0 emoji-converted.json"
|
||||
echo " $0 $ROOTDIR/web/src/app/emojis.js"
|
||||
echo " $0 $ROOTDIR/web/src/app/emojisMapped.js"
|
||||
echo " $0 $ROOTDIR/docs/emojis.md"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$1" == *.js ]]; then
|
||||
if [[ "$1" == *emojisMapped.js ]]; then
|
||||
# Small alias -> emoji lookup used to render tags as emojis. Precomputed so the full
|
||||
# emoji dataset (emojis.js) stays out of the main web bundle.
|
||||
echo -n "// This file is generated by scripts/emoji-convert.sh -- alias to emoji lookup
|
||||
// Original data source: https://github.com/github/gemoji/blob/master/db/emoji.json
|
||||
export default " > "$1"
|
||||
cat "$SCRIPTDIR/emoji.json" | jq -jc '[.[] | .aliases[] as $a | {key: $a, value: .emoji}] | from_entries' >> "$1"
|
||||
echo ";" >> "$1"
|
||||
elif [[ "$1" == *.js ]]; then
|
||||
echo -n "// This file is generated by scripts/emoji-convert.sh to reduce the size
|
||||
// Original data source: https://github.com/github/gemoji/blob/master/db/emoji.json
|
||||
export const rawEmojis = " > "$1"
|
||||
|
||||
+2
-1
@@ -1 +1,2 @@
|
||||
src/app/emojis.js
|
||||
src/app/emojis.js
|
||||
src/app/emojisMapped.js
|
||||
@@ -2,3 +2,4 @@ build/
|
||||
dist/
|
||||
public/static/langs/
|
||||
src/app/emojis.js
|
||||
src/app/emojisMapped.js
|
||||
@@ -67,6 +67,7 @@
|
||||
"action_bar_profile_logout": "Logout",
|
||||
"action_bar_sign_in": "Sign in",
|
||||
"action_bar_sign_up": "Sign up",
|
||||
"action_bar_reload": "Reload app",
|
||||
"message_bar_type_message": "Type a message here",
|
||||
"message_bar_error_publishing": "Error publishing notification",
|
||||
"message_bar_show_dialog": "Show publish dialog",
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { rawEmojis } from "./emojis";
|
||||
|
||||
// Format emojis (see emoji.js)
|
||||
export default Object.fromEntries(rawEmojis.flatMap((emoji) => emoji.aliases.map((alias) => [alias, emoji.emoji])));
|
||||
// This file is generated by scripts/emoji-convert.sh -- alias to emoji lookup
|
||||
// Original data source: https://github.com/github/gemoji/blob/master/db/emoji.json
|
||||
export default {"grinning":"😀","smiley":"😃","smile":"😄","grin":"😁","laughing":"😆","satisfied":"😆","sweat_smile":"😅","rofl":"🤣","joy":"😂","slightly_smiling_face":"🙂","upside_down_face":"🙃","wink":"😉","blush":"😊","innocent":"😇","smiling_face_with_three_hearts":"🥰","heart_eyes":"😍","star_struck":"🤩","kissing_heart":"😘","kissing":"😗","relaxed":"☺️","kissing_closed_eyes":"😚","kissing_smiling_eyes":"😙","smiling_face_with_tear":"🥲","yum":"😋","stuck_out_tongue":"😛","stuck_out_tongue_winking_eye":"😜","zany_face":"🤪","stuck_out_tongue_closed_eyes":"😝","money_mouth_face":"🤑","hugs":"🤗","hand_over_mouth":"🤭","shushing_face":"🤫","thinking":"🤔","zipper_mouth_face":"🤐","raised_eyebrow":"🤨","neutral_face":"😐","expressionless":"😑","no_mouth":"😶","face_in_clouds":"😶🌫️","smirk":"😏","unamused":"😒","roll_eyes":"🙄","grimacing":"😬","face_exhaling":"😮💨","lying_face":"🤥","relieved":"😌","pensive":"😔","sleepy":"😪","drooling_face":"🤤","sleeping":"😴","mask":"😷","face_with_thermometer":"🤒","face_with_head_bandage":"🤕","nauseated_face":"🤢","vomiting_face":"🤮","sneezing_face":"🤧","hot_face":"🥵","cold_face":"🥶","woozy_face":"🥴","dizzy_face":"😵","face_with_spiral_eyes":"😵💫","exploding_head":"🤯","cowboy_hat_face":"🤠","partying_face":"🥳","disguised_face":"🥸","sunglasses":"😎","nerd_face":"🤓","monocle_face":"🧐","confused":"😕","worried":"😟","slightly_frowning_face":"🙁","frowning_face":"☹️","open_mouth":"😮","hushed":"😯","astonished":"😲","flushed":"😳","pleading_face":"🥺","frowning":"😦","anguished":"😧","fearful":"😨","cold_sweat":"😰","disappointed_relieved":"😥","cry":"😢","sob":"😭","scream":"😱","confounded":"😖","persevere":"😣","disappointed":"😞","sweat":"😓","weary":"😩","tired_face":"😫","yawning_face":"🥱","triumph":"😤","rage":"😡","pout":"😡","angry":"😠","cursing_face":"🤬","smiling_imp":"😈","imp":"👿","skull":"💀","skull_and_crossbones":"☠️","hankey":"💩","poop":"💩","shit":"💩","clown_face":"🤡","japanese_ogre":"👹","japanese_goblin":"👺","ghost":"👻","alien":"👽","space_invader":"👾","robot":"🤖","smiley_cat":"😺","smile_cat":"😸","joy_cat":"😹","heart_eyes_cat":"😻","smirk_cat":"😼","kissing_cat":"😽","scream_cat":"🙀","crying_cat_face":"😿","pouting_cat":"😾","see_no_evil":"🙈","hear_no_evil":"🙉","speak_no_evil":"🙊","kiss":"💋","love_letter":"💌","cupid":"💘","gift_heart":"💝","sparkling_heart":"💖","heartpulse":"💗","heartbeat":"💓","revolving_hearts":"💞","two_hearts":"💕","heart_decoration":"💟","heavy_heart_exclamation":"❣️","broken_heart":"💔","heart_on_fire":"❤️🔥","mending_heart":"❤️🩹","heart":"❤️","orange_heart":"🧡","yellow_heart":"💛","green_heart":"💚","blue_heart":"💙","purple_heart":"💜","brown_heart":"🤎","black_heart":"🖤","white_heart":"🤍","100":"💯","anger":"💢","boom":"💥","collision":"💥","dizzy":"💫","sweat_drops":"💦","dash":"💨","hole":"🕳️","bomb":"💣","speech_balloon":"💬","eye_speech_bubble":"👁️🗨️","left_speech_bubble":"🗨️","right_anger_bubble":"🗯️","thought_balloon":"💭","zzz":"💤","wave":"👋","raised_back_of_hand":"🤚","raised_hand_with_fingers_splayed":"🖐️","hand":"✋","raised_hand":"✋","vulcan_salute":"🖖","ok_hand":"👌","pinched_fingers":"🤌","pinching_hand":"🤏","v":"✌️","crossed_fingers":"🤞","love_you_gesture":"🤟","metal":"🤘","call_me_hand":"🤙","point_left":"👈","point_right":"👉","point_up_2":"👆","middle_finger":"🖕","fu":"🖕","point_down":"👇","point_up":"☝️","+1":"👍","thumbsup":"👍","-1":"👎","thumbsdown":"👎","fist_raised":"✊","fist":"✊","fist_oncoming":"👊","facepunch":"👊","punch":"👊","fist_left":"🤛","fist_right":"🤜","clap":"👏","raised_hands":"🙌","open_hands":"👐","palms_up_together":"🤲","handshake":"🤝","pray":"🙏","writing_hand":"✍️","nail_care":"💅","selfie":"🤳","muscle":"💪","mechanical_arm":"🦾","mechanical_leg":"🦿","leg":"🦵","foot":"🦶","ear":"👂","ear_with_hearing_aid":"🦻","nose":"👃","brain":"🧠","anatomical_heart":"🫀","lungs":"🫁","tooth":"🦷","bone":"🦴","eyes":"👀","eye":"👁️","tongue":"👅","lips":"👄","baby":"👶","child":"🧒","boy":"👦","girl":"👧","adult":"🧑","blond_haired_person":"👱","man":"👨","bearded_person":"🧔","man_beard":"🧔♂️","woman_beard":"🧔♀️","red_haired_man":"👨🦰","curly_haired_man":"👨🦱","white_haired_man":"👨🦳","bald_man":"👨🦲","woman":"👩","red_haired_woman":"👩🦰","person_red_hair":"🧑🦰","curly_haired_woman":"👩🦱","person_curly_hair":"🧑🦱","white_haired_woman":"Line truncated
|
||||
@@ -48,7 +48,8 @@ import RefreshIcon from "@mui/icons-material/Refresh";
|
||||
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
|
||||
import CelebrationIcon from "@mui/icons-material/Celebration";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { ContentCopy, Public } from "@mui/icons-material";
|
||||
import ContentCopy from "@mui/icons-material/ContentCopy";
|
||||
import Public from "@mui/icons-material/Public";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import routes from "./routes";
|
||||
import { copyToClipboard, formatBytes, formatShortDate, formatShortDateTime, formatShortDuration, openUrl } from "../app/utils";
|
||||
|
||||
@@ -6,9 +6,12 @@ import { useLocation, useNavigate } from "react-router-dom";
|
||||
import MoreVertIcon from "@mui/icons-material/MoreVert";
|
||||
import NotificationsIcon from "@mui/icons-material/Notifications";
|
||||
import NotificationsOffIcon from "@mui/icons-material/NotificationsOff";
|
||||
import RefreshIcon from "@mui/icons-material/Refresh";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import AccountCircleIcon from "@mui/icons-material/AccountCircle";
|
||||
import { Logout, Person, Settings } from "@mui/icons-material";
|
||||
import Logout from "@mui/icons-material/Logout";
|
||||
import Person from "@mui/icons-material/Person";
|
||||
import Settings from "@mui/icons-material/Settings";
|
||||
import session from "../app/Session";
|
||||
import logo from "../img/ntfy.svg";
|
||||
import subscriptionManager from "../app/SubscriptionManager";
|
||||
@@ -88,6 +91,7 @@ const ActionBar = (props) => {
|
||||
<Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1 }}>
|
||||
{title}
|
||||
</Typography>
|
||||
{isLaunchedPWA && <ReloadIcon />}
|
||||
{props.selected && <SettingsIcons subscription={props.selected} onUnsubscribe={props.onUnsubscribe} />}
|
||||
<ProfileIcon />
|
||||
</Toolbar>
|
||||
@@ -124,6 +128,31 @@ const SettingsIcons = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
// ReloadIcon hard-refreshes the app. A plain reload would just serve the precached PWA shell,
|
||||
// so we first purge the service worker caches to force fresh assets from the network.
|
||||
const ReloadIcon = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleReload = async () => {
|
||||
try {
|
||||
if ("caches" in window) {
|
||||
const keys = await caches.keys();
|
||||
await Promise.all(keys.map((key) => caches.delete(key)));
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("[ActionBar] Error clearing caches during reload", e);
|
||||
} finally {
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<IconButton color="inherit" size="large" edge="end" onClick={handleReload} aria-label={t("action_bar_reload")}>
|
||||
<RefreshIcon />
|
||||
</IconButton>
|
||||
);
|
||||
};
|
||||
|
||||
const ProfileIcon = () => {
|
||||
const { t } = useTranslation();
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
import { useRef, useState } from "react";
|
||||
import { Typography, Box, TextField, ClickAwayListener, Fade, InputAdornment, styled, IconButton, Popper } from "@mui/material";
|
||||
import { Close } from "@mui/icons-material";
|
||||
import Close from "@mui/icons-material/Close";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { splitNoEmpty } from "../app/utils";
|
||||
import { rawEmojis } from "../app/emojis";
|
||||
|
||||
@@ -4,7 +4,8 @@ import { Typography, TextField, Button, Box, IconButton, InputAdornment } from "
|
||||
import WarningAmberIcon from "@mui/icons-material/WarningAmber";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Visibility, VisibilityOff } from "@mui/icons-material";
|
||||
import Visibility from "@mui/icons-material/Visibility";
|
||||
import VisibilityOff from "@mui/icons-material/VisibilityOff";
|
||||
import accountApi from "../app/AccountApi";
|
||||
import AvatarBox from "./AvatarBox";
|
||||
import session from "../app/Session";
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import * as React from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useRemark } from "react-remark";
|
||||
import styled from "@emotion/styled";
|
||||
|
||||
const MarkdownContainer = styled("div")`
|
||||
line-height: 1;
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p,
|
||||
pre,
|
||||
ul,
|
||||
ol,
|
||||
blockquote {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
blockquote,
|
||||
pre {
|
||||
border-radius: 3px;
|
||||
background: ${(props) => (props.theme.palette.mode === "light" ? "#f5f5f5" : "#333")};
|
||||
}
|
||||
|
||||
pre {
|
||||
overflow-x: scroll;
|
||||
padding: 0.9rem;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol,
|
||||
blockquote {
|
||||
padding-inline: 1rem;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
// Renders text/markdown notification bodies. react-remark (and its remark/unified
|
||||
// dependency stack) is heavy, so this component lives in its own module and is loaded
|
||||
// lazily by Notifications.jsx -- it only ships when a markdown message is actually shown.
|
||||
const MarkdownContent = ({ content }) => {
|
||||
const [reactContent, setMarkdownSource] = useRemark();
|
||||
|
||||
useEffect(() => {
|
||||
setMarkdownSource(content);
|
||||
}, [content]);
|
||||
|
||||
return <MarkdownContainer>{reactContent}</MarkdownContainer>;
|
||||
};
|
||||
|
||||
export default MarkdownContent;
|
||||
@@ -27,7 +27,10 @@ import Person from "@mui/icons-material/Person";
|
||||
import SettingsIcon from "@mui/icons-material/Settings";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { ChatBubble, MoreVert, NotificationsOffOutlined, Send } from "@mui/icons-material";
|
||||
import ChatBubble from "@mui/icons-material/ChatBubble";
|
||||
import MoreVert from "@mui/icons-material/MoreVert";
|
||||
import NotificationsOffOutlined from "@mui/icons-material/NotificationsOffOutlined";
|
||||
import Send from "@mui/icons-material/Send";
|
||||
import ArticleIcon from "@mui/icons-material/Article";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import CelebrationIcon from "@mui/icons-material/Celebration";
|
||||
|
||||
@@ -17,15 +17,13 @@ import {
|
||||
Button,
|
||||
} from "@mui/material";
|
||||
import * as React from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Suspense, lazy, useEffect, useState } from "react";
|
||||
import CheckIcon from "@mui/icons-material/Check";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { useLiveQuery } from "dexie-react-hooks";
|
||||
import InfiniteScroll from "react-infinite-scroll-component";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { useOutletContext } from "react-router-dom";
|
||||
import { useRemark } from "react-remark";
|
||||
import styled from "@emotion/styled";
|
||||
import {
|
||||
copyToClipboard,
|
||||
formatBytes,
|
||||
@@ -172,64 +170,19 @@ const autolink = (s) => {
|
||||
return <>{parts}</>;
|
||||
};
|
||||
|
||||
const MarkdownContainer = styled("div")`
|
||||
line-height: 1;
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p,
|
||||
pre,
|
||||
ul,
|
||||
ol,
|
||||
blockquote {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
blockquote,
|
||||
pre {
|
||||
border-radius: 3px;
|
||||
background: ${(props) => (props.theme.palette.mode === "light" ? "#f5f5f5" : "#333")};
|
||||
}
|
||||
|
||||
pre {
|
||||
overflow-x: scroll;
|
||||
padding: 0.9rem;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol,
|
||||
blockquote {
|
||||
padding-inline: 1rem;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
const MarkdownContent = ({ content }) => {
|
||||
const [reactContent, setMarkdownSource] = useRemark();
|
||||
|
||||
useEffect(() => {
|
||||
setMarkdownSource(content);
|
||||
}, [content]);
|
||||
|
||||
return <MarkdownContainer>{reactContent}</MarkdownContainer>;
|
||||
};
|
||||
// Loaded lazily so the heavy react-remark/unified markdown stack is only fetched when a
|
||||
// text/markdown notification is actually rendered (see MarkdownContent.jsx).
|
||||
const MarkdownContent = lazy(() => import("./MarkdownContent"));
|
||||
|
||||
const NotificationBody = ({ notification }) => {
|
||||
const displayAsMarkdown = notification.content_type === "text/markdown";
|
||||
const formatted = formatMessage(notification);
|
||||
if (displayAsMarkdown) {
|
||||
return <MarkdownContent content={formatted} />;
|
||||
return (
|
||||
<Suspense fallback={null}>
|
||||
<MarkdownContent content={formatted} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
return autolink(formatted);
|
||||
};
|
||||
|
||||
@@ -33,7 +33,7 @@ import CloseIcon from "@mui/icons-material/Close";
|
||||
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
|
||||
import { useLiveQuery } from "dexie-react-hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Info } from "@mui/icons-material";
|
||||
import Info from "@mui/icons-material/Info";
|
||||
import { useOutletContext } from "react-router-dom";
|
||||
import userManager from "../app/UserManager";
|
||||
import { playSound, shortUrl, shuffle, sounds, validUrl } from "../app/utils";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from "react";
|
||||
import { useContext, useEffect, useRef, useState } from "react";
|
||||
import { Suspense, lazy, useContext, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
Checkbox,
|
||||
Chip,
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import InsertEmoticonIcon from "@mui/icons-material/InsertEmoticon";
|
||||
import { Close } from "@mui/icons-material";
|
||||
import Close from "@mui/icons-material/Close";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import priority1 from "../img/priority-1.svg";
|
||||
import priority2 from "../img/priority-2.svg";
|
||||
@@ -35,13 +35,16 @@ import AttachmentIcon from "./AttachmentIcon";
|
||||
import DialogFooter from "./DialogFooter";
|
||||
import api from "../app/Api";
|
||||
import userManager from "../app/UserManager";
|
||||
import EmojiPicker from "./EmojiPicker";
|
||||
import session from "../app/Session";
|
||||
import routes from "./routes";
|
||||
import accountApi from "../app/AccountApi";
|
||||
import { UnauthorizedError } from "../app/errors";
|
||||
import AccountContext from "./AccountContext";
|
||||
|
||||
// Loaded lazily so the full emoji dataset (~300 KB) is only fetched when the publish
|
||||
// dialog is opened, not in the initial app bundle (see EmojiPicker.jsx).
|
||||
const EmojiPicker = lazy(() => import("./EmojiPicker"));
|
||||
|
||||
const PublishDialog = (props) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
@@ -398,7 +401,9 @@ const PublishDialog = (props) => {
|
||||
}
|
||||
/>
|
||||
<div style={{ display: "flex" }}>
|
||||
<EmojiPicker anchorEl={emojiPickerAnchorEl} onEmojiPick={handleEmojiPick} onClose={handleEmojiClose} />
|
||||
<Suspense fallback={null}>
|
||||
<EmojiPicker anchorEl={emojiPickerAnchorEl} onEmojiPick={handleEmojiPick} onClose={handleEmojiClose} />
|
||||
</Suspense>
|
||||
<DialogIconButton disabled={disabled} onClick={handleEmojiClick} aria-label={t("publish_dialog_emoji_picker_show")}>
|
||||
<InsertEmoticonIcon />
|
||||
</DialogIconButton>
|
||||
|
||||
@@ -17,7 +17,8 @@ import {
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Check, DeleteForever } from "@mui/icons-material";
|
||||
import Check from "@mui/icons-material/Check";
|
||||
import DeleteForever from "@mui/icons-material/DeleteForever";
|
||||
import { validTopic } from "../app/utils";
|
||||
import DialogFooter from "./DialogFooter";
|
||||
import session from "../app/Session";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as React from "react";
|
||||
import { Lock, Public } from "@mui/icons-material";
|
||||
import Lock from "@mui/icons-material/Lock";
|
||||
import Public from "@mui/icons-material/Public";
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
export const PermissionReadWrite = React.forwardRef((props, ref) => <PermissionInternal icon={Public} ref={ref} {...props} />);
|
||||
|
||||
@@ -4,7 +4,8 @@ import { TextField, Button, Box, Typography, InputAdornment, IconButton } from "
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import WarningAmberIcon from "@mui/icons-material/WarningAmber";
|
||||
import { Visibility, VisibilityOff } from "@mui/icons-material";
|
||||
import Visibility from "@mui/icons-material/Visibility";
|
||||
import VisibilityOff from "@mui/icons-material/VisibilityOff";
|
||||
import accountApi from "../app/AccountApi";
|
||||
import AvatarBox from "./AvatarBox";
|
||||
import session from "../app/Session";
|
||||
|
||||
@@ -19,18 +19,16 @@ import {
|
||||
} from "@mui/material";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import {
|
||||
Clear,
|
||||
ClearAll,
|
||||
Edit,
|
||||
EnhancedEncryption,
|
||||
Lock,
|
||||
LockOpen,
|
||||
Notifications,
|
||||
NotificationsOff,
|
||||
RemoveCircle,
|
||||
Send,
|
||||
} from "@mui/icons-material";
|
||||
import Clear from "@mui/icons-material/Clear";
|
||||
import ClearAll from "@mui/icons-material/ClearAll";
|
||||
import Edit from "@mui/icons-material/Edit";
|
||||
import EnhancedEncryption from "@mui/icons-material/EnhancedEncryption";
|
||||
import Lock from "@mui/icons-material/Lock";
|
||||
import LockOpen from "@mui/icons-material/LockOpen";
|
||||
import Notifications from "@mui/icons-material/Notifications";
|
||||
import NotificationsOff from "@mui/icons-material/NotificationsOff";
|
||||
import RemoveCircle from "@mui/icons-material/RemoveCircle";
|
||||
import Send from "@mui/icons-material/Send";
|
||||
import subscriptionManager from "../app/SubscriptionManager";
|
||||
import DialogFooter from "./DialogFooter";
|
||||
import accountApi, { Role } from "../app/AccountApi";
|
||||
|
||||
@@ -24,7 +24,8 @@ import {
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { Check, Close } from "@mui/icons-material";
|
||||
import Check from "@mui/icons-material/Check";
|
||||
import Close from "@mui/icons-material/Close";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { UnauthorizedError } from "../app/errors";
|
||||
import { formatBytes, formatNumber, formatPrice, formatShortDate } from "../app/utils";
|
||||
|
||||
Reference in new issue
Block a user