feat: add warning messages

This commit is contained in:
kyranjamie
2021-09-20 12:10:40 +02:00
committed by kyranjamie
parent 92aee034b3
commit b470c7a574
12 changed files with 196 additions and 10 deletions

View File

@@ -0,0 +1,5 @@
---
'@stacks/wallet-web': minor
---
Adds messages sourced from the main branch of the repository

View File

@@ -159,6 +159,23 @@ jobs:
- name: Test
run: yarn test:unit
check-message-schema:
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 14.x
- name: Install ajv-cli
runs: yarn global add ajv-cli
- name: Validate message schema
runs: ajv-cli validate -s scripts/wallet-comms.schema.json -d wallet-comms.json
build:
runs-on: ubuntu-latest

1
config/wallet-comms.json Normal file
View File

@@ -0,0 +1 @@
{}

26
config/wallet-comms.md Normal file
View File

@@ -0,0 +1,26 @@
# Hiro Wallet messages
All Hiro Wallet instances make HTTP requests to the file located in `config/wallet-comms.json`. If there are messages, either global or pinned to a matching version, they will be displayed on the wallet's home screen.
If there are no messages, the object should be empty: `{}`
The schema of the `wallet-comms.json` file is validated by Github Actions.
### Example
A message targeting all versions is referred to with the key `global`. The JSON may look something like:
```json
{
"global": [
{
"purpose": "info",
"title": "API experiencing slowness",
"text": "Balances and transactions may take longer to update",
"learnMoreUrl": "https://stacks.co",
"publishedAt": "2021-09-27T07:57:37.397Z",
"dismissible": false
}
]
}
```

View File

@@ -0,0 +1,43 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"type": "object",
"description": "Dictionary describing the application versions for which the messages should be displayed. Use `global` for all versions.",
"patternProperties": {
"^[a-zA-Z0-9]*$": {
"type": "array",
"description": "ONLY FIRST ITEM WILL SHOW IN WALLET.",
"items": {
"type": "object",
"required": ["purpose", "title", "text", "publishedAt", "dismissible"],
"properties": {
"purpose": {
"type": "string",
"enum": ["info", "warning", "error"],
"description": "Add additional semantics to the purpose of the message. May be used in UI to indicate style variants"
},
"title": {
"type": "string",
"description": "Short headline that introduces the issue at hand"
},
"text": {
"type": "string",
"description": "Additional context to explain what the issue concerns"
},
"publishedAt": {
"type": "string",
"format": "date-time",
"description": "Estimate date time at which the message has been published"
},
"learnMoreUrl": {
"type": "string",
"description": "URL to additional resources such as blog or forum post"
},
"dismissible": {
"type": "boolean",
"description": "NOT IMPLEMENTED IN WALLET. Whether or not the message can be dismissed by the user"
}
}
}
}
}
}

View File

@@ -22,6 +22,10 @@ export const POPUP_WIDTH = 442;
export const POPUP_HEIGHT = 646;
export const MICROBLOCKS_ENABLED = !IS_TEST_ENV && true;
export const GITHUB_ORG = 'blockstack';
export const GITHUB_REPO = 'stacks-wallet-web';
export const GITHUB_PRIMARY_BRANCH = 'main';
export const SIP_010 = {
mainnet: {
address: 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE',

View File

@@ -0,0 +1,30 @@
import { GITHUB_ORG, GITHUB_PRIMARY_BRANCH, GITHUB_REPO } from '@common/constants';
import { useQuery } from 'react-query';
export interface HiroMessage {
title: string;
text: string;
purpose: 'error' | 'info' | 'warning';
publishedAt: string;
dismissible: boolean;
learnMoreUrl?: string;
}
type HiroMessagesResponse = Record<string, HiroMessage[]>;
const githubCommsConfigRawUrl = `https://raw.githubusercontent.com/${GITHUB_ORG}/${GITHUB_REPO}/${GITHUB_PRIMARY_BRANCH}/config/wallet-comms.json`;
async function fetchHiroMessages(): Promise<HiroMessagesResponse> {
return fetch(githubCommsConfigRawUrl).then(msg => msg.json());
}
export function useHiroMessages() {
const { data } = useQuery(['walletComms'], fetchHiroMessages, {
// As we're fetching from Github, a third-party, we want
// to avoid any unnecessary stress on their services, so
// we use quite slow stale/retry times
staleTime: 1000 * 60 * 10,
retryDelay: 1000 * 60,
});
return data;
}

View File

@@ -45,6 +45,7 @@ export const Header: React.FC<HeaderProps> = memo(props => {
alignItems={hideActions ? 'center' : 'flex-start'}
justifyContent="space-between"
position="relative"
scrollSnapAlign="start"
{...rest}
>
{!title ? (

View File

@@ -62,6 +62,7 @@ export const PopupContainer: React.FC<PopupHomeProps> = ({ children, header, req
maxHeight="100vh"
position="relative"
overflow="auto"
scrollSnapType="y proximity"
>
{header || null}
<Flex

View File

@@ -0,0 +1,37 @@
import React, { FC } from 'react';
import { Box, color, Flex, Text } from '@stacks/ui';
import { FiInfo } from 'react-icons/fi';
import { HiroMessage } from '@common/hooks/use-hiro-messages';
export const HiroMessageItem: FC<HiroMessage> = props => {
const { title, text, learnMoreUrl } = props;
return (
<Flex>
<Box mr="tight" mt="2px">
<FiInfo size="16px" color={color('accent')} />
</Box>
<Box>
<Text display="block" textStyle="body.small.medium" lineHeight="20px">
{title}
</Text>
<Text display="inline" textStyle="caption" mt="extra-tight" mr="extra-tight">
{text}
</Text>
{learnMoreUrl && (
<Text
as="a"
display="inline-block"
textStyle="caption"
textDecoration="underline"
href={learnMoreUrl}
whiteSpace="nowrap"
target="_blank"
>
Learn more
</Text>
)}
</Box>
</Flex>
);
};

View File

@@ -0,0 +1,18 @@
import React from 'react';
import { Box, Flex, FlexProps } from '@stacks/ui';
import { useHiroMessages } from '@common/hooks/use-hiro-messages';
import { HiroMessageItem } from './components/hiro-message-item';
export const HiroMessages = (props: FlexProps) => {
const messages = useHiroMessages();
if (!messages || !messages.global || !messages.global[0]) return null;
return (
<Box pt="tight" scrollSnapAlign="start" scrollPadding="20%">
<Flex background="#F7F8FD" borderRadius="8px" p="base" {...props}>
<HiroMessageItem {...messages.global[0]} />
</Flex>
</Box>
);
};

View File

@@ -1,25 +1,28 @@
import React from 'react';
import { Outlet } from 'react-router-dom';
import { Stack } from '@stacks/ui';
import { Box, Stack } from '@stacks/ui';
import { PopupContainer } from '@components/popup/container';
import { Header } from '@components/header';
import { BalancesAndActivity } from '@components/popup/balances-and-activity';
import { UserAccount } from '@pages/home/components/user-area';
import { HomeActions } from '@pages/home/components/actions';
const PageTop = () => (
<>
<UserAccount />
<HomeActions />
</>
);
import { HiroMessages } from '@features/hiro-messages/hiro-messages';
export const PopupHome = () => {
return (
<>
<PopupContainer header={<Header />} requestType="auth">
<PopupContainer
header={
<>
<HiroMessages mx="tight" />
<Header pt="base-tight" />
</>
}
requestType="auth"
>
<Stack data-testid="home-page" flexGrow={1} spacing="loose">
<PageTop />
<UserAccount />
<HomeActions />
<BalancesAndActivity />
</Stack>
</PopupContainer>