notifications: adding initial structure

This commit is contained in:
Hunter Miller 2021-08-24 18:02:07 -05:00
parent aaaee41e95
commit b4bd2c3bea
12 changed files with 187 additions and 61 deletions

View File

@ -5,11 +5,14 @@
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Landscape • Home</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@400;600&display=swap" rel="stylesheet">
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@400;600&display=swap"
rel="stylesheet"
/>
</head>
<body class="text-sm font-sans text-gray-900 bg-white antialiased">
<body class="text-sm leading-6 font-sans text-gray-900 bg-white antialiased">
<div id="app"></div>
<script type="module" src="/src/main.tsx"></script>
</body>

View File

@ -0,0 +1,14 @@
import React, { HTMLAttributes } from 'react';
type ElbowProps = HTMLAttributes<SVGSVGElement>;
export const Elbow = (props: ElbowProps) => (
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M11 1V5C11 9.41828 14.5817 13 19 13H23"
className="stroke-current"
strokeWidth="2"
strokeLinecap="round"
/>
</svg>
);

View File

@ -1,20 +1,57 @@
import React, { useEffect } from 'react';
import { Link } from 'react-router-dom';
import { Button } from '../components/Button';
import { useHarkStore } from '../state/hark';
import { Notification } from '../state/hark-types';
import { useLeapStore } from './Nav';
import { BasicNotification } from './notifications/BasicNotification';
import { SystemNotification } from './notifications/SystemNotification';
function renderNotification(notification: Notification) {
if (notification.type === 'system-updates-blocked') {
return <SystemNotification notification={notification} />;
}
return <BasicNotification notification={notification} />;
}
const Empty = () => (
<section className="flex justify-center items-center min-h-[480px] text-gray-400 space-y-2">
<span className="h4">All clear!</span>
</section>
);
export const Notifications = () => {
const select = useLeapStore((state) => state.select);
const select = useLeapStore((s) => s.select);
const notifications = useHarkStore((s) => s.notifications);
const hasNotifications = notifications.length > 0;
useEffect(() => {
select('Notifications');
}, []);
return (
<div className="p-4 md:p-8 space-y-8">
<h2 className="h4 text-gray-500">Recent Apps</h2>
<div className="min-h-[150px] rounded-xl bg-gray-100" />
<hr className="-mx-4 md:-mx-8" />
<h2 className="h4 text-gray-500">Recent Developers</h2>
<div className="min-h-[150px] rounded-xl bg-gray-100" />
<div className="p-4 md:p-8">
<header className="space-x-2 mb-8">
<Button variant="secondary" className="py-1.5 px-6 rounded-full">
Mark All as Read
</Button>
<Button
as={Link}
variant="secondary"
to="/leap/system-preferences/notifications"
className="py-1.5 px-6 rounded-full"
>
Notification Settings
</Button>
</header>
{!hasNotifications && <Empty />}
{hasNotifications && (
<section className="min-h-[480px] text-gray-400 space-y-2">
{notifications.map((n) => renderNotification(n))}
</section>
)}
</div>
);
};

View File

@ -0,0 +1,10 @@
import React from 'react';
import { BasicNotification as BasicNotificationType } from '../../state/hark-types';
interface BasicNotificationProps {
notification: BasicNotificationType;
}
export const BasicNotification = ({ notification }: BasicNotificationProps) => (
<div>{notification.message}</div>
);

View File

@ -0,0 +1,37 @@
import { pick } from 'lodash-es';
import React from 'react';
import { AppList } from '../../components/AppList';
import { Elbow } from '../../components/icons/Elbow';
import { useCharges } from '../../state/docket';
import { SystemNotification as SystemNotificationType } from '../../state/hark-types';
interface SystemNotificationProps {
notification: SystemNotificationType;
}
export const SystemNotification = ({ notification }: SystemNotificationProps) => {
const keys = notification.charges;
const charges = useCharges();
const blockedCharges = Object.values(pick(charges, keys));
return (
<section
className="notification pl-12 text-black bg-orange-100"
aria-labelledby="system-updates-blocked"
>
<header id="system-updates-blocked" className="relative -left-8 space-y-2">
<div className="flex space-x-2">
<span className="inline-block w-6 h-6 bg-orange-500 rounded-full" />
<span className="font-medium">Landscape</span>
</div>
<div className="flex space-x-2">
<Elbow className="w-6 h-6 text-gray-300" />
<h2 id="blocked-apps">
The following ({blockedCharges.length}) apps blocked a System Update:
</h2>
<AppList apps={blockedCharges} labelledBy="blocked-apps" />
</div>
</header>
</section>
);
};

View File

@ -22,6 +22,7 @@ import {
import { kilnRevive, kilnSuspend } from '@urbit/api/hood';
import api from './api';
import { mockAllies, mockCharges, mockTreaties } from './mock-data';
import { fakeRequest } from './util';
const useMockData = import.meta.env.MODE === 'mock';
@ -38,14 +39,6 @@ interface DocketState {
uninstallDocket: (desk: string) => Promise<number | void>;
}
async function fakeRequest<T>(data: T, time = 300): Promise<T> {
return new Promise((resolve) => {
setTimeout(() => {
resolve(data);
}, time);
});
}
const useDocketState = create<DocketState>((set, get) => ({
fetchCharges: async () => {
const charg = useMockData

View File

@ -0,0 +1,18 @@
/**
* I know this doesn't match our current hark type scheme, but since we're talking
* about changing that I decided to just throw something together to at least test
* this flow for updates.
*/
export interface SystemNotification {
type: 'system-updates-blocked';
charges: string[];
}
export interface BasicNotification {
type: 'basic';
time: string;
message: string;
}
export type Notification = BasicNotification | SystemNotification;

View File

@ -0,0 +1,12 @@
import create from 'zustand';
import { Notification } from './hark-types';
import { mockBlockedChargeNotification } from './mock-data';
import { useMockData } from './util';
interface HarkStore {
notifications: Notification[];
}
export const useHarkStore = create<HarkStore>(() => ({
notifications: useMockData ? [mockBlockedChargeNotification] : []
}));

View File

@ -1,6 +1,7 @@
import _ from 'lodash-es';
import { Allies, Charges, DocketHrefGlob, Treaties, Treaty } from '@urbit/api/docket';
import systemUrl from '../assets/system.png';
import { SystemNotification } from './hark-types';
export const appMetaData: Pick<Treaty, 'cass' | 'hash' | 'website' | 'license' | 'version'> = {
cass: '~2021.8.11..05.11.10..b721',
@ -149,3 +150,8 @@ export const mockAllies: Allies = [
'~nalrex_bannus',
'~nalrys'
].reduce((acc, val) => ({ ...acc, [val]: charter }), {});
export const mockBlockedChargeNotification: SystemNotification = {
type: 'system-updates-blocked',
charges: ['~zod/groups', '~zod/pomodoro']
};

View File

@ -1,4 +1,4 @@
import { DocketHref } from "@urbit/api/docket";
import { DocketHref } from '@urbit/api/docket';
export function makeKeyFn(key: string) {
return (childKeys: string[] = []) => {
@ -16,7 +16,6 @@ export async function fakeRequest<T>(data: T, time = 300): Promise<T> {
});
}
export function getAppHref(href: DocketHref) {
return 'site' in href ? href.site : `/apps/${href.glob.base}`;
}

View File

@ -22,6 +22,10 @@
@apply min-w-52 p-4 rounded-xl;
}
.notification {
@apply p-4 bg-gray-100 rounded-xl;
}
.spinner {
@apply inline-flex items-center w-6 h-6 animate-spin;
}

View File

@ -7,68 +7,61 @@ module.exports = {
theme: {
extend: {
colors: {
transparent: "transparent",
white: "#FFFFFF",
black: "#000000",
transparent: 'transparent',
white: '#FFFFFF',
black: '#000000',
gray: {
...colors.trueGray,
100: "#F2F2F2",
200: "#CCCCCC",
300: "#B3B3B3",
400: "#808080",
500: "#666666",
100: '#F2F2F2',
200: '#CCCCCC',
300: '#B3B3B3',
400: '#808080',
500: '#666666'
},
blue: {
100: "#E9F5FF",
200: "#D3EBFF",
300: "#BCE2FF",
400: "#219DFF",
100: '#E9F5FF',
200: '#D3EBFF',
300: '#BCE2FF',
400: '#219DFF'
},
red: {
100: "#FFF6F5",
200: "#FFC6C3",
400: "#FF4136",
100: '#FFF6F5',
200: '#FFC6C3',
400: '#FF4136'
},
green: {
100: "#E6F5F0",
200: "#B3E2D1",
400: "#009F65",
100: '#E6F5F0',
200: '#B3E2D1',
400: '#009F65'
},
yellow: {
100: "#FFF9E6",
200: "#FFEEB3",
300: "#FFDD66",
400: "#FFC700",
100: '#FFF9E6',
200: '#FFEEB3',
300: '#FFDD66',
400: '#FFC700'
},
orange: colors.orange
},
fontFamily: {
sans: [
'"Inter"',
'"Inter UI"',
"-apple-system",
"BlinkMacSystemFont",
'-apple-system',
'BlinkMacSystemFont',
'"San Francisco"',
'"Helvetica Neue"',
"Arial",
"sans-serif",
],
mono: [
'"Source Code Pro"',
'"Roboto mono"',
'"Courier New"',
"monospace",
'Arial',
'sans-serif'
],
mono: ['"Source Code Pro"', '"Roboto mono"', '"Courier New"', 'monospace']
},
minWidth: theme => theme('spacing'),
},
minWidth: (theme) => theme('spacing')
}
},
variants: {
extend: {
opacity: ['hover-none']
},
}
},
plugins: [
require('@tailwindcss/aspect-ratio'),
require('tailwindcss-touch')()
],
}
plugins: [require('@tailwindcss/aspect-ratio'), require('tailwindcss-touch')()]
};