diff --git a/desktop-app/src/common/constants.ts b/desktop-app/src/common/constants.ts
index 6a04890c3..e4dd3d361 100644
--- a/desktop-app/src/common/constants.ts
+++ b/desktop-app/src/common/constants.ts
@@ -16,6 +16,13 @@ export const PREVIEW_LAYOUTS = {
export type PreviewLayout =
typeof PREVIEW_LAYOUTS[keyof typeof PREVIEW_LAYOUTS];
+export type Notification = {
+ id: string;
+ link?: string;
+ linkText?: string;
+ text: string;
+};
+
export interface OpenUrlArgs {
url: string;
}
diff --git a/desktop-app/src/main/main.ts b/desktop-app/src/main/main.ts
index e4f8084b7..494fa57fb 100644
--- a/desktop-app/src/main/main.ts
+++ b/desktop-app/src/main/main.ts
@@ -263,6 +263,10 @@ const createWindow = async () => {
ipcMain.on('stop-watcher', async () => {
await stopWatchFiles();
});
+
+ ipcMain.handle('get-app-version', () => {
+ return app.getVersion();
+ });
};
app.on('open-url', async (event, url) => {
diff --git a/desktop-app/src/renderer/components/Notifications/Notification.tsx b/desktop-app/src/renderer/components/Notifications/Notification.tsx
new file mode 100644
index 000000000..97f374eb6
--- /dev/null
+++ b/desktop-app/src/renderer/components/Notifications/Notification.tsx
@@ -0,0 +1,33 @@
+import {
+ IPC_MAIN_CHANNELS,
+ Notification as NotificationType,
+} from 'common/constants';
+import Button from '../Button';
+
+const Notification = ({ notification }: { notification: NotificationType }) => {
+ const handleLinkClick = (url: string) => {
+ window.electron.ipcRenderer.sendMessage(IPC_MAIN_CHANNELS.OPEN_EXTERNAL, {
+ url,
+ });
+ };
+
+ return (
+
+
{notification.text}
+ {notification.link && notification.linkText && (
+
+ )}
+
+ );
+};
+
+export default Notification;
diff --git a/desktop-app/src/renderer/components/Notifications/NotificationEmptyStatus.tsx b/desktop-app/src/renderer/components/Notifications/NotificationEmptyStatus.tsx
new file mode 100644
index 000000000..c6830dbb3
--- /dev/null
+++ b/desktop-app/src/renderer/components/Notifications/NotificationEmptyStatus.tsx
@@ -0,0 +1,9 @@
+const NotificationEmptyStatus = () => {
+ return (
+
+
You are all caught up! No new notifications at the moment.
+
+ );
+};
+
+export default NotificationEmptyStatus;
diff --git a/desktop-app/src/renderer/components/Notifications/Notifications.tsx b/desktop-app/src/renderer/components/Notifications/Notifications.tsx
new file mode 100644
index 000000000..c5835ce7d
--- /dev/null
+++ b/desktop-app/src/renderer/components/Notifications/Notifications.tsx
@@ -0,0 +1,27 @@
+import { useSelector } from 'react-redux';
+import { selectNotifications } from 'renderer/store/features/renderer';
+import { v4 as uuidv4 } from 'uuid';
+import { Notification as NotificationType } from 'common/constants';
+import Notification from './Notification';
+import NotificationEmptyStatus from './NotificationEmptyStatus';
+
+const Notifications = () => {
+ const notificationsState = useSelector(selectNotifications);
+
+ return (
+
+
Notifications
+
+ {(!notificationsState || notificationsState?.length === 0) && (
+
+ )}
+ {notificationsState?.length > 0 &&
+ notificationsState?.map((notification: NotificationType) => (
+
+ ))}
+
+
+ );
+};
+
+export default Notifications;
diff --git a/desktop-app/src/renderer/components/Notifications/NotificationsBubble.tsx b/desktop-app/src/renderer/components/Notifications/NotificationsBubble.tsx
new file mode 100644
index 000000000..43dc1cb29
--- /dev/null
+++ b/desktop-app/src/renderer/components/Notifications/NotificationsBubble.tsx
@@ -0,0 +1,10 @@
+const NotificationsBubble = () => {
+ return (
+
+
+
+
+ );
+};
+
+export default NotificationsBubble;
diff --git a/desktop-app/src/renderer/components/Previewer/Device/Toolbar.tsx b/desktop-app/src/renderer/components/Previewer/Device/Toolbar.tsx
index 41a950e08..1b4a56640 100644
--- a/desktop-app/src/renderer/components/Previewer/Device/Toolbar.tsx
+++ b/desktop-app/src/renderer/components/Previewer/Device/Toolbar.tsx
@@ -1,5 +1,5 @@
import { Icon } from '@iconify/react';
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
import Button from 'renderer/components/Button';
import useSound from 'use-sound';
import { ScreenshotArgs, ScreenshotResult } from 'main/screenshot';
@@ -8,6 +8,9 @@ import WebPage from 'main/screenshot/webpage';
import screenshotSfx from 'renderer/assets/sfx/screenshot.mp3';
import { updateWebViewHeightAndScale } from 'common/webViewUtils';
+import useCheckVersion from 'renderer/components/useCheckVersion/useCheckVersion';
+import { useDispatch } from 'react-redux';
+import { setNotifications } from 'renderer/store/features/renderer';
import { ColorBlindnessTools } from './ColorBlindnessTools';
interface Props {
@@ -21,6 +24,13 @@ interface Props {
isIndividualLayout: boolean;
}
+const newVersionText = {
+ id: 'new-version',
+ text: 'There is a new version available.',
+ link: 'https://responsively.app/download',
+ linkText: 'See More',
+};
+
const Toolbar = ({
webview,
device,
@@ -38,6 +48,16 @@ const Toolbar = ({
useState(false);
const [rotated, setRotated] = useState(false);
+ const dispatch = useDispatch();
+
+ const { isNewVersionAvailable } = useCheckVersion();
+
+ useEffect(() => {
+ if (isNewVersionAvailable) {
+ dispatch(setNotifications(newVersionText));
+ }
+ }, [dispatch, isNewVersionAvailable]);
+
const refreshView = () => {
if (webview) {
webview.reload();
diff --git a/desktop-app/src/renderer/components/ToolBar/Menu/Flyout/index.tsx b/desktop-app/src/renderer/components/ToolBar/Menu/Flyout/index.tsx
index a4ac6d3ac..c707105b8 100644
--- a/desktop-app/src/renderer/components/ToolBar/Menu/Flyout/index.tsx
+++ b/desktop-app/src/renderer/components/ToolBar/Menu/Flyout/index.tsx
@@ -1,8 +1,6 @@
-import { useDispatch } from 'react-redux';
-
-import Button from 'renderer/components/Button';
-import { APP_VIEWS, setAppView } from 'renderer/store/features/ui';
import MasonryLayout from 'renderer/components/Masonry/MasonryLayout';
+import Notifications from 'renderer/components/Notifications/Notifications';
+import { Divider } from 'renderer/components/Divider';
import Devtools from './Devtools';
import UITheme from './UITheme';
import Zoom from './Zoom';
@@ -12,17 +10,11 @@ import PreviewLayout from './PreviewLayout';
import Bookmark from './Bookmark';
import { Settings } from './Settings';
-const Divider = () => (
-
-);
-
interface Props {
closeFlyout: () => void;
}
const MenuFlyout = ({ closeFlyout }: Props) => {
- const dispatch = useDispatch();
-
return (
@@ -38,6 +30,8 @@ const MenuFlyout = ({ closeFlyout }: Props) => {
+
+
);
};
diff --git a/desktop-app/src/renderer/components/ToolBar/Menu/index.tsx b/desktop-app/src/renderer/components/ToolBar/Menu/index.tsx
index ddafa6f42..f2e2ac1cf 100644
--- a/desktop-app/src/renderer/components/ToolBar/Menu/index.tsx
+++ b/desktop-app/src/renderer/components/ToolBar/Menu/index.tsx
@@ -3,11 +3,20 @@ import { useDetectClickOutside } from 'react-detect-click-outside';
import Button from 'renderer/components/Button';
import { useDispatch, useSelector } from 'react-redux';
import { closeMenuFlyout, selectMenuFlyout } from 'renderer/store/features/ui';
+import { selectNotifications } from 'renderer/store/features/renderer';
+import useLocalStorage from 'renderer/components/useLocalStorage/useLocalStorage';
+import NotificationsBubble from 'renderer/components/Notifications/NotificationsBubble';
import MenuFlyout from './Flyout';
const Menu = () => {
const dispatch = useDispatch();
const isMenuFlyoutOpen = useSelector(selectMenuFlyout);
+ const notifications = useSelector(selectNotifications);
+
+ const [hasNewNotifications, setHasNewNotifications] = useLocalStorage(
+ 'hasNewNotifications',
+ true
+ );
const ref = useDetectClickOutside({
onTriggered: () => {
@@ -20,6 +29,7 @@ const Menu = () => {
const handleFlyout = () => {
dispatch(closeMenuFlyout(!isMenuFlyoutOpen));
+ setHasNewNotifications(false);
};
const onClose = () => {
@@ -27,9 +37,12 @@ const Menu = () => {
};
return (
-
+
diff --git a/desktop-app/src/renderer/components/ToolBar/index.tsx b/desktop-app/src/renderer/components/ToolBar/index.tsx
index 9a8d0c857..51739039c 100644
--- a/desktop-app/src/renderer/components/ToolBar/index.tsx
+++ b/desktop-app/src/renderer/components/ToolBar/index.tsx
@@ -6,6 +6,7 @@ import {
setIsCapturingScreenshot,
setIsInspecting,
setRotate,
+ setNotifications,
} from 'renderer/store/features/renderer';
import { Icon } from '@iconify/react';
import { ScreenshotAllArgs } from 'main/screenshot';
@@ -103,9 +104,7 @@ const ToolBar = () => {
return (
-
-