* 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 {
|
||||
@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"
|
||||
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">
|
||||
<label>
|
||||
<label data-test="list-label">
|
||||
{{ feed.label || $t("no_label") }}
|
||||
</label>
|
||||
</li>
|
||||
@@ -16,7 +16,7 @@
|
||||
</button>
|
||||
</div>
|
||||
<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>
|
||||
</li>
|
||||
</div>
|
||||
@@ -54,8 +54,8 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
deleteFeed(feed) {
|
||||
fb.deleteFeed(feed.id)
|
||||
async deleteFeed(feed) {
|
||||
await fb.deleteFeed(feed.id)
|
||||
this.$toast.error(this.$t("deleted"), {
|
||||
icon: "delete",
|
||||
})
|
||||
|
||||
@@ -39,205 +39,144 @@ export default {
|
||||
icon: "vpn_key",
|
||||
})
|
||||
},
|
||||
signInWithGoogle() {
|
||||
const provider = new firebase.auth.GoogleAuthProvider()
|
||||
const self = this
|
||||
firebase
|
||||
.auth()
|
||||
.signInWithPopup(provider)
|
||||
.then(({ additionalUserInfo }) => {
|
||||
if (additionalUserInfo.isNewUser) {
|
||||
self.$toast.info(`${self.$t("turn_on")} ${self.$t("sync")}`, {
|
||||
icon: "sync",
|
||||
duration: null,
|
||||
closeOnSwipe: false,
|
||||
action: {
|
||||
text: self.$t("yes"),
|
||||
onClick: (e, toastObject) => {
|
||||
fb.writeSettings("syncHistory", true)
|
||||
fb.writeSettings("syncCollections", true)
|
||||
fb.writeSettings("syncEnvironments", true)
|
||||
self.$router.push({ path: "/settings" })
|
||||
toastObject.remove()
|
||||
},
|
||||
async signInWithGoogle() {
|
||||
try {
|
||||
const { additionUserInfo } = await fb.signInUserWithGoogle()
|
||||
|
||||
if (additionalUserInfo.isNewUser) {
|
||||
this.$toast.info(`${this.$t("turn_on")} ${this.$t("sync")}`, {
|
||||
icon: "sync",
|
||||
duration: null,
|
||||
closeOnSwipe: false,
|
||||
action: {
|
||||
text: this.$t("yes"),
|
||||
onClick: (e, toastObject) => {
|
||||
fb.writeSettings("syncHistory", true)
|
||||
fb.writeSettings("syncCollections", true)
|
||||
fb.writeSettings("syncEnvironments", true)
|
||||
this.$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")}`, {
|
||||
icon: "vpn_key",
|
||||
duration: null,
|
||||
closeOnSwipe: false,
|
||||
action: {
|
||||
text: self.$t("yes"),
|
||||
onClick: (e, toastObject) => {
|
||||
// All the other cases are external providers.
|
||||
// Construct provider object for that provider.
|
||||
// TODO: implement getProviderForProviderId.
|
||||
const provider = new firebase.auth.GithubAuthProvider()
|
||||
// At this point, you should let the user know that they already has an account
|
||||
// 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()
|
||||
})
|
||||
})
|
||||
this.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.
|
||||
const methods = await fb.getSignInMethodsForEmail(email)
|
||||
|
||||
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() {
|
||||
const provider = new firebase.auth.GithubAuthProvider()
|
||||
const self = this
|
||||
firebase
|
||||
.auth()
|
||||
.signInWithPopup(provider)
|
||||
.then(({ additionalUserInfo }) => {
|
||||
if (additionalUserInfo.isNewUser) {
|
||||
self.$toast.info(`${self.$t("turn_on")} ${self.$t("sync")}`, {
|
||||
icon: "sync",
|
||||
duration: null,
|
||||
closeOnSwipe: false,
|
||||
action: {
|
||||
text: self.$t("yes"),
|
||||
onClick: (e, toastObject) => {
|
||||
fb.writeSettings("syncHistory", true)
|
||||
fb.writeSettings("syncCollections", true)
|
||||
fb.writeSettings("syncEnvironments", true)
|
||||
self.$router.push({ path: "/settings" })
|
||||
toastObject.remove()
|
||||
},
|
||||
async signInWithGithub() {
|
||||
try {
|
||||
const { additionalUserInfo } = await fb.signInUserWithGithub()
|
||||
|
||||
if (additionalUserInfo.isNewUser) {
|
||||
this.$toast.info(`${this.$t("turn_on")} ${this.$t("sync")}`, {
|
||||
icon: "sync",
|
||||
duration: null,
|
||||
closeOnSwipe: false,
|
||||
action: {
|
||||
text: this.$t("yes"),
|
||||
onClick: (e, toastObject) => {
|
||||
fb.writeSettings("syncHistory", true)
|
||||
fb.writeSettings("syncCollections", true)
|
||||
fb.writeSettings("syncEnvironments", true)
|
||||
this.$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")}`, {
|
||||
icon: "vpn_key",
|
||||
duration: null,
|
||||
closeOnSwipe: false,
|
||||
action: {
|
||||
text: self.$t("yes"),
|
||||
onClick: (e, toastObject) => {
|
||||
// All the other cases are external providers.
|
||||
// Construct provider object for that provider.
|
||||
// TODO: implement getProviderForProviderId.
|
||||
const provider = new firebase.auth.GoogleAuthProvider()
|
||||
// At this point, you should let the user know that they already has an account
|
||||
// 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()
|
||||
})
|
||||
})
|
||||
this.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.
|
||||
const methods = await fb.getSignInMethodsForEmail(email)
|
||||
|
||||
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>
|
||||
|
||||
<script>
|
||||
import firebase from "firebase/app"
|
||||
import { fb } from "~/helpers/fb"
|
||||
import exitToAppIcon from "~/static/icons/exit_to_app-24px.svg?inline"
|
||||
|
||||
@@ -20,20 +19,18 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
logout() {
|
||||
fb.currentUser = null
|
||||
const self = this
|
||||
firebase
|
||||
.auth()
|
||||
.signOut()
|
||||
.catch((err) => {
|
||||
self.$toast.show(err.message || err, {
|
||||
icon: "error",
|
||||
})
|
||||
async logout() {
|
||||
try {
|
||||
await fb.signOutUser()
|
||||
|
||||
this.$toast.info(this.$t("logged_out"), {
|
||||
icon: "vpn_key",
|
||||
})
|
||||
self.$toast.info(this.$t("logged_out"), {
|
||||
icon: "vpn_key",
|
||||
})
|
||||
} catch (err) {
|
||||
this.$toast.show(err.message || err, {
|
||||
icon: "error",
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2,26 +2,13 @@
|
||||
<pw-section class="green" icon="history" :label="$t('history')" ref="history">
|
||||
<div class="show-on-large-screen">
|
||||
<input aria-label="Search" type="search" :placeholder="$t('search')" v-model="filterText" />
|
||||
<!-- <button class="icon">
|
||||
<button class="icon">
|
||||
<i class="material-icons">search</i>
|
||||
</button> -->
|
||||
</button>
|
||||
</div>
|
||||
<div class="virtual-list" :class="{ filled: filteredHistory.length }">
|
||||
<ul
|
||||
v-for="(entry, index) in filteredHistory"
|
||||
:key="index"
|
||||
class="flex-col border-b border-dashed border-brdColor"
|
||||
>
|
||||
<ul v-for="(entry, index) in filteredHistory" :key="index" class="entry">
|
||||
<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
|
||||
class="icon"
|
||||
:class="{ stared: entry.star }"
|
||||
@@ -34,46 +21,16 @@
|
||||
{{ entry.star ? "star" : "star_border" }}
|
||||
</i>
|
||||
</button>
|
||||
<li class="relative">
|
||||
<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">
|
||||
<li>
|
||||
<input
|
||||
:aria-label="$t('label')"
|
||||
type="text"
|
||||
readonly
|
||||
:value="entry.label"
|
||||
:placeholder="$t('no_label')"
|
||||
class="bg-transparent"
|
||||
class="bg-color"
|
||||
/>
|
||||
</li> -->
|
||||
</li>
|
||||
<!-- <li>
|
||||
<button
|
||||
class="icon"
|
||||
@@ -88,21 +45,67 @@
|
||||
</i>
|
||||
</button>
|
||||
</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 class="show-on-large-screen">
|
||||
</div> -->
|
||||
<div class="show-on-large-screen">
|
||||
<li>
|
||||
<input
|
||||
:aria-label="$t('url')"
|
||||
type="text"
|
||||
readonly
|
||||
:value="`${entry.url}${entry.path}`"
|
||||
:value="entry.url"
|
||||
:placeholder="$t('no_url')"
|
||||
class="bg-transparent"
|
||||
/>
|
||||
</li>
|
||||
<!-- <li>
|
||||
<li>
|
||||
<input
|
||||
:aria-label="$t('path')"
|
||||
type="text"
|
||||
@@ -110,7 +113,7 @@
|
||||
:value="entry.path"
|
||||
:placeholder="$t('no_path')"
|
||||
/>
|
||||
</li> -->
|
||||
</li>
|
||||
</div>
|
||||
<transition name="fade">
|
||||
<div v-if="showMore" class="show-on-large-screen">
|
||||
@@ -145,7 +148,7 @@
|
||||
</transition>
|
||||
</ul>
|
||||
</div>
|
||||
<ul class="flex-col" :class="{ hidden: filteredHistory.length != 0 || history.length === 0 }">
|
||||
<ul :class="{ hidden: filteredHistory.length != 0 || history.length === 0 }">
|
||||
<li>
|
||||
<label>{{ $t("nothing_found") }} "{{ filterText }}"</label>
|
||||
</li>
|
||||
@@ -245,27 +248,50 @@
|
||||
|
||||
<style scoped lang="scss">
|
||||
.virtual-list {
|
||||
max-height: calc(100vh - 288px);
|
||||
max-height: calc(100vh - 290px);
|
||||
|
||||
[readonly] {
|
||||
@apply cursor-default;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
@apply transition;
|
||||
@apply ease-in-out;
|
||||
@apply duration-200;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.fade-enter,
|
||||
.fade-leave-to {
|
||||
@apply opacity-0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.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) {
|
||||
@@ -274,7 +300,7 @@
|
||||
}
|
||||
|
||||
.labels {
|
||||
@apply hidden;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -316,22 +342,20 @@ export default {
|
||||
fb.currentUser !== null
|
||||
? fb.currentHistory
|
||||
: JSON.parse(window.localStorage.getItem("history")) || []
|
||||
return this.history
|
||||
.filter((entry) => {
|
||||
const filterText = this.filterText.toLowerCase()
|
||||
return Object.keys(entry).some((key) => {
|
||||
let value = entry[key]
|
||||
value = typeof value !== "string" ? value.toString() : value
|
||||
return value.toLowerCase().includes(filterText)
|
||||
})
|
||||
return this.history.filter((entry) => {
|
||||
const filterText = this.filterText.toLowerCase()
|
||||
return Object.keys(entry).some((key) => {
|
||||
let value = entry[key]
|
||||
value = typeof value !== "string" ? value.toString() : value
|
||||
return value.toLowerCase().includes(filterText)
|
||||
})
|
||||
.reverse()
|
||||
})
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clearHistory() {
|
||||
async clearHistory() {
|
||||
if (fb.currentUser !== null) {
|
||||
fb.clearHistory()
|
||||
await fb.clearHistory()
|
||||
}
|
||||
this.history = []
|
||||
this.filterText = ""
|
||||
@@ -352,9 +376,9 @@ export default {
|
||||
}
|
||||
)
|
||||
},
|
||||
deleteHistory(entry) {
|
||||
async deleteHistory(entry) {
|
||||
if (fb.currentUser !== null) {
|
||||
fb.deleteHistory(entry)
|
||||
await fb.deleteHistory(entry)
|
||||
}
|
||||
this.history.splice(this.history.indexOf(entry), 1)
|
||||
if (this.history.length === 0) {
|
||||
@@ -440,9 +464,9 @@ export default {
|
||||
toggleCollapse() {
|
||||
this.showMore = !this.showMore
|
||||
},
|
||||
toggleStar(entry) {
|
||||
async toggleStar(entry) {
|
||||
if (fb.currentUser !== null) {
|
||||
fb.toggleStar(entry, !entry.star)
|
||||
await fb.toggleStar(entry, !entry.star)
|
||||
}
|
||||
entry.star = !entry.star
|
||||
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",
|
||||
measurementId: process.env.MEASUREMENT_ID || "G-ERJ6025CEB",
|
||||
}
|
||||
firebase.initializeApp(firebaseConfig)
|
||||
|
||||
// a reference to the users collection
|
||||
const usersCollection = firebase.firestore().collection("users")
|
||||
export const authProviders = {
|
||||
google: () => new firebase.auth.GoogleAuthProvider(),
|
||||
github: () => new firebase.auth.GithubAuthProvider(),
|
||||
}
|
||||
|
||||
// the shared state object that any vue component
|
||||
// can get access to
|
||||
export const fb = {
|
||||
currentUser: null,
|
||||
currentFeeds: [],
|
||||
currentSettings: [],
|
||||
currentHistory: [],
|
||||
currentCollections: [],
|
||||
currentEnvironments: [],
|
||||
writeFeeds: async (message, label) => {
|
||||
export class FirebaseInstance {
|
||||
constructor(fbapp, authProviders) {
|
||||
this.app = fbapp
|
||||
this.authProviders = authProviders
|
||||
|
||||
this.usersCollection = this.app.firestore().collection("users")
|
||||
|
||||
this.currentUser = null
|
||||
this.currentFeeds = []
|
||||
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 = {
|
||||
createdOn: new Date(),
|
||||
author: fb.currentUser.uid,
|
||||
author_name: fb.currentUser.displayName,
|
||||
author_image: fb.currentUser.photoURL,
|
||||
author: this.currentUser.uid,
|
||||
author_name: this.currentUser.displayName,
|
||||
author_image: this.currentUser.photoURL,
|
||||
message,
|
||||
label,
|
||||
}
|
||||
usersCollection
|
||||
.doc(fb.currentUser.uid)
|
||||
.collection("feeds")
|
||||
.add(dt)
|
||||
.catch((e) => console.error("error inserting", dt, e))
|
||||
},
|
||||
deleteFeed: (id) => {
|
||||
usersCollection
|
||||
.doc(fb.currentUser.uid)
|
||||
.collection("feeds")
|
||||
.doc(id)
|
||||
.delete()
|
||||
.catch((e) => console.error("error deleting", id, e))
|
||||
},
|
||||
writeSettings: async (setting, value) => {
|
||||
|
||||
try {
|
||||
await this.usersCollection.doc(this.currentUser.uid).collection("feeds").add(dt)
|
||||
} catch (e) {
|
||||
console.error("error inserting", dt, e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
async deleteFeed(id) {
|
||||
try {
|
||||
await this.usersCollection.doc(this.currentUser.uid).collection("feeds").doc(id).delete()
|
||||
} catch (e) {
|
||||
console.error("error deleting", id, e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
async writeSettings(setting, value) {
|
||||
const st = {
|
||||
updatedOn: new Date(),
|
||||
author: fb.currentUser.uid,
|
||||
author_name: fb.currentUser.displayName,
|
||||
author_image: fb.currentUser.photoURL,
|
||||
author: this.currentUser.uid,
|
||||
author_name: this.currentUser.displayName,
|
||||
author_image: this.currentUser.photoURL,
|
||||
name: setting,
|
||||
value,
|
||||
}
|
||||
usersCollection
|
||||
.doc(fb.currentUser.uid)
|
||||
.collection("settings")
|
||||
.doc(setting)
|
||||
.set(st)
|
||||
.catch((e) => console.error("error updating", st, e))
|
||||
},
|
||||
writeHistory: async (entry) => {
|
||||
|
||||
try {
|
||||
await this.usersCollection
|
||||
.doc(this.currentUser.uid)
|
||||
.collection("settings")
|
||||
.doc(setting)
|
||||
.set(st)
|
||||
} catch (e) {
|
||||
console.error("error updating", st, e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
async writeHistory(entry) {
|
||||
const hs = entry
|
||||
usersCollection
|
||||
.doc(fb.currentUser.uid)
|
||||
.collection("history")
|
||||
.add(hs)
|
||||
.catch((e) => console.error("error inserting", hs, e))
|
||||
},
|
||||
deleteHistory: (entry) => {
|
||||
usersCollection
|
||||
.doc(fb.currentUser.uid)
|
||||
.collection("history")
|
||||
.doc(entry.id)
|
||||
.delete()
|
||||
.catch((e) => console.error("error deleting", entry, e))
|
||||
},
|
||||
clearHistory: () => {
|
||||
usersCollection
|
||||
.doc(fb.currentUser.uid)
|
||||
|
||||
try {
|
||||
await this.usersCollection.doc(this.currentUser.uid).collection("history").add(hs)
|
||||
} catch (e) {
|
||||
console.error("error inserting", hs, e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
async deleteHistory(entry) {
|
||||
try {
|
||||
await this.usersCollection
|
||||
.doc(this.currentUser.uid)
|
||||
.collection("history")
|
||||
.doc(entry.id)
|
||||
.delete()
|
||||
} catch (e) {
|
||||
console.error("error deleting", entry, e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
async clearHistory() {
|
||||
const { docs } = await this.usersCollection
|
||||
.doc(this.currentUser.uid)
|
||||
.collection("history")
|
||||
.get()
|
||||
.then(({ docs }) => {
|
||||
docs.forEach((e) => fb.deleteHistory(e))
|
||||
})
|
||||
},
|
||||
toggleStar: (entry, value) => {
|
||||
usersCollection
|
||||
.doc(fb.currentUser.uid)
|
||||
.collection("history")
|
||||
.doc(entry.id)
|
||||
.update({ star: value })
|
||||
.catch((e) => console.error("error deleting", entry, e))
|
||||
},
|
||||
writeCollections: async (collection) => {
|
||||
|
||||
await Promise.all(docs.map((e) => this.deleteHistory(e)))
|
||||
}
|
||||
|
||||
async toggleStar(entry, value) {
|
||||
try {
|
||||
await this.usersCollection
|
||||
.doc(this.currentUser.uid)
|
||||
.collection("history")
|
||||
.doc(entry.id)
|
||||
.update({ star: value })
|
||||
} catch (e) {
|
||||
console.error("error deleting", entry, e)
|
||||
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
async writeCollections(collection) {
|
||||
const cl = {
|
||||
updatedOn: new Date(),
|
||||
author: fb.currentUser.uid,
|
||||
author_name: fb.currentUser.displayName,
|
||||
author_image: fb.currentUser.photoURL,
|
||||
author: this.currentUser.uid,
|
||||
author_name: this.currentUser.displayName,
|
||||
author_image: this.currentUser.photoURL,
|
||||
collection,
|
||||
}
|
||||
usersCollection
|
||||
.doc(fb.currentUser.uid)
|
||||
.collection("collections")
|
||||
.doc("sync")
|
||||
.set(cl)
|
||||
.catch((e) => console.error("error updating", cl, e))
|
||||
},
|
||||
writeEnvironments: async (environment) => {
|
||||
|
||||
try {
|
||||
await this.usersCollection
|
||||
.doc(this.currentUser.uid)
|
||||
.collection("collections")
|
||||
.doc("sync")
|
||||
.set(cl)
|
||||
} catch (e) {
|
||||
console.error("error updating", cl, e)
|
||||
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
async writeEnvironments(environment) {
|
||||
const ev = {
|
||||
updatedOn: new Date(),
|
||||
author: fb.currentUser.uid,
|
||||
author_name: fb.currentUser.displayName,
|
||||
author_image: fb.currentUser.photoURL,
|
||||
author: this.currentUser.uid,
|
||||
author_name: this.currentUser.displayName,
|
||||
author_image: this.currentUser.photoURL,
|
||||
environment,
|
||||
}
|
||||
usersCollection
|
||||
.doc(fb.currentUser.uid)
|
||||
.collection("environments")
|
||||
.doc("sync")
|
||||
.set(ev)
|
||||
.catch((e) => console.error("error updating", ev, e))
|
||||
},
|
||||
|
||||
try {
|
||||
await this.usersCollection
|
||||
.doc(this.currentUser.uid)
|
||||
.collection("environments")
|
||||
.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
|
||||
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
|
||||
}
|
||||
})
|
||||
export const fb = new FirebaseInstance(firebase.initializeApp(firebaseConfig), authProviders)
|
||||
|
||||
270
package-lock.json
generated
270
package-lock.json
generated
@@ -7964,6 +7964,66 @@
|
||||
"@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": {
|
||||
"version": "5.0.2",
|
||||
"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",
|
||||
"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": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
|
||||
"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": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz",
|
||||
"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": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||
"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": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
|
||||
@@ -10668,6 +10932,12 @@
|
||||
"integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=",
|
||||
"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": {
|
||||
"version": "4.8.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.unionby/-/lodash.unionby-4.8.0.tgz",
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
"babel-jest": "^26.5.2",
|
||||
"eslint": "^7.11.0",
|
||||
"eslint-plugin-vue": "^7.0.1",
|
||||
"firebase-mock": "^2.3.2",
|
||||
"husky": "^4.3.0",
|
||||
"jest": "^26.5.3",
|
||||
"jest-serializer-vue": "^2.0.2",
|
||||
@@ -78,6 +79,7 @@
|
||||
],
|
||||
"watchman": false,
|
||||
"moduleNameMapper": {
|
||||
".+\\.(svg)\\?inline$": "<rootDir>/__mocks__/svgMock.js",
|
||||
"^~/(.*)$": "<rootDir>/$1",
|
||||
"^~~/(.*)$": "<rootDir>/$1"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user