* Cherry picking refactored fb.js from #879 by @AndrewBastin * Fixed a minor UI glitch in History section * Removed logout success toast testcase Co-authored-by: Andrew Bastin <andrewbastin.k@gmail.com>
This commit is contained in:
1
__mocks__/svgMock.js
Normal file
1
__mocks__/svgMock.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default {}
|
||||||
@@ -293,6 +293,7 @@ hr {
|
|||||||
|
|
||||||
button {
|
button {
|
||||||
@apply justify-start;
|
@apply justify-start;
|
||||||
|
@apply text-left;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
133
components/firebase/__tests__/feeds.spec.js
Normal file
133
components/firebase/__tests__/feeds.spec.js
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import feeds from "../feeds"
|
||||||
|
import { shallowMount } from "@vue/test-utils"
|
||||||
|
|
||||||
|
jest.mock("~/helpers/fb", () => {
|
||||||
|
return {
|
||||||
|
__esModule: true,
|
||||||
|
fb: {
|
||||||
|
currentFeeds: [
|
||||||
|
{
|
||||||
|
id: "test1",
|
||||||
|
label: "First",
|
||||||
|
message: "First Message",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "test2",
|
||||||
|
label: "Second",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "test3",
|
||||||
|
message: "Third Message",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "test4",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
deleteFeed: jest.fn(() => Promise.resolve()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const { fb } = require("~/helpers/fb")
|
||||||
|
|
||||||
|
const factory = () =>
|
||||||
|
shallowMount(feeds, {
|
||||||
|
mocks: {
|
||||||
|
$t: (text) => text,
|
||||||
|
$toast: {
|
||||||
|
error: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fb.deleteFeed.mockClear()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("feeds", () => {
|
||||||
|
test("mounts properly when proper components are given", () => {
|
||||||
|
const wrapper = factory()
|
||||||
|
|
||||||
|
expect(wrapper).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("renders all the current feeds", () => {
|
||||||
|
const wrapper = factory()
|
||||||
|
|
||||||
|
expect(wrapper.findAll("div[data-test='list-item']").wrappers).toHaveLength(4)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("feeds with no label displays the 'no_label' message", () => {
|
||||||
|
const wrapper = factory()
|
||||||
|
|
||||||
|
expect(
|
||||||
|
wrapper
|
||||||
|
.findAll("label[data-test='list-label']")
|
||||||
|
.wrappers.map((e) => e.text())
|
||||||
|
.filter((text) => text == "no_label")
|
||||||
|
).toHaveLength(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("feeds with no message displays the 'empty' message", () => {
|
||||||
|
const wrapper = factory()
|
||||||
|
|
||||||
|
expect(
|
||||||
|
wrapper
|
||||||
|
.findAll("li[data-test='list-message']")
|
||||||
|
.wrappers.map((e) => e.text())
|
||||||
|
.filter((text) => text == "empty")
|
||||||
|
).toHaveLength(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("labels in the list are proper", () => {
|
||||||
|
const wrapper = factory()
|
||||||
|
|
||||||
|
expect(wrapper.findAll("label[data-test='list-label']").wrappers.map((e) => e.text())).toEqual([
|
||||||
|
"First",
|
||||||
|
"Second",
|
||||||
|
"no_label",
|
||||||
|
"no_label",
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("messages in the list are proper", () => {
|
||||||
|
const wrapper = factory()
|
||||||
|
|
||||||
|
expect(wrapper.findAll("li[data-test='list-message']").wrappers.map((e) => e.text())).toEqual([
|
||||||
|
"First Message",
|
||||||
|
"empty",
|
||||||
|
"Third Message",
|
||||||
|
"empty",
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("clicking on the delete button deletes the feed", async () => {
|
||||||
|
const wrapper = factory()
|
||||||
|
|
||||||
|
const deleteButton = wrapper.find("button")
|
||||||
|
|
||||||
|
await deleteButton.trigger("click")
|
||||||
|
|
||||||
|
expect(fb.deleteFeed).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("correct feed is passed to from the list for deletion", async () => {
|
||||||
|
const wrapper = factory()
|
||||||
|
|
||||||
|
const deleteButton = wrapper.find("button")
|
||||||
|
|
||||||
|
await deleteButton.trigger("click")
|
||||||
|
|
||||||
|
expect(fb.deleteFeed).toHaveBeenCalledWith("test1")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("renders the 'empty' label if no elements in the current feeds", () => {
|
||||||
|
jest.spyOn(fb, "currentFeeds", "get").mockReturnValueOnce([])
|
||||||
|
|
||||||
|
const wrapper = factory()
|
||||||
|
|
||||||
|
expect(wrapper.findAll("li").wrappers).toHaveLength(1)
|
||||||
|
|
||||||
|
expect(wrapper.find("li").text()).toEqual("empty")
|
||||||
|
})
|
||||||
|
})
|
||||||
93
components/firebase/__tests__/inputform.spec.js
Normal file
93
components/firebase/__tests__/inputform.spec.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import inputform from "../inputform"
|
||||||
|
import { shallowMount } from "@vue/test-utils"
|
||||||
|
|
||||||
|
jest.mock("~/helpers/fb", () => {
|
||||||
|
return {
|
||||||
|
__esModule: true,
|
||||||
|
fb: {
|
||||||
|
writeFeeds: jest.fn(() => Promise.resolve()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const { fb } = require("~/helpers/fb")
|
||||||
|
|
||||||
|
const factory = () =>
|
||||||
|
shallowMount(inputform, {
|
||||||
|
mocks: {
|
||||||
|
$t: (text) => text,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fb.writeFeeds.mockClear()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("inputform", () => {
|
||||||
|
test("mounts properly", () => {
|
||||||
|
const wrapper = factory()
|
||||||
|
|
||||||
|
expect(wrapper).toBeTruthy()
|
||||||
|
})
|
||||||
|
test("calls writeFeeds when submitted properly", async () => {
|
||||||
|
const wrapper = factory()
|
||||||
|
|
||||||
|
const addButton = wrapper.find("button")
|
||||||
|
const [messageInput, labelInput] = wrapper.findAll("input").wrappers
|
||||||
|
|
||||||
|
await messageInput.setValue("test message")
|
||||||
|
await labelInput.setValue("test label")
|
||||||
|
|
||||||
|
await addButton.trigger("click")
|
||||||
|
|
||||||
|
expect(fb.writeFeeds).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
test("doesn't call writeFeeds when submitted without a data", async () => {
|
||||||
|
const wrapper = factory()
|
||||||
|
|
||||||
|
const addButton = wrapper.find("button")
|
||||||
|
|
||||||
|
await addButton.trigger("click")
|
||||||
|
|
||||||
|
expect(fb.writeFeeds).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
test("doesn't call writeFeeds when message or label is null", async () => {
|
||||||
|
const wrapper = factory()
|
||||||
|
|
||||||
|
const addButton = wrapper.find("button")
|
||||||
|
const [messageInput, labelInput] = wrapper.findAll("input").wrappers
|
||||||
|
|
||||||
|
await messageInput.setValue(null)
|
||||||
|
await labelInput.setValue(null)
|
||||||
|
|
||||||
|
await addButton.trigger("click")
|
||||||
|
|
||||||
|
expect(fb.writeFeeds).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
test("doesn't call writeFeeds when message or label is empty", async () => {
|
||||||
|
const wrapper = factory()
|
||||||
|
|
||||||
|
const addButton = wrapper.find("button")
|
||||||
|
const [messageInput, labelInput] = wrapper.findAll("input").wrappers
|
||||||
|
|
||||||
|
await messageInput.setValue("")
|
||||||
|
await labelInput.setValue("")
|
||||||
|
|
||||||
|
await addButton.trigger("click")
|
||||||
|
|
||||||
|
expect(fb.writeFeeds).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
test("calls writeFeeds with correct values", async () => {
|
||||||
|
const wrapper = factory()
|
||||||
|
|
||||||
|
const addButton = wrapper.find("button")
|
||||||
|
const [messageInput, labelInput] = wrapper.findAll("input").wrappers
|
||||||
|
|
||||||
|
await messageInput.setValue("test message")
|
||||||
|
await labelInput.setValue("test label")
|
||||||
|
|
||||||
|
await addButton.trigger("click")
|
||||||
|
|
||||||
|
expect(fb.writeFeeds).toHaveBeenCalledWith("test message", "test label")
|
||||||
|
})
|
||||||
|
})
|
||||||
66
components/firebase/__tests__/logout.spec.js
Normal file
66
components/firebase/__tests__/logout.spec.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import logout from "../logout"
|
||||||
|
import { shallowMount, createLocalVue } from "@vue/test-utils"
|
||||||
|
|
||||||
|
jest.mock("~/helpers/fb", () => {
|
||||||
|
return {
|
||||||
|
__esModule: true,
|
||||||
|
fb: {
|
||||||
|
signOutUser: jest.fn(() => Promise.resolve()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const { fb } = require("~/helpers/fb")
|
||||||
|
|
||||||
|
const $toast = {
|
||||||
|
info: jest.fn(),
|
||||||
|
show: jest.fn(),
|
||||||
|
}
|
||||||
|
|
||||||
|
const localVue = createLocalVue()
|
||||||
|
|
||||||
|
localVue.directive("close-popover", {})
|
||||||
|
|
||||||
|
const factory = () =>
|
||||||
|
shallowMount(logout, {
|
||||||
|
mocks: {
|
||||||
|
$t: (text) => text,
|
||||||
|
$toast,
|
||||||
|
},
|
||||||
|
localVue,
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fb.signOutUser.mockClear()
|
||||||
|
$toast.info.mockClear()
|
||||||
|
$toast.show.mockClear()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("logout", () => {
|
||||||
|
test("mounts properly", () => {
|
||||||
|
const wrapper = factory()
|
||||||
|
|
||||||
|
expect(wrapper).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("clicking the logout button fires the logout firebase function", async () => {
|
||||||
|
const wrapper = factory()
|
||||||
|
|
||||||
|
const button = wrapper.find("button")
|
||||||
|
|
||||||
|
await button.trigger("click")
|
||||||
|
|
||||||
|
expect(fb.signOutUser).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("failed signout request fires a error toast", async () => {
|
||||||
|
fb.signOutUser.mockImplementationOnce(() => Promise.reject("test reject"))
|
||||||
|
|
||||||
|
const wrapper = factory()
|
||||||
|
const button = wrapper.find("button")
|
||||||
|
await button.trigger("click")
|
||||||
|
|
||||||
|
expect($toast.show).toHaveBeenCalledTimes(1)
|
||||||
|
expect($toast.show).toHaveBeenCalledWith("test reject", expect.anything())
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -5,9 +5,9 @@
|
|||||||
:key="feed.id"
|
:key="feed.id"
|
||||||
class="flex-col py-2 border-b border-dashed border-brdColor"
|
class="flex-col py-2 border-b border-dashed border-brdColor"
|
||||||
>
|
>
|
||||||
<div class="show-on-large-screen">
|
<div data-test="list-item" class="show-on-large-screen">
|
||||||
<li class="info">
|
<li class="info">
|
||||||
<label>
|
<label data-test="list-label">
|
||||||
{{ feed.label || $t("no_label") }}
|
{{ feed.label || $t("no_label") }}
|
||||||
</label>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="show-on-large-screen">
|
<div class="show-on-large-screen">
|
||||||
<li class="info clamb-3">
|
<li data-test="list-message" class="info clamb-3">
|
||||||
<label>{{ feed.message || $t("empty") }}</label>
|
<label>{{ feed.message || $t("empty") }}</label>
|
||||||
</li>
|
</li>
|
||||||
</div>
|
</div>
|
||||||
@@ -54,8 +54,8 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
deleteFeed(feed) {
|
async deleteFeed(feed) {
|
||||||
fb.deleteFeed(feed.id)
|
await fb.deleteFeed(feed.id)
|
||||||
this.$toast.error(this.$t("deleted"), {
|
this.$toast.error(this.$t("deleted"), {
|
||||||
icon: "delete",
|
icon: "delete",
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -39,205 +39,144 @@ export default {
|
|||||||
icon: "vpn_key",
|
icon: "vpn_key",
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
signInWithGoogle() {
|
async signInWithGoogle() {
|
||||||
const provider = new firebase.auth.GoogleAuthProvider()
|
try {
|
||||||
const self = this
|
const { additionUserInfo } = await fb.signInUserWithGoogle()
|
||||||
firebase
|
|
||||||
.auth()
|
if (additionalUserInfo.isNewUser) {
|
||||||
.signInWithPopup(provider)
|
this.$toast.info(`${this.$t("turn_on")} ${this.$t("sync")}`, {
|
||||||
.then(({ additionalUserInfo }) => {
|
icon: "sync",
|
||||||
if (additionalUserInfo.isNewUser) {
|
duration: null,
|
||||||
self.$toast.info(`${self.$t("turn_on")} ${self.$t("sync")}`, {
|
closeOnSwipe: false,
|
||||||
icon: "sync",
|
action: {
|
||||||
duration: null,
|
text: this.$t("yes"),
|
||||||
closeOnSwipe: false,
|
onClick: (e, toastObject) => {
|
||||||
action: {
|
fb.writeSettings("syncHistory", true)
|
||||||
text: self.$t("yes"),
|
fb.writeSettings("syncCollections", true)
|
||||||
onClick: (e, toastObject) => {
|
fb.writeSettings("syncEnvironments", true)
|
||||||
fb.writeSettings("syncHistory", true)
|
this.$router.push({ path: "/settings" })
|
||||||
fb.writeSettings("syncCollections", true)
|
toastObject.remove()
|
||||||
fb.writeSettings("syncEnvironments", true)
|
|
||||||
self.$router.push({ path: "/settings" })
|
|
||||||
toastObject.remove()
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})
|
},
|
||||||
}
|
})
|
||||||
self.showLoginSuccess()
|
}
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
// An error happened.
|
|
||||||
if (err.code === "auth/account-exists-with-different-credential") {
|
|
||||||
// Step 2.
|
|
||||||
// User's email already exists.
|
|
||||||
// The pending Google credential.
|
|
||||||
const pendingCred = err.credential
|
|
||||||
// The provider account's email address.
|
|
||||||
const email = err.email
|
|
||||||
// Get sign-in methods for this email.
|
|
||||||
firebase
|
|
||||||
.auth()
|
|
||||||
.fetchSignInMethodsForEmail(email)
|
|
||||||
.then((methods) => {
|
|
||||||
// Step 3.
|
|
||||||
// If the user has several sign-in methods,
|
|
||||||
// the first method in the list will be the "recommended" method to use.
|
|
||||||
if (methods[0] === "password") {
|
|
||||||
// Asks the user their password.
|
|
||||||
// In real scenario, you should handle this asynchronously.
|
|
||||||
const password = promptUserForPassword() // TODO: implement promptUserForPassword.
|
|
||||||
auth
|
|
||||||
.signInWithEmailAndPassword(email, password)
|
|
||||||
.then((
|
|
||||||
user // Step 4a.
|
|
||||||
) => user.linkWithCredential(pendingCred))
|
|
||||||
.then(() => {
|
|
||||||
// Google account successfully linked to the existing Firebase user.
|
|
||||||
self.showLoginSuccess()
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.$toast.info(`${self.$t("login_with")}`, {
|
this.showLoginSuccess()
|
||||||
icon: "vpn_key",
|
} catch (err) {
|
||||||
duration: null,
|
// An error happened.
|
||||||
closeOnSwipe: false,
|
if (err.code === "auth/account-exists-with-different-credential") {
|
||||||
action: {
|
// Step 2.
|
||||||
text: self.$t("yes"),
|
// User's email already exists.
|
||||||
onClick: (e, toastObject) => {
|
// The pending Google credential.
|
||||||
// All the other cases are external providers.
|
const pendingCred = err.credential
|
||||||
// Construct provider object for that provider.
|
// The provider account's email address.
|
||||||
// TODO: implement getProviderForProviderId.
|
const email = err.email
|
||||||
const provider = new firebase.auth.GithubAuthProvider()
|
// Get sign-in methods for this email.
|
||||||
// At this point, you should let the user know that they already has an account
|
const methods = await fb.getSignInMethodsForEmail(email)
|
||||||
// but with a different provider, and let them validate the fact they want to
|
|
||||||
// sign in with this provider.
|
|
||||||
// Sign in to provider. Note: browsers usually block popup triggered asynchronously,
|
|
||||||
// so in real scenario you should ask the user to click on a "continue" button
|
|
||||||
// that will trigger the signInWithPopup.
|
|
||||||
firebase
|
|
||||||
.auth()
|
|
||||||
.signInWithPopup(provider)
|
|
||||||
.then(({ user }) => {
|
|
||||||
// Remember that the user may have signed in with an account that has a different email
|
|
||||||
// address than the first one. This can happen as Firebase doesn't control the provider's
|
|
||||||
// sign in flow and the user is free to login using whichever account they own.
|
|
||||||
// Step 4b.
|
|
||||||
// Link to Google credential.
|
|
||||||
// As we have access to the pending credential, we can directly call the link method.
|
|
||||||
user.linkAndRetrieveDataWithCredential(pendingCred).then((usercred) => {
|
|
||||||
// Google account successfully linked to the existing Firebase user.
|
|
||||||
self.showLoginSuccess()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
toastObject.remove()
|
// Step 3.
|
||||||
},
|
// If the user has several sign-in methods,
|
||||||
},
|
// the first method in the list will be the "recommended" method to use.
|
||||||
})
|
if (methods[0] === "password") {
|
||||||
})
|
// Asks the user their password.
|
||||||
|
// In real scenario, you should handle this asynchronously.
|
||||||
|
const password = promptUserForPassword() // TODO: implement promptUserForPassword.
|
||||||
|
const user = await fb.signInWithEmailAndPassword(email, password)
|
||||||
|
|
||||||
|
await user.linkWithCredential(pendingCred)
|
||||||
|
this.showLoginSuccess()
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
this.$toast.info(`${this.$t("login_with")}`, {
|
||||||
|
icon: "vpn_key",
|
||||||
|
duration: null,
|
||||||
|
closeOnSwipe: false,
|
||||||
|
action: {
|
||||||
|
text: this.$t("yes"),
|
||||||
|
onClick: async (e, toastObject) => {
|
||||||
|
const user = await fb.signInWithGithub()
|
||||||
|
await user.linkAndRetrieveDataWithCredential(pendingCred)
|
||||||
|
|
||||||
|
this.showLoginSuccess()
|
||||||
|
|
||||||
|
toastObject.remove()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
signInWithGithub() {
|
async signInWithGithub() {
|
||||||
const provider = new firebase.auth.GithubAuthProvider()
|
try {
|
||||||
const self = this
|
const { additionalUserInfo } = await fb.signInUserWithGithub()
|
||||||
firebase
|
|
||||||
.auth()
|
if (additionalUserInfo.isNewUser) {
|
||||||
.signInWithPopup(provider)
|
this.$toast.info(`${this.$t("turn_on")} ${this.$t("sync")}`, {
|
||||||
.then(({ additionalUserInfo }) => {
|
icon: "sync",
|
||||||
if (additionalUserInfo.isNewUser) {
|
duration: null,
|
||||||
self.$toast.info(`${self.$t("turn_on")} ${self.$t("sync")}`, {
|
closeOnSwipe: false,
|
||||||
icon: "sync",
|
action: {
|
||||||
duration: null,
|
text: this.$t("yes"),
|
||||||
closeOnSwipe: false,
|
onClick: (e, toastObject) => {
|
||||||
action: {
|
fb.writeSettings("syncHistory", true)
|
||||||
text: self.$t("yes"),
|
fb.writeSettings("syncCollections", true)
|
||||||
onClick: (e, toastObject) => {
|
fb.writeSettings("syncEnvironments", true)
|
||||||
fb.writeSettings("syncHistory", true)
|
this.$router.push({ path: "/settings" })
|
||||||
fb.writeSettings("syncCollections", true)
|
toastObject.remove()
|
||||||
fb.writeSettings("syncEnvironments", true)
|
|
||||||
self.$router.push({ path: "/settings" })
|
|
||||||
toastObject.remove()
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})
|
},
|
||||||
}
|
})
|
||||||
self.showLoginSuccess()
|
}
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
// An error happened.
|
|
||||||
if (err.code === "auth/account-exists-with-different-credential") {
|
|
||||||
// Step 2.
|
|
||||||
// User's email already exists.
|
|
||||||
// The pending Google credential.
|
|
||||||
const pendingCred = err.credential
|
|
||||||
// The provider account's email address.
|
|
||||||
const email = err.email
|
|
||||||
// Get sign-in methods for this email.
|
|
||||||
firebase
|
|
||||||
.auth()
|
|
||||||
.fetchSignInMethodsForEmail(email)
|
|
||||||
.then((methods) => {
|
|
||||||
// Step 3.
|
|
||||||
// If the user has several sign-in methods,
|
|
||||||
// the first method in the list will be the "recommended" method to use.
|
|
||||||
if (methods[0] === "password") {
|
|
||||||
// Asks the user their password.
|
|
||||||
// In real scenario, you should handle this asynchronously.
|
|
||||||
const password = promptUserForPassword() // TODO: implement promptUserForPassword.
|
|
||||||
firebase
|
|
||||||
.auth()
|
|
||||||
.signInWithEmailAndPassword(email, password)
|
|
||||||
.then((
|
|
||||||
user // Step 4a.
|
|
||||||
) => user.linkWithCredential(pendingCred))
|
|
||||||
.then(() => {
|
|
||||||
// Google account successfully linked to the existing Firebase user.
|
|
||||||
self.showLoginSuccess()
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.$toast.info(`${self.$t("login_with")}`, {
|
this.showLoginSuccess()
|
||||||
icon: "vpn_key",
|
} catch (err) {
|
||||||
duration: null,
|
// An error happened.
|
||||||
closeOnSwipe: false,
|
if (err.code === "auth/account-exists-with-different-credential") {
|
||||||
action: {
|
// Step 2.
|
||||||
text: self.$t("yes"),
|
// User's email already exists.
|
||||||
onClick: (e, toastObject) => {
|
// The pending Google credential.
|
||||||
// All the other cases are external providers.
|
const pendingCred = err.credential
|
||||||
// Construct provider object for that provider.
|
// The provider account's email address.
|
||||||
// TODO: implement getProviderForProviderId.
|
const email = err.email
|
||||||
const provider = new firebase.auth.GoogleAuthProvider()
|
// Get sign-in methods for this email.
|
||||||
// At this point, you should let the user know that they already has an account
|
const methods = await fb.getSignInMethodsForEmail(email)
|
||||||
// but with a different provider, and let them validate the fact they want to
|
|
||||||
// sign in with this provider.
|
|
||||||
// Sign in to provider. Note: browsers usually block popup triggered asynchronously,
|
|
||||||
// so in real scenario you should ask the user to click on a "continue" button
|
|
||||||
// that will trigger the signInWithPopup.
|
|
||||||
firebase
|
|
||||||
.auth()
|
|
||||||
.signInWithPopup(provider)
|
|
||||||
.then(({ user }) => {
|
|
||||||
// Remember that the user may have signed in with an account that has a different email
|
|
||||||
// address than the first one. This can happen as Firebase doesn't control the provider's
|
|
||||||
// sign in flow and the user is free to login using whichever account they own.
|
|
||||||
// Step 4b.
|
|
||||||
// Link to Google credential.
|
|
||||||
// As we have access to the pending credential, we can directly call the link method.
|
|
||||||
user.linkAndRetrieveDataWithCredential(pendingCred).then((usercred) => {
|
|
||||||
self.showLoginSuccess()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
toastObject.remove()
|
// Step 3.
|
||||||
},
|
// If the user has several sign-in methods,
|
||||||
},
|
// the first method in the list will be the "recommended" method to use.
|
||||||
})
|
if (methods[0] === "password") {
|
||||||
})
|
// Asks the user their password.
|
||||||
|
// In real scenario, you should handle this asynchronously.
|
||||||
|
const password = promptUserForPassword() // TODO: implement promptUserForPassword.
|
||||||
|
|
||||||
|
const user = await fb.signInWithEmailAndPassword(email, password)
|
||||||
|
await user.linkWithCredential(pendingCred)
|
||||||
|
|
||||||
|
this.showLoginSuccess()
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
this.$toast.info(`${this.$t("login_with")}`, {
|
||||||
|
icon: "vpn_key",
|
||||||
|
duration: null,
|
||||||
|
closeOnSwipe: false,
|
||||||
|
action: {
|
||||||
|
text: this.$t("yes"),
|
||||||
|
onClick: async (e, toastObject) => {
|
||||||
|
const { user } = await fb.signInUserWithGoogle()
|
||||||
|
await user.linkAndRetrieveDataWithCredential(pendingCred)
|
||||||
|
|
||||||
|
this.showLoginSuccess()
|
||||||
|
|
||||||
|
toastObject.remove()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import firebase from "firebase/app"
|
|
||||||
import { fb } from "~/helpers/fb"
|
import { fb } from "~/helpers/fb"
|
||||||
import exitToAppIcon from "~/static/icons/exit_to_app-24px.svg?inline"
|
import exitToAppIcon from "~/static/icons/exit_to_app-24px.svg?inline"
|
||||||
|
|
||||||
@@ -20,20 +19,18 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
logout() {
|
async logout() {
|
||||||
fb.currentUser = null
|
try {
|
||||||
const self = this
|
await fb.signOutUser()
|
||||||
firebase
|
|
||||||
.auth()
|
this.$toast.info(this.$t("logged_out"), {
|
||||||
.signOut()
|
icon: "vpn_key",
|
||||||
.catch((err) => {
|
|
||||||
self.$toast.show(err.message || err, {
|
|
||||||
icon: "error",
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
self.$toast.info(this.$t("logged_out"), {
|
} catch (err) {
|
||||||
icon: "vpn_key",
|
this.$toast.show(err.message || err, {
|
||||||
})
|
icon: "error",
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,26 +2,13 @@
|
|||||||
<pw-section class="green" icon="history" :label="$t('history')" ref="history">
|
<pw-section class="green" icon="history" :label="$t('history')" ref="history">
|
||||||
<div class="show-on-large-screen">
|
<div class="show-on-large-screen">
|
||||||
<input aria-label="Search" type="search" :placeholder="$t('search')" v-model="filterText" />
|
<input aria-label="Search" type="search" :placeholder="$t('search')" v-model="filterText" />
|
||||||
<!-- <button class="icon">
|
<button class="icon">
|
||||||
<i class="material-icons">search</i>
|
<i class="material-icons">search</i>
|
||||||
</button> -->
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="virtual-list" :class="{ filled: filteredHistory.length }">
|
<div class="virtual-list" :class="{ filled: filteredHistory.length }">
|
||||||
<ul
|
<ul v-for="(entry, index) in filteredHistory" :key="index" class="entry">
|
||||||
v-for="(entry, index) in filteredHistory"
|
|
||||||
:key="index"
|
|
||||||
class="flex-col border-b border-dashed border-brdColor"
|
|
||||||
>
|
|
||||||
<div class="show-on-large-screen">
|
<div class="show-on-large-screen">
|
||||||
<button
|
|
||||||
class="icon"
|
|
||||||
:id="'use-button#' + index"
|
|
||||||
@click="useHistory(entry)"
|
|
||||||
:aria-label="$t('edit')"
|
|
||||||
v-tooltip="$t('restore')"
|
|
||||||
>
|
|
||||||
<i class="material-icons">restore</i>
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
class="icon"
|
class="icon"
|
||||||
:class="{ stared: entry.star }"
|
:class="{ stared: entry.star }"
|
||||||
@@ -34,46 +21,16 @@
|
|||||||
{{ entry.star ? "star" : "star_border" }}
|
{{ entry.star ? "star" : "star_border" }}
|
||||||
</i>
|
</i>
|
||||||
</button>
|
</button>
|
||||||
<li class="relative">
|
<li>
|
||||||
<input
|
|
||||||
:aria-label="$t('method')"
|
|
||||||
type="text"
|
|
||||||
readonly
|
|
||||||
:value="`${entry.method} ${entry.status}`"
|
|
||||||
:class="findEntryStatus(entry).className"
|
|
||||||
:style="{ '--status-code': entry.status }"
|
|
||||||
class="bg-transparent"
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
<v-popover>
|
|
||||||
<button class="tooltip-target icon" v-tooltip="$t('options')">
|
|
||||||
<i class="material-icons">more_vert</i>
|
|
||||||
</button>
|
|
||||||
<template slot="popover">
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
class="icon"
|
|
||||||
:id="'delete-button#' + index"
|
|
||||||
@click="deleteHistory(entry)"
|
|
||||||
:aria-label="$t('delete')"
|
|
||||||
v-close-popover
|
|
||||||
>
|
|
||||||
<deleteIcon class="material-icons" />
|
|
||||||
<span>{{ $t("delete") }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</v-popover>
|
|
||||||
<!-- <li class="relative">
|
|
||||||
<input
|
<input
|
||||||
:aria-label="$t('label')"
|
:aria-label="$t('label')"
|
||||||
type="text"
|
type="text"
|
||||||
readonly
|
readonly
|
||||||
:value="entry.label"
|
:value="entry.label"
|
||||||
:placeholder="$t('no_label')"
|
:placeholder="$t('no_label')"
|
||||||
class="bg-transparent"
|
class="bg-color"
|
||||||
/>
|
/>
|
||||||
</li> -->
|
</li>
|
||||||
<!-- <li>
|
<!-- <li>
|
||||||
<button
|
<button
|
||||||
class="icon"
|
class="icon"
|
||||||
@@ -88,21 +45,67 @@
|
|||||||
</i>
|
</i>
|
||||||
</button>
|
</button>
|
||||||
</li> -->
|
</li> -->
|
||||||
|
<v-popover>
|
||||||
|
<button class="tooltip-target icon" v-tooltip="$t('options')">
|
||||||
|
<i class="material-icons">more_vert</i>
|
||||||
|
</button>
|
||||||
|
<template slot="popover">
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
class="icon"
|
||||||
|
:id="'use-button#' + index"
|
||||||
|
@click="useHistory(entry)"
|
||||||
|
:aria-label="$t('edit')"
|
||||||
|
v-close-popover
|
||||||
|
>
|
||||||
|
<i class="material-icons">restore</i>
|
||||||
|
<span>{{ $t("restore") }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
class="icon"
|
||||||
|
:id="'delete-button#' + index"
|
||||||
|
@click="deleteHistory(entry)"
|
||||||
|
:aria-label="$t('delete')"
|
||||||
|
v-close-popover
|
||||||
|
>
|
||||||
|
<deleteIcon class="material-icons" />
|
||||||
|
<span>{{ $t("delete") }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</v-popover>
|
||||||
|
</div>
|
||||||
|
<div class="show-on-large-screen">
|
||||||
|
<li class="method-list-item">
|
||||||
|
<input
|
||||||
|
:aria-label="$t('method')"
|
||||||
|
type="text"
|
||||||
|
readonly
|
||||||
|
:value="entry.method"
|
||||||
|
:class="findEntryStatus(entry).className"
|
||||||
|
:style="{ '--status-code': entry.status }"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="entry-status-code"
|
||||||
|
:class="findEntryStatus(entry).className"
|
||||||
|
:style="{ '--status-code': entry.status }"
|
||||||
|
>{{ entry.status }}</span
|
||||||
|
>
|
||||||
|
</li>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="show-on-large-screen">
|
|
||||||
</div> -->
|
|
||||||
<div class="show-on-large-screen">
|
<div class="show-on-large-screen">
|
||||||
<li>
|
<li>
|
||||||
<input
|
<input
|
||||||
:aria-label="$t('url')"
|
:aria-label="$t('url')"
|
||||||
type="text"
|
type="text"
|
||||||
readonly
|
readonly
|
||||||
:value="`${entry.url}${entry.path}`"
|
:value="entry.url"
|
||||||
:placeholder="$t('no_url')"
|
:placeholder="$t('no_url')"
|
||||||
class="bg-transparent"
|
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
<!-- <li>
|
<li>
|
||||||
<input
|
<input
|
||||||
:aria-label="$t('path')"
|
:aria-label="$t('path')"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -110,7 +113,7 @@
|
|||||||
:value="entry.path"
|
:value="entry.path"
|
||||||
:placeholder="$t('no_path')"
|
:placeholder="$t('no_path')"
|
||||||
/>
|
/>
|
||||||
</li> -->
|
</li>
|
||||||
</div>
|
</div>
|
||||||
<transition name="fade">
|
<transition name="fade">
|
||||||
<div v-if="showMore" class="show-on-large-screen">
|
<div v-if="showMore" class="show-on-large-screen">
|
||||||
@@ -145,7 +148,7 @@
|
|||||||
</transition>
|
</transition>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<ul class="flex-col" :class="{ hidden: filteredHistory.length != 0 || history.length === 0 }">
|
<ul :class="{ hidden: filteredHistory.length != 0 || history.length === 0 }">
|
||||||
<li>
|
<li>
|
||||||
<label>{{ $t("nothing_found") }} "{{ filterText }}"</label>
|
<label>{{ $t("nothing_found") }} "{{ filterText }}"</label>
|
||||||
</li>
|
</li>
|
||||||
@@ -245,27 +248,50 @@
|
|||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.virtual-list {
|
.virtual-list {
|
||||||
max-height: calc(100vh - 288px);
|
max-height: calc(100vh - 290px);
|
||||||
|
|
||||||
[readonly] {
|
[readonly] {
|
||||||
@apply cursor-default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.fade-enter-active,
|
.fade-enter-active,
|
||||||
.fade-leave-active {
|
.fade-leave-active {
|
||||||
@apply transition;
|
transition: all 0.2s;
|
||||||
@apply ease-in-out;
|
|
||||||
@apply duration-200;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fade-enter,
|
.fade-enter,
|
||||||
.fade-leave-to {
|
.fade-leave-to {
|
||||||
@apply opacity-0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stared {
|
.stared {
|
||||||
@apply text-yellow-200;
|
color: #f8e81c !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul,
|
||||||
|
ol {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.method-list-item {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
span {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
font-family: "Roboto Mono", monospace;
|
||||||
|
font-weight: 400;
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry {
|
||||||
|
border-bottom: 1px dashed var(--brd-color);
|
||||||
|
padding: 0 0 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
@@ -274,7 +300,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.labels {
|
.labels {
|
||||||
@apply hidden;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -316,22 +342,20 @@ export default {
|
|||||||
fb.currentUser !== null
|
fb.currentUser !== null
|
||||||
? fb.currentHistory
|
? fb.currentHistory
|
||||||
: JSON.parse(window.localStorage.getItem("history")) || []
|
: JSON.parse(window.localStorage.getItem("history")) || []
|
||||||
return this.history
|
return this.history.filter((entry) => {
|
||||||
.filter((entry) => {
|
const filterText = this.filterText.toLowerCase()
|
||||||
const filterText = this.filterText.toLowerCase()
|
return Object.keys(entry).some((key) => {
|
||||||
return Object.keys(entry).some((key) => {
|
let value = entry[key]
|
||||||
let value = entry[key]
|
value = typeof value !== "string" ? value.toString() : value
|
||||||
value = typeof value !== "string" ? value.toString() : value
|
return value.toLowerCase().includes(filterText)
|
||||||
return value.toLowerCase().includes(filterText)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.reverse()
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
clearHistory() {
|
async clearHistory() {
|
||||||
if (fb.currentUser !== null) {
|
if (fb.currentUser !== null) {
|
||||||
fb.clearHistory()
|
await fb.clearHistory()
|
||||||
}
|
}
|
||||||
this.history = []
|
this.history = []
|
||||||
this.filterText = ""
|
this.filterText = ""
|
||||||
@@ -352,9 +376,9 @@ export default {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
deleteHistory(entry) {
|
async deleteHistory(entry) {
|
||||||
if (fb.currentUser !== null) {
|
if (fb.currentUser !== null) {
|
||||||
fb.deleteHistory(entry)
|
await fb.deleteHistory(entry)
|
||||||
}
|
}
|
||||||
this.history.splice(this.history.indexOf(entry), 1)
|
this.history.splice(this.history.indexOf(entry), 1)
|
||||||
if (this.history.length === 0) {
|
if (this.history.length === 0) {
|
||||||
@@ -440,9 +464,9 @@ export default {
|
|||||||
toggleCollapse() {
|
toggleCollapse() {
|
||||||
this.showMore = !this.showMore
|
this.showMore = !this.showMore
|
||||||
},
|
},
|
||||||
toggleStar(entry) {
|
async toggleStar(entry) {
|
||||||
if (fb.currentUser !== null) {
|
if (fb.currentUser !== null) {
|
||||||
fb.toggleStar(entry, !entry.star)
|
await fb.toggleStar(entry, !entry.star)
|
||||||
}
|
}
|
||||||
entry.star = !entry.star
|
entry.star = !entry.star
|
||||||
updateOnLocalStorage("history", this.history)
|
updateOnLocalStorage("history", this.history)
|
||||||
|
|||||||
1309
helpers/__tests__/fb.spec.js
Normal file
1309
helpers/__tests__/fb.spec.js
Normal file
File diff suppressed because it is too large
Load Diff
470
helpers/fb.js
470
helpers/fb.js
@@ -13,213 +13,313 @@ const firebaseConfig = {
|
|||||||
appId: process.env.APP_ID || "1:421993993223:web:ec0baa8ee8c02ffa1fc6a2",
|
appId: process.env.APP_ID || "1:421993993223:web:ec0baa8ee8c02ffa1fc6a2",
|
||||||
measurementId: process.env.MEASUREMENT_ID || "G-ERJ6025CEB",
|
measurementId: process.env.MEASUREMENT_ID || "G-ERJ6025CEB",
|
||||||
}
|
}
|
||||||
firebase.initializeApp(firebaseConfig)
|
|
||||||
|
|
||||||
// a reference to the users collection
|
export const authProviders = {
|
||||||
const usersCollection = firebase.firestore().collection("users")
|
google: () => new firebase.auth.GoogleAuthProvider(),
|
||||||
|
github: () => new firebase.auth.GithubAuthProvider(),
|
||||||
|
}
|
||||||
|
|
||||||
// the shared state object that any vue component
|
export class FirebaseInstance {
|
||||||
// can get access to
|
constructor(fbapp, authProviders) {
|
||||||
export const fb = {
|
this.app = fbapp
|
||||||
currentUser: null,
|
this.authProviders = authProviders
|
||||||
currentFeeds: [],
|
|
||||||
currentSettings: [],
|
this.usersCollection = this.app.firestore().collection("users")
|
||||||
currentHistory: [],
|
|
||||||
currentCollections: [],
|
this.currentUser = null
|
||||||
currentEnvironments: [],
|
this.currentFeeds = []
|
||||||
writeFeeds: async (message, label) => {
|
this.currentSettings = []
|
||||||
|
this.currentHistory = []
|
||||||
|
this.currentCollections = []
|
||||||
|
this.currentEnvironments = []
|
||||||
|
this.currentTeams = []
|
||||||
|
|
||||||
|
this.app.auth().onAuthStateChanged((user) => {
|
||||||
|
if (user) {
|
||||||
|
this.currentUser = user
|
||||||
|
this.currentUser.providerData.forEach((profile) => {
|
||||||
|
let us = {
|
||||||
|
updatedOn: new Date(),
|
||||||
|
provider: profile.providerId,
|
||||||
|
name: profile.displayName,
|
||||||
|
email: profile.email,
|
||||||
|
photoUrl: profile.photoURL,
|
||||||
|
uid: profile.uid,
|
||||||
|
}
|
||||||
|
this.usersCollection
|
||||||
|
.doc(this.currentUser.uid)
|
||||||
|
.set(us)
|
||||||
|
.catch((e) => console.error("error updating", us, e))
|
||||||
|
})
|
||||||
|
|
||||||
|
this.usersCollection
|
||||||
|
.doc(this.currentUser.uid)
|
||||||
|
.collection("feeds")
|
||||||
|
.orderBy("createdOn", "desc")
|
||||||
|
.onSnapshot((feedsRef) => {
|
||||||
|
const feeds = []
|
||||||
|
feedsRef.forEach((doc) => {
|
||||||
|
const feed = doc.data()
|
||||||
|
feed.id = doc.id
|
||||||
|
feeds.push(feed)
|
||||||
|
})
|
||||||
|
this.currentFeeds = feeds
|
||||||
|
})
|
||||||
|
|
||||||
|
this.usersCollection
|
||||||
|
.doc(this.currentUser.uid)
|
||||||
|
.collection("settings")
|
||||||
|
.onSnapshot((settingsRef) => {
|
||||||
|
const settings = []
|
||||||
|
settingsRef.forEach((doc) => {
|
||||||
|
const setting = doc.data()
|
||||||
|
setting.id = doc.id
|
||||||
|
settings.push(setting)
|
||||||
|
})
|
||||||
|
this.currentSettings = settings
|
||||||
|
})
|
||||||
|
|
||||||
|
this.usersCollection
|
||||||
|
.doc(this.currentUser.uid)
|
||||||
|
.collection("history")
|
||||||
|
.onSnapshot((historyRef) => {
|
||||||
|
const history = []
|
||||||
|
historyRef.forEach((doc) => {
|
||||||
|
const entry = doc.data()
|
||||||
|
entry.id = doc.id
|
||||||
|
history.push(entry)
|
||||||
|
})
|
||||||
|
this.currentHistory = history
|
||||||
|
})
|
||||||
|
|
||||||
|
this.usersCollection
|
||||||
|
.doc(this.currentUser.uid)
|
||||||
|
.collection("collections")
|
||||||
|
.onSnapshot((collectionsRef) => {
|
||||||
|
const collections = []
|
||||||
|
collectionsRef.forEach((doc) => {
|
||||||
|
const collection = doc.data()
|
||||||
|
collection.id = doc.id
|
||||||
|
collections.push(collection)
|
||||||
|
})
|
||||||
|
if (collections.length > 0) {
|
||||||
|
this.currentCollections = collections[0].collection
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.usersCollection
|
||||||
|
.doc(this.currentUser.uid)
|
||||||
|
.collection("environments")
|
||||||
|
.onSnapshot((environmentsRef) => {
|
||||||
|
const environments = []
|
||||||
|
environmentsRef.forEach((doc) => {
|
||||||
|
const environment = doc.data()
|
||||||
|
environment.id = doc.id
|
||||||
|
environments.push(environment)
|
||||||
|
})
|
||||||
|
if (environments.length > 0) {
|
||||||
|
this.currentEnvironments = environments[0].environment
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.usersCollection
|
||||||
|
.doc(this.currentUser.uid)
|
||||||
|
.collection("teams")
|
||||||
|
.onSnapshot((teamsRef) => {
|
||||||
|
const teams = []
|
||||||
|
teamsRef.forEach((doc) => {
|
||||||
|
const team = doc.data()
|
||||||
|
team.id = doc.id
|
||||||
|
teams.push(team)
|
||||||
|
})
|
||||||
|
this.currentTeams = teams[0].team
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.currentUser = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async signInUserWithGoogle() {
|
||||||
|
return await this.app.auth().signInWithPopup(this.authProviders.google())
|
||||||
|
}
|
||||||
|
|
||||||
|
async signInUserWithGithub() {
|
||||||
|
return await this.app.auth().signInWithPopup(this.authProviders.github())
|
||||||
|
}
|
||||||
|
|
||||||
|
async signInWithEmailAndPassword(email, password) {
|
||||||
|
return await this.app.auth().signInWithEmailAndPassword(email, password)
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSignInMethodsForEmail(email) {
|
||||||
|
return await this.app.auth().fetchSignInMethodsForEmail(email)
|
||||||
|
}
|
||||||
|
|
||||||
|
async signOutUser() {
|
||||||
|
if (!this.currentUser) throw new Error("No user has logged in")
|
||||||
|
|
||||||
|
await this.app.auth().signOut()
|
||||||
|
this.currentUser = null
|
||||||
|
}
|
||||||
|
|
||||||
|
async writeFeeds(message, label) {
|
||||||
const dt = {
|
const dt = {
|
||||||
createdOn: new Date(),
|
createdOn: new Date(),
|
||||||
author: fb.currentUser.uid,
|
author: this.currentUser.uid,
|
||||||
author_name: fb.currentUser.displayName,
|
author_name: this.currentUser.displayName,
|
||||||
author_image: fb.currentUser.photoURL,
|
author_image: this.currentUser.photoURL,
|
||||||
message,
|
message,
|
||||||
label,
|
label,
|
||||||
}
|
}
|
||||||
usersCollection
|
|
||||||
.doc(fb.currentUser.uid)
|
try {
|
||||||
.collection("feeds")
|
await this.usersCollection.doc(this.currentUser.uid).collection("feeds").add(dt)
|
||||||
.add(dt)
|
} catch (e) {
|
||||||
.catch((e) => console.error("error inserting", dt, e))
|
console.error("error inserting", dt, e)
|
||||||
},
|
throw e
|
||||||
deleteFeed: (id) => {
|
}
|
||||||
usersCollection
|
}
|
||||||
.doc(fb.currentUser.uid)
|
|
||||||
.collection("feeds")
|
async deleteFeed(id) {
|
||||||
.doc(id)
|
try {
|
||||||
.delete()
|
await this.usersCollection.doc(this.currentUser.uid).collection("feeds").doc(id).delete()
|
||||||
.catch((e) => console.error("error deleting", id, e))
|
} catch (e) {
|
||||||
},
|
console.error("error deleting", id, e)
|
||||||
writeSettings: async (setting, value) => {
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async writeSettings(setting, value) {
|
||||||
const st = {
|
const st = {
|
||||||
updatedOn: new Date(),
|
updatedOn: new Date(),
|
||||||
author: fb.currentUser.uid,
|
author: this.currentUser.uid,
|
||||||
author_name: fb.currentUser.displayName,
|
author_name: this.currentUser.displayName,
|
||||||
author_image: fb.currentUser.photoURL,
|
author_image: this.currentUser.photoURL,
|
||||||
name: setting,
|
name: setting,
|
||||||
value,
|
value,
|
||||||
}
|
}
|
||||||
usersCollection
|
|
||||||
.doc(fb.currentUser.uid)
|
try {
|
||||||
.collection("settings")
|
await this.usersCollection
|
||||||
.doc(setting)
|
.doc(this.currentUser.uid)
|
||||||
.set(st)
|
.collection("settings")
|
||||||
.catch((e) => console.error("error updating", st, e))
|
.doc(setting)
|
||||||
},
|
.set(st)
|
||||||
writeHistory: async (entry) => {
|
} catch (e) {
|
||||||
|
console.error("error updating", st, e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async writeHistory(entry) {
|
||||||
const hs = entry
|
const hs = entry
|
||||||
usersCollection
|
|
||||||
.doc(fb.currentUser.uid)
|
try {
|
||||||
.collection("history")
|
await this.usersCollection.doc(this.currentUser.uid).collection("history").add(hs)
|
||||||
.add(hs)
|
} catch (e) {
|
||||||
.catch((e) => console.error("error inserting", hs, e))
|
console.error("error inserting", hs, e)
|
||||||
},
|
throw e
|
||||||
deleteHistory: (entry) => {
|
}
|
||||||
usersCollection
|
}
|
||||||
.doc(fb.currentUser.uid)
|
|
||||||
.collection("history")
|
async deleteHistory(entry) {
|
||||||
.doc(entry.id)
|
try {
|
||||||
.delete()
|
await this.usersCollection
|
||||||
.catch((e) => console.error("error deleting", entry, e))
|
.doc(this.currentUser.uid)
|
||||||
},
|
.collection("history")
|
||||||
clearHistory: () => {
|
.doc(entry.id)
|
||||||
usersCollection
|
.delete()
|
||||||
.doc(fb.currentUser.uid)
|
} catch (e) {
|
||||||
|
console.error("error deleting", entry, e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearHistory() {
|
||||||
|
const { docs } = await this.usersCollection
|
||||||
|
.doc(this.currentUser.uid)
|
||||||
.collection("history")
|
.collection("history")
|
||||||
.get()
|
.get()
|
||||||
.then(({ docs }) => {
|
|
||||||
docs.forEach((e) => fb.deleteHistory(e))
|
await Promise.all(docs.map((e) => this.deleteHistory(e)))
|
||||||
})
|
}
|
||||||
},
|
|
||||||
toggleStar: (entry, value) => {
|
async toggleStar(entry, value) {
|
||||||
usersCollection
|
try {
|
||||||
.doc(fb.currentUser.uid)
|
await this.usersCollection
|
||||||
.collection("history")
|
.doc(this.currentUser.uid)
|
||||||
.doc(entry.id)
|
.collection("history")
|
||||||
.update({ star: value })
|
.doc(entry.id)
|
||||||
.catch((e) => console.error("error deleting", entry, e))
|
.update({ star: value })
|
||||||
},
|
} catch (e) {
|
||||||
writeCollections: async (collection) => {
|
console.error("error deleting", entry, e)
|
||||||
|
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async writeCollections(collection) {
|
||||||
const cl = {
|
const cl = {
|
||||||
updatedOn: new Date(),
|
updatedOn: new Date(),
|
||||||
author: fb.currentUser.uid,
|
author: this.currentUser.uid,
|
||||||
author_name: fb.currentUser.displayName,
|
author_name: this.currentUser.displayName,
|
||||||
author_image: fb.currentUser.photoURL,
|
author_image: this.currentUser.photoURL,
|
||||||
collection,
|
collection,
|
||||||
}
|
}
|
||||||
usersCollection
|
|
||||||
.doc(fb.currentUser.uid)
|
try {
|
||||||
.collection("collections")
|
await this.usersCollection
|
||||||
.doc("sync")
|
.doc(this.currentUser.uid)
|
||||||
.set(cl)
|
.collection("collections")
|
||||||
.catch((e) => console.error("error updating", cl, e))
|
.doc("sync")
|
||||||
},
|
.set(cl)
|
||||||
writeEnvironments: async (environment) => {
|
} catch (e) {
|
||||||
|
console.error("error updating", cl, e)
|
||||||
|
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async writeEnvironments(environment) {
|
||||||
const ev = {
|
const ev = {
|
||||||
updatedOn: new Date(),
|
updatedOn: new Date(),
|
||||||
author: fb.currentUser.uid,
|
author: this.currentUser.uid,
|
||||||
author_name: fb.currentUser.displayName,
|
author_name: this.currentUser.displayName,
|
||||||
author_image: fb.currentUser.photoURL,
|
author_image: this.currentUser.photoURL,
|
||||||
environment,
|
environment,
|
||||||
}
|
}
|
||||||
usersCollection
|
|
||||||
.doc(fb.currentUser.uid)
|
try {
|
||||||
.collection("environments")
|
await this.usersCollection
|
||||||
.doc("sync")
|
.doc(this.currentUser.uid)
|
||||||
.set(ev)
|
.collection("environments")
|
||||||
.catch((e) => console.error("error updating", ev, e))
|
.doc("sync")
|
||||||
},
|
.set(ev)
|
||||||
|
} catch (e) {
|
||||||
|
console.error("error updating", ev, e)
|
||||||
|
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async writeTeams(team) {
|
||||||
|
const ev = {
|
||||||
|
updatedOn: new Date(),
|
||||||
|
author: this.currentUser.uid,
|
||||||
|
author_name: this.currentUser.displayName,
|
||||||
|
author_image: this.currentUser.photoURL,
|
||||||
|
team: team,
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.usersCollection.doc(this.currentUser.uid).collection("teams").doc("sync").set(ev)
|
||||||
|
} catch (e) {
|
||||||
|
console.error("error updating", ev, e)
|
||||||
|
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// When a user logs in or out, save that in the store
|
export const fb = new FirebaseInstance(firebase.initializeApp(firebaseConfig), authProviders)
|
||||||
firebase.auth().onAuthStateChanged((user) => {
|
|
||||||
if (user) {
|
|
||||||
fb.currentUser = user
|
|
||||||
fb.currentUser.providerData.forEach((profile) => {
|
|
||||||
let us = {
|
|
||||||
updatedOn: new Date(),
|
|
||||||
provider: profile.providerId,
|
|
||||||
name: profile.displayName,
|
|
||||||
email: profile.email,
|
|
||||||
photoUrl: profile.photoURL,
|
|
||||||
uid: profile.uid,
|
|
||||||
}
|
|
||||||
usersCollection
|
|
||||||
.doc(fb.currentUser.uid)
|
|
||||||
.set(us)
|
|
||||||
.catch((e) => console.error("error updating", us, e))
|
|
||||||
})
|
|
||||||
|
|
||||||
usersCollection
|
|
||||||
.doc(fb.currentUser.uid)
|
|
||||||
.collection("feeds")
|
|
||||||
.orderBy("createdOn", "desc")
|
|
||||||
.onSnapshot((feedsRef) => {
|
|
||||||
const feeds = []
|
|
||||||
feedsRef.forEach((doc) => {
|
|
||||||
const feed = doc.data()
|
|
||||||
feed.id = doc.id
|
|
||||||
feeds.push(feed)
|
|
||||||
})
|
|
||||||
fb.currentFeeds = feeds
|
|
||||||
})
|
|
||||||
|
|
||||||
usersCollection
|
|
||||||
.doc(fb.currentUser.uid)
|
|
||||||
.collection("settings")
|
|
||||||
.onSnapshot((settingsRef) => {
|
|
||||||
const settings = []
|
|
||||||
settingsRef.forEach((doc) => {
|
|
||||||
const setting = doc.data()
|
|
||||||
setting.id = doc.id
|
|
||||||
settings.push(setting)
|
|
||||||
})
|
|
||||||
fb.currentSettings = settings
|
|
||||||
})
|
|
||||||
|
|
||||||
usersCollection
|
|
||||||
.doc(fb.currentUser.uid)
|
|
||||||
.collection("history")
|
|
||||||
.onSnapshot((historyRef) => {
|
|
||||||
const history = []
|
|
||||||
historyRef.forEach((doc) => {
|
|
||||||
const entry = doc.data()
|
|
||||||
entry.id = doc.id
|
|
||||||
history.push(entry)
|
|
||||||
})
|
|
||||||
fb.currentHistory = history
|
|
||||||
})
|
|
||||||
|
|
||||||
usersCollection
|
|
||||||
.doc(fb.currentUser.uid)
|
|
||||||
.collection("collections")
|
|
||||||
.onSnapshot((collectionsRef) => {
|
|
||||||
const collections = []
|
|
||||||
collectionsRef.forEach((doc) => {
|
|
||||||
const collection = doc.data()
|
|
||||||
collection.id = doc.id
|
|
||||||
collections.push(collection)
|
|
||||||
})
|
|
||||||
if (collections.length > 0) {
|
|
||||||
fb.currentCollections = collections[0].collection
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
usersCollection
|
|
||||||
.doc(fb.currentUser.uid)
|
|
||||||
.collection("environments")
|
|
||||||
.onSnapshot((environmentsRef) => {
|
|
||||||
const environments = []
|
|
||||||
environmentsRef.forEach((doc) => {
|
|
||||||
const environment = doc.data()
|
|
||||||
environment.id = doc.id
|
|
||||||
environments.push(environment)
|
|
||||||
})
|
|
||||||
if (environments.length > 0) {
|
|
||||||
fb.currentEnvironments = environments[0].environment
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
fb.currentUser = null
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|||||||
270
package-lock.json
generated
270
package-lock.json
generated
@@ -7964,6 +7964,66 @@
|
|||||||
"@firebase/util": "0.3.2"
|
"@firebase/util": "0.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"firebase-auto-ids": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/firebase-auto-ids/-/firebase-auto-ids-1.1.0.tgz",
|
||||||
|
"integrity": "sha1-N4/7hZCIjDfrLdi3BGWa0PSfkGE=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"firebase-mock": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/firebase-mock/-/firebase-mock-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-K5Bl46uCBbMBxK/qnSM5PvJXTy7cyUsPabfUysKEmXX26aZs4QpbLgwL+0qaNw+NOyIKSe3vXsi/Ktm5NJP35w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"firebase-auto-ids": "~1.1.0",
|
||||||
|
"lodash.assign": "^4.2.0",
|
||||||
|
"lodash.assignin": "^4.2.0",
|
||||||
|
"lodash.bind": "^4.2.1",
|
||||||
|
"lodash.clone": "^4.5.0",
|
||||||
|
"lodash.clonedeep": "^4.5.0",
|
||||||
|
"lodash.clonedeepwith": "^4.5.0",
|
||||||
|
"lodash.compact": "^3.0.1",
|
||||||
|
"lodash.difference": "^4.5.0",
|
||||||
|
"lodash.every": "^4.6.0",
|
||||||
|
"lodash.filter": "^4.6.0",
|
||||||
|
"lodash.find": "^4.6.0",
|
||||||
|
"lodash.findindex": "^4.6.0",
|
||||||
|
"lodash.foreach": "^4.5.0",
|
||||||
|
"lodash.forin": "^4.4.0",
|
||||||
|
"lodash.get": "^4.4.2",
|
||||||
|
"lodash.has": "^4.5.2",
|
||||||
|
"lodash.includes": "^4.3.0",
|
||||||
|
"lodash.indexof": "^4.0.5",
|
||||||
|
"lodash.isempty": "^4.4.0",
|
||||||
|
"lodash.isequal": "^4.5.0",
|
||||||
|
"lodash.isfunction": "^3.0.9",
|
||||||
|
"lodash.isnumber": "^3.0.3",
|
||||||
|
"lodash.isobject": "^3.0.2",
|
||||||
|
"lodash.isstring": "^4.0.1",
|
||||||
|
"lodash.isundefined": "^3.0.1",
|
||||||
|
"lodash.keys": "^4.2.0",
|
||||||
|
"lodash.map": "^4.6.0",
|
||||||
|
"lodash.merge": "^4.6.1",
|
||||||
|
"lodash.noop": "^3.0.1",
|
||||||
|
"lodash.orderby": "^4.6.0",
|
||||||
|
"lodash.reduce": "^4.6.0",
|
||||||
|
"lodash.remove": "^4.7.0",
|
||||||
|
"lodash.set": "^4.3.2",
|
||||||
|
"lodash.size": "^4.2.0",
|
||||||
|
"lodash.toarray": "^4.4.0",
|
||||||
|
"lodash.union": "^4.6.0",
|
||||||
|
"rsvp": "^3.6.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"rsvp": {
|
||||||
|
"version": "3.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.6.2.tgz",
|
||||||
|
"integrity": "sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"flat": {
|
"flat": {
|
||||||
"version": "5.0.2",
|
"version": "5.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
|
||||||
@@ -10624,21 +10684,225 @@
|
|||||||
"resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
|
||||||
"integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0="
|
"integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0="
|
||||||
},
|
},
|
||||||
|
"lodash.assign": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
|
||||||
|
"integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"lodash.assignin": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz",
|
||||||
|
"integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"lodash.bind": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz",
|
||||||
|
"integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"lodash.camelcase": {
|
"lodash.camelcase": {
|
||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
|
||||||
"integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY="
|
"integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY="
|
||||||
},
|
},
|
||||||
|
"lodash.clone": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz",
|
||||||
|
"integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"lodash.clonedeep": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
||||||
|
"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"lodash.clonedeepwith": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.clonedeepwith/-/lodash.clonedeepwith-4.5.0.tgz",
|
||||||
|
"integrity": "sha1-buMFc6A6GmDWcKYu8zwQzxr9vdQ=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"lodash.compact": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.compact/-/lodash.compact-3.0.1.tgz",
|
||||||
|
"integrity": "sha1-VAzjg3dFl1gHRx4WtKK6IeclbKU=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"lodash.difference": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz",
|
||||||
|
"integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"lodash.every": {
|
||||||
|
"version": "4.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.every/-/lodash.every-4.6.0.tgz",
|
||||||
|
"integrity": "sha1-64mYS+vENkJ5uzrvu9HKGb+mxqc=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"lodash.filter": {
|
||||||
|
"version": "4.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz",
|
||||||
|
"integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"lodash.find": {
|
||||||
|
"version": "4.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.find/-/lodash.find-4.6.0.tgz",
|
||||||
|
"integrity": "sha1-ywcE1Hq3F4n/oN6Ll92Sb7iLE7E=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"lodash.findindex": {
|
||||||
|
"version": "4.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.findindex/-/lodash.findindex-4.6.0.tgz",
|
||||||
|
"integrity": "sha1-oyRd7mH7m24GJLU1ElYku2nBEQY=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"lodash.foreach": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz",
|
||||||
|
"integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"lodash.forin": {
|
||||||
|
"version": "4.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.forin/-/lodash.forin-4.4.0.tgz",
|
||||||
|
"integrity": "sha1-XT8grlZAEfvog4H32YlJyclRlzE=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"lodash.get": {
|
||||||
|
"version": "4.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
|
||||||
|
"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"lodash.has": {
|
||||||
|
"version": "4.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz",
|
||||||
|
"integrity": "sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"lodash.includes": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||||
|
"integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"lodash.indexof": {
|
||||||
|
"version": "4.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.indexof/-/lodash.indexof-4.0.5.tgz",
|
||||||
|
"integrity": "sha1-U3FK3Czd1u2HY4+JOqm2wk4x7zw=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"lodash.isempty": {
|
||||||
|
"version": "4.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz",
|
||||||
|
"integrity": "sha1-b4bL7di+TsmHvpqvM8loTbGzHn4=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"lodash.isequal": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
||||||
|
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"lodash.isfunction": {
|
||||||
|
"version": "3.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz",
|
||||||
|
"integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"lodash.isnumber": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
|
||||||
|
"integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"lodash.isobject": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz",
|
||||||
|
"integrity": "sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"lodash.isstring": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
||||||
|
"integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"lodash.isundefined": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz",
|
||||||
|
"integrity": "sha1-I+89lTVWUgOmbO/VuDD4SJEa+0g=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"lodash.kebabcase": {
|
"lodash.kebabcase": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz",
|
||||||
"integrity": "sha1-hImxyw0p/4gZXM7KRI/21swpXDY="
|
"integrity": "sha1-hImxyw0p/4gZXM7KRI/21swpXDY="
|
||||||
},
|
},
|
||||||
|
"lodash.keys": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-4.2.0.tgz",
|
||||||
|
"integrity": "sha1-oIYCrBLk+4P5H8H7ejYKTZujUgU=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"lodash.map": {
|
||||||
|
"version": "4.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz",
|
||||||
|
"integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"lodash.memoize": {
|
"lodash.memoize": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||||
"integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4="
|
"integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4="
|
||||||
},
|
},
|
||||||
|
"lodash.merge": {
|
||||||
|
"version": "4.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||||
|
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"lodash.noop": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.noop/-/lodash.noop-3.0.1.tgz",
|
||||||
|
"integrity": "sha1-OBiPTWUKOkdCWEObluxFsyYXEzw=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"lodash.orderby": {
|
||||||
|
"version": "4.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.orderby/-/lodash.orderby-4.6.0.tgz",
|
||||||
|
"integrity": "sha1-5pfwTOXXhSL1TZM4syuBozk+TrM=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"lodash.reduce": {
|
||||||
|
"version": "4.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz",
|
||||||
|
"integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"lodash.remove": {
|
||||||
|
"version": "4.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.remove/-/lodash.remove-4.7.0.tgz",
|
||||||
|
"integrity": "sha1-8x0x58OaBpDVB07A02JxYjNO5iY=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"lodash.set": {
|
||||||
|
"version": "4.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz",
|
||||||
|
"integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"lodash.size": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.size/-/lodash.size-4.2.0.tgz",
|
||||||
|
"integrity": "sha1-cf517T6r2yvLc6GwtPUcOS7ie4Y=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"lodash.sortby": {
|
"lodash.sortby": {
|
||||||
"version": "4.7.0",
|
"version": "4.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
|
||||||
@@ -10668,6 +10932,12 @@
|
|||||||
"integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=",
|
"integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"lodash.union": {
|
||||||
|
"version": "4.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
|
||||||
|
"integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"lodash.unionby": {
|
"lodash.unionby": {
|
||||||
"version": "4.8.0",
|
"version": "4.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.unionby/-/lodash.unionby-4.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.unionby/-/lodash.unionby-4.8.0.tgz",
|
||||||
|
|||||||
@@ -61,6 +61,7 @@
|
|||||||
"babel-jest": "^26.5.2",
|
"babel-jest": "^26.5.2",
|
||||||
"eslint": "^7.11.0",
|
"eslint": "^7.11.0",
|
||||||
"eslint-plugin-vue": "^7.0.1",
|
"eslint-plugin-vue": "^7.0.1",
|
||||||
|
"firebase-mock": "^2.3.2",
|
||||||
"husky": "^4.3.0",
|
"husky": "^4.3.0",
|
||||||
"jest": "^26.5.3",
|
"jest": "^26.5.3",
|
||||||
"jest-serializer-vue": "^2.0.2",
|
"jest-serializer-vue": "^2.0.2",
|
||||||
@@ -78,6 +79,7 @@
|
|||||||
],
|
],
|
||||||
"watchman": false,
|
"watchman": false,
|
||||||
"moduleNameMapper": {
|
"moduleNameMapper": {
|
||||||
|
".+\\.(svg)\\?inline$": "<rootDir>/__mocks__/svgMock.js",
|
||||||
"^~/(.*)$": "<rootDir>/$1",
|
"^~/(.*)$": "<rootDir>/$1",
|
||||||
"^~~/(.*)$": "<rootDir>/$1"
|
"^~~/(.*)$": "<rootDir>/$1"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user