2021-06-30 05:24:43 +02:00
|
|
|
/*
|
|
|
|
This file is part of TALER
|
|
|
|
(C) 2016 GNUnet e.V.
|
|
|
|
|
|
|
|
TALER is free software; you can redistribute it and/or modify it under the
|
|
|
|
terms of the GNU General Public License as published by the Free Software
|
|
|
|
Foundation; either version 3, or (at your option) any later version.
|
|
|
|
|
|
|
|
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
|
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
|
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License along with
|
|
|
|
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
|
|
|
*/
|
|
|
|
|
2021-07-06 17:44:25 +02:00
|
|
|
import { i18n, Timestamp } from "@gnu-taler/taler-util";
|
2021-11-15 15:18:58 +01:00
|
|
|
import {
|
|
|
|
ProviderInfo,
|
|
|
|
ProviderPaymentStatus,
|
|
|
|
} from "@gnu-taler/taler-wallet-core";
|
|
|
|
import {
|
|
|
|
differenceInMonths,
|
|
|
|
formatDuration,
|
|
|
|
intervalToDuration,
|
|
|
|
} from "date-fns";
|
2021-08-23 21:51:49 +02:00
|
|
|
import { Fragment, JSX, VNode, h } from "preact";
|
2021-07-10 04:15:49 +02:00
|
|
|
import {
|
2021-11-15 15:18:58 +01:00
|
|
|
BoldLight,
|
|
|
|
ButtonPrimary,
|
|
|
|
ButtonSuccess,
|
|
|
|
Centered,
|
|
|
|
CenteredText,
|
|
|
|
CenteredBoldText,
|
|
|
|
PopupBox,
|
|
|
|
RowBorderGray,
|
|
|
|
SmallText,
|
|
|
|
SmallLightText,
|
2021-07-10 04:15:49 +02:00
|
|
|
} from "../components/styled";
|
2021-07-08 20:23:53 +02:00
|
|
|
import { useBackupStatus } from "../hooks/useBackupStatus";
|
2021-08-19 05:34:47 +02:00
|
|
|
import { Pages } from "../NavigationBar";
|
2021-06-30 05:24:43 +02:00
|
|
|
|
2021-07-06 17:44:25 +02:00
|
|
|
interface Props {
|
|
|
|
onAddProvider: () => void;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function BackupPage({ onAddProvider }: Props): VNode {
|
2021-11-15 15:18:58 +01:00
|
|
|
const status = useBackupStatus();
|
2021-07-01 05:35:41 +02:00
|
|
|
if (!status) {
|
2021-11-15 15:18:58 +01:00
|
|
|
return <div>Loading...</div>;
|
2021-07-01 05:35:41 +02:00
|
|
|
}
|
2021-11-15 15:18:58 +01:00
|
|
|
return (
|
|
|
|
<BackupView
|
|
|
|
providers={status.providers}
|
|
|
|
onAddProvider={onAddProvider}
|
|
|
|
onSyncAll={status.sync}
|
|
|
|
/>
|
|
|
|
);
|
2021-06-30 05:24:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface ViewProps {
|
2021-11-15 15:18:58 +01:00
|
|
|
providers: ProviderInfo[];
|
2021-07-06 17:44:25 +02:00
|
|
|
onAddProvider: () => void;
|
2021-07-08 20:23:53 +02:00
|
|
|
onSyncAll: () => Promise<void>;
|
2021-06-30 05:24:43 +02:00
|
|
|
}
|
|
|
|
|
2021-11-15 15:18:58 +01:00
|
|
|
export function BackupView({
|
|
|
|
providers,
|
|
|
|
onAddProvider,
|
|
|
|
onSyncAll,
|
|
|
|
}: ViewProps): VNode {
|
2021-06-30 05:24:43 +02:00
|
|
|
return (
|
2021-07-12 19:47:13 +02:00
|
|
|
<PopupBox>
|
2021-07-10 04:15:49 +02:00
|
|
|
<section>
|
2021-11-15 15:18:58 +01:00
|
|
|
{providers.map((provider) => (
|
|
|
|
<BackupLayout
|
|
|
|
status={provider.paymentStatus}
|
|
|
|
timestamp={provider.lastSuccessfulBackupTimestamp}
|
|
|
|
id={provider.syncProviderBaseUrl}
|
|
|
|
active={provider.active}
|
|
|
|
title={provider.name}
|
|
|
|
/>
|
|
|
|
))}
|
|
|
|
{!providers.length && (
|
|
|
|
<Centered style={{ marginTop: 100 }}>
|
|
|
|
<BoldLight>No backup providers configured</BoldLight>
|
|
|
|
<ButtonSuccess onClick={onAddProvider}>
|
|
|
|
<i18n.Translate>Add provider</i18n.Translate>
|
|
|
|
</ButtonSuccess>
|
|
|
|
</Centered>
|
2021-07-10 04:15:49 +02:00
|
|
|
)}
|
|
|
|
</section>
|
2021-11-15 15:18:58 +01:00
|
|
|
{!!providers.length && (
|
|
|
|
<footer>
|
|
|
|
<div />
|
|
|
|
<div>
|
|
|
|
<ButtonPrimary onClick={onSyncAll}>
|
|
|
|
{providers.length > 1 ? (
|
|
|
|
<i18n.Translate>Sync all backups</i18n.Translate>
|
|
|
|
) : (
|
|
|
|
<i18n.Translate>Sync now</i18n.Translate>
|
|
|
|
)}
|
|
|
|
</ButtonPrimary>
|
|
|
|
<ButtonSuccess onClick={onAddProvider}>Add provider</ButtonSuccess>
|
|
|
|
</div>
|
|
|
|
</footer>
|
|
|
|
)}
|
2021-07-10 04:15:49 +02:00
|
|
|
</PopupBox>
|
2021-11-15 15:18:58 +01:00
|
|
|
);
|
2021-06-30 23:24:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
interface TransactionLayoutProps {
|
2021-07-10 04:15:49 +02:00
|
|
|
status: ProviderPaymentStatus;
|
2021-06-30 23:24:08 +02:00
|
|
|
timestamp?: Timestamp;
|
|
|
|
title: string;
|
2021-07-08 20:23:53 +02:00
|
|
|
id: string;
|
|
|
|
active: boolean;
|
2021-06-30 23:24:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function BackupLayout(props: TransactionLayoutProps): JSX.Element {
|
|
|
|
const date = !props.timestamp ? undefined : new Date(props.timestamp.t_ms);
|
|
|
|
const dateStr = date?.toLocaleString([], {
|
|
|
|
dateStyle: "medium",
|
|
|
|
timeStyle: "short",
|
|
|
|
} as any);
|
2021-07-10 04:15:49 +02:00
|
|
|
|
2021-06-30 23:24:08 +02:00
|
|
|
return (
|
2021-07-13 20:33:28 +02:00
|
|
|
<RowBorderGray>
|
2021-07-10 04:15:49 +02:00
|
|
|
<div style={{ color: !props.active ? "grey" : undefined }}>
|
2021-11-15 15:18:58 +01:00
|
|
|
<a
|
|
|
|
href={Pages.provider_detail.replace(
|
|
|
|
":pid",
|
|
|
|
encodeURIComponent(props.id),
|
|
|
|
)}
|
|
|
|
>
|
|
|
|
<span>{props.title}</span>
|
|
|
|
</a>
|
|
|
|
|
|
|
|
{dateStr && (
|
|
|
|
<SmallText style={{ marginTop: 5 }}>Last synced: {dateStr}</SmallText>
|
|
|
|
)}
|
|
|
|
{!dateStr && (
|
|
|
|
<SmallLightText style={{ marginTop: 5 }}>Not synced</SmallLightText>
|
|
|
|
)}
|
2021-06-30 23:24:08 +02:00
|
|
|
</div>
|
2021-07-10 04:15:49 +02:00
|
|
|
<div>
|
2021-11-15 15:18:58 +01:00
|
|
|
{props.status?.type === "paid" ? (
|
|
|
|
<ExpirationText until={props.status.paidUntil} />
|
|
|
|
) : (
|
2021-07-10 04:15:49 +02:00
|
|
|
<div>{props.status.type}</div>
|
2021-11-15 15:18:58 +01:00
|
|
|
)}
|
2021-06-30 23:24:08 +02:00
|
|
|
</div>
|
2021-07-13 20:33:28 +02:00
|
|
|
</RowBorderGray>
|
2021-06-30 23:24:08 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-07-10 04:15:49 +02:00
|
|
|
function ExpirationText({ until }: { until: Timestamp }) {
|
2021-11-15 15:18:58 +01:00
|
|
|
return (
|
|
|
|
<Fragment>
|
|
|
|
<CenteredText> Expires in </CenteredText>
|
|
|
|
<CenteredBoldText {...{ color: colorByTimeToExpire(until) }}>
|
|
|
|
{" "}
|
|
|
|
{daysUntil(until)}{" "}
|
|
|
|
</CenteredBoldText>
|
|
|
|
</Fragment>
|
|
|
|
);
|
2021-07-10 04:15:49 +02:00
|
|
|
}
|
|
|
|
|
2021-07-08 20:23:53 +02:00
|
|
|
function colorByTimeToExpire(d: Timestamp) {
|
2021-11-15 15:18:58 +01:00
|
|
|
if (d.t_ms === "never") return "rgb(28, 184, 65)";
|
|
|
|
const months = differenceInMonths(d.t_ms, new Date());
|
|
|
|
return months > 1 ? "rgb(28, 184, 65)" : "rgb(223, 117, 20)";
|
2021-07-08 20:23:53 +02:00
|
|
|
}
|
|
|
|
|
2021-06-30 23:24:08 +02:00
|
|
|
function daysUntil(d: Timestamp) {
|
2021-11-15 15:18:58 +01:00
|
|
|
if (d.t_ms === "never") return undefined;
|
2021-06-30 23:24:08 +02:00
|
|
|
const duration = intervalToDuration({
|
|
|
|
start: d.t_ms,
|
|
|
|
end: new Date(),
|
2021-11-15 15:18:58 +01:00
|
|
|
});
|
2021-06-30 23:24:08 +02:00
|
|
|
const str = formatDuration(duration, {
|
2021-11-15 15:18:58 +01:00
|
|
|
delimiter: ", ",
|
2021-06-30 23:24:08 +02:00
|
|
|
format: [
|
2021-11-15 15:18:58 +01:00
|
|
|
duration?.years
|
|
|
|
? "years"
|
|
|
|
: duration?.months
|
|
|
|
? "months"
|
|
|
|
: duration?.days
|
|
|
|
? "days"
|
|
|
|
: duration.hours
|
|
|
|
? "hours"
|
|
|
|
: "minutes",
|
|
|
|
],
|
|
|
|
});
|
|
|
|
return `${str}`;
|
|
|
|
}
|