diff --git a/packages/concordia-app/package.json b/packages/concordia-app/package.json
index 9b842e9..6645ee2 100644
--- a/packages/concordia-app/package.json
+++ b/packages/concordia-app/package.json
@@ -26,7 +26,7 @@
"dependencies": {
"@ezerous/breeze": "~0.4.0",
"@ezerous/drizzle": "~0.4.1",
- "@ezerous/eth-identity-provider": "^0.1.0",
+ "@ezerous/eth-identity-provider": "~0.1.2",
"@reduxjs/toolkit": "~1.4.0",
"@welldone-software/why-did-you-render": "^6.0.0-rc.1",
"concordia-contracts": "~0.1.0",
diff --git a/packages/concordia-app/public/locales/en/translation.json b/packages/concordia-app/public/locales/en/translation.json
index 61d79a1..74f1e18 100644
--- a/packages/concordia-app/public/locales/en/translation.json
+++ b/packages/concordia-app/public/locales/en/translation.json
@@ -2,6 +2,15 @@
"board.header.no.topics.message": "There are no topics yet!",
"board.sub.header.no.topics.guest": "Sign up and be the first to post.",
"board.sub.header.no.topics.user": "Be the first to post.",
+ "clear.databases.modal.cancel.button": "Cancel, keep databases",
+ "clear.databases.modal.clear.button": "Yes, delete databases",
+ "clear.databases.modal.clearing.progress.message": "This might take a minute...",
+ "clear.databases.modal.clearing.progress.title": "Clearing all Concordia databases",
+ "clear.databases.modal.description.body.user": "Although this action is generally recoverable some of your topics and posts may be permanently lost.",
+ "clear.databases.modal.description.pre": "You are about to clear the Concordia databases stored locally in your browser.",
+ "clear.databases.modal.form.username.label.guest": "Please type concordia to confirm.",
+ "clear.databases.modal.form.username.label.user": "Please type your username to confirm.",
+ "clear.databases.modal.title": "Clear all Concordia databases. Are you sure?",
"custom.loading.tab.pane.default.generic.message": "Magic in the background",
"edit.information.modal.form.cancel.button": "Cancel",
"edit.information.modal.form.error.invalid.profile.picture.url.message": "The profile picture URL provided is not valid.",
@@ -17,6 +26,7 @@
"post.form.subject.field.placeholder": "Subject",
"post.list.row.post.id": "#{{id}}",
"profile.general.tab.address.row.title": "Account address:",
+ "profile.general.tab.clear.databases.button.title": "Clear databases",
"profile.general.tab.edit.info.button.title": "Edit information",
"profile.general.tab.location.row.not.set": "Not set",
"profile.general.tab.location.row.title": "Location:",
@@ -52,6 +62,7 @@
"register.form.sign.up.step.error.message.header": "Form contains errors",
"register.form.sign.up.step.title": "Sign Up",
"register.p.account.address": "Account address:",
+ "topbar.button.clear.databases": "Clear databases",
"topbar.button.create.topic": "Create topic",
"topbar.button.profile": "Profile",
"topbar.button.register": "Sign Up",
diff --git a/packages/concordia-app/src/components/ClearDatabasesModal/index.jsx b/packages/concordia-app/src/components/ClearDatabasesModal/index.jsx
new file mode 100644
index 0000000..26e47fe
--- /dev/null
+++ b/packages/concordia-app/src/components/ClearDatabasesModal/index.jsx
@@ -0,0 +1,151 @@
+import React, {
+ useCallback, useMemo, useState,
+ useEffect,
+} from 'react';
+import {
+ Button, Form, Input, Modal,
+} from 'semantic-ui-react';
+import PropTypes from 'prop-types';
+import { useTranslation } from 'react-i18next';
+import { useSelector } from 'react-redux';
+import purgeIndexedDBs from '../../utils/indexedDB/indexedDBUtils';
+
+const ClearDatabasesModal = (props) => {
+ const {
+ open, onDatabasesCleared, onCancel,
+ } = props;
+ const [confirmationInput, setConfirmationInput] = useState('');
+ const [userConfirmed, setUserConfirmed] = useState(false);
+ const [isClearing, setIsClearing] = useState(false);
+ const user = useSelector((state) => state.user);
+ const { t } = useTranslation();
+
+ useEffect(() => {
+ if (user.hasSignedUp && confirmationInput === user.username) {
+ setUserConfirmed(true);
+ } else if (!user.hasSignedUp && confirmationInput === 'concordia') {
+ setUserConfirmed(true);
+ } else {
+ setUserConfirmed(false);
+ }
+ }, [confirmationInput, user.hasSignedUp, user.username]);
+
+ const handleSubmit = useCallback(() => {
+ setIsClearing(true);
+
+ purgeIndexedDBs()
+ .then(() => {
+ onDatabasesCleared();
+ }).catch((reason) => console.log(reason));
+ }, [onDatabasesCleared]);
+
+ const onCancelTry = useCallback(() => {
+ if (!isClearing) {
+ setConfirmationInput('');
+ onCancel();
+ }
+ }, [isClearing, onCancel]);
+
+ const handleInputChange = (event, { value }) => { setConfirmationInput(value); };
+
+ const modalContent = useMemo(() => {
+ if (isClearing) {
+ return (
+ <>
+
+ {t('clear.databases.modal.clearing.progress.message')}
+
+ >
+ );
+ }
+
+ if (user.hasSignedUp) {
+ return (
+ <>
+
+ {t('clear.databases.modal.description.pre')}
+
+
+ {t('clear.databases.modal.description.body.user')}
+
+
+
+
+
+
+ >
+ );
+ }
+
+ return (
+ <>
+
+ {t('clear.databases.modal.description.pre')}
+
+
+
+
+
+
+ >
+ );
+ }, [confirmationInput, isClearing, t, user.hasSignedUp]);
+
+ return useMemo(() => (
+
+
+ {isClearing
+ ? t('clear.databases.modal.clearing.progress.title')
+ : t('clear.databases.modal.title')}
+
+
+
+ {modalContent}
+
+
+
+ {!isClearing && (
+
+
+
+
+ )}
+
+ ), [handleSubmit, isClearing, modalContent, onCancelTry, open, t, userConfirmed]);
+};
+
+ClearDatabasesModal.defaultProps = {
+ open: false,
+};
+
+ClearDatabasesModal.propTypes = {
+ open: PropTypes.bool,
+ onDatabasesCleared: PropTypes.func.isRequired,
+ onCancel: PropTypes.func.isRequired,
+};
+
+export default ClearDatabasesModal;
diff --git a/packages/concordia-app/src/index.jsx b/packages/concordia-app/src/index.jsx
index 4f21227..a29f530 100644
--- a/packages/concordia-app/src/index.jsx
+++ b/packages/concordia-app/src/index.jsx
@@ -1,3 +1,4 @@
+import './utils/indexedDB/patchIndexedDB';
import './utils/wdyr';
import React, { Suspense } from 'react';
import { render } from 'react-dom';
diff --git a/packages/concordia-app/src/layouts/MainLayout/MainLayoutMenu/index.jsx b/packages/concordia-app/src/layouts/MainLayout/MainLayoutMenu/index.jsx
index 11524eb..3c51293 100644
--- a/packages/concordia-app/src/layouts/MainLayout/MainLayoutMenu/index.jsx
+++ b/packages/concordia-app/src/layouts/MainLayout/MainLayoutMenu/index.jsx
@@ -1,16 +1,32 @@
-import React from 'react';
+import React, { useState } from 'react';
import { Menu } from 'semantic-ui-react';
import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router';
import { useSelector } from 'react-redux';
import AppContext from '../../../components/AppContext';
import appLogo from '../../../assets/images/app_logo.png';
+import ClearDatabasesModal from '../../../components/ClearDatabasesModal';
const MainLayoutMenu = () => {
const hasSignedUp = useSelector((state) => state.user.hasSignedUp);
+ const [isClearDatabasesOpen, setIsClearDatabasesOpen] = useState(false);
const history = useHistory();
const { t } = useTranslation();
+ const handleClearDatabasesClick = () => {
+ setIsClearDatabasesOpen(true);
+ };
+
+ const handleDatabasesCleared = () => {
+ setIsClearDatabasesOpen(false);
+ history.push('/home');
+ window.location.reload(false);
+ };
+
+ const handleCancelDatabasesClear = () => {
+ setIsClearDatabasesOpen(false);
+ };
+
return (
{() => (
@@ -23,6 +39,14 @@ const MainLayoutMenu = () => {
>
+
+ {t('topbar.button.clear.databases')}
+
{hasSignedUp && history.location.pathname === '/home' && (
{
)}
+
+
)}
diff --git a/packages/concordia-app/src/options/breezeOptions.js b/packages/concordia-app/src/options/breezeOptions.js
index 41984c0..3f3a15f 100644
--- a/packages/concordia-app/src/options/breezeOptions.js
+++ b/packages/concordia-app/src/options/breezeOptions.js
@@ -10,6 +10,7 @@ const REACT_APP_RENDEZVOUS_PORT = process.env.REACT_APP_RENDEZVOUS_PORT || REACT
const breezeOptions = {
ipfs: {
+ repo: 'concordia',
config: {
Addresses: {
Swarm: [
diff --git a/packages/concordia-app/src/utils/indexedDB/indexedDBUtils.js b/packages/concordia-app/src/utils/indexedDB/indexedDBUtils.js
new file mode 100644
index 0000000..bc4b3be
--- /dev/null
+++ b/packages/concordia-app/src/utils/indexedDB/indexedDBUtils.js
@@ -0,0 +1,22 @@
+import { breeze } from '../../redux/store';
+
+const purgeIndexedDBs = async () => {
+ const { ipfs, orbit } = breeze;
+
+ if (orbit) await orbit.stop();
+ if (ipfs) await ipfs.stop();
+
+ const databases = await indexedDB.databases();
+ return Promise.all(
+ databases.map((db) => new Promise(
+ (resolve, reject) => {
+ const request = indexedDB.deleteDatabase(db.name);
+ request.onblocked = resolve;
+ request.onsuccess = resolve;
+ request.onerror = reject;
+ },
+ )),
+ );
+};
+
+export default purgeIndexedDBs;
diff --git a/packages/concordia-app/src/utils/indexedDB/patchIndexedDB.js b/packages/concordia-app/src/utils/indexedDB/patchIndexedDB.js
new file mode 100644
index 0000000..185ecda
--- /dev/null
+++ b/packages/concordia-app/src/utils/indexedDB/patchIndexedDB.js
@@ -0,0 +1,46 @@
+/* Patches browsers that do not yet support indexedDB.databases()
+ (https://developer.mozilla.org/en-US/docs/Web/API/IDBFactory/databases)
+ See also https://gist.github.com/rmehner/b9a41d9f659c9b1c3340#gistcomment-3449418) */
+if (window.indexedDB && typeof window.indexedDB.databases === 'undefined') {
+ const LOCALSTORAGE_CACHE_KEY = 'indexedDBDatabases';
+
+ // Store a key value map of databases
+ const getFromStorage = () => JSON.parse(window.localStorage[LOCALSTORAGE_CACHE_KEY] || '{}');
+
+ // Write the database to local storage
+ const writeToStorage = (value) => { window.localStorage[LOCALSTORAGE_CACHE_KEY] = JSON.stringify(value); };
+
+ IDBFactory.prototype.databases = () => Promise.resolve(
+ Object.entries(getFromStorage()).reduce((acc, [name, version]) => {
+ acc.push({ name, version });
+ return acc;
+ }, []),
+ );
+
+ // Intercept the existing open handler to write our DBs names
+ // and versions to localStorage
+ const { open } = IDBFactory.prototype;
+
+ // eslint-disable-next-line func-names
+ IDBFactory.prototype.open = function (...args) {
+ const dbName = args[0];
+ const version = args[1] || 1;
+ const existing = getFromStorage();
+ writeToStorage({ ...existing, [dbName]: version });
+ return open.apply(this, args);
+ };
+
+ // Intercept the existing deleteDatabase handler remove our
+ // dbNames from localStorage
+ const { deleteDatabase } = IDBFactory.prototype;
+
+ // eslint-disable-next-line func-names
+ IDBFactory.prototype.deleteDatabase = function (...args) {
+ const dbName = args[0];
+ const existing = getFromStorage();
+ delete existing[dbName];
+ writeToStorage(existing);
+ return deleteDatabase.apply(this, args);
+ };
+ console.debug('IndexedDB patched successfully!');
+}