Skip to content
This repository has been archived by the owner on Jun 16, 2022. It is now read-only.

Terms of use update popup #2442

Merged
merged 16 commits into from
May 5, 2022
Merged
Show file tree
Hide file tree
Changes from 10 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
220 changes: 0 additions & 220 deletions TERMS.es.md

This file was deleted.

209 changes: 0 additions & 209 deletions TERMS.fr.md

This file was deleted.

259 changes: 119 additions & 140 deletions TERMS.md

Large diffs are not rendered by default.

220 changes: 0 additions & 220 deletions TERMS.ru.md

This file was deleted.

70 changes: 70 additions & 0 deletions src/components/CheckTermOfUseUpdate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React, { ReactNode, useCallback, useMemo } from "react";
import { Linking } from "react-native";
import { useTranslation } from "react-i18next";
import { BottomDrawer, Flex, Icons, Link, Text } from "@ledgerhq/native-ui";
import styled from "styled-components/native";

import { useTermsAccept } from "../logic/terms";
import { useLocale } from "../context/Locale";
import Button from "./Button";
import Alert from "./Alert";
import { urls } from "../config/urls";

const Description = styled(Text).attrs(() => ({
color: "neutral.c70",
}))``;

const Divider = styled(Flex).attrs(() => ({
my: 4,
height: 1,
backgroundColor: "neutral.c40",
}))``;

const Update = ({ children }: { children: ReactNode }) => (
<Flex flexDirection="row">
<Description mr={2}>{"•"}</Description>
<Description>{children}</Description>
</Flex>
);

const CheckTermOfUseUpdateModal = () => {
const { t } = useTranslation();
const { locale } = useLocale();
const [accepted, accept] = useTermsAccept();

const termsURL = useMemo(() => urls.terms[locale] || urls.terms.en, [locale]);

const handleLink = useCallback(() => {
Linking.openURL(termsURL);
}, [termsURL]);

return (
<BottomDrawer
id="TermOfUseUpdate"
noCloseButton={true}
title={t("updatedTerms.title")}
isOpen={!accepted}
>
<Flex mb={6}>
<Description>{t("updatedTerms.body.intro")}</Description>
<Flex my={4}>
<Update>{t("updatedTerms.body.bulletPoints.0")}</Update>
<Update>{t("updatedTerms.body.bulletPoints.1")}</Update>
<Update>{t("updatedTerms.body.bulletPoints.2")}</Update>
</Flex>
<Description>{t("updatedTerms.body.agreement")}</Description>
</Flex>
<Alert type="help" noIcon>
<Link type="color" onPress={handleLink} Icon={Icons.ExternalLinkMedium}>
{t("updatedTerms.link")}
</Link>
</Alert>
<Divider />
<Button type="main" outline={false} onPress={accept}>
{t("updatedTerms.cta")}
</Button>
</BottomDrawer>
);
};

export default CheckTermOfUseUpdateModal;
14 changes: 14 additions & 0 deletions src/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -3928,6 +3928,20 @@
"service": "Terms of service",
"subTitle": "Please take some time to review our Terms of service and Privacy Policy"
},
"updatedTerms": {
"title": "Terms of Use Update",
"body": {
"intro": "Hi! We've updated our Ledger Live Terms of Use with the aim to make them clearer and to reflect Ledger Live's newly available services and features. Key updates are focused on:",
"bulletPoints": [
"Clarifying what services are available and how they work",
"Explaining how fees for Services work",
"Improving our notification process to make sure that you are properly informed of any new changes to our Terms of Use"
],
"agreement": "By clicking on \"Continue\" you agree that you have read and accept the Terms of Use below."
},
"link": "Terms of Use",
"cta": "Continue"
},
"exchange": {
"buy": {
"tabTitle": "Buy",
Expand Down
61 changes: 34 additions & 27 deletions src/logic/terms.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,45 @@ import AsyncStorage from "@react-native-async-storage/async-storage";
export const url =
"https://github.com/LedgerHQ/ledger-live-mobile/blob/master/TERMS.md";

const termsUrlLocalized = {
en:
"https://raw.githubusercontent.com/LedgerHQ/ledger-live-mobile/master/TERMS.md",
fr:
"https://raw.githubusercontent.com/LedgerHQ/ledger-live-mobile/master/TERMS.fr.md",
es:
"https://raw.githubusercontent.com/LedgerHQ/ledger-live-mobile/master/TERMS.es.md",
zh:
"https://raw.githubusercontent.com/LedgerHQ/ledger-live-mobile/master/TERMS.zh.md",
ru:
"https://raw.githubusercontent.com/LedgerHQ/ledger-live-mobile/master/TERMS.ru.md",
};
const legacyTermsUrl =
"https://raw.githubusercontent.com/LedgerHQ/ledger-live-mobile/master/TERMS.md";

const currentTermsRequired = "2019-12-04";
const currentTermsRequired = "2022-05-10";
const currentLendingTermsRequired = "2020-11-10";

export async function isAcceptedTerms() {
const acceptedTermsVersion = await AsyncStorage.getItem(
"acceptedTermsVersion",
);
return acceptedTermsVersion === currentTermsRequired;
function isAcceptedVersionUpToDate(acceptedVersion, currentVersion) {
if (!acceptedVersion) {
return false;
}

try {
const acceptedTermsVersion = new Date(acceptedVersion);
const currentTermsVersion = new Date(currentVersion);

return acceptedTermsVersion >= currentTermsVersion;
} catch (error) {
console.error(`Failed to parse terms version's dates: ${error}`);

return false;
}
}

export async function acceptTerms() {
await AsyncStorage.setItem("acceptedTermsVersion", currentTermsRequired);
export async function isAcceptedTerms() {
return isAcceptedVersionUpToDate(
await AsyncStorage.getItem("acceptedTermsVersion"),
currentTermsRequired,
);
}

export async function isAcceptedLendingTerms() {
const acceptedLendingTermsVersion = await AsyncStorage.getItem(
"acceptedLendingTermsVersion",
return isAcceptedVersionUpToDate(
await AsyncStorage.getItem("acceptedLendingTermsVersion"),
currentLendingTermsRequired,
);
return acceptedLendingTermsVersion === currentLendingTermsRequired;
}

export async function acceptTerms() {
await AsyncStorage.setItem("acceptedTermsVersion", currentTermsRequired);
}

export async function acceptLendingTerms() {
Expand All @@ -46,8 +53,8 @@ export async function acceptLendingTerms() {
);
}

export async function load(locale: string) {
const url = termsUrlLocalized[locale] || termsUrlLocalized.en;
export async function load() {
const url = legacyTermsUrl;
const r = await fetch(url);
if (r.status >= 400 && r.status < 600) {
throw new Error("");
Expand All @@ -56,11 +63,11 @@ export async function load(locale: string) {
return markdown;
}

export const useTerms = (locale: string) => {
export const useTerms = () => {
const [terms, setTerms] = useState(null);
const [error, setError] = useState(null);

const loadTerms = () => load(locale).then(setTerms, setError);
const loadTerms = () => load().then(setTerms, setError);

useEffect(() => {
loadTerms();
Expand Down
5 changes: 2 additions & 3 deletions src/screens/Portfolio/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import Carousel from "../../components/Carousel";
import Header from "./Header";
import TrackScreen from "../../analytics/TrackScreen";
import MigrateAccountsBanner from "../MigrateAccounts/Banner";
import RequireTerms from "../../components/RequireTerms";
import { NavigatorName, ScreenName } from "../../const";
import FabActions from "../../components/FabActions";
import FirmwareUpdateBanner from "../../components/FirmwareUpdateBanner";
Expand All @@ -44,6 +43,7 @@ import MarketSection from "./MarketSection";
import AddAccountsModal from "../AddAccounts/AddAccountsModal";
import { useProviders } from "../Swap/SwapEntry";
import CheckLanguageAvailability from "../../components/CheckLanguageAvailability";
import CheckTermOfUseUpdate from "../../components/CheckTermOfUseUpdate";

export { default as PortfolioTabIcon } from "./TabIcon";

Expand Down Expand Up @@ -286,9 +286,8 @@ function PortfolioScreen({ navigation }: Props) {
<>
<FirmwareUpdateBanner />
<ContentContainer>
<RequireTerms />
<CheckLanguageAvailability />

<CheckTermOfUseUpdate />
<TrackScreen
category="Portfolio"
accountsLength={accounts.length}
Expand Down