From d344b381c6dd563f0b42e109940e5558436661fc Mon Sep 17 00:00:00 2001
From: lingdocs <71590811+lingdocs@users.noreply.github.com>
Date: Sat, 21 Aug 2021 19:55:37 +0400
Subject: [PATCH] bare bones working with new auth
---
website/package.json | 1 -
website/src/App.test.tsx | 779 ------------------
website/src/App.tsx | 155 +---
website/src/lib/backend-calls.ts | 91 +-
website/src/lib/firebase.ts | 30 -
.../{backend-types.ts => functions-types.ts} | 39 +-
website/src/lib/level-management.ts | 18 +-
...-storage.test.ts => local-storage.test.ts} | 3 +-
.../{options-storage.ts => local-storage.ts} | 25 +
website/src/lib/options-reducer.test.ts | 10 +-
website/src/lib/options-reducer.ts | 6 -
website/src/lib/pouch-dbs.ts | 135 +--
website/src/lib/search-all-inflections.ts | 6 +-
website/src/lib/submissions.ts | 22 +-
website/src/screens/Account.tsx | 341 ++------
website/src/screens/EntryEditor.tsx | 20 +-
website/src/screens/IsolatedEntry.tsx | 16 +-
website/src/screens/Options.tsx | 6 +-
website/src/screens/Results.tsx | 22 +-
website/src/screens/ReviewTasks.tsx | 6 +-
website/src/types.d.ts | 4 +-
website/yarn.lock | 457 +---------
22 files changed, 318 insertions(+), 1874 deletions(-)
delete mode 100644 website/src/App.test.tsx
delete mode 100644 website/src/lib/firebase.ts
rename website/src/lib/{backend-types.ts => functions-types.ts} (73%)
rename website/src/lib/{options-storage.test.ts => local-storage.test.ts} (96%)
rename website/src/lib/{options-storage.ts => local-storage.ts} (57%)
diff --git a/website/package.json b/website/package.json
index d623877..1b29bdd 100644
--- a/website/package.json
+++ b/website/package.json
@@ -18,7 +18,6 @@
"classnames": "^2.2.6",
"cron": "^1.8.2",
"dayjs": "^1.10.4",
- "firebase": "^8.3.0",
"lokijs": "^1.5.11",
"mousetrap": "^1.6.5",
"nano": "^9.0.3",
diff --git a/website/src/App.test.tsx b/website/src/App.test.tsx
deleted file mode 100644
index 3a3ed66..0000000
--- a/website/src/App.test.tsx
+++ /dev/null
@@ -1,779 +0,0 @@
-/**
- * Copyright (c) 2021 lingdocs.com
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- */
-
-// TODO: IndexedDB mocking not working for couchdb - it defaults to disk storage
-// tslint:disable-next-line
-// require("fake-indexeddb/auto");
-// // tslint:disable-next-line
-// const FDBFactory = require("fake-indexeddb/lib/FDBFactory");
-
-import { render, screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react';
-import { Types as T } from "@lingdocs/pashto-inflector";
-import { Router, BrowserRouter } from "react-router-dom";
-import App from './App';
-import { dictionary } from "./lib/dictionary";
-import {
- mockResults,
-} from "./lib/dictionary-mock-fillers";
-import userEvent from '@testing-library/user-event';
-import { createMemoryHistory } from 'history';
-import {
- loadUserInfo,
- upgradeAccount,
- publishDictionary,
-} from "./lib/backend-calls";
-import {
- addSubmission, sendSubmissions,
-} from "./lib/submissions";
-import * as BT from "./lib/backend-types";
-jest.mock("./lib/submissions");
-jest.mock("./lib/backend-calls");
-jest.mock("./lib/pouch-dbs");
-jest.mock("./lib/wordlist-database");
-jest.mock("react-ga");
-
-const mockUserInfo = {
- displayName: "Bob Billywinkle",
- email: "bob@example.com",
- providerData: [],
-};
-
-const mockCouchDbStudent: BT.CouchDbUser = {
- _id: "123",
- type: "user",
- name: "123",
- email: mockUserInfo.email,
- providerData: [],
- displayName: mockUserInfo.displayName,
- roles: [],
- level: "student",
- userdbPassword: "12345",
-}
-
-const mockCouchDbEditor: BT.CouchDbUser = {
- ...mockCouchDbStudent,
- level: "editor",
-}
-
-jest.mock("./lib/firebase", (): any => {
- class mockAuth {
- constructor() {
- this.signIn = this.signIn.bind(this);
- this.onAuthStateChanged = this.onAuthStateChanged.bind(this);
- this.unsubscribeAll = this.unsubscribeAll.bind(this);
- }
- private mockUser = {
- displayName: "Bob Billywinkle",
- email: "bob@example.com",
- providerData: [],
- delete: () => {
- this.currentUser = null;
- return Promise.resolve();
- },
- };
- private observers: ((user: any) => void)[] = [];
- public currentUser: any = null;
- onAuthStateChanged (callback: () => void) {
- this.observers.push(callback);
- callback();
- return () => { this.unsubscribeAll() };
- }
- unsubscribeAll () {
- this.observers = [];
- }
- signOut () {
- this.currentUser = null;
- this.observers.forEach((item) => {
- item.call(undefined, this.mockUser);
- });
- return null;
- }
- signIn () {
- this.currentUser = this.mockUser;
- this.observers.forEach((item) => {
- item.call(undefined, this.mockUser);
- });
- return null;
- }
- }
- return {
- auth: new mockAuth(),
- };
-});
-
-
-
-jest.mock('react-firebaseui/StyledFirebaseAuth', () => function (props: any) {
- return
- Sign In
-
;
-});
-
-const allMockEntries: T.DictionaryEntry[] = Object.keys(mockResults).reduce((all: T.DictionaryEntry[], key: string) => (
- // @ts-ignore
- [...all, ...mockResults[key]]
-), []);
-
-const fakeDictInfo: T.DictionaryInfo = {
- title: "not found",
- license: "not found",
- release: 0,
- numberOfEntries: 0,
- url: "not found",
- infoUrl: "not found",
-};
-
-const fakeDictionary: DictionaryAPI = {
- initialize: () => Promise.resolve({
- response: "loaded from saved",
- dictionaryInfo: fakeDictInfo,
- }),
- update: () => Promise.resolve({
- response: "no need for update",
- dictionaryInfo: fakeDictInfo,
- }),
- search: function(state: State): T.DictionaryEntry[] {
- if (state.options.searchType === "alphabetical") {
- return state.searchValue === "ا" ? mockResults.alphabeticalA : [];
- }
- if (state.options.language === "Pashto") {
- return state.searchValue === "کور"
- ? mockResults.pashtoKor
- : [];
- }
- if (state.options.language === "English") {
- return state.searchValue === "tired"
- ? mockResults.englishTired as T.DictionaryEntry[]
- : [];
- }
- return [];
- },
- getNewWordsThisMonth: function(): T.DictionaryEntry[] {
- return [];
- },
- findOneByTs: function(ts: number): T.DictionaryEntry | undefined {
- return allMockEntries.find((entry) => entry.ts === ts);
- },
- findRelatedEntries: function(entry: T.DictionaryEntry): T.DictionaryEntry[] {
- // TODO: Better mock
- return allMockEntries.filter((e) => e.e.includes("house"));
- },
- exactPashtoSearch: function(search: string ): T.DictionaryEntry[] {
- return [];
- },
-};
-
-const dictionaryPublishResponse = "dictionary published";
-
-beforeEach(() => {
- jest.clearAllMocks();
- jest.spyOn(dictionary, "initialize").mockImplementation(() => Promise.resolve("loaded from saved"));
- jest.spyOn(dictionary, "search").mockImplementation(fakeDictionary.search);
- jest.spyOn(dictionary, "findOneByTs").mockImplementation(fakeDictionary.findOneByTs);
- jest.spyOn(dictionary, "findRelatedEntries").mockImplementation(fakeDictionary.findRelatedEntries);
- jest.spyOn(dictionary, "exactPashtoSearch").mockImplementation(fakeDictionary.exactPashtoSearch);
- loadUserInfo.mockResolvedValue(undefined);
- // fetchSuggestions.mockResolvedValue({ ok: true, suggestions: [] });
- upgradeAccount.mockImplementation(async (password: string): Promise => {
- if (password === "correct password") {
- return { ok: true, message: "user upgraded to student" };
- }
- return {
- ok: false,
- error: "incorrect password",
- };
- });
- publishDictionary.mockResolvedValue(dictionaryPublishResponse);
- localStorage.clear();
- // indexedDB = new FDBFactory();
-});
-
-// TODO: feed it a fake mini dictionary through JSON - to get more realistic testing
-// don't mock the dictionary object
-
-test('renders loading', async () => {
- jest.spyOn(dictionary, "initialize").mockImplementation(() => Promise.resolve("loaded from saved"));
- render( );
- const text = screen.getByText(/loading/i);
- expect(text).toBeInTheDocument();
- await waitFor(() => screen.getByText(/lingdocs pashto dictionary/i));
-});
-
-test('renders error loading', async () => {
- jest.spyOn(dictionary, "initialize").mockImplementation(() => Promise.reject());
-
- render( );
- await waitFor(() => screen.getByText(/error loading/i));
-});
-
-test('renders dictionary loaded', async () => {
- render( );
- await waitFor(() => screen.getByText(/lingdocs pashto dictionary/i));
-});
-
-test('searches on type', async () => {
- const history = createMemoryHistory();
- render( );
- await waitFor(() => screen.getByText(/lingdocs pashto dictionary/i));
- // Search Pashto
- let searchInput = screen.getByPlaceholderText(/search pashto/i);
- userEvent.type(searchInput, "کور");
- mockResults.pashtoKor.slice(0, 10).forEach((result) => {
- expect(screen.getAllByText(result.e)[0]).toBeInTheDocument();
- expect(screen.getAllByText(result.p)[0]).toBeInTheDocument();
- expect(screen.getAllByText(result.f)[0]).toBeInTheDocument();
- });
- expect(history.location.pathname).toBe("/search");
- // Clear
- userEvent.type(searchInput, "{backspace}{backspace}{backspace}");
- mockResults.pashtoKor.slice(0, 10).forEach((result) => {
- expect(screen.queryByText(result.e)).toBeNull();
- expect(screen.queryByText(result.p)).toBeNull();
- expect(screen.queryByText(result.f)).toBeNull();
- });
- expect(history.location.pathname).toBe("/");
- // Switch To English
- const languageToggle = screen.getByTestId("languageToggle");
- userEvent.click(languageToggle);
- expect(screen.queryByPlaceholderText(/search pashto/i)).toBeNull();
- searchInput = screen.getByPlaceholderText(/search english/i);
- userEvent.type(searchInput, "tired");
- mockResults.englishTired.slice(0, 10).forEach((result) => {
- expect(screen.getAllByText(result.e)[0]).toBeInTheDocument();
- expect(screen.getAllByText(result.p)[0]).toBeInTheDocument();
- expect(screen.getAllByText(result.f)[0]).toBeInTheDocument();
- });
- expect(history.location.pathname).toBe("/search");
- // Clear
- const clearButton = screen.getByTestId("clearButton");
- userEvent.click(clearButton);
- mockResults.englishTired.slice(0, 10).forEach((result) => {
- expect(screen.queryByText(result.e)).toBeNull();
- expect(screen.queryByText(result.p)).toBeNull();
- expect(screen.queryByText(result.f)).toBeNull();
- });
- // Search again
- userEvent.type(searchInput, "tired");
- mockResults.englishTired.slice(0, 10).forEach((result) => {
- expect(screen.getAllByText(result.e)[0]).toBeInTheDocument();
- expect(screen.getAllByText(result.p)[0]).toBeInTheDocument();
- expect(screen.getAllByText(result.f)[0]).toBeInTheDocument();
- });
- // Go back
- history.goBack();
- expect(history.location.pathname).toBe("/");
-});
-
-test('does alphabetical browse search', async () => {
- render( );
- await waitFor(() => screen.getByText(/lingdocs pashto dictionary/i));
- expect(screen.queryByText(/alphabetical browsing mode/i)).toBeNull();
- const searchTypeButton = screen.getByTestId("searchTypeToggle");
- userEvent.click(searchTypeButton);
- expect(screen.queryByText(/alphabetical browsing mode/i)).toBeInTheDocument();
- const searchInput = screen.getByPlaceholderText(/browse/i);
- userEvent.type(searchInput, "ا");
- mockResults.alphabeticalA.forEach((entry) => {
- expect(screen.queryAllByText(entry.e)).toBeTruthy;
- });
- userEvent.type(searchInput, "{backspace}");
- userEvent.type(searchInput, "ززززز");
- expect(screen.queryByText(/no results found/i)).toBeInTheDocument();
- expect(screen.queryByText(/You are using alphabetical browsing mode/i)).toBeInTheDocument();
-});
-
-test('isolates word on click', async () => {
- const history = createMemoryHistory();
- render( );
- await waitFor(() => screen.getByText(/lingdocs pashto dictionary/i));
- let searchInput = screen.getByPlaceholderText(/search pashto/i);
- userEvent.type(searchInput, "کور");
- expect(history.location.pathname).toBe("/search");
- const firstResult = screen.getByText(mockResults.pashtoKor[0].e);
- userEvent.click(firstResult);
- expect(screen.getByText(/related words/i)).toBeInTheDocument();
- expect(history.location.pathname).toBe("/word");
- const params = new URLSearchParams(history.location.search);
- const wordId = params.get("id");
- expect(wordId && parseInt(wordId)).toBe(mockResults.pashtoKor[0].ts);
- // should leave word when going back
- history.goBack();
- expect(history.location.pathname).toBe("/search");
- // go back to word when going forward
- history.goForward();
- expect(history.location.pathname).toBe("/word");
- expect(screen.getByText(/related words/i)).toBeInTheDocument();
- // leave word when clearing
- const clearButton = screen.getByTestId("clearButton");
- userEvent.click(clearButton);
- expect(history.location.pathname).toBe("/")
- expect(screen.queryByText(/related words/i)).toBeNull();
- userEvent.type(searchInput, "کور");
- expect(history.location.pathname).toBe("/search");
- const firstResultb = screen.getByText(mockResults.pashtoKor[0].e);
- userEvent.click(firstResultb);
- expect(history.location.pathname).toBe("/word");
- // leave word when searching
- const input = screen.getByTestId("searchInput");
- userEvent.type(input, "سړی");
- expect(history.location.pathname).toBe("/search");
- expect(screen.queryByText(/related words/i)).toBeNull();
- expect(screen.queryByText(/no results found/i)).toBeTruthy();
- const clearButton1 = screen.getByTestId("clearButton");
- userEvent.click(clearButton1);
- expect(history.location.pathname).toBe("/");
- // search click on a word again
- userEvent.type(searchInput, "کور");
- expect(history.location.pathname).toBe("/search");
- const firstResultc = screen.getByText(mockResults.pashtoKor[0].e);
- userEvent.click(firstResultc);
- expect(history.location.pathname).toBe("/word")
- expect(screen.getByText(/related words/i)).toBeInTheDocument();
- expect(history.location.search).toBe(`?id=${mockResults.pashtoKor[0].ts}`);
- const relatedEntry = mockResults.pashtoKor.filter((entry) => entry.e.includes("house"))[1] as T.DictionaryEntry;
- const otherResult = screen.getByText(relatedEntry.p);
- userEvent.click(otherResult);
- expect(history.location.pathname).toBe(`/word`);
- expect(history.location.search).toBe(`?id=${relatedEntry.ts}`);
- // search for a word that uses a complement
- userEvent.click(clearButton1);
- const languageToggle = screen.getByTestId("languageToggle");
- userEvent.click(languageToggle);
- userEvent.type(searchInput, "tired");
- const resultWComplement = mockResults.englishTired.find((entry) => entry.c.includes(" comp.") && entry.l) as T.DictionaryEntry;
- userEvent.click(screen.getByText(resultWComplement.e));
- expect(history.location.pathname).toBe(`/word`);
- expect(history.location.search).toBe(`?id=${resultWComplement.ts}`);
- expect(screen.queryByText(resultWComplement.e)).toBeInTheDocument();
-});
-
-test('shows about page', async () => {
- render( );
- await waitFor(() => screen.getByText(/lingdocs pashto dictionary/i));
- const aboutButton = screen.getByText(/about/i);
- userEvent.click(aboutButton);
- expect(screen.queryByText(/inspiration and sources/i)).toBeInTheDocument();
- const homeButton = screen.getByText(/home/i);
- userEvent.click(homeButton);
- expect(screen.queryByText(/inspiration and sources/i)).toBeNull();
-});
-
-test('starts on about page when starting from /about', async () => {
- const history = createMemoryHistory();
- history.push("/about");
- render( );
- await waitFor(() => screen.getAllByText(/about/i));
- expect(screen.queryByText(/inspiration and sources/i)).toBeInTheDocument();
- const homeButton = screen.getByText(/home/i);
- userEvent.click(homeButton);
- expect(screen.queryByText(/inspiration and sources/i)).toBeNull();
-});
-
-test('shows settings page / settings page works', async () => {
- render( );
- await waitFor(() => screen.getByText(/lingdocs pashto dictionary/i));
- const settingsButton = screen.getAllByText(/settings/i)[0];
- userEvent.click(settingsButton);
- expect(screen.queryByText(/diacritics/i)).toBeInTheDocument();
- const homeButton = screen.getByText(/home/i);
- userEvent.click(homeButton);
- expect(screen.queryByText(/diacritics/i)).toBeNull();
- // play with settings
- const settingsButton1 = screen.getAllByText(/settings/i)[0];
- userEvent.click(settingsButton1);
- const darkButton = screen.getByText(/dark/i);
- userEvent.click(darkButton);
- const lightButton = screen.getByText(/light/i);
- userEvent.click(lightButton);
-});
-
-test('starts on settings page when starting from /settings', async () => {
- const history = createMemoryHistory();
- history.push("/settings");
- render( );
- await waitFor(() => screen.getAllByText(/settings/i));
- expect(screen.queryByText(/diacritics/i)).toBeInTheDocument();
- const homeButton = screen.getByText(/home/i);
- userEvent.click(homeButton);
- expect(screen.queryByText(/diacritics/i)).toBeNull();
-});
-
-test('persists settings', async () => {
- const history = createMemoryHistory();
- history.push("/settings");
- const { unmount, rerender } = render( );
- await waitFor(() => screen.getAllByText(/settings/i));
- const darkButton = screen.getByText(/dark/i);
- const lightButton = screen.getByText(/light/i);
- expect(darkButton.className.toString().includes("active")).toBe(false);
- expect(lightButton.className.toString().includes("active")).toBe(true);
- userEvent.click(darkButton);
- expect(darkButton.className.toString().includes("active")).toBe(true);
- expect(lightButton.className.toString().includes("active")).toBe(false);
- const afghanSp = screen.getByText(/afghan/i);
- const pakSp = screen.getByText(/pakistani ی/i);
- expect(afghanSp.className.toString().includes("active")).toBe(true);
- expect(pakSp.className.toString().includes("active")).toBe(false);
- userEvent.click(pakSp);
- expect(afghanSp.className.toString().includes("active")).toBe(false);
- expect(pakSp.className.toString().includes("active")).toBe(true);
- unmount();
- rerender( );
- await waitFor(() => screen.getAllByText(/settings/i));
- const afghanSp1 = screen.getByText(/afghan/i);
- const pakSp1 = screen.getByText(/pakistani ی/i);
- const darkButton1 = screen.getByText(/dark/i);
- const lightButton1 = screen.getByText(/light/i);
- expect(darkButton1.className.toString().includes("active")).toBe(true);
- expect(lightButton1.className.toString().includes("active")).toBe(false);
- expect(afghanSp1.className.toString().includes("active")).toBe(false);
- expect(pakSp1.className.toString().includes("active")).toBe(true);
-});
-
-test('starts on home page when starting on invalid page', async () => {
- const history = createMemoryHistory();
- history.push("/search");
- render( );
- await waitFor(() => screen.getAllByText(/lingdocs pashto dictionary/i));
- expect(history.location.pathname).toBe("/");
-});
-
-test('starts on home page when starting on an unauthorized page', async () => {
- const history = createMemoryHistory();
- history.push("/edits");
- render( );
- await waitFor(() => screen.getAllByText(/lingdocs pashto dictionary/i));
- expect(history.location.pathname).toBe("/");
-});
-
-test('starts on isolated word when starting from /word?id=_____', async () => {
- const history = createMemoryHistory();
- const entry = mockResults.pashtoKor[0];
- history.push(`/word?id=${entry.ts}`);
- render( );
- await waitFor(() => screen.getAllByText(/related words/i));
- expect(screen.queryAllByText(entry.p)).toBeTruthy();
-});
-
-test('says word not found if starting on /word?id=_____ with an unfound id', async () => {
- const history = createMemoryHistory();
- const entry = mockResults.pashtoKor[0];
- history.push(`/word?id=${entry.ts + 20000}`);
- render( );
- await waitFor(() => screen.getAllByText(/word not found/i));
-});
-
-test('goes to home page if starts with /word but without an id param', async () => {
- const history = createMemoryHistory();
- const entry = mockResults.pashtoKor[0];
- history.push(`/word?badparam=${entry.ts}`);
- render( );
- await waitFor(() => screen.getAllByText(/lingdocs pashto dictionary/i));
- expect(history.location.pathname).toBe("/");
-});
-
-test('sign in and out of account works', async () => {
- const history = createMemoryHistory();
- render( );
- await waitFor(() => screen.getByText(/lingdocs pashto dictionary/i));
- userEvent.click(screen.getByText(/sign in/i));
- expect(screen.queryByText(/sign in to be able to/i)).toBeInTheDocument();
- userEvent.click(screen.getByTestId("mockSignInButton"));
- expect(screen.queryByText(new RegExp(mockUserInfo.email))).toBeInTheDocument();
- expect(screen.queryByText(new RegExp(mockUserInfo.displayName))).toBeInTheDocument();
- userEvent.click(screen.getByText(/home/i));
- // now to get back to the account page there should be an account button, not a sign-in button
- expect(screen.queryByText(/sign in/i)).toBeNull();
- userEvent.click(screen.getByText(/account/i));
- userEvent.click(screen.getByTestId("signoutButton"));
- expect(history.location.pathname).toBe("/");
- expect(screen.getByText(/sign in/i)).toBeInTheDocument();
- // sign back in and delete account
- userEvent.click(screen.getByText(/sign in/i));
- userEvent.click(screen.getByTestId("mockSignInButton"));
- userEvent.click(screen.getByText(/delete account/i));
- expect(screen.queryByText(/yes, delete my account/i)).toBeInTheDocument();
- userEvent.click(screen.getByText(/no, cancel/i));
- await waitForElementToBeRemoved(() => screen.queryByText(/yes, delete my account/i));
- userEvent.click(screen.getByText(/delete account/i));
- userEvent.click(screen.getByText(/yes, delete my account/i));
- await waitFor(() => screen.queryByText(/Your account has been deleted/i));
- expect(history.location.pathname).toBe("/account");
- userEvent.click(screen.getAllByText(/home/i)[0]);
- expect(history.location.pathname).toBe("/");
-});
-
-test('word edit suggestion works', async () => {
- const history = createMemoryHistory();
- render( );
- await waitFor(() => screen.getByText(/lingdocs pashto dictionary/i));
- // first try without signing in
- expect(screen.getByText(/sign in/i)).toBeInTheDocument();
- let searchInput = screen.getByPlaceholderText(/search pashto/i);
- userEvent.type(searchInput, "کور");
- expect(history.location.pathname).toBe("/search");
- let firstResult = screen.getByText(mockResults.pashtoKor[0].e);
- userEvent.click(firstResult);
- expect(screen.getByText(/related words/i)).toBeInTheDocument();
- // the edit button should not be there
- expect(screen.queryByTestId(/editEntryButton/i)).toBeNull();
- // nor should the finalEdit button
- expect(screen.queryByTestId(/finalEditEntryButton/i)).toBeNull();
- // sign in to be able to suggest an edit
- history.goBack();
- history.goBack();
- userEvent.click(screen.getByText(/sign in/i));
- userEvent.click(screen.getByTestId("mockSignInButton"));
- expect(sendSubmissions).toHaveBeenCalledTimes(1);
- userEvent.click(screen.getByText(/home/i));
- userEvent.type(searchInput, "کور");
- firstResult = screen.getByText(mockResults.pashtoKor[0].e);
- userEvent.click(firstResult);
- // the final edit button should not be there
- expect(screen.queryByTestId(/finalEditEntryButton/i)).toBeNull();
- userEvent.click(screen.getByTestId(/editEntryButton/i));
- userEvent.type(screen.getByLabelText(/Suggest correction\/edit:/i), "my suggestion");
- userEvent.click(screen.getByText(/cancel/i));
- expect(screen.queryByLabelText(/Suggest correction\/edit:/i)).toBeNull();
- userEvent.click(screen.getByTestId(/editEntryButton/i));
- userEvent.type(screen.getByLabelText(/Suggest correction\/edit:/i), "my comment");
- userEvent.click(screen.getByText(/submit/i));
- expect(screen.queryByText(/Thank you for your help!/i)).toBeInTheDocument();
- expect(addSubmission).toHaveBeenCalledTimes(1);
- expect(addSubmission).toHaveBeenCalledWith(expect.objectContaining({
- entry: mockResults.pashtoKor[0],
- comment: "my comment",
- }), "basic");
- history.goBack();
- history.goBack();
- userEvent.click(screen.getByText(/account/i));
- userEvent.click(screen.getByText(/sign out/i));
-});
-
-test('upgrade account works', async () => {
- const history = createMemoryHistory();
- render( );
- await waitFor(() => screen.getByText(/lingdocs pashto dictionary/i));
- userEvent.click(screen.getByText(/sign in/i));
- expect(screen.queryByText(/sign in to be able to/i)).toBeInTheDocument();
- userEvent.click(screen.getByTestId("mockSignInButton"));
- expect(screen.queryByText(new RegExp(mockUserInfo.email))).toBeInTheDocument();
- expect(screen.queryByText(new RegExp(mockUserInfo.displayName))).toBeInTheDocument();
- expect(screen.queryByText(/level: basic/i)).toBeInTheDocument();
- userEvent.click(screen.getByText(/upgrade account/i));
- userEvent.type(screen.getByLabelText(/upgrade password:/i), "something wrong");
- userEvent.click(screen.getByText(/upgrade my account/i));
- await waitFor(() => screen.queryByText(/incorrect password/i));
- userEvent.click(screen.getByText(/cancel/i));
- await waitFor(() => screen.getByText(/upgrade account/i));
- userEvent.click(screen.getByText(/upgrade account/i));
- userEvent.type(screen.getByLabelText(/upgrade password:/i), "correct password");
- loadUserInfo.mockResolvedValue(mockCouchDbStudent);
- userEvent.click(screen.getByText(/upgrade my account/i));
- await waitForElementToBeRemoved(() => screen.getAllByText(/upgrade account/i));
- userEvent.click(screen.getByText(/sign out/i));
-});
-
-test('editor priveledges show up and allow you to make a final edit of an entry', async () => {
- loadUserInfo.mockResolvedValue(mockCouchDbEditor);
- const history = createMemoryHistory();
- render( );
- await waitFor(() => screen.getByText(/lingdocs pashto dictionary/i));
- userEvent.click(screen.getByText(/sign in/i));
- userEvent.click(screen.getByTestId("mockSignInButton"));
- await waitFor(() => screen.getByText(/account level: editor/i));
- expect(sendSubmissions).toHaveBeenCalledTimes(1);
- userEvent.click(screen.getByText(/home/i));
- expect(screen.getByText(/editor priveleges active/i)).toBeInTheDocument()
- let searchInput = screen.getByPlaceholderText(/search pashto/i);
- userEvent.type(searchInput, "کور");
- expect(history.location.pathname).toBe("/search");
- let firstResult = screen.getByText(mockResults.pashtoKor[0].e);
- userEvent.click(firstResult);
- expect(screen.getByText(/related words/i)).toBeInTheDocument();
- // the edit button should be there
- expect(screen.getByTestId("editEntryButton")).toBeInTheDocument();
- // the final edit button should also be there
- expect(screen.getByTestId("finalEditEntryButton")).toBeInTheDocument();
- userEvent.click(screen.getByTestId("finalEditEntryButton"));
- userEvent.type(screen.getByLabelText(/english/i), " adding more in english");
- userEvent.click(screen.getByLabelText(/no inflection/i));
- userEvent.click(screen.getByText(/submit/i));
- expect(screen.getByText(/edit submitted\/saved/i)).toBeInTheDocument();
- expect(addSubmission).toHaveBeenCalledTimes(1);
- expect(addSubmission).toHaveBeenCalledWith(expect.objectContaining({
- type: "entry edit",
- entry: {
- ...mockResults.pashtoKor[0],
- e: mockResults.pashtoKor[0].e + " adding more in english",
- noInf: true,
- },
- }), "editor");
- userEvent.click(screen.getByTestId(/navItemHome/i));
- userEvent.click(screen.getByText(/account/i));
- userEvent.click(screen.getByText(/sign out/i));
-});
-
-test('editor should be able to publish the dictionary', async () => {
- loadUserInfo.mockResolvedValue(undefined);
- const history = createMemoryHistory();
- render( );
- await waitFor(() => screen.getByText(/lingdocs pashto dictionary/i));
- userEvent.click(screen.getByText(/sign in/i));
- userEvent.click(screen.getByTestId("mockSignInButton"));
- await waitFor(() => screen.getByText(/account level: basic/i));
- // publish dictionary option should not be available to non editor
- expect(screen.queryByText(/publish dictionary/i)).toBeNull();
- userEvent.click(screen.getByText(/sign out/i));
- userEvent.click(screen.getByText(/sign in/i));
- loadUserInfo.mockResolvedValue(mockCouchDbStudent);
- userEvent.click(screen.getByTestId("mockSignInButton"));
- await waitFor(() => screen.getByText(/account level: student/i));
- // publish dictionary option should not be available to non editor
- expect(screen.queryByText(/publish dictionary/i)).toBeNull();
- userEvent.click(screen.getByText(/sign out/i));
- userEvent.click(screen.getByText(/sign in/i));
- loadUserInfo.mockResolvedValue(mockCouchDbEditor);
- userEvent.click(screen.getByTestId("mockSignInButton"));
- await waitFor(() => screen.getByText(/account level: editor/i));
- // publish dictionary options should only be available to editor
- userEvent.click(screen.getByText(/publish dictionary/i));
- expect(screen.getByText(/processing\.\.\./i)).toBeInTheDocument();
- await waitFor(() => screen.getByText(JSON.stringify(dictionaryPublishResponse, null, "\\t")));
- userEvent.click(screen.getByText(/sign out/i));
-});
-
-test('wordlist should be hidden from basic users and available for upgraded users', async () => {
- loadUserInfo.mockResolvedValue(undefined);
- const history = createMemoryHistory();
- render( );
- await waitFor(() => screen.getByText(/lingdocs pashto dictionary/i));
- // doesn't exist on basic accounts signed in or not
- expect(screen.queryByText(/wordlist/i)).toBeNull();
- userEvent.click(screen.getByText(/sign in/i));
- userEvent.click(screen.getByTestId("mockSignInButton"));
- await waitFor(() => screen.queryByText(mockUserInfo.displayName));
- userEvent.click(screen.getByText(/home/i));
- expect(screen.queryByText(/wordlist/i)).toBeNull();
- userEvent.type(screen.getByPlaceholderText(/search pashto/i), "کور");
- expect(history.location.pathname).toBe("/search");
- userEvent.click(screen.getByText(mockResults.pashtoKor[0].e));
- expect(screen.getByText(/related words/i)).toBeInTheDocument();
- // shouldn't be able to see the add to wordlist star
- expect(screen.queryByTestId("emptyStarButton")).toBeNull();
- expect(screen.queryByTestId("fullStarButton")).toBeNull();
- history.goBack();
- history.goBack();
- userEvent.click(screen.getByText(/account/i));
- userEvent.click(screen.getByText(/sign out/i));
- loadUserInfo.mockResolvedValue(mockCouchDbStudent);
- // does exist for student account
- userEvent.click(screen.getByText(/sign in/i));
- userEvent.click(screen.getByTestId("mockSignInButton"));
- await waitFor(() => screen.getByText(/level: student/i));
- userEvent.click(screen.getByText(/home/i));
- expect(screen.getByText(/wordlist/i)).toBeInTheDocument();
- userEvent.type(screen.getByPlaceholderText(/search pashto/i), "کور");
- expect(history.location.pathname).toBe("/search");
- userEvent.click(screen.getByText(mockResults.pashtoKor[0].e));
- expect(screen.getByText(/related words/i)).toBeInTheDocument();
- // should be able to see the word list star
- expect(screen.queryByTestId("emptyStarButton")).toBeInTheDocument();
- history.goBack();
- history.goBack();
- userEvent.click(screen.getByText(/account/i));
- userEvent.click(screen.getByText(/sign out/i));
- loadUserInfo.mockResolvedValue(mockCouchDbEditor);
- // also exists for editor account
- userEvent.click(screen.getByText(/sign in/i));
- userEvent.click(screen.getByTestId("mockSignInButton"));
- await waitFor(() => screen.getByText(/level: editor/i));
- userEvent.click(screen.getByText(/home/i));
- expect(screen.getByText(/wordlist/i)).toBeInTheDocument();
- userEvent.type(screen.getByPlaceholderText(/search pashto/i), "کور");
- expect(history.location.pathname).toBe("/search");
- userEvent.click(screen.getByText(mockResults.pashtoKor[0].e));
- expect(screen.getByText(/related words/i)).toBeInTheDocument();
- expect(screen.getByTestId("emptyStarButton")).toBeInTheDocument();
- history.goBack();
- history.goBack();
- userEvent.click(screen.getByText(/account/i));
- userEvent.click(screen.getByText(/sign out/i));
-});
-
-// test('wordlist adding and removing should work', async () => {
-// const wordNotes = "my test notes";
-// const noteAddition = " and some more";
-// const wordToAdd = mockResults.pashtoKor[0];
-// loadUserInfo.mockResolvedValue(mockCouchDbStudent);
-// const history = createMemoryHistory();
-// render( );
-// await waitFor(() => screen.getByText(/lingdocs pashto dictionary/i));
-// userEvent.click(screen.getByText(/sign in/i));
-// userEvent.click(screen.getByTestId("mockSignInButton"));
-// await waitFor(() => screen.getByText(/level: student/i));
-// userEvent.click(screen.getByText(/home/i));
-// expect(screen.getByText(/wordlist/i)).toBeInTheDocument();
-// userEvent.type(screen.getByPlaceholderText(/search pashto/i), "کور");
-// expect(history.location.pathname).toBe("/search");
-// userEvent.click(screen.getByText(wordToAdd.e));
-// // should be able to see the word list star
-// expect(screen.getByTestId("emptyStarButton")).toBeInTheDocument();
-// userEvent.click(screen.getByTestId("emptyStarButton"));
-// await waitFor(() => screen.getByTestId("fullStarButton"));
-// userEvent.type(screen.getByTestId("wordlistWordContextForm"), wordNotes);
-// userEvent.click(screen.getByText(/save context/i));
-// userEvent.click(screen.getByTestId("backButton"));
-// userEvent.click(screen.getByTestId("backButton"));
-// // should have one word in wordlist for review
-// userEvent.click(screen.getByText("Wordlist (1)"));
-// // should appear on screen with notes
-// userEvent.click(screen.getByText(/browse/i));
-// expect(screen.getByText(wordNotes)).toBeInTheDocument();
-// // notes should be editable
-// userEvent.click(screen.getByText(wordToAdd.e));
-// userEvent.type(screen.getByText(wordNotes), noteAddition);
-// userEvent.click(screen.getByText(/save context/i));
-// await waitFor(() => screen.getByText(/context saved/i));
-// userEvent.click(screen.getByText(wordToAdd.e));
-// expect(screen.queryByText(/context saved/)).toBeNull();
-// expect(screen.getByText(wordNotes + noteAddition)).toBeInTheDocument();
-// // should be able to delete from the browsing screen
-// userEvent.click(screen.getByText(wordToAdd.e));
-// userEvent.click(screen.getByText(/delete/i));
-// await waitForElementToBeRemoved(() => screen.getByText(wordToAdd.e));
-// userEvent.click(screen.getByText(/home/i));
-// // now try adding and deleting a word from the isolated word screen
-// userEvent.type(screen.getByPlaceholderText(/search pashto/i), "کور");
-// expect(history.location.pathname).toBe("/search");
-// userEvent.click(screen.getByText(wordToAdd.e));
-// expect(screen.getByTestId("emptyStarButton")).toBeInTheDocument();
-// userEvent.click(screen.getByTestId("emptyStarButton"));
-// await waitFor(() => screen.getByTestId("fullStarButton"));
-// userEvent.click(screen.getByTestId("backButton"));
-// userEvent.click(screen.getByTestId("backButton"));
-// userEvent.click(screen.getByText(/wordlist.*/i));
-// userEvent.click(screen.getByText(/browse/i));
-// // go back to isolated word screen from the dictionary entry button
-// userEvent.click(screen.getByText(wordToAdd.e));
-// userEvent.click(screen.getByText(/dictionary entry/i));
-// expect(screen.getByText(/related words/i)).toBeInTheDocument();
-// expect(history.location.pathname).toBe("/word");
-// // delete the word from the wordlist from the isolated word screen
-// userEvent.click(screen.getByTestId("fullStarButton"));
-// userEvent.click(screen.getByText(/cancel/i));
-// userEvent.click(screen.getByTestId("fullStarButton"));
-// userEvent.click(screen.getByTestId("confirmDeleteFromWordlist"));
-// await waitFor(() => screen.getByTestId("emptyStarButton"));
-// userEvent.click(screen.getByTestId("backButton"));
-// expect(screen.queryByText(/wordlist is empty/i)).toBeInTheDocument();
-// });
-
-// TODO: REMOVE waitFor(() => screen.//queryByText// )
-
-// TODO: Test review
diff --git a/website/src/App.tsx b/website/src/App.tsx
index 62036db..ee4643e 100644
--- a/website/src/App.tsx
+++ b/website/src/App.tsx
@@ -6,6 +6,9 @@
*
*/
+// TODO: Put the DB sync on the localDb object, and then have it cancel()'ed and removed as part of the deinitialization
+// sync on initialization and cancel sync on de-initialization
+
import { Component } from "react";
import { defaultTextOptions } from "@lingdocs/pashto-inflector";
import { withRouter, Route, RouteComponentProps, Link } from "react-router-dom";
@@ -21,32 +24,31 @@ import ReviewTasks from "./screens/ReviewTasks";
import EntryEditor from "./screens/EntryEditor";
import IsolatedEntry from "./screens/IsolatedEntry";
import Wordlist from "./screens/Wordlist";
-import { saveOptions, readOptions } from "./lib/options-storage";
+import { wordlistEnabled } from "./lib/level-management";
+import {
+ saveOptions,
+ readOptions,
+ saveUser,
+ readUser,
+} from "./lib/local-storage";
import { dictionary, pageSize } from "./lib/dictionary";
import optionsReducer from "./lib/options-reducer";
import hitBottom from "./lib/hitBottom";
import getWordId from "./lib/get-word-id";
-import { auth } from "./lib/firebase";
import { CronJob } from "cron";
import Mousetrap from "mousetrap";
import {
sendSubmissions,
} from "./lib/submissions";
import {
- loadUserInfo,
+ getUser,
} from "./lib/backend-calls";
-import * as BT from "./lib/backend-types";
import {
getWordlist,
} from "./lib/wordlist-database";
import {
- wordlistEnabled,
-} from "./lib/level-management";
-import {
- deInitializeLocalDb,
- initializeLocalDb,
- startLocalDbSync,
- getLocalDbName,
+ startLocalDbs,
+ stopLocalDbs,
getAllDocsLocalDb,
} from "./lib/pouch-dbs";
import {
@@ -95,7 +97,6 @@ class App extends Component {
theme: /* istanbul ignore next */ (window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches) ? "dark" : "light",
textOptions: defaultTextOptions,
- level: "basic",
wordlistMode: "browse",
wordlistReviewLanguage: "Pashto",
wordlistReviewBadge: true,
@@ -107,13 +108,14 @@ class App extends Component {
results: [],
wordlist: [],
reviewTasks: [],
+ user: readUser(),
};
this.handleOptionsUpdate = this.handleOptionsUpdate.bind(this);
this.handleSearchValueChange = this.handleSearchValueChange.bind(this);
this.handleIsolateEntry = this.handleIsolateEntry.bind(this);
this.handleScroll = this.handleScroll.bind(this);
this.handleGoBack = this.handleGoBack.bind(this);
- this.handleLoadUserInfo = this.handleLoadUserInfo.bind(this);
+ this.handleLoadUser = this.handleLoadUser.bind(this);
this.handleRefreshWordlist = this.handleRefreshWordlist.bind(this);
this.handleRefreshReviewTasks = this.handleRefreshReviewTasks.bind(this);
this.handleDictionaryUpdate = this.handleDictionaryUpdate.bind(this);
@@ -124,7 +126,7 @@ class App extends Component {
if (!possibleLandingPages.includes(this.props.location.pathname)) {
this.props.history.replace("/");
}
- if (prod && (this.state.options.level !== "editor")) {
+ if (prod && (!(this.state.user?.level === "editor"))) {
ReactGA.pageview(window.location.pathname + window.location.search);
}
dictionary.initialize().then((r) => {
@@ -133,11 +135,8 @@ class App extends Component {
dictionaryInfo: r.dictionaryInfo,
});
// incase it took forever and timed out - might need to reinitialize the wordlist here ??
- if (wordlistEnabled(this.state)) {
- initializeLocalDb("wordlist", this.handleRefreshWordlist, auth.currentUser ? auth.currentUser.uid : undefined);
- }
- if (this.state.options.level === "editor") {
- initializeLocalDb("reviewTasks", this.handleRefreshReviewTasks);
+ if (this.state.user) {
+ startLocalDbs(this.state.user, { wordlist: this.handleRefreshWordlist, reviewTasks: this.handleRefreshReviewTasks });
}
if (this.props.location.pathname === "/word") {
const wordId = getWordId(this.props.location.search);
@@ -182,32 +181,6 @@ class App extends Component {
}
});
}
- this.unregisterAuthObserver = auth.onAuthStateChanged((user) => {
- if (user) {
- if (wordlistEnabled(this.state)) {
- initializeLocalDb("wordlist", this.handleRefreshWordlist, user.uid);
- }
- sendSubmissions();
- this.handleLoadUserInfo().catch(console.error);
- this.networkCronJob.stop();
- this.networkCronJob.start();
- } else {
- // signed out
- this.networkCronJob.stop();
- if (this.wordlistSync) {
- this.wordlistSync.cancel();
- this.wordlistSync = undefined;
- }
- if (this.reviewTastsSync) {
- this.reviewTastsSync.cancel();
- this.reviewTastsSync = undefined;
- }
- deInitializeLocalDb("wordlist");
- deInitializeLocalDb("reviewTasks");
- this.handleOptionsUpdate({ type: "changeUserLevel", payload: "basic" });
- }
- this.forceUpdate();
- });
Mousetrap.bind(["ctrl+down", "ctrl+up", "command+down", "command+up"], (e) => {
if (e.repeat) return;
this.handleOptionsUpdate({ type: "toggleLanguage" });
@@ -218,7 +191,7 @@ class App extends Component {
});
Mousetrap.bind(["ctrl+\\", "command+\\"], (e) => {
if (e.repeat) return;
- if (this.state.options.level === "basic") return;
+ if (this.state.user?.level === "basic") return;
if (this.props.location.pathname !== "/wordlist") {
this.props.history.push("/wordlist");
} else {
@@ -229,14 +202,8 @@ class App extends Component {
public componentWillUnmount() {
window.removeEventListener("scroll", this.handleScroll);
- this.unregisterAuthObserver();
this.networkCronJob.stop();
- if (this.wordlistSync) {
- this.wordlistSync.cancel();
- }
- if (this.reviewTastsSync) {
- this.reviewTastsSync.cancel();
- }
+ stopLocalDbs();
Mousetrap.unbind(["ctrl+down", "ctrl+up", "command+down", "command+up"]);
Mousetrap.unbind(["ctrl+b", "command+b"]);
Mousetrap.unbind(["ctrl+\\", "command+\\"]);
@@ -244,7 +211,7 @@ class App extends Component {
public componentDidUpdate(prevProps: RouteComponentProps) {
if (this.props.location.pathname !== prevProps.location.pathname) {
- if (prod && (this.state.options.level !== "editor")) {
+ if (prod && (!(this.state.user?.level === "editor"))) {
ReactGA.pageview(window.location.pathname + window.location.search);
}
if (this.props.location.pathname === "/") {
@@ -256,12 +223,12 @@ class App extends Component {
page: 1,
});
}
- if (editorOnlyPages.includes(this.props.location.pathname) && this.state.options.level !== "editor") {
+ if (editorOnlyPages.includes(this.props.location.pathname) && !(this.state.user?.level === "editor")) {
this.props.history.replace("/");
}
}
if (getWordId(this.props.location.search) !== getWordId(prevProps.location.search)) {
- if (prod && (this.state.options.level !== "editor")) {
+ if (prod && ((this.state.user?.level !== "editor"))) {
ReactGA.pageview(window.location.pathname + window.location.search);
}
const wordId = getWordId(this.props.location.search);
@@ -277,54 +244,19 @@ class App extends Component {
// }
}
- private unregisterAuthObserver() {
- // will be filled in on mount
- }
-
- private wordlistSync: PouchDB.Replication.Sync | undefined = undefined;
- private reviewTastsSync: PouchDB.Replication.Sync | undefined = undefined;
-
- private async handleLoadUserInfo(): Promise {
+ private async handleLoadUser(): Promise {
try {
- const userInfo = await loadUserInfo();
- const differentUserInfoLevel = userInfo && (userInfo.level !== this.state.options.level);
- const needToDowngrade = (!userInfo && wordlistEnabled(this.state));
- if (differentUserInfoLevel || needToDowngrade) {
- this.handleOptionsUpdate({
- type: "changeUserLevel",
- payload: userInfo ? userInfo.level : "basic",
- });
+ const user = await getUser();
+ if (user === "offline") return;
+ this.setState({ user });
+ saveUser(user);
+ if (user) {
+ startLocalDbs(user, { wordlist: this.handleRefreshWordlist, reviewTasks: this.handleRefreshReviewTasks });
+ } else {
+ stopLocalDbs();
}
- if (!userInfo) return undefined;
- // only sync wordlist for upgraded accounts
- if (userInfo && wordlistEnabled(userInfo.level)) {
- // TODO: GO OVER THIS HORRENDOUS BLOCK
- if (userInfo.level === "editor") {
- initializeLocalDb("reviewTasks", this.handleRefreshReviewTasks);
- if (!this.reviewTastsSync) {
- this.reviewTastsSync = startLocalDbSync("reviewTasks", { name: userInfo.name, password: userInfo.userdbPassword });
- }
- }
- const wordlistName = getLocalDbName("wordlist") ?? "";
- const usersWordlistInitialized = wordlistName.includes(userInfo.name);
- if (this.wordlistSync && usersWordlistInitialized) {
- // sync already started for the correct db, don't start it again
- return userInfo;
- }
- if (this.wordlistSync) {
- this.wordlistSync.cancel();
- this.wordlistSync = undefined;
- }
- if (!usersWordlistInitialized) {
- initializeLocalDb("wordlist", this.handleRefreshWordlist, userInfo.name);
- }
- this.wordlistSync = startLocalDbSync("wordlist", { name: userInfo.name, password: userInfo.userdbPassword });
- }
- return userInfo;
} catch (err) {
console.error("error checking user level", err);
- // don't downgrade the level if it's editor/studend and offline (can't check user info)
- return undefined;
}
}
@@ -403,7 +335,7 @@ class App extends Component {
private networkCronJob = new CronJob("1/5 * * * *", () => {
// TODO: check for new dictionary (in a seperate cron job - not dependant on the user being signed in)
- this.handleLoadUserInfo();
+ this.handleLoadUser();
sendSubmissions();
this.handleDictionaryUpdate();
});
@@ -463,7 +395,7 @@ class App extends Component {
{this.state.options.searchType === "alphabetical" &&
Alphabetical browsing mode
}
- {this.state.options.level === "editor" &&
+ {this.state.user?.level === "editor" &&
Editor priveleges active
New Entry
@@ -478,7 +410,7 @@ class App extends Component
{
-
+
@@ -492,10 +424,7 @@ class App extends Component {
}
- {
- this.props.history.replace("/");
- auth.signOut();
- })} />
+
{
isolateEntry={this.handleIsolateEntry}
/>
- {wordlistEnabled(this.state) &&
+ {wordlistEnabled(this.state.user) &&
}
- {this.state.options.level === "editor" &&
+ {this.state.user?.level === "editor" &&
}
- {this.state.options.level === "editor" &&
+ {this.state.user?.level === "editor" &&
}
>
@@ -534,15 +463,15 @@ class App extends Component {
-
- {wordlistEnabled(this.state) &&
+
+ {wordlistEnabled(this.state.user) &&
}
- {this.state.options.level === "editor" &&
+ {this.state.user?.level === "editor" &&
{
- const res = await tokenFetch("publishDictionary");
- if (!res) {
- throw new Error("Connection error/offline");
+async function accountApiFetch(url: string, method: "GET" | "POST" | "PUT" | "DELETE" = "GET"): Promise {
+ const response = await fetch(accountBaseUrl + url, {
+ method,
+ credentials: "include",
+ });
+ return await response.json() as AT.APIResponse;
+}
+
+export async function publishDictionary(): Promise {
+ return {
+ ok: true,
+ // @ts-ignore
+ info: {},
+ };
+}
+
+export async function upgradeAccount(password: string): Promise {
+ return {
+ ok: false,
+ error: "incorrect password",
+ };
+}
+
+export async function postSubmissions(submissions: FT.SubmissionsRequest): Promise {
+ // return await tokenFetch("submissions", "POST", submissions) as FT.SubmissionsResponse;
+ return {
+ ok: true,
+ message: "received",
+ submissions: [],
}
- return res;
}
-export async function upgradeAccount(password: string): Promise {
- const res = await tokenFetch("upgradeUser", "POST", { password });
- if (!res) {
- throw new Error("Connection error/offline");
- }
- return res;
-}
-
-export async function postSubmissions(submissions: BT.SubmissionsRequest): Promise {
- return await tokenFetch("submissions", "POST", submissions) as BT.SubmissionsResponse;
-}
-
-export async function loadUserInfo(): Promise {
- const res = await tokenFetch("getUserInfo", "GET") as BT.GetUserInfoResponse;
- return "user" in res ? res.user : undefined;
-}
-
-// TODO: HARD TYPING OF THIS WITH THE subUrl and return values etc?
-async function tokenFetch(subUrl: string, method?: "GET" | "POST", body?: any): Promise {
- if (!auth.currentUser) {
- throw new Error("not signed in");
- }
+export async function getUser(): Promise {
try {
- const token = await auth.currentUser.getIdToken();
- const response = await fetch(`${functionsBaseUrl}${subUrl}`, {
- method,
- headers: {
- "Content-Type": "application/json",
- "Authorization": `Bearer ${token}`,
- },
- ...body ? {
- body: JSON.stringify(body),
- } : {},
- });
- return await response.json();
- } catch (err) {
- console.error(err);
- throw err;
+ const response = await accountApiFetch("user");
+ if ("user" in response) {
+ return response.user;
+ }
+ return undefined;
+ } catch (e) {
+ console.error(e);
+ return "offline";
}
}
diff --git a/website/src/lib/firebase.ts b/website/src/lib/firebase.ts
deleted file mode 100644
index 27bf643..0000000
--- a/website/src/lib/firebase.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import firebase from "firebase/app";
-import "firebase/auth";
-
-// Configure Firebase.
-const config = {
- apiKey: "AIzaSyDZrG2BpQi0MGktEKXL6mIWeAYEn_gFacw",
- authDomain: "lingdocs.firebaseapp.com",
- projectId: "lingdocs",
-};
-
-firebase.initializeApp(config);
-
-export const authUiConfig = {
- // Popup signin flow rather than redirect flow.
- signInFlow: "popup",
- signInOptions: [
- firebase.auth.EmailAuthProvider.PROVIDER_ID,
- firebase.auth.GithubAuthProvider.PROVIDER_ID,
- // twitter auth is set up, but not using because it doesn't provide an email
- // firebase.auth.TwitterAuthProvider.PROVIDER_ID,
- // firebase.auth.GoogleAuthProvider.PROVIDER_ID,
- ],
- callbacks: {
- // Avoid redirects after sign-in.
- signInSuccessWithAuthResult: () => false,
- },
-};
-
-export const auth = firebase.auth();
-
diff --git a/website/src/lib/backend-types.ts b/website/src/lib/functions-types.ts
similarity index 73%
rename from website/src/lib/backend-types.ts
rename to website/src/lib/functions-types.ts
index f5d4f34..7f02789 100644
--- a/website/src/lib/backend-types.ts
+++ b/website/src/lib/functions-types.ts
@@ -7,6 +7,7 @@
*/
import { Types as T } from "@lingdocs/pashto-inflector";
+import * as AT from "./account-types";
export type PublishDictionaryResponse = {
ok: true,
@@ -16,20 +17,18 @@ export type PublishDictionaryResponse = {
errors: T.DictionaryEntryError[],
};
-export type UserInfo = {
- uid: string,
- email: string | null,
- displayName: string | null,
-}
-
export type Submission = Edit | ReviewTask;
export type Edit = EntryEdit | NewEntry | EntryDeletion
export type SubmissionBase = {
- sTs: number,
- user: UserInfo,
_id: string,
+ sTs: number,
+ user: {
+ userId: AT.UUID,
+ name: string,
+ email: string,
+ },
}
export type ReviewTask = Issue | EditSuggestion | EntrySuggestion;
@@ -74,30 +73,6 @@ export type SubmissionsResponse = {
submissions: Submission[],
};
-export type UserLevel = "basic" | "student" | "editor";
-
-export type CouchDbUser = {
- _id: string,
- type: "user",
- _rev?: string,
- name: string,
- email: string,
- providerData: any,
- displayName: string,
- roles: [],
- password?: string,
- level: UserLevel,
- userdbPassword: string,
-}
-
-export type GetUserInfoResponse = {
- ok: true,
- message: "no couchdb user found",
-} | {
- ok: true,
- user: CouchDbUser,
-}
-
export type UpgradeUserResponse = {
ok: false,
error: "incorrect password",
diff --git a/website/src/lib/level-management.ts b/website/src/lib/level-management.ts
index da5f632..990a602 100644
--- a/website/src/lib/level-management.ts
+++ b/website/src/lib/level-management.ts
@@ -1,14 +1,6 @@
-/**
- * Copyright (c) 2021 lingdocs.com
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- */
+import type { LingdocsUser } from "./account-types";
-export function wordlistEnabled(state: State | UserLevel): boolean {
- const level = (typeof state === "string")
- ? state
- : state.options.level;
- return level !== "basic";
-}
+export function wordlistEnabled(user: LingdocsUser | undefined): boolean {
+ if (!user) return false;
+ return user.level !== "basic";
+}
\ No newline at end of file
diff --git a/website/src/lib/options-storage.test.ts b/website/src/lib/local-storage.test.ts
similarity index 96%
rename from website/src/lib/options-storage.test.ts
rename to website/src/lib/local-storage.test.ts
index daf04de..f44fe0b 100644
--- a/website/src/lib/options-storage.test.ts
+++ b/website/src/lib/local-storage.test.ts
@@ -6,7 +6,7 @@
*
*/
-import { saveOptions, readOptions, optionsLocalStorageName } from "./options-storage";
+import { saveOptions, readOptions, optionsLocalStorageName } from "./local-storage";
import {
defaultTextOptions,
} from "@lingdocs/pashto-inflector";
@@ -16,7 +16,6 @@ const optionsStub: Options = {
searchType: "fuzzy",
theme: "dark",
textOptions: defaultTextOptions,
- level: "student",
wordlistMode: "browse",
wordlistReviewLanguage: "Pashto",
wordlistReviewBadge: true,
diff --git a/website/src/lib/options-storage.ts b/website/src/lib/local-storage.ts
similarity index 57%
rename from website/src/lib/options-storage.ts
rename to website/src/lib/local-storage.ts
index 4983dab..8d4182f 100644
--- a/website/src/lib/options-storage.ts
+++ b/website/src/lib/local-storage.ts
@@ -6,7 +6,10 @@
*
*/
+import * as AT from "./account-types";
+
export const optionsLocalStorageName = "options2";
+export const userLocalStorageName = "user1";
export function saveOptions(options: Options): void {
localStorage.setItem(optionsLocalStorageName, JSON.stringify(options));
@@ -31,4 +34,26 @@ export const readOptions = (): Options | undefined => {
console.error("error parsing saved state JSON", e);
return undefined;
}
+};
+
+export function saveUser(user: AT.LingdocsUser | undefined): void {
+ if (user) {
+ localStorage.setItem(userLocalStorageName, JSON.stringify(user));
+ } else {
+ localStorage.removeItem(userLocalStorageName);
+ }
+};
+
+export const readUser = (): AT.LingdocsUser | undefined => {
+ const userRaw = localStorage.getItem(userLocalStorageName);
+ if (!userRaw) {
+ return undefined;
+ }
+ try {
+ const user = JSON.parse(userRaw) as AT.LingdocsUser;
+ return user;
+ } catch (e) {
+ console.error("error parsing saved user JSON", e);
+ return undefined;
+ }
};
\ No newline at end of file
diff --git a/website/src/lib/options-reducer.test.ts b/website/src/lib/options-reducer.test.ts
index c35b5bb..3018fe8 100644
--- a/website/src/lib/options-reducer.test.ts
+++ b/website/src/lib/options-reducer.test.ts
@@ -6,7 +6,6 @@ const options: Options = {
language: "Pashto",
searchType: "fuzzy",
theme: "light",
- level: "basic",
wordlistMode: "browse",
wordlistReviewLanguage: "Pashto",
wordlistReviewBadge: true,
@@ -33,11 +32,6 @@ test("options reducer should work", () => {
...options,
theme: "dark",
});
- expect(optionsReducer(options, { type: "changeUserLevel", payload: "student" }))
- .toEqual({
- ...options,
- level: "student",
- });
expect(optionsReducer(options, { type: "changeWordlistMode", payload: "review" }))
.toEqual({
...options,
@@ -66,12 +60,12 @@ test("options reducer should work", () => {
pTextSize: "largest",
},
});
- expect(optionsReducer(options, { type: "changeSpelling", payload: "Pakistani" }))
+ expect(optionsReducer(options, { type: "changeSpelling", payload: "Pakistani ی" }))
.toEqual({
...options,
textOptions: {
...defaultTextOptions,
- spelling: "Pakistani",
+ spelling: "Pakistani ی",
},
});
expect(optionsReducer(options, { type: "changePhonetics", payload: "ipa" }))
diff --git a/website/src/lib/options-reducer.ts b/website/src/lib/options-reducer.ts
index 61f4e61..faa709d 100644
--- a/website/src/lib/options-reducer.ts
+++ b/website/src/lib/options-reducer.ts
@@ -23,12 +23,6 @@ function optionsReducer(options: Options, action: OptionsAction): Options {
searchBarPosition: action.payload,
};
}
- if (action.type === "changeUserLevel") {
- return {
- ...options,
- level: action.payload,
- };
- }
if (action.type === "changeWordlistMode") {
return {
...options,
diff --git a/website/src/lib/pouch-dbs.ts b/website/src/lib/pouch-dbs.ts
index 3de1438..311d6d0 100644
--- a/website/src/lib/pouch-dbs.ts
+++ b/website/src/lib/pouch-dbs.ts
@@ -1,75 +1,112 @@
import PouchDB from "pouchdb";
-import * as BT from "./backend-types";
+import * as AT from "./account-types";
+import * as FT from "./functions-types";
type LocalDbType = "submissions" | "wordlist" | "reviewTasks";
-type LocalDb = null | { refresh: () => void, db: PouchDB.Database };
+
+const localDbTypes: LocalDbType[] = ["submissions", "wordlist", "reviewTasks"];
+
+type UnsyncedLocalDb = {
+ refresh: () => void,
+ db: PouchDB.Database,
+};
+
+type SyncedLocalDb = UnsyncedLocalDb & {
+ sync: PouchDB.Replication.Sync,
+};
+
+type DBS = {
+ submissions: undefined | UnsyncedLocalDb,
+ wordlist: undefined | SyncedLocalDb,
+ reviewTasks: undefined | SyncedLocalDb,
+};
+
type DbInput = {
type: "wordlist",
doc: WordlistWord,
} | {
type: "submissions",
- doc: BT.Submission,
+ doc: FT.Submission,
} | {
type: "reviewTasks",
- doc: BT.ReviewTask,
+ doc: FT.ReviewTask,
};
-const dbs: Record = {
+const dbs: DBS = {
/* for anyone logged in - for edits/suggestions submissions */
- submissions: null,
+ submissions: undefined,
/* for students and above - personal wordlist database */
- wordlist: null,
+ wordlist: undefined,
/* for editors only - edits/suggestions (submissions) for review */
- reviewTasks: null,
+ reviewTasks: undefined,
};
-export function initializeLocalDb(type: LocalDbType, refresh: () => void, uid?: string | undefined) {
+export function startLocalDbs(user: AT.LingdocsUser, refreshFns: { wordlist: () => void, reviewTasks: () => void }) {
+ if (user.level === "basic") {
+ initializeLocalDb("submissions", () => null, user);
+ }
+ if (user.level === "student") {
+ initializeLocalDb("submissions", () => null, user);
+ initializeLocalDb("wordlist", refreshFns.wordlist, user);
+ }
+ if (user.level === "editor") {
+ deInitializeLocalDb("submissions");
+ initializeLocalDb("reviewTasks", refreshFns.reviewTasks, user);
+ initializeLocalDb("wordlist", refreshFns.wordlist, user);
+ }
+}
+
+function deInitializeLocalDb(type: LocalDbType) {
+ const db = dbs[type];
+ if (db && "sync" in db) {
+ db.sync.cancel();
+ }
+ dbs[type] = undefined;
+}
+
+export function stopLocalDbs() {
+ localDbTypes.forEach((type) => {
+ deInitializeLocalDb(type);
+ });
+}
+
+function initializeLocalDb(type: LocalDbType, refresh: () => void, user: AT.LingdocsUser) {
const name = type === "wordlist"
- ? `userdb-${uid? stringToHex(uid) : "guest"}`
+ ? `userdb-${stringToHex(user.userId)}`
: type === "submissions"
? "submissions"
: "review-tasks";
const db = dbs[type];
// only initialize the db if it doesn't exist or if it has a different name
if ((!db) || (db.db?.name !== name)) {
- dbs[type] = {
- db: new PouchDB(name),
- refresh,
- };
+ if (type === "submissions") {
+ dbs[type] = {
+ refresh,
+ db: new PouchDB(name),
+ };
+ } else {
+ dbs[type]?.sync.cancel();
+ const db = new PouchDB(name);
+ const pass = "userDbPassword" in user ? user.userDbPassword : "";
+ dbs[type] = {
+ db,
+ refresh,
+ sync: db.sync(
+ `https://${user.userId}:${pass}@couch.lingdocs.com/${name}`,
+ { live: true, retry: true },
+ ).on("change", (info) => {
+ if (info.direction === "pull") {
+ refresh();
+ }
+ }).on("error", (error) => {
+ console.error(error);
+ }),
+ };
+ }
refresh();
}
}
-export function getLocalDbName(type: LocalDbType) {
- return dbs[type]?.db.name;
-}
-
-export function deInitializeLocalDb(type: LocalDbType) {
- dbs[type] = null;
-}
-
-export function startLocalDbSync(
- type: "wordlist" | "reviewTasks",
- auth: { name: string, password: string },
-) {
- const localDb = dbs[type];
- if (!localDb) {
- console.error(`unable to start sync because ${type} database is not initialized`);
- return;
- }
- const sync = localDb.db.sync(
- `https://${auth.name}:${auth.password}@couchdb.lingdocs.com/${localDb.db.name}`,
- { live: true, retry: true },
- ).on("change", (info) => {
- if (info.direction === "pull") {
- localDb.refresh();
- }
- }).on("error", (error) => {
- console.error(error);
- });
- return sync;
-}
-
export async function addToLocalDb({ type, doc }: DbInput) {
const localDb = dbs[type];
if (!localDb) {
@@ -99,10 +136,10 @@ export async function updateLocalDbDoc({ type, doc }: DbInput, id: string) {
return updated;
}
-export async function getAllDocsLocalDb(type: "submissions", limit?: number): Promise;
+export async function getAllDocsLocalDb(type: "submissions", limit?: number): Promise;
export async function getAllDocsLocalDb(type: "wordlist", limit?: number): Promise;
-export async function getAllDocsLocalDb(type: "reviewTasks", limit?: number): Promise
-export async function getAllDocsLocalDb(type: LocalDbType, limit?: number): Promise {
+export async function getAllDocsLocalDb(type: "reviewTasks", limit?: number): Promise
+export async function getAllDocsLocalDb(type: LocalDbType, limit?: number): Promise {
const localDb = dbs[type];
if (!localDb) {
throw new Error(`unable to get all docs from ${type} database - not initialized`);
@@ -116,11 +153,11 @@ export async function getAllDocsLocalDb(type: LocalDbType, limit?: number): Prom
const docs = result.rows.map((row) => row.doc) as unknown;
switch (type) {
case "submissions":
- return docs as BT.Submission[];
+ return docs as FT.Submission[];
case "wordlist":
return docs as WordlistWordDoc[];
case "reviewTasks":
- return docs as BT.ReviewTask[];
+ return docs as FT.ReviewTask[];
}
}
diff --git a/website/src/lib/search-all-inflections.ts b/website/src/lib/search-all-inflections.ts
index 91db554..e39face 100644
--- a/website/src/lib/search-all-inflections.ts
+++ b/website/src/lib/search-all-inflections.ts
@@ -28,7 +28,7 @@ function fFuzzy(f: string): string {
}
export function searchAllInflections(allDocs: T.DictionaryEntry[], searchValue: string): { entry: T.DictionaryEntry, results: InflectionSearchResult[] }[] {
- const timerLabel = "Search inflections";
+ // const timerLabel = "Search inflections";
const beg = fFuzzy(searchValue.slice(0, 2));
const preSearchFun = isPashtoScript(searchValue)
? (ps: T.PsString) => ps.p.slice(0, 2) === beg
@@ -37,7 +37,7 @@ export function searchAllInflections(allDocs: T.DictionaryEntry[], searchValue:
const searchFun = isPashtoScript(searchValue)
? (ps: T.PsString) => ps.p === searchValue
: (ps: T.PsString) => !!ps.f.match(fRegex);
- console.time(timerLabel);
+ // console.time(timerLabel);
const results = allDocs.reduce((all: { entry: T.DictionaryEntry, results: InflectionSearchResult[] }[], entry) => {
const type = isNounAdjOrVerb(entry);
if (entry.c && type === "verb") {
@@ -74,6 +74,6 @@ export function searchAllInflections(allDocs: T.DictionaryEntry[], searchValue:
}
return all;
}, []);
- console.timeEnd(timerLabel);
+ // console.timeEnd(timerLabel);
return results;
}
\ No newline at end of file
diff --git a/website/src/lib/submissions.ts b/website/src/lib/submissions.ts
index 3bcd086..8b1eb86 100644
--- a/website/src/lib/submissions.ts
+++ b/website/src/lib/submissions.ts
@@ -1,28 +1,22 @@
-import * as BT from "./backend-types";
-import { auth } from "./firebase";
+import * as FT from "./functions-types";
+import * as AT from "./account-types";
import {
postSubmissions,
} from "./backend-calls";
import {
- initializeLocalDb,
addToLocalDb,
getAllDocsLocalDb,
deleteFromLocalDb,
} from "./pouch-dbs";
-initializeLocalDb("submissions", () => null);
-
-export function submissionBase(): BT.SubmissionBase {
- if (!auth.currentUser) {
- throw new Error("not signed in");
- }
+export function submissionBase(user: AT.LingdocsUser): FT.SubmissionBase {
return {
sTs: Date.now(),
_id: new Date().toJSON(),
user: {
- uid: auth.currentUser.uid,
- email: auth.currentUser.email,
- displayName: auth.currentUser.displayName,
+ name: user.name,
+ email: user.email || "",
+ userId: user.userId,
},
};
}
@@ -48,8 +42,8 @@ export async function sendSubmissions() {
}
}
-export async function addSubmission(submission: BT.Submission, level: BT.UserLevel) {
- if (level === "editor" && (submission.type === "issue" || submission.type === "entry suggestion" || submission.type === "edit suggestion")) {
+export async function addSubmission(submission: FT.Submission, user: AT.LingdocsUser) {
+ if (user.level === "editor" && (submission.type === "issue" || submission.type === "entry suggestion" || submission.type === "edit suggestion")) {
await addToLocalDb({ type: "reviewTasks", doc: submission })
} else {
await addToLocalDb({ type: "submissions", doc: submission });
diff --git a/website/src/screens/Account.tsx b/website/src/screens/Account.tsx
index 2e4fa71..d817b74 100644
--- a/website/src/screens/Account.tsx
+++ b/website/src/screens/Account.tsx
@@ -1,89 +1,54 @@
-import { useState, useEffect } from "react";
-import { Modal, Button } from "react-bootstrap";
-import { Link } from "react-router-dom";
-import { auth, authUiConfig } from "../lib/firebase";
-import StyledFirebaseAuth from "react-firebaseui/StyledFirebaseAuth";
import {
- upgradeAccount,
+ useState,
+ // useEffect,
+} from "react";
+// import { Modal, Button } from "react-bootstrap";
+import {
+ // upgradeAccount,
publishDictionary,
} from "../lib/backend-calls";
-import LoadingElipses from "../components/LoadingElipses";
+// import LoadingElipses from "../components/LoadingElipses";
import { Helmet } from "react-helmet";
+import * as AT from "../lib/account-types";
const capitalize = (s: string): string => {
// if (!s) return "";
return s.charAt(0).toUpperCase() + s.slice(1);
}
-const Account = ({ handleSignOut, level, loadUserInfo }: {
- handleSignOut: () => void,
- loadUserInfo: () => void,
- level: UserLevel,
-}) => {
- const [showingDeleteConfirmation, setShowingDeleteConfirmation] = useState(false);
- const [showingUpgradePrompt, setShowingUpgradePrompt] = useState(false);
- const [upgradePassword, setUpgradePassword] = useState("");
- const [upgradeError, setUpgradeError] = useState("");
- const [accountDeleted, setAccountDeleted] = useState(false);
- const [accountDeleteError, setAccountDeleteError] = useState("");
- const [emailVerification, setEmailVerification] = useState<"unverified" | "sent" | "verified">("verified");
- const [waiting, setWaiting] = useState(false);
+const Account = ({ user }: { user: AT.LingdocsUser | undefined }) => {
+ // const [showingUpgradePrompt, setShowingUpgradePrompt] = useState(false);
+ // const [upgradePassword, setUpgradePassword] = useState("");
+ // const [upgradeError, setUpgradeError] = useState("");
+ // const [waiting, setWaiting] = useState(false);
const [publishingStatus, setPublishingStatus] = useState(undefined);
- const [showingPasswordChange, setShowingPasswordChange] = useState(false);
- const [password, setPassword] = useState("");
- const [passwordConfirmed, setPasswordConfirmed] = useState("");
- const [passwordError, setPasswordError] = useState("");
- const [showingUpdateEmail, setShowingUpdateEmail] = useState(false);
- const [updateEmailError, setUpdateEmailError] = useState("");
- const [newEmail, setNewEmail] = useState("");
- const user = auth.currentUser;
- const hasPasswordProvider = user?.providerData?.some((d) => d?.providerId === "password");
- useEffect(() => {
- setShowingDeleteConfirmation(false);
- setShowingUpgradePrompt(false);
- setUpgradePassword("");
- setUpgradeError("");
- setWaiting(false);
- }, []);
- useEffect(() => {
- setEmailVerification((user && user.emailVerified) ? "verified" : "unverified");
- }, [user]);
- function handleDelete() {
- auth.currentUser?.delete().then(() => {
- setAccountDeleteError("");
- setShowingDeleteConfirmation(false);
- setAccountDeleted(true);
- }).catch((err) => {
- console.error(err);
- setAccountDeleteError(err.message);
- });
- }
- function closeUpgrade() {
- setShowingUpgradePrompt(false);
- setUpgradePassword("");
- setUpgradeError("");
- }
- function closeUpdateEmail() {
- setShowingUpdateEmail(false);
- setNewEmail("");
- setUpdateEmailError("");
- }
- async function handleUpgrade() {
- setUpgradeError("");
- setWaiting(true);
- upgradeAccount(upgradePassword).then((res) => {
- setWaiting(false);
- if (res.ok) {
- loadUserInfo();
- closeUpgrade();
- } else {
- setUpgradeError("Incorrect password");
- }
- }).catch((err) => {
- setWaiting(false);
- setUpgradeError(err.message);
- });
- }
+ // useEffect(() => {
+ // setShowingUpgradePrompt(false);
+ // setUpgradePassword("");
+ // setUpgradeError("");
+ // setWaiting(false);
+ // }, []);
+ // function closeUpgrade() {
+ // setShowingUpgradePrompt(false);
+ // setUpgradePassword("");
+ // setUpgradeError("");
+ // }
+ // async function handleUpgrade() {
+ // setUpgradeError("");
+ // setWaiting(true);
+ // upgradeAccount(upgradePassword).then((res) => {
+ // setWaiting(false);
+ // if (res.ok) {
+ // loadUserInfo();
+ // closeUpgrade();
+ // } else {
+ // setUpgradeError("Incorrect password");
+ // }
+ // }).catch((err) => {
+ // setWaiting(false);
+ // setUpgradeError(err.message);
+ // });
+ // }
function handlePublish() {
setPublishingStatus("publishing");
publishDictionary().then((response) => {
@@ -93,56 +58,6 @@ const Account = ({ handleSignOut, level, loadUserInfo }: {
setPublishingStatus("Offline or connection error");
});
}
- function handleVerifyEmail() {
- if (!user) return;
- user.sendEmailVerification();
- setEmailVerification("sent");
- }
- function handleUpdateEmail() {
- if (!user) return;
- user.updateEmail(newEmail).then(() => {
- setShowingUpdateEmail(false);
- }).catch((err) => {
- setUpdateEmailError(err.message);
- });
- }
- function closePasswordChange() {
- setShowingPasswordChange(false);
- setPassword("");
- setPasswordConfirmed("");
- }
- function handlePasswordChange() {
- if (!user) return;
- if (password === "") {
- setPasswordError("Please enter a password");
- return;
- }
- if (password !== passwordConfirmed) {
- setPasswordError("Your passwords do not match");
- return;
- }
- user.updatePassword(password).then(() => {
- closePasswordChange();
- }).catch((err) => {
- setPasswordError(err.message);
- });
- }
- if (accountDeleted) {
- return
-
-
- Account Deleted - LingDocs Pashto Dictionary
-
-
-
Your account has been deleted 🙋♂️
-
-
-
- Home
-
-
-
- }
if (!user) {
return
@@ -151,23 +66,8 @@ const Account = ({ handleSignOut, level, loadUserInfo }: {
Sign In - LingDocs Pashto Dictionary
Sign in to be able to suggest words/edits
-
For people who previously signed in with Google. Sorry, there is a problem now and you can't get to your previous account! 😬 Don't worry, all your info is safe and it will be restored in the near future. Stay tuned.
-
{
- // const newUser = res.additionalUserInfo?.isNewUser;
- // const emailVerified = res.user.emailVerified;
- // if (newUser && !emailVerified) {
- // res.user.sendEmailVerification();
- // setEmailVerification("sent");
- // }
- // return false;
- // }}
- firebaseAuth={auth} />
- ;
+
}
- const defaultProviderId = user.providerData[0]?.providerId;
return (
@@ -176,7 +76,7 @@ const Account = ({ handleSignOut, level, loadUserInfo }: {
Account - LingDocs Pashto Dictionary
Account
- {level === "editor" &&
+ {user.level === "editor" &&
Editor Tools
{publishingStatus !== "publishing" &&
@@ -197,44 +97,34 @@ const Account = ({ handleSignOut, level, loadUserInfo }: {
}
- {user.photoURL &&
+ {/* {user.p &&
-
}
+
} */}
- Name: {user.displayName}
+ Name: {user.name}
{user.email &&
-
Email: {user.email}
- {emailVerification === "unverified" &&
- Verify Email
- }
-
- {emailVerification === "unverified" &&
- Please Verify Your Email Address
-
}
- {emailVerification === "sent" &&
- 📧 Check your email for the confirmation message
-
}
+
Email: {user.email}
}
- Account Level: {capitalize(level)}
+ Account Level: {capitalize(user.level)}
-
Sign Out
-
+ */}
Account Admin
-
- {level === "basic" &&
+ {user.level === "basic" && setShowingUpgradePrompt(true)}
@@ -242,53 +132,8 @@ const Account = ({ handleSignOut, level, loadUserInfo }: {
>
Upgrade Account
}
- setShowingPasswordChange(true)}
- >
- {!hasPasswordProvider ? "Add" : "Change"} Password
-
- setShowingUpdateEmail(true)}
- >
- Update Email
-
-
-
-
setShowingDeleteConfirmation(true)}>
- Delete Account
-
-
setShowingDeleteConfirmation(false)}>
-
- Delete Account?
-
- Are your sure you want to delete your account? This can't be undone.
- {accountDeleteError &&
-
- {accountDeleteError}
-
-
- Sign Out
-
-
}
-
- setShowingDeleteConfirmation(false)}>
- No, cancel
-
-
- Yes, delete my account
-
-
-
-
+ */}
+ {/*
Upgrade Account
@@ -318,85 +163,7 @@ const Account = ({ handleSignOut, level, loadUserInfo }: {
Upgrade my account
-
-
-
- {hasPasswordProvider ? "Change" : "Add"} Password
-
- {!hasPasswordProvider &&
- You can create a password here if you would like to sign in with your email and password, instead of just signing in with {defaultProviderId}.
- }
-
- New Password:
- {
- setPassword(e.target.value);
- setPasswordError("");
- }}
- />
- Confirm New Password:
- {
- setPasswordConfirmed(e.target.value);
- setPasswordError("");
- }}
- />
-
- {passwordError && }
-
- {waiting && }
-
- Cancel
-
-
- Change Password
-
-
-
-
-
- Update Email
-
-
- New Email:
- {
- setNewEmail(e.target.value);
- setUpdateEmailError("");
- }}
- />
-
- {updateEmailError &&
-
- {updateEmailError}
-
-
}
-
- {waiting && }
-
- Cancel
-
-
- Update Email
-
-
-
+ */}
);
};
diff --git a/website/src/screens/EntryEditor.tsx b/website/src/screens/EntryEditor.tsx
index 619dec3..6dafe6f 100644
--- a/website/src/screens/EntryEditor.tsx
+++ b/website/src/screens/EntryEditor.tsx
@@ -18,7 +18,7 @@ import {
validateEntry,
} from "@lingdocs/pashto-inflector";
import Entry from "../components/Entry";
-import * as BT from "../lib/backend-types";
+import * as FT from "../lib/functions-types";
import {
submissionBase,
addSubmission,
@@ -136,18 +136,20 @@ function EntryEditor({ state, dictionary, searchParams }: {
}
}
function handleDelete() {
- const submission: BT.EntryDeletion = {
- ...submissionBase(),
+ if (!state.user) return;
+ const submission: FT.EntryDeletion = {
+ ...submissionBase(state.user),
type: "entry deletion",
ts: entry.ts,
};
- addSubmission(submission, state.options.level);
+ addSubmission(submission, state.user);
setDeleted(true);
}
function handleSubmit(e: any) {
setErroneousFields([]);
setErrors([]);
e.preventDefault();
+ if (!state.user) return;
const result = validateEntry(entry);
if ("errors" in result) {
setErroneousFields(result.erroneousFields);
@@ -155,12 +157,12 @@ function EntryEditor({ state, dictionary, searchParams }: {
return;
}
// TODO: Check complement if checkComplement
- const submission: BT.NewEntry | BT.EntryEdit = {
- ...submissionBase(),
+ const submission: FT.NewEntry | FT.EntryEdit = {
+ ...submissionBase(state.user),
type: entry.ts === 1 ? "new entry" : "entry edit",
entry: { ...entry, ts: entry.ts === 1 ? Date.now() : entry.ts },
};
- addSubmission(submission, state.options.level);
+ addSubmission(submission, state.user);
setSubmitted(true);
// TODO: Remove from suggestions
// if (willDeleteSuggestion && sTs) {
@@ -306,8 +308,8 @@ function EntryEditor({ state, dictionary, searchParams }: {
))}
-
Submit
-
Delete Entry
+
Submit
+
Delete Entry
{sTs &&
w.entry.ts === state.isolatedEntry?.ts);
function submitEdit() {
if (!state.isolatedEntry) return;
+ if (!state.user) return;
addSubmission({
- ...submissionBase(),
+ ...submissionBase(state.user),
type: "edit suggestion",
entry: state.isolatedEntry,
comment,
- }, state.options.level);
+ }, state.user);
setEditing(false);
setComment("");
setEditSubmitted(true);
@@ -108,10 +106,10 @@ function IsolatedEntry({ state, dictionary, isolateEntry }: {
isolateEntry={isolateEntry}
/>
- {auth.currentUser &&
+ {state.user &&
- {state.options.level === "editor" &&
+ {state.user.level === "editor" &&
- {wordlistEnabled(state) &&
void,
}) {
return
@@ -152,7 +154,7 @@ function Options({
ctrl / ⌘ + b
clear search
- {wordlistEnabled(options.level) &&
+ {wordlistEnabled(state.user) &&
ctrl / ⌘ + \
show/hide wordlist
}
@@ -173,7 +175,7 @@ function Options({
handleChange={(p) => optionsDispatch({ type: "changeSearchBarPosition", payload: p as SearchBarPosition })}
/>
Bottom position doesn't work well with iPhones.
- {wordlistEnabled(options.level) && <>
+ {wordlistEnabled(state.user) && <>
Show Number of Wordlist Words for Review
) {
event.preventDefault();
+ if (!state.user) return;
const p = pashto;
const f = phonetics;
const e = english;
- const newEntry: BT.EntrySuggestion = {
- ...submissionBase(),
+ const newEntry: FT.EntrySuggestion = {
+ ...submissionBase(state.user),
type: "entry suggestion",
entry: { ts: 0, i: 0, p, f, g: "", e },
comment,
};
- addSubmission(newEntry, state.options.level);
+ addSubmission(newEntry, state.user);
setSuggestionState("received");
}
function handlePowerSearch() {
@@ -91,16 +91,16 @@ function Results({ state, isolateEntry }: {
LingDocs Pashto Dictionary
- {(auth.currentUser && (window.location.pathname !== "/word") && suggestionState === "none" && powerResults === undefined) &&
}
{(powerResults === undefined && suggestionState === "none" && window.location.pathname === "/search") &&
@@ -142,7 +142,7 @@ function Results({ state, isolateEntry }: {
isolateEntry={isolateEntry}
/>
))}
- {(auth.currentUser && (suggestionState === "editing")) &&
+ {(state.user && (suggestionState === "editing")) &&
Suggest an entry for the dictionary:
@@ -199,7 +199,7 @@ function Results({ state, isolateEntry }: {
@@ -207,7 +207,7 @@ function Results({ state, isolateEntry }: {
diff --git a/website/src/screens/ReviewTasks.tsx b/website/src/screens/ReviewTasks.tsx
index afe0bd7..00c947a 100644
--- a/website/src/screens/ReviewTasks.tsx
+++ b/website/src/screens/ReviewTasks.tsx
@@ -1,6 +1,6 @@
import Entry from "../components/Entry";
import { Link } from "react-router-dom";
-import * as BT from "../lib/backend-types";
+import * as FT from "../lib/functions-types";
import {
deleteFromLocalDb,
} from "../lib/pouch-dbs";
@@ -9,7 +9,7 @@ import {
} from "@lingdocs/pashto-inflector";
import { Helmet } from "react-helmet";
-function ReviewTask({ reviewTask, textOptions }: { reviewTask: BT.ReviewTask, textOptions: T.TextOptions }) {
+function ReviewTask({ reviewTask, textOptions }: { reviewTask: FT.ReviewTask, textOptions: T.TextOptions }) {
function handleDelete() {
deleteFromLocalDb("reviewTasks", reviewTask._id);
}
@@ -40,7 +40,7 @@ function ReviewTask({ reviewTask, textOptions }: { reviewTask: BT.ReviewTask, te
}
"{reviewTask.comment}"
-
{reviewTask.user.displayName} - {reviewTask.user.email}
+
{reviewTask.user.name} - {reviewTask.user.email}
diff --git a/website/src/types.d.ts b/website/src/types.d.ts
index fa9fe2f..7061929 100644
--- a/website/src/types.d.ts
+++ b/website/src/types.d.ts
@@ -22,7 +22,6 @@ type Options = {
searchType: SearchType,
theme: Theme,
textOptions: import("@lingdocs/pashto-inflector").Types.TextOptions,
- level: UserLevel,
wordlistMode: WordlistMode,
wordlistReviewLanguage: Language,
wordlistReviewBadge: boolean,
@@ -39,8 +38,9 @@ type State = {
isolatedEntry: import("@lingdocs/pashto-inflector").Types.DictionaryEntry | undefined,
results: import("@lingdocs/pashto-inflector").Types.DictionaryEntry[],
wordlist: WordlistWord[],
- reviewTasks: import("./lib/backend-types").ReviewTask[],
+ reviewTasks: import("./lib/functions-types").ReviewTask[],
dictionaryInfo: import("@lingdocs/pashto-inflector").Types.DictionaryInfo | undefined,
+ user: undefined | import("./lib/account-types").LingdocsUser,
}
type OptionsAction = {
diff --git a/website/yarn.lock b/website/yarn.lock
index 22d7ff4..15781a4 100644
--- a/website/yarn.lock
+++ b/website/yarn.lock
@@ -1234,276 +1234,11 @@
minimatch "^3.0.4"
strip-json-comments "^3.1.1"
-"@firebase/analytics-types@0.6.0":
- version "0.6.0"
- resolved "https://registry.yarnpkg.com/@firebase/analytics-types/-/analytics-types-0.6.0.tgz#164116ebe8d3b338272acc7f9904cac38556d6cd"
- integrity sha512-kbMawY0WRPyL/lbknBkme4CNLl+Gw+E9G4OpNeXAauqoQiNkBgpIvZYy7BRT4sNGhZbxdxXxXbruqUwDzLmvTw==
-
-"@firebase/analytics@0.6.17":
- version "0.6.17"
- resolved "https://registry.yarnpkg.com/@firebase/analytics/-/analytics-0.6.17.tgz#61df8155f474e7eb1cc180dd7ba70c6f0100f102"
- integrity sha512-Iiip24vQw7p+dBxoGWP2WvVqV2tOdLPjWw6OP6a+8vgss9PRsjKE2AAskruqveMUO4Ox5uPW65wdgeJxoFoMvQ==
- dependencies:
- "@firebase/analytics-types" "0.6.0"
- "@firebase/component" "0.5.5"
- "@firebase/installations" "0.4.31"
- "@firebase/logger" "0.2.6"
- "@firebase/util" "1.2.0"
- tslib "^2.1.0"
-
-"@firebase/app-check-interop-types@0.1.0":
- version "0.1.0"
- resolved "https://registry.yarnpkg.com/@firebase/app-check-interop-types/-/app-check-interop-types-0.1.0.tgz#83afd9d41f99166c2bdb2d824e5032e9edd8fe53"
- integrity sha512-uZfn9s4uuRsaX5Lwx+gFP3B6YsyOKUE+Rqa6z9ojT4VSRAsZFko9FRn6OxQUA1z5t5d08fY4pf+/+Dkd5wbdbA==
-
-"@firebase/app-check-types@0.3.1":
- version "0.3.1"
- resolved "https://registry.yarnpkg.com/@firebase/app-check-types/-/app-check-types-0.3.1.tgz#1084723debad3ad9e7997d3b356165d275c25fcc"
- integrity sha512-KJ+BqJbdNsx4QT/JIT1yDj5p6D+QN97iJs3GuHnORrqL+DU3RWc9nSYQsrY6Tv9jVWcOkMENXAgDT484vzsm2w==
-
-"@firebase/app-check@0.3.1":
- version "0.3.1"
- resolved "https://registry.yarnpkg.com/@firebase/app-check/-/app-check-0.3.1.tgz#78210513455ea6da437cb5fcf18239db9cd1e7de"
- integrity sha512-5OWXzhXdtrmOqn2aN44FyNTwjlZJrip/3WC7UlMeUBPi3apuUgChHTolZ1oITszGdw52lP3r5SOCDtRsNtbkJg==
- dependencies:
- "@firebase/app-check-interop-types" "0.1.0"
- "@firebase/app-check-types" "0.3.1"
- "@firebase/component" "0.5.5"
- "@firebase/logger" "0.2.6"
- "@firebase/util" "1.2.0"
- tslib "^2.1.0"
-
-"@firebase/app-types@0.6.3":
- version "0.6.3"
- resolved "https://registry.yarnpkg.com/@firebase/app-types/-/app-types-0.6.3.tgz#3f10514786aad846d74cd63cb693556309918f4b"
- integrity sha512-/M13DPPati7FQHEQ9Minjk1HGLm/4K4gs9bR4rzLCWJg64yGtVC0zNg9gDpkw9yc2cvol/mNFxqTtd4geGrwdw==
-
-"@firebase/app@0.6.29":
- version "0.6.29"
- resolved "https://registry.yarnpkg.com/@firebase/app/-/app-0.6.29.tgz#e2f88274b39917ab766f9fe73da48c353eaed557"
- integrity sha512-duCzk9/BSVVsb5Y9b0rnvGSuD5zQA/JghiQsccRl+lA4xiUYjFudTU4cVFftkw+0zzeYBHn4KiVxchsva1O9dA==
- dependencies:
- "@firebase/app-types" "0.6.3"
- "@firebase/component" "0.5.5"
- "@firebase/logger" "0.2.6"
- "@firebase/util" "1.2.0"
- dom-storage "2.1.0"
- tslib "^2.1.0"
- xmlhttprequest "1.8.0"
-
-"@firebase/auth-interop-types@0.1.6":
- version "0.1.6"
- resolved "https://registry.yarnpkg.com/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz#5ce13fc1c527ad36f1bb1322c4492680a6cf4964"
- integrity sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==
-
-"@firebase/auth-types@0.10.3":
- version "0.10.3"
- resolved "https://registry.yarnpkg.com/@firebase/auth-types/-/auth-types-0.10.3.tgz#2be7dd93959c8f5304c63e09e98718e103464d8c"
- integrity sha512-zExrThRqyqGUbXOFrH/sowuh2rRtfKHp9SBVY2vOqKWdCX1Ztn682n9WLtlUDsiYVIbBcwautYWk2HyCGFv0OA==
-
-"@firebase/auth@0.16.8":
- version "0.16.8"
- resolved "https://registry.yarnpkg.com/@firebase/auth/-/auth-0.16.8.tgz#4edd44673d3711e94cfa1e6b03883214ae1f2255"
- integrity sha512-mR0UXG4LirWIfOiCWxVmvz1o23BuKGxeItQ2cCUgXLTjNtWJXdcky/356iTUsd7ZV5A78s2NHeN5tIDDG6H4rg==
- dependencies:
- "@firebase/auth-types" "0.10.3"
-
-"@firebase/component@0.5.5":
- version "0.5.5"
- resolved "https://registry.yarnpkg.com/@firebase/component/-/component-0.5.5.tgz#849ccf7cbf0398a43058f274ffcd43620ae9521f"
- integrity sha512-L41SdS/4a164jx2iGfakJgaBUPPBI3DI+RrUlmh3oHSUljTeCwfj/Nhcv3S7e2lyXsGFJtAyepfPUx4IQ05crw==
- dependencies:
- "@firebase/util" "1.2.0"
- tslib "^2.1.0"
-
-"@firebase/database-types@0.7.3":
- version "0.7.3"
- resolved "https://registry.yarnpkg.com/@firebase/database-types/-/database-types-0.7.3.tgz#819f16dd4c767c864b460004458620f265a3f735"
- integrity sha512-dSOJmhKQ0nL8O4EQMRNGpSExWCXeHtH57gGg0BfNAdWcKhC8/4Y+qfKLfWXzyHvrSecpLmO0SmAi/iK2D5fp5A==
- dependencies:
- "@firebase/app-types" "0.6.3"
-
-"@firebase/database@0.10.9":
- version "0.10.9"
- resolved "https://registry.yarnpkg.com/@firebase/database/-/database-0.10.9.tgz#79f7b03cbe8a127dddfb7ea7748a3e923990f046"
- integrity sha512-Jxi9SiE4cNOftO9YKlG71ccyWFw4kSM9AG/xYu6vWXUGBr39Uw1TvYougANOcU21Q0TP4J08VPGnOnpXk/FGbQ==
- dependencies:
- "@firebase/auth-interop-types" "0.1.6"
- "@firebase/component" "0.5.5"
- "@firebase/database-types" "0.7.3"
- "@firebase/logger" "0.2.6"
- "@firebase/util" "1.2.0"
- faye-websocket "0.11.3"
- tslib "^2.1.0"
-
-"@firebase/firestore-types@2.3.0":
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/@firebase/firestore-types/-/firestore-types-2.3.0.tgz#baf5c9470ba8be96bf0d76b83b413f03104cf565"
- integrity sha512-QTW7NP7nDL0pgT/X53lyj+mIMh4nRQBBTBlRNQBt7eSyeqBf3ag3bxdQhCg358+5KbjYTC2/O6QtX9DlJZmh1A==
-
-"@firebase/firestore@2.3.10":
- version "2.3.10"
- resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-2.3.10.tgz#76d5137e5c37d33ccf3c5d77a9261c73493494b2"
- integrity sha512-O+XpaZVhDIBK2fMwBUBR2BuhaXF6zTmz+afAuXAx18DK+2rFfLefbALZLaUYw0Aabe9pryy0c7OenzRbHA8n4Q==
- dependencies:
- "@firebase/component" "0.5.5"
- "@firebase/firestore-types" "2.3.0"
- "@firebase/logger" "0.2.6"
- "@firebase/util" "1.2.0"
- "@firebase/webchannel-wrapper" "0.5.1"
- "@grpc/grpc-js" "^1.3.2"
- "@grpc/proto-loader" "^0.6.0"
- node-fetch "2.6.1"
- tslib "^2.1.0"
-
-"@firebase/functions-types@0.4.0":
- version "0.4.0"
- resolved "https://registry.yarnpkg.com/@firebase/functions-types/-/functions-types-0.4.0.tgz#0b789f4fe9a9c0b987606c4da10139345b40f6b9"
- integrity sha512-3KElyO3887HNxtxNF1ytGFrNmqD+hheqjwmT3sI09FaDCuaxGbOnsXAXH2eQ049XRXw9YQpHMgYws/aUNgXVyQ==
-
-"@firebase/functions@0.6.14":
- version "0.6.14"
- resolved "https://registry.yarnpkg.com/@firebase/functions/-/functions-0.6.14.tgz#f6b452a53dc15299595bd079dd6ed4afb59e1a8c"
- integrity sha512-Gthru/wHPQqkn651MenVM+qKVFFqIyFcNT3qfJUacibqrKlvDtYtaCMjFGAkChuGnYzNVnXJIaNrIHkEIII4Hg==
- dependencies:
- "@firebase/component" "0.5.5"
- "@firebase/functions-types" "0.4.0"
- "@firebase/messaging-types" "0.5.0"
- node-fetch "2.6.1"
- tslib "^2.1.0"
-
-"@firebase/installations-types@0.3.4":
- version "0.3.4"
- resolved "https://registry.yarnpkg.com/@firebase/installations-types/-/installations-types-0.3.4.tgz#589a941d713f4f64bf9f4feb7f463505bab1afa2"
- integrity sha512-RfePJFovmdIXb6rYwtngyxuEcWnOrzdZd9m7xAW0gRxDIjBT20n3BOhjpmgRWXo/DAxRmS7bRjWAyTHY9cqN7Q==
-
-"@firebase/installations@0.4.31":
- version "0.4.31"
- resolved "https://registry.yarnpkg.com/@firebase/installations/-/installations-0.4.31.tgz#dbde30c0542fb4343b075f0574d4e0d0f4b49aa7"
- integrity sha512-qWolhAgMHvD3avsNCl+K8+untzoDDFQIRR8At8kyWMKKosy0vttdWTWzjvDoZbyKU6r0RNlxDUWAgV88Q8EudQ==
- dependencies:
- "@firebase/component" "0.5.5"
- "@firebase/installations-types" "0.3.4"
- "@firebase/util" "1.2.0"
- idb "3.0.2"
- tslib "^2.1.0"
-
-"@firebase/logger@0.2.6":
- version "0.2.6"
- resolved "https://registry.yarnpkg.com/@firebase/logger/-/logger-0.2.6.tgz#3aa2ca4fe10327cabf7808bd3994e88db26d7989"
- integrity sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==
-
-"@firebase/messaging-types@0.5.0":
- version "0.5.0"
- resolved "https://registry.yarnpkg.com/@firebase/messaging-types/-/messaging-types-0.5.0.tgz#c5d0ef309ced1758fda93ef3ac70a786de2e73c4"
- integrity sha512-QaaBswrU6umJYb/ZYvjR5JDSslCGOH6D9P136PhabFAHLTR4TWjsaACvbBXuvwrfCXu10DtcjMxqfhdNIB1Xfg==
-
-"@firebase/messaging@0.7.15":
- version "0.7.15"
- resolved "https://registry.yarnpkg.com/@firebase/messaging/-/messaging-0.7.15.tgz#d3b9a053331238480860c71385819babda2076f3"
- integrity sha512-81t6iJtqMBJF5LHTjDhlHUpbPZOV6dKhW0TueAoON4omc0SaDXgf4nnk6JkvZRfdcuOaP8848Cv53tvZPFFAYQ==
- dependencies:
- "@firebase/component" "0.5.5"
- "@firebase/installations" "0.4.31"
- "@firebase/messaging-types" "0.5.0"
- "@firebase/util" "1.2.0"
- idb "3.0.2"
- tslib "^2.1.0"
-
-"@firebase/performance-types@0.0.13":
- version "0.0.13"
- resolved "https://registry.yarnpkg.com/@firebase/performance-types/-/performance-types-0.0.13.tgz#58ce5453f57e34b18186f74ef11550dfc558ede6"
- integrity sha512-6fZfIGjQpwo9S5OzMpPyqgYAUZcFzZxHFqOyNtorDIgNXq33nlldTL/vtaUZA8iT9TT5cJlCrF/jthKU7X21EA==
-
-"@firebase/performance@0.4.17":
- version "0.4.17"
- resolved "https://registry.yarnpkg.com/@firebase/performance/-/performance-0.4.17.tgz#b160a4352f682c1039b49ec9d24d6c473a31b3c3"
- integrity sha512-uhDs9rhdMrGraYHcd3CTRkGtcNap4hp6rAHTwJNIX56Z3RzQ1VW2ea9vvesl7EjFtEIPU0jfdrS32wV+qer5DQ==
- dependencies:
- "@firebase/component" "0.5.5"
- "@firebase/installations" "0.4.31"
- "@firebase/logger" "0.2.6"
- "@firebase/performance-types" "0.0.13"
- "@firebase/util" "1.2.0"
- tslib "^2.1.0"
-
-"@firebase/polyfill@0.3.36":
- version "0.3.36"
- resolved "https://registry.yarnpkg.com/@firebase/polyfill/-/polyfill-0.3.36.tgz#c057cce6748170f36966b555749472b25efdb145"
- integrity sha512-zMM9oSJgY6cT2jx3Ce9LYqb0eIpDE52meIzd/oe/y70F+v9u1LDqk5kUF5mf16zovGBWMNFmgzlsh6Wj0OsFtg==
- dependencies:
- core-js "3.6.5"
- promise-polyfill "8.1.3"
- whatwg-fetch "2.0.4"
-
-"@firebase/remote-config-types@0.1.9":
- version "0.1.9"
- resolved "https://registry.yarnpkg.com/@firebase/remote-config-types/-/remote-config-types-0.1.9.tgz#fe6bbe4d08f3b6e92fce30e4b7a9f4d6a96d6965"
- integrity sha512-G96qnF3RYGbZsTRut7NBX0sxyczxt1uyCgXQuH/eAfUCngxjEGcZQnBdy6mvSdqdJh5mC31rWPO4v9/s7HwtzA==
-
-"@firebase/remote-config@0.1.42":
- version "0.1.42"
- resolved "https://registry.yarnpkg.com/@firebase/remote-config/-/remote-config-0.1.42.tgz#84573ac2f1ee49cb9d4327a25c5625f2e274695d"
- integrity sha512-hWwtAZmYLB274bxjV2cdMYhyBCUUqbYErihGx3rMyab76D+VbIxOuKJb2z0DS67jQG+SA3pr9/MtWsTPHV/l9g==
- dependencies:
- "@firebase/component" "0.5.5"
- "@firebase/installations" "0.4.31"
- "@firebase/logger" "0.2.6"
- "@firebase/remote-config-types" "0.1.9"
- "@firebase/util" "1.2.0"
- tslib "^2.1.0"
-
-"@firebase/storage-types@0.4.1":
- version "0.4.1"
- resolved "https://registry.yarnpkg.com/@firebase/storage-types/-/storage-types-0.4.1.tgz#da6582ae217e3db485c90075dc71100ca5064cc6"
- integrity sha512-IM4cRzAnQ6QZoaxVZ5MatBzqXVcp47hOlE28jd9xXw1M9V7gfjhmW0PALGFQx58tPVmuUwIKyoEbHZjV4qRJwQ==
-
-"@firebase/storage@0.6.2":
- version "0.6.2"
- resolved "https://registry.yarnpkg.com/@firebase/storage/-/storage-0.6.2.tgz#0355c903a9aa5a5958c87e8cae37f5f1b0e1d58e"
- integrity sha512-qbYx+KOxnD5andWAtVPgMHFa/62jdLCgPGdfIRQIEgBoYibk3KigRYVdnfKRLcCO3Vip63BRUvyeqfiUHNNu2g==
- dependencies:
- "@firebase/component" "0.5.5"
- "@firebase/storage-types" "0.4.1"
- "@firebase/util" "1.2.0"
- node-fetch "2.6.1"
- tslib "^2.1.0"
-
-"@firebase/util@1.2.0":
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/@firebase/util/-/util-1.2.0.tgz#4d4e419bf8c9bc1bc51308d1953dc2e4353c0770"
- integrity sha512-8W9TTGImXr9cu+oyjBJ7yjoEd/IVAv0pBZA4c1uIuKrpGZi2ee38m+8xlZOBRmsAaOU/tR9DXz1WF/oeM6Fb7Q==
- dependencies:
- tslib "^2.1.0"
-
-"@firebase/webchannel-wrapper@0.5.1":
- version "0.5.1"
- resolved "https://registry.yarnpkg.com/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.5.1.tgz#a64d1af3c62e3bb89576ec58af880980a562bf4e"
- integrity sha512-dZMzN0uAjwJXWYYAcnxIwXqRTZw3o14hGe7O6uhwjD1ZQWPVYA5lASgnNskEBra0knVBsOXB4KXg+HnlKewN/A==
-
"@fortawesome/fontawesome-free@^5.15.2":
version "5.15.4"
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.4.tgz#ecda5712b61ac852c760d8b3c79c96adca5554e5"
integrity sha512-eYm8vijH/hpzr/6/1CJ/V/Eb1xQFW2nnUKArb3z+yUWv7HTwj6M7SP957oMjfZjAHU6qpoNc2wQvIxBLWYa/Jg==
-"@grpc/grpc-js@^1.3.2":
- version "1.3.7"
- resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.3.7.tgz#58b687aff93b743aafde237fd2ee9a3259d7f2d8"
- integrity sha512-CKQVuwuSPh40tgOkR7c0ZisxYRiN05PcKPW72mQL5y++qd7CwBRoaJZvU5xfXnCJDFBmS3qZGQ71Frx6Ofo2XA==
- dependencies:
- "@types/node" ">=12.12.47"
-
-"@grpc/proto-loader@^0.6.0":
- version "0.6.4"
- resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.6.4.tgz#5438c0d771e92274e77e631babdc14456441cbdc"
- integrity sha512-7xvDvW/vJEcmLUltCUGOgWRPM8Oofv0eCFSVMuKqaqWJaXSzmB+m9hiyqe34QofAl4WAzIKUZZlinIF9FOHyTQ==
- dependencies:
- "@types/long" "^4.0.1"
- lodash.camelcase "^4.3.0"
- long "^4.0.0"
- protobufjs "^6.10.0"
- yargs "^16.1.1"
-
"@hapi/address@2.x.x":
version "2.1.4"
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5"
@@ -1803,59 +1538,6 @@
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.9.3.tgz#8b68da1ebd7fc603999cf6ebee34a4899a14b88e"
integrity sha512-xDu17cEfh7Kid/d95kB6tZsLOmSWKCZKtprnhVepjsSaCij+lM3mItSJDuuHDMbCWTh8Ejmebwb+KONcCJ0eXQ==
-"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"
- integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78=
-
-"@protobufjs/base64@^1.1.2":
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735"
- integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==
-
-"@protobufjs/codegen@^2.0.4":
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb"
- integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==
-
-"@protobufjs/eventemitter@^1.1.0":
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70"
- integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A=
-
-"@protobufjs/fetch@^1.1.0":
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45"
- integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=
- dependencies:
- "@protobufjs/aspromise" "^1.1.1"
- "@protobufjs/inquire" "^1.1.0"
-
-"@protobufjs/float@^1.0.2":
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1"
- integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=
-
-"@protobufjs/inquire@^1.1.0":
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089"
- integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=
-
-"@protobufjs/path@^1.1.2":
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d"
- integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=
-
-"@protobufjs/pool@^1.1.0":
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54"
- integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=
-
-"@protobufjs/utf8@^1.1.0":
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
- integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=
-
"@restart/context@^2.1.4":
version "2.1.4"
resolved "https://registry.yarnpkg.com/@restart/context/-/context-2.1.4.tgz#a99d87c299a34c28bd85bb489cb07bfd23149c02"
@@ -2250,11 +1932,6 @@
resolved "https://registry.yarnpkg.com/@types/lokijs/-/lokijs-1.5.5.tgz#bae743a9ae24d1a106b8291c31ab1e1ad28e00d2"
integrity sha512-TAvlc6vfYZnQVqPBVF3ITE33aSomqRLHOsZb5u1jQdmQxvj+LOLgbt8VaAJh85Tx7xXdWcsdeEO13i6TQBfV+w==
-"@types/long@^4.0.1":
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"
- integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==
-
"@types/mime@^1":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"
@@ -2275,7 +1952,7 @@
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==
-"@types/node@*", "@types/node@>=12.12.47", "@types/node@>=13.7.0":
+"@types/node@*":
version "16.6.1"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.6.1.tgz#aee62c7b966f55fc66c7b6dfa1d58db2a616da61"
integrity sha512-Sr7BhXEAer9xyGuCN3Ek9eg9xPviCF2gfu9kTfuU2HkTVAMYSDeX40fvpmo72n5nansg3nsBjuQBrsS28r+NUw==
@@ -4254,15 +3931,6 @@ cliui@^6.0.0:
strip-ansi "^6.0.0"
wrap-ansi "^6.2.0"
-cliui@^7.0.2:
- version "7.0.4"
- resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
- integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==
- dependencies:
- string-width "^4.2.0"
- strip-ansi "^6.0.0"
- wrap-ansi "^7.0.0"
-
clone-buffer@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58"
@@ -4520,11 +4188,6 @@ core-js-pure@^3.16.0:
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.16.2.tgz#0ef4b79cabafb251ea86eb7d139b42bd98c533e8"
integrity sha512-oxKe64UH049mJqrKkynWp6Vu0Rlm/BTXO/bJZuN2mmR3RtOFNepLlSWDd1eo16PzHpQAoNG97rLU1V/YxesJjw==
-core-js@3.6.5:
- version "3.6.5"
- resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a"
- integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==
-
core-js@^2.4.0, core-js@^2.5.3:
version "2.6.12"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
@@ -5220,11 +4883,6 @@ dom-serializer@^1.0.1:
domhandler "^4.2.0"
entities "^2.0.0"
-dom-storage@2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/dom-storage/-/dom-storage-2.1.0.tgz#00fb868bc9201357ea243c7bcfd3304c1e34ea39"
- integrity sha512-g6RpyWXzl0RR6OTElHKBl7nwnK87GUyZMYC7JWsB/IA73vpqK2K6LT39x4VepLxlSsWBFrPVLnsSR5Jyty0+2Q==
-
domain-browser@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
@@ -6043,13 +5701,6 @@ fastq@^1.6.0:
dependencies:
reusify "^1.0.4"
-faye-websocket@0.11.3:
- version "0.11.3"
- resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e"
- integrity sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==
- dependencies:
- websocket-driver ">=0.5.1"
-
faye-websocket@^0.11.3:
version "0.11.4"
resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da"
@@ -6186,27 +5837,6 @@ find-up@^3.0.0:
dependencies:
locate-path "^3.0.0"
-firebase@^8.3.0:
- version "8.9.1"
- resolved "https://registry.yarnpkg.com/firebase/-/firebase-8.9.1.tgz#9b3bc8e69830cd8bcabd3d296068e9d9ff481f35"
- integrity sha512-4aKRynB0LSWneYTPwWlAUbcJbgSS11lZRIo9MLNQh1uCo9BxRIYq/r3CCDJOx59tI2nwyv4RXf7hdkgSTF5FYw==
- dependencies:
- "@firebase/analytics" "0.6.17"
- "@firebase/app" "0.6.29"
- "@firebase/app-check" "0.3.1"
- "@firebase/app-types" "0.6.3"
- "@firebase/auth" "0.16.8"
- "@firebase/database" "0.10.9"
- "@firebase/firestore" "2.3.10"
- "@firebase/functions" "0.6.14"
- "@firebase/installations" "0.4.31"
- "@firebase/messaging" "0.7.15"
- "@firebase/performance" "0.4.17"
- "@firebase/polyfill" "0.3.36"
- "@firebase/remote-config" "0.1.42"
- "@firebase/storage" "0.6.2"
- "@firebase/util" "1.2.0"
-
firebaseui@^4.7.1:
version "4.8.1"
resolved "https://registry.yarnpkg.com/firebaseui/-/firebaseui-4.8.1.tgz#29ccbc9dfd579c4453725f88e9cf81c8ea62c580"
@@ -6411,7 +6041,7 @@ gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2:
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
-get-caller-file@^2.0.1, get-caller-file@^2.0.5:
+get-caller-file@^2.0.1:
version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
@@ -6934,11 +6564,6 @@ icss-utils@^4.0.0, icss-utils@^4.1.1:
dependencies:
postcss "^7.0.14"
-idb@3.0.2:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/idb/-/idb-3.0.2.tgz#c8e9122d5ddd40f13b60ae665e4862f8b13fa384"
- integrity sha512-+FLa/0sTXqyux0o6C+i2lOR0VoS60LU/jzUo5xjfY6+7sEEgy4Gz1O7yFBXvjd7N0NyIGWIRg8DcQSLEG+VSPw==
-
identity-obj-proxy@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz#94d2bda96084453ef36fbc5aaec37e0f79f1fc14"
@@ -8424,11 +8049,6 @@ lodash._reinterpolate@^3.0.0:
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=
-lodash.camelcase@^4.3.0:
- version "4.3.0"
- resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
- integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY=
-
lodash.clonedeep@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
@@ -8489,11 +8109,6 @@ lokijs@^1.5.11:
resolved "https://registry.yarnpkg.com/lokijs/-/lokijs-1.5.12.tgz#cb55b37009bdf09ee7952a6adddd555b893653a0"
integrity sha512-Q5ALD6JiS6xAUWCwX3taQmgwxyveCtIIuL08+ml0nHwT3k0S/GIFJN+Hd38b1qYIMaE5X++iqsqWVksz7SYW+Q==
-long@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
- integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==
-
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
@@ -10602,11 +10217,6 @@ promise-inflight@^1.0.1:
resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=
-promise-polyfill@8.1.3:
- version "8.1.3"
- resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.1.3.tgz#8c99b3cf53f3a91c68226ffde7bde81d7f904116"
- integrity sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==
-
promise-polyfill@^8.1.3:
version "8.2.0"
resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.2.0.tgz#367394726da7561457aba2133c9ceefbd6267da0"
@@ -10652,25 +10262,6 @@ prop-types@^15.6.2, prop-types@^15.7.2:
object-assign "^4.1.1"
react-is "^16.8.1"
-protobufjs@^6.10.0:
- version "6.11.2"
- resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.2.tgz#de39fabd4ed32beaa08e9bb1e30d08544c1edf8b"
- integrity sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==
- dependencies:
- "@protobufjs/aspromise" "^1.1.2"
- "@protobufjs/base64" "^1.1.2"
- "@protobufjs/codegen" "^2.0.4"
- "@protobufjs/eventemitter" "^1.1.0"
- "@protobufjs/fetch" "^1.1.0"
- "@protobufjs/float" "^1.0.2"
- "@protobufjs/inquire" "^1.1.0"
- "@protobufjs/path" "^1.1.2"
- "@protobufjs/pool" "^1.1.0"
- "@protobufjs/utf8" "^1.1.0"
- "@types/long" "^4.0.1"
- "@types/node" ">=13.7.0"
- long "^4.0.0"
-
protocol-buffers-schema@^3.3.1:
version "3.5.2"
resolved "https://registry.yarnpkg.com/protocol-buffers-schema/-/protocol-buffers-schema-3.5.2.tgz#38ad35ba768607a5ed2375f8db4c2ecc5ea293c8"
@@ -12797,7 +12388,7 @@ tslib@^1.8.1:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
-tslib@^2.0.3, tslib@^2.1.0:
+tslib@^2.0.3:
version "2.3.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
@@ -13405,11 +12996,6 @@ whatwg-encoding@^1.0.5:
dependencies:
iconv-lite "0.4.24"
-whatwg-fetch@2.0.4:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f"
- integrity sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==
-
whatwg-fetch@^3.4.1:
version "3.6.2"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz#dced24f37f2624ed0281725d51d0e2e3fe677f8c"
@@ -13656,15 +13242,6 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
-wrap-ansi@^7.0.0:
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
- integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
- dependencies:
- ansi-styles "^4.0.0"
- string-width "^4.1.0"
- strip-ansi "^6.0.0"
-
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
@@ -13714,11 +13291,6 @@ xmldom@0.1.x:
resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.31.tgz#b76c9a1bd9f0a9737e5a72dc37231cf38375e2ff"
integrity sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==
-xmlhttprequest@1.8.0:
- version "1.8.0"
- resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc"
- integrity sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=
-
xtend@^4.0.0, xtend@^4.0.2, xtend@~4.0.0, xtend@~4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
@@ -13736,11 +13308,6 @@ y18n@^4.0.0:
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
-y18n@^5.0.5:
- version "5.0.8"
- resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
- integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
-
yallist@^3.0.2:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
@@ -13772,11 +13339,6 @@ yargs-parser@^18.1.2:
camelcase "^5.0.0"
decamelize "^1.2.0"
-yargs-parser@^20.2.2:
- version "20.2.9"
- resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
- integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
-
yargs@^13.3.2:
version "13.3.2"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd"
@@ -13810,19 +13372,6 @@ yargs@^15.4.1:
y18n "^4.0.0"
yargs-parser "^18.1.2"
-yargs@^16.1.1:
- version "16.2.0"
- resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
- integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
- dependencies:
- cliui "^7.0.2"
- escalade "^3.1.1"
- get-caller-file "^2.0.5"
- require-directory "^2.1.1"
- string-width "^4.2.0"
- y18n "^5.0.5"
- yargs-parser "^20.2.2"
-
yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"