styles for provider detail page

This commit is contained in:
Sebastian 2021-07-12 14:47:13 -03:00
parent 1c0cce3f58
commit 0cf52a2812
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
6 changed files with 152 additions and 137 deletions

View File

@ -52,8 +52,32 @@ export const decorators = [
(Story, { kind }) => { (Story, { kind }) => {
if (kind.startsWith('popup')) { if (kind.startsWith('popup')) {
return <div class="popup-container"> return <div class="popup-container">
<link key="1" rel="stylesheet" type="text/css" href="/style/pure.css" /> <style>{`
<link key="2" rel="stylesheet" type="text/css" href="/style/popup.css" /> html {
font-family: sans-serif; /* 1 */
}
body {
margin: 0;
}`}
</style>
<style>{`
html {
}
h1 {
font-size: 2em;
}
input {
font: inherit;
}
body {
margin: 0;
font-size: 100%;
padding: 0;
overflow: hidden;
background-color: #f8faf7;
font-family: Arial, Helvetica, sans-serif;
}`}
</style>
<div style={{ padding: 8, width: 'calc(400px - 16px - 2px)', height: 'calc(320px - 34px - 16px - 2px)', border: 'black solid 1px' }}> <div style={{ padding: 8, width: 'calc(400px - 16px - 2px)', height: 'calc(320px - 34px - 16px - 2px)', border: 'black solid 1px' }}>
<Story /> <Story />
</div> </div>

View File

@ -0,0 +1,18 @@
import { useState } from "preact/hooks";
import arrowDown from '../../static/img/chevron-down.svg';
import { ErrorBox } from "./styled";
export function ErrorMessage({ title, description }: { title?: string; description?: string; }) {
const [showErrorDetail, setShowErrorDetail] = useState(false);
if (!title)
return null;
return <ErrorBox>
<div>
<p>{title}</p>
{ description && <button onClick={() => { setShowErrorDetail(v => !v); }}>
<img style={{ height: '1.5em' }} src={arrowDown} />
</button> }
</div>
{showErrorDetail && <p>{description}</p>}
</ErrorBox>;
}

View File

@ -4,6 +4,13 @@ import type * as Linaria from '@linaria/core';
import { styled } from '@linaria/react'; import { styled } from '@linaria/react';
export const PaymentStatus = styled.span<{color:string}>`
padding: 5px;
border-radius: 5px;
color: white;
background-color: ${p => p.color};
`
export const PopupBox = styled.div` export const PopupBox = styled.div`
height: calc(320px - 34px - 16px); height: calc(320px - 34px - 16px);
display: flex; display: flex;
@ -11,23 +18,40 @@ export const PopupBox = styled.div`
justify-content: space-between; justify-content: space-between;
& > section { & > section {
// this margin will send the section up when used with a header
margin-bottom: auto;
overflow: auto; overflow: auto;
table td {
padding: 5px 10px;
}
table tr {
border-bottom: 1px solid black;
border-top: 1px solid black;
}
}
& > header {
flex-direction: row;
justify-content: space-between;
display: flex;
margin-bottom: 5px;
& > div {
align-self: center;
}
} }
& > footer { & > footer {
padding-top: 5px; padding-top: 5px;
flex-direction: row; flex-direction: row;
justify-content: flex-end; justify-content: space-between;
display: flex; display: flex;
& > button { & button {
margin-left: 5px; margin-left: 5px;
} }
} }
& > section > h1 {
margin-top: 0.3em;
margin-bottom: 0.3em;
}
` `
export const Button = styled.button` export const Button = styled.button`

View File

@ -20,8 +20,8 @@ import { ProviderInfo, ProviderPaymentStatus } from "@gnu-taler/taler-wallet-cor
import { differenceInMonths, formatDuration, intervalToDuration } from "date-fns"; import { differenceInMonths, formatDuration, intervalToDuration } from "date-fns";
import { FunctionalComponent, Fragment, JSX, VNode, AnyComponent } from "preact"; import { FunctionalComponent, Fragment, JSX, VNode, AnyComponent } from "preact";
import { import {
BoldLight, ButtonPrimary, ButtonSuccess, Centered, BoldLight, ButtonPrimary, ButtonSuccess, Centered,
CenteredText, CenteredTextBold, PopupBox, Row, CenteredText, CenteredTextBold, PopupBox, Row,
SmallText, SmallTextLight SmallText, SmallTextLight
} from "../components/styled"; } from "../components/styled";
import { useBackupStatus } from "../hooks/useBackupStatus"; import { useBackupStatus } from "../hooks/useBackupStatus";
@ -47,7 +47,7 @@ export interface ViewProps {
export function BackupView({ providers, onAddProvider, onSyncAll }: ViewProps): VNode { export function BackupView({ providers, onAddProvider, onSyncAll }: ViewProps): VNode {
return ( return (
<PopupBox style={{ justifyContent: !providers.length ? 'center' : 'space-between' }}> <PopupBox>
<section> <section>
{providers.map((provider) => <BackupLayout {providers.map((provider) => <BackupLayout
status={provider.paymentStatus} status={provider.paymentStatus}
@ -57,18 +57,21 @@ export function BackupView({ providers, onAddProvider, onSyncAll }: ViewProps):
title={provider.syncProviderBaseUrl} title={provider.syncProviderBaseUrl}
/> />
)} )}
{!providers.length && <Centered> {!providers.length && <Centered style={{marginTop: 100}}>
<BoldLight>No backup providers configured</BoldLight> <BoldLight>No backup providers configured</BoldLight>
<ButtonSuccess onClick={onAddProvider}><i18n.Translate>Add provider</i18n.Translate></ButtonSuccess> <ButtonSuccess onClick={onAddProvider}><i18n.Translate>Add provider</i18n.Translate></ButtonSuccess>
</Centered>} </Centered>}
</section> </section>
{!!providers.length && <footer> {!!providers.length && <footer>
<ButtonPrimary onClick={onSyncAll}>{ <div />
providers.length > 1 ? <div>
<i18n.Translate>Sync all backups</i18n.Translate> : <ButtonPrimary onClick={onSyncAll}>{
<i18n.Translate>Sync now</i18n.Translate> providers.length > 1 ?
}</ButtonPrimary> <i18n.Translate>Sync all backups</i18n.Translate> :
<ButtonSuccess onClick={onAddProvider}>Add provider</ButtonSuccess> <i18n.Translate>Sync now</i18n.Translate>
}</ButtonPrimary>
<ButtonSuccess onClick={onAddProvider}>Add provider</ButtonSuccess>
</div>
</footer>} </footer>}
</PopupBox> </PopupBox>
) )
@ -111,7 +114,7 @@ function BackupLayout(props: TransactionLayoutProps): JSX.Element {
function ExpirationText({ until }: { until: Timestamp }) { function ExpirationText({ until }: { until: Timestamp }) {
return <Fragment> return <Fragment>
<CenteredText> Expires in </CenteredText> <CenteredText> Expires in </CenteredText>
<CenteredTextBold {...({color:colorByTimeToExpire(until)})}> {daysUntil(until)} </CenteredTextBold> <CenteredTextBold {...({ color: colorByTimeToExpire(until) })}> {daysUntil(until)} </CenteredTextBold>
</Fragment> </Fragment>
} }

View File

@ -1,6 +1,9 @@
import { Amounts, BackupBackupProviderTerms, i18n } from "@gnu-taler/taler-util"; import { Amounts, BackupBackupProviderTerms, i18n } from "@gnu-taler/taler-util";
import { Fragment, VNode } from "preact"; import { VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { Checkbox } from "../components/Checkbox";
import { ErrorMessage } from "../components/ErrorMessage";
import { Button, ButtonPrimary, Input, LightText, PopupBox, SmallTextLight } from "../components/styled/index";
import * as wxApi from "../wxApi"; import * as wxApi from "../wxApi";
interface Props { interface Props {
@ -49,26 +52,6 @@ export function ProviderAddPage({ onBack }: Props): VNode {
/> />
} }
interface TermsOfServiceProps {
onCancel: () => void;
onAccept: () => void;
}
function TermsOfService({ onCancel, onAccept }: TermsOfServiceProps) {
return <div style={{ display: 'flex', flexDirection: 'column' }}>
<section style={{ height: 'calc(320px - 34px - 34px - 16px)', overflow: 'auto' }}>
<div>
Here we will place the complete text of terms of service
</div>
</section>
<footer style={{ marginTop: 'auto', display: 'flex', flexShrink: 0 }}>
<button class="pure-button" onClick={onCancel}><i18n.Translate>cancel</i18n.Translate></button>
<div style={{ width: '100%', flexDirection: 'row', justifyContent: 'flex-end', display: 'flex' }}>
<button class="pure-button" onClick={onAccept}><i18n.Translate>accept</i18n.Translate></button>
</div>
</footer>
</div>
}
export interface SetUrlViewProps { export interface SetUrlViewProps {
initialValue?: string; initialValue?: string;
@ -76,23 +59,6 @@ export interface SetUrlViewProps {
onVerify: (s: string) => Promise<string | undefined>; onVerify: (s: string) => Promise<string | undefined>;
withError?: string; withError?: string;
} }
import arrowDown from '../../static/img/chevron-down.svg';
import { Button, ButtonPrimary, ErrorBox, Input, LightText, PopupBox, SmallTextLight } from "../components/styled/index";
import { Checkbox } from "../components/Checkbox";
function ErrorMessage({ title, description }: { title?: string, description?: string }) {
const [showErrorDetail, setShowErrorDetail] = useState(false);
if (!title) return null
return <ErrorBox>
<div>
<p>{title}</p>
<button onClick={() => { setShowErrorDetail(v => !v) }} >
<img style={{ height: '1.5em' }} src={arrowDown} />
</button>
</div>
{showErrorDetail && <p>{description}</p>}
</ErrorBox>
}
export function SetUrlView({ initialValue, onCancel, onVerify, withError }: SetUrlViewProps) { export function SetUrlView({ initialValue, onCancel, onVerify, withError }: SetUrlViewProps) {
const [value, setValue] = useState<string>(initialValue || "") const [value, setValue] = useState<string>(initialValue || "")
@ -113,7 +79,7 @@ export function SetUrlView({ initialValue, onCancel, onVerify, withError }: SetU
</Input> </Input>
</p> </p>
</section> </section>
<footer style={{ justifyContent: 'space-between' }}> <footer>
<Button onClick={onCancel}><i18n.Translate> &lt; Back</i18n.Translate></Button> <Button onClick={onCancel}><i18n.Translate> &lt; Back</i18n.Translate></Button>
<ButtonPrimary <ButtonPrimary
disabled={!value} disabled={!value}
@ -150,7 +116,7 @@ export function ConfirmProviderView({ url, provider, onCancel, onConfirm }: Conf
</p> </p>
<Checkbox label="Accept terms of service" name="terms" onToggle={() => setAccepted(old => !old)} enabled={accepted}/> <Checkbox label="Accept terms of service" name="terms" onToggle={() => setAccepted(old => !old)} enabled={accepted}/>
</section> </section>
<footer style={{ justifyContent: 'space-between' }}> <footer>
<Button onClick={onCancel}><i18n.Translate> &lt; Back</i18n.Translate></Button> <Button onClick={onCancel}><i18n.Translate> &lt; Back</i18n.Translate></Button>
<ButtonPrimary <ButtonPrimary
disabled={!accepted} disabled={!accepted}

View File

@ -19,6 +19,8 @@ import { i18n, Timestamp } from "@gnu-taler/taler-util";
import { ProviderInfo, ProviderPaymentStatus, ProviderPaymentType } from "@gnu-taler/taler-wallet-core"; import { ProviderInfo, ProviderPaymentStatus, ProviderPaymentType } from "@gnu-taler/taler-wallet-core";
import { format, formatDuration, intervalToDuration } from "date-fns"; import { format, formatDuration, intervalToDuration } from "date-fns";
import { Fragment, VNode } from "preact"; import { Fragment, VNode } from "preact";
import { ErrorMessage } from "../components/ErrorMessage";
import { Button, ButtonDestructive, ButtonPrimary, PaymentStatus, PopupBox } from "../components/styled";
import { useProviderStatus } from "../hooks/useProviderStatus"; import { useProviderStatus } from "../hooks/useProviderStatus";
interface Props { interface Props {
@ -52,24 +54,14 @@ export interface ViewProps {
} }
export function ProviderView({ info, onDelete, onSync, onBack, onExtend }: ViewProps): VNode { export function ProviderView({ info, onDelete, onSync, onBack, onExtend }: ViewProps): VNode {
function Footer() {
return <footer style={{ marginTop: 'auto', display: 'flex', flexShrink: 0 }}>
<button class="pure-button" onClick={onBack}><i18n.Translate>back</i18n.Translate></button>
<div style={{ width: '100%', flexDirection: 'row', justifyContent: 'flex-end', display: 'flex' }}>
{info && <button class="pure-button button-destructive" disabled onClick={onDelete}><i18n.Translate>remove</i18n.Translate></button>}
{info && <button class="pure-button button-secondary" disabled style={{ marginLeft: 5 }} onClick={onExtend}><i18n.Translate>extend</i18n.Translate></button>}
{info && <button class="pure-button button-secondary" style={{ marginLeft: 5 }} onClick={onSync}><i18n.Translate>sync now</i18n.Translate></button>}
</div>
</footer>
}
function Error() { function Error() {
if (info?.lastError) { if (info?.lastError) {
return <Fragment> return <ErrorMessage title={info.lastError.hint} />
<div class="errorbox" style={{ marginTop: 10 }} > // <div class="errorbox" style={{ marginTop: 10 }} >
<div style={{ height: 0, textAlign: 'right', color: 'gray', fontSize: 'small' }}>last time tried {!info.lastAttemptedBackupTimestamp || info.lastAttemptedBackupTimestamp.t_ms === 'never' ? 'never' : format(new Date(info.lastAttemptedBackupTimestamp.t_ms), 'dd/MM/yyyy HH:mm:ss')}</div> // <div style={{ height: 0, textAlign: 'right', color: 'gray', fontSize: 'small' }}>last time tried {!info.lastAttemptedBackupTimestamp || info.lastAttemptedBackupTimestamp.t_ms === 'never' ? 'never' : format(new Date(info.lastAttemptedBackupTimestamp.t_ms), 'dd/MM/yyyy HH:mm:ss')}</div>
<p>{info.lastError.hint}</p> // <p>{info.lastError.hint}</p>
</div> // </div>
</Fragment> // </Fragment>
} }
if (info?.backupProblem) { if (info?.backupProblem) {
switch (info.backupProblem.type) { switch (info.backupProblem.type) {
@ -89,7 +81,8 @@ export function ProviderView({ info, onDelete, onSync, onBack, onExtend }: ViewP
} }
return null return null
} }
function colorByStatus(status: ProviderPaymentType | undefined) {
function colorByStatus(status: ProviderPaymentType) {
switch (status) { switch (status) {
case ProviderPaymentType.InsufficientBalance: case ProviderPaymentType.InsufficientBalance:
return 'rgb(223, 117, 20)' return 'rgb(223, 117, 20)'
@ -103,14 +96,10 @@ export function ProviderView({ info, onDelete, onSync, onBack, onExtend }: ViewP
return 'rgb(202, 60, 60)' return 'rgb(202, 60, 60)'
case ProviderPaymentType.TermsChanged: case ProviderPaymentType.TermsChanged:
return 'rgb(202, 60, 60)' return 'rgb(202, 60, 60)'
default:
break;
} }
return undefined
} }
function descriptionByStatus(status: ProviderPaymentStatus | undefined) { function descriptionByStatus(status: ProviderPaymentStatus) {
if (!status) return ''
switch (status.type) { switch (status.type) {
case ProviderPaymentType.InsufficientBalance: case ProviderPaymentType.InsufficientBalance:
return 'no enough balance to make the payment' return 'no enough balance to make the payment'
@ -125,69 +114,60 @@ export function ProviderView({ info, onDelete, onSync, onBack, onExtend }: ViewP
} }
case ProviderPaymentType.Pending: case ProviderPaymentType.Pending:
return '' return ''
default:
break;
} }
return undefined
} }
return ( return (
<div style={{ height: 'calc(320px - 34px - 16px)', overflow: 'auto' }}> <PopupBox>
<style>{` <header>
table td { <PaymentStatus color={colorByStatus(info.paymentStatus.type)}>{info.paymentStatus.type}</PaymentStatus>
padding: 5px 10px;
}
`}</style>
<div style={{ display: 'flex', flexDirection: 'column' }}>
<section style={{ flex: '1 0 auto', height: 'calc(320px - 34px - 34px - 16px)', overflow: 'auto' }}>
<span style={{ padding: 5, display: 'inline-block', backgroundColor: colorByStatus(info?.paymentStatus.type), borderRadius: 5, color: 'white' }}>{info?.paymentStatus.type}</span>
{/* {info && <span style={{ float: "right", fontSize: "small", color: "gray", padding: 5 }}>
From <b>{info.syncProviderBaseUrl}</b>
</span>} */}
{info && <div style={{ float: 'right', fontSize: "large", padding: 5 }}>{info.terms?.annualFee} / year</div>}
<Error /> {info.terms && <div>{info.terms.annualFee} / year</div>}
</header>
<section>
<Error />
<h3>{info.syncProviderBaseUrl}</h3>
<p>{daysSince(info?.lastSuccessfulBackupTimestamp)} </p>
<p>{descriptionByStatus(info.paymentStatus)}</p>
{info.paymentStatus.type === ProviderPaymentType.TermsChanged && <div>
<p>terms has changed, extending the service will imply accepting the new terms of service</p>
<table>
<thead>
<tr>
<td></td>
<td>old</td>
<td> -&gt;</td>
<td>new</td>
</tr>
</thead>
<tbody>
<h3>{info?.syncProviderBaseUrl}</h3> <tr>
<div style={{ display: "flex", flexDirection: "row", justifyContent: "space-between", }}> <td>fee</td>
<div>{daysSince(info?.lastSuccessfulBackupTimestamp)} </div> <td>{info.paymentStatus.oldTerms.annualFee}</td>
</div> <td>-&gt;</td>
<td>{info.paymentStatus.newTerms.annualFee}</td>
</tr>
<tr>
<td>storage</td>
<td>{info.paymentStatus.oldTerms.storageLimitInMegabytes}</td>
<td>-&gt;</td>
<td>{info.paymentStatus.newTerms.storageLimitInMegabytes}</td>
</tr>
</tbody>
</table>
</div>}
<p>{descriptionByStatus(info?.paymentStatus)}</p> </section>
<footer>
{info?.paymentStatus.type === ProviderPaymentType.TermsChanged && <div> <Button onClick={onBack}><i18n.Translate> &lt; back</i18n.Translate></Button>
<p>terms has changed, extending the service will imply accepting the new terms of service</p> <div>
<table> <ButtonDestructive disabled onClick={onDelete}><i18n.Translate>remove</i18n.Translate></ButtonDestructive>
<thead> <ButtonPrimary disabled onClick={onExtend}><i18n.Translate>extend</i18n.Translate></ButtonPrimary>
<tr> <ButtonPrimary onClick={onSync}><i18n.Translate>sync now</i18n.Translate></ButtonPrimary>
<td></td> </div>
<td>old</td> </footer>
<td> -&gt;</td> </PopupBox>
<td>new</td>
</tr>
</thead>
<tbody>
<tr>
<td>fee</td>
<td>{info.paymentStatus.oldTerms.annualFee}</td>
<td>-&gt;</td>
<td>{info.paymentStatus.newTerms.annualFee}</td>
</tr>
<tr>
<td>storage</td>
<td>{info.paymentStatus.oldTerms.storageLimitInMegabytes}</td>
<td>-&gt;</td>
<td>{info.paymentStatus.newTerms.storageLimitInMegabytes}</td>
</tr>
</tbody>
</table>
</div>}
</section>
<Footer />
</div>
</div>
) )
} }