Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"tabs",
"activeTab",
"storage",
"webRequest"
"webRequest",
"notifications"
],
"content_scripts": [
{
Expand Down
19 changes: 19 additions & 0 deletions src/pointsdk/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,22 @@ export type Network = {
currency_code: string;
tokens?: Token[];
};

export type PointNotification = {
id: number;
block_number: string;
timestamp: number;
identity: string;
address: string;
contract: string;
event: string;
arguments: Record<string, unknown>;
viewed: boolean;
log: Record<string, unknown>;
};

export type BlockRange = {
from: number;
to: number;
latest?: number;
};
18 changes: 17 additions & 1 deletion src/pointsdk/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ import {
SubscriptionMessages,
SubscriptionEvent,
SubscriptionParams,
IdentityData
IdentityData,
PointNotification,
BlockRange
} from './index.d';

const getSdk = (host: string, version: string, swal: any): PointType => {
Expand Down Expand Up @@ -781,6 +783,20 @@ const getSdk = (host: string, version: string, swal: any): PointType => {
api.get<T>(`identity/ownerToIdentity/${owner}`, args),
me: () => api.get<IdentityData>('identity/isIdentityRegistered/')
},
notifications: {
unread: () => api.get<PointNotification[]>('notifications/unread'),
scan: ({from, to, latest}: Partial<BlockRange>) => {
const endpoint = 'notifications/scan';
const query = new URLSearchParams();
if (from) query.append('from', String(from));
if (to) query.append('to', String(to));
if (latest) query.append('latest', String(latest));
const queryStr = query.toString();
const url = queryStr ? `${endpoint}?${queryStr}` : endpoint;
return api.get<PointNotification[]>(url);
},
markRead: (id: number) => api.get(`notifications/read/${id}`)
},
...(host === 'https://point' && !window.top.IS_GATEWAY
? {
point: {
Expand Down
16 changes: 15 additions & 1 deletion src/popup/App.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
import React, {FunctionComponent} from 'react';
import {MemoryRouter as Router, Routes, Route} from 'react-router-dom';
import UIThemeProvider from 'pointsdk/theme/UIThemeProvider';
import {BlockchainContext, useBlockchain} from './context/blockchain';
import {NotificationsProvider} from './context/notifications';
import Layout from './components/Layout';
import Notifications from './components/Notifications';

const App: FunctionComponent = () => {
const blockchainContext = useBlockchain();

return (
<BlockchainContext.Provider value={blockchainContext}>
<Layout />
<NotificationsProvider>
<UIThemeProvider>
<Router>
<Routes>
<Route path="/notifications" element={<Notifications />} />
<Route path="/" element={<Layout />} />
</Routes>
</Router>
</UIThemeProvider>
</NotificationsProvider>
</BlockchainContext.Provider>
);
};

export default App;
15 changes: 15 additions & 0 deletions src/popup/components/BackArrow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React, {FunctionComponent} from 'react';
import {Link} from 'react-router-dom';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';

type Props = {
to: string;
};

const BackArrow: FunctionComponent<Props> = ({to}: Props) => (
<Link to={to}>
<ArrowBackIcon color="disabled" />
</Link>
);

export default BackArrow;
67 changes: 32 additions & 35 deletions src/popup/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import Typography from '@mui/material/Typography';
import CircularProgress from '@mui/material/CircularProgress';
import Divider from '@mui/material/Divider';
import {BlockchainContext} from '../context/blockchain';
import UIThemeProvider from 'pointsdk/theme/UIThemeProvider';
import NetworkSwitcher from './NetworkSwitcher';
import UserData from './UserData';
import Links from './Links';
Expand All @@ -17,41 +16,39 @@ const Layout: FunctionComponent = () => {
const {loading} = useContext(BlockchainContext);

return (
<UIThemeProvider>
<Box sx={{minWidth: 370}}>
{' '}
<Box
display="flex"
alignItems="center"
justifyContent="flex-end"
position="fixed"
right="0"
>
<img src={PointLogo} alt="point-logo" width={20} />
<Typography ml="2px" mr={2} variant="caption">
PointSDK <Version />
</Typography>
</Box>
{loading ? (
<Box display="flex" px={12} py={20} alignItems="center" justifyContent="center">
<CircularProgress size={24} />
<Typography ml={1}>Loading...</Typography>
</Box>
) : (
<Fragment>
<NetworkSwitcher />
<Divider />
<UserData />
<Divider />
<Balance />
<Divider />
<Links />
<Divider />
<Tokens />
</Fragment>
)}
<Box sx={{minWidth: 370}}>
{' '}
<Box
display="flex"
alignItems="center"
justifyContent="flex-end"
position="fixed"
right="0"
>
<img src={PointLogo} alt="point-logo" width={20} />
<Typography ml="2px" mr={2} variant="caption">
PointSDK <Version />
</Typography>
</Box>
</UIThemeProvider>
{loading ? (
<Box display="flex" px={12} py={20} alignItems="center" justifyContent="center">
<CircularProgress size={24} />
<Typography ml={1}>Loading...</Typography>
</Box>
) : (
<Fragment>
<NetworkSwitcher />
<Divider />
<UserData />
<Divider />
<Balance />
<Divider />
<Links />
<Divider />
<Tokens />
</Fragment>
)}
</Box>
);
};

Expand Down
27 changes: 26 additions & 1 deletion src/popup/components/Links.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import React, {FunctionComponent} from 'react';
import {useNavigate} from 'react-router-dom';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
import HomeIcon from '@mui/icons-material/Home';
import AccountBalanceWalletIcon from '@mui/icons-material/AccountBalanceWallet';
import NotificationsIcon from '@mui/icons-material/Notifications';
import Badge from '@mui/material/Badge';
import browser from 'webextension-polyfill';
import {useNotifications} from '../context/notifications';

const Links: FunctionComponent = () => {
const navigate = useNavigate();
const {notifications} = useNotifications();

const go = async (url: string) => {
// Open new tab with the desired URL.
await browser.tabs.create({url});
Expand All @@ -19,7 +26,7 @@ const Links: FunctionComponent = () => {
<Typography variant="caption" sx={{opacity: 0.7}}>
Quick Links
</Typography>
<Box display="flex" alignItems="baseline" my={1}>
<Box display="flex" justifyContent="space-between" my={1}>
<Box
display="flex"
alignItems="center"
Expand All @@ -43,6 +50,24 @@ const Links: FunctionComponent = () => {
My Wallet
</Typography>
</Box>
<Box
display="flex"
alignItems="center"
sx={{cursor: 'pointer'}}
onClick={() => navigate('/notifications')}
>
<Badge
color="primary"
badgeContent={notifications.length}
max={99}
anchorOrigin={{vertical: 'top', horizontal: 'left'}}
>
<NotificationsIcon fontSize="small" />
</Badge>
<Typography variant="body2" ml="2px">
Notifications
</Typography>
</Box>
</Box>
</Box>
);
Expand Down
120 changes: 120 additions & 0 deletions src/popup/components/Notification.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import React, {FunctionComponent} from 'react';
import browser from 'webextension-polyfill';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button';
import CloseIcon from '@mui/icons-material/Close';
import {PointNotification} from 'pointsdk/pointsdk/index.d';

type ParsedNotification = {
title: string;
from: string;
date: string;
link: string;
};

const socialActionToTile: Record<string, (id: string | number) => string> = {
POST_LIKE: (id: string | number) => `Post #${id} got a new like`,
POST_LIKE_DELETE: (id: string | number) => `Post #${id} got an unlike`,
POST_DISLIKE: (id: string | number) => `Post #${id} got a new dislike`,
POST_CREATE: (id: string | number) => `New post #${id}`,
POST_EDIT: (id: string | number) => `Post #${id} has been edited`,
POST_DELETE: (id: string | number) => `Post #${id} has been deleted`,
POST_FLAG: (id: string | number) => `Post #${id} has been flagged`,
POST_COMMENT_CREATE: (id: string | number) => `New comment on post #${id}`,
POST_COMMENT_DELETE: (id: string | number) => `Comment deleted on post #${id}`,
COMMENT_EDIT: (id: string | number) => `Comment edited on post #${id}`,
COMMENT_DELETE: (id: string | number) => `Comment deleted on post #${id}`
};

function parseNotification(n: PointNotification): ParsedNotification {
const data = n.arguments;
if (n.identity === 'social.point' && n.event === 'StateChange') {
return {
from: n.identity,
title: socialActionToTile[data.changeAction as string]
? socialActionToTile[data.changeAction as string]!(data.id as string)
: `New ${n.event}`,
date: new Date(n.timestamp).toLocaleDateString(),
link: String(data.changeAction).includes('DELETE')
? ''
: `https://social.point/post/${data.id as string}`
};
}

if (n.identity === 'email.point' && n.event === 'RecipientAdded') {
return {
from: n.identity,
title: 'You got email',
date: new Date(n.timestamp).toLocaleDateString(),
link: `https://email.point/show?id=${data.id as string}`
};
}

return {
from: n.identity,
title: `New ${n.event}`,
date: new Date(n.timestamp).toLocaleDateString(),
link: ''
};
}

type Props = {
n: PointNotification;
onMarkRead: (id: number) => void;
};

const Notification: FunctionComponent<Props> = ({n, onMarkRead}: Props) => {
const {title, from, date, link} = parseNotification(n);

const go = async (url: string) => {
// Open new tab with the desired URL.
await browser.tabs.create({url});
// Close the extension popup window.
window.close();
};

return (
<Box
sx={{
borderRadius: 4,
background:
'linear-gradient(to bottom, rgba(0, 10, 23, .8) 0%, rgb(0, 10, 23) 100%)',
color: 'text.secondary',
padding: 2,
margin: 2,
overflowX: 'auto'
}}
>
<Box display="flex" alignItems="center" justifyContent="space-between">
<Typography fontWeight="bold" variant="h6">
{title}
</Typography>
<Button
size="small"
variant="text"
color="inherit"
onClick={() => onMarkRead(n.id)}
title="Dismiss"
>
<CloseIcon />
</Button>
</Box>
<Box>
<Typography variant="caption">
App: <strong>{from}</strong>
</Typography>
<Typography variant="caption" ml={2}>
On: <strong>{date}</strong>
</Typography>
</Box>
{link ? (
<Button variant="text" size="small" onClick={() => go(link)}>
View
</Button>
) : null}
</Box>
);
};

export default Notification;
Loading