Merge pull request #1053 from Human-Connection/refine-social-media

🍰 Refine social media
This commit is contained in:
Wolfgang Huß 2019-07-29 16:08:08 +02:00 committed by GitHub
commit a542b33f79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 535 additions and 225 deletions

View File

@ -163,6 +163,7 @@ const permissions = shield(
DeletePost: isAuthor,
report: isAuthenticated,
CreateSocialMedia: isAuthenticated,
UpdateSocialMedia: isAuthenticated,
DeleteSocialMedia: isAuthenticated,
// AddBadgeRewarded: isAdmin,
// RemoveBadgeRewarded: isAdmin,

View File

@ -3,9 +3,6 @@ import { neo4jgraphql } from 'neo4j-graphql-js'
export default {
Mutation: {
CreateSocialMedia: async (object, params, context, resolveInfo) => {
/**
* TODO?: Creates double Nodes!
*/
const socialMedia = await neo4jgraphql(object, params, context, resolveInfo, false)
const session = context.driver.session()
await session.run(
@ -26,5 +23,21 @@ export default {
return socialMedia
},
UpdateSocialMedia: async (object, params, context, resolveInfo) => {
const session = context.driver.session()
await session.run(
`MATCH (owner: User { id: $userId })-[:OWNED]->(socialMedia: SocialMedia { id: $socialMediaId })
SET socialMedia.url = $socialMediaUrl
RETURN owner`,
{
userId: context.user.id,
socialMediaId: params.id,
socialMediaUrl: params.url,
},
)
session.close()
return params
},
},
}

View File

@ -5,9 +5,26 @@ import { host, login, gql } from '../../jest/helpers'
const factory = Factory()
describe('SocialMedia', () => {
let client
let headers
const mutationC = gql`
let client, headers, variables, mutation
const ownerParams = {
email: 'owner@example.com',
password: '1234',
id: '1234',
name: 'Pippi Langstrumpf',
}
const userParams = {
email: 'someuser@example.com',
password: 'abcd',
id: 'abcd',
name: 'Kalle Blomqvist',
}
const url = 'https://twitter.com/pippi-langstrumpf'
const newUrl = 'https://twitter.com/bullerby'
const createSocialMediaMutation = gql`
mutation($url: String!) {
CreateSocialMedia(url: $url) {
id
@ -15,7 +32,15 @@ describe('SocialMedia', () => {
}
}
`
const mutationD = gql`
const updateSocialMediaMutation = gql`
mutation($id: ID!, $url: String!) {
UpdateSocialMedia(id: $id, url: $url) {
id
url
}
}
`
const deleteSocialMediaMutation = gql`
mutation($id: ID!) {
DeleteSocialMedia(id: $id) {
id
@ -24,92 +49,139 @@ describe('SocialMedia', () => {
}
`
beforeEach(async () => {
await factory.create('User', {
avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/jimmuirhead/128.jpg',
id: 'acb2d923-f3af-479e-9f00-61b12e864666',
name: 'Matilde Hermiston',
slug: 'matilde-hermiston',
role: 'user',
email: 'test@example.org',
password: '1234',
})
await factory.create('User', userParams)
await factory.create('User', ownerParams)
})
afterEach(async () => {
await factory.cleanDatabase()
})
describe('unauthenticated', () => {
it('throws authorization error', async () => {
client = new GraphQLClient(host)
const variables = {
url: 'http://nsosp.org',
}
await expect(client.request(mutationC, variables)).rejects.toThrow('Not Authorised')
describe('create social media', () => {
beforeEach(() => {
variables = { url }
mutation = createSocialMediaMutation
})
describe('unauthenticated', () => {
it('throws authorization error', async () => {
client = new GraphQLClient(host)
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
})
})
describe('authenticated', () => {
beforeEach(async () => {
headers = await login(userParams)
client = new GraphQLClient(host, { headers })
})
it('creates social media with correct URL', async () => {
await expect(client.request(mutation, variables)).resolves.toEqual(
expect.objectContaining({
CreateSocialMedia: {
id: expect.any(String),
url: url,
},
}),
)
})
it('rejects empty string', async () => {
variables = { url: '' }
await expect(client.request(mutation, variables)).rejects.toThrow(
'"url" is not allowed to be empty',
)
})
it('rejects invalid URLs', async () => {
variables = { url: 'not-a-url' }
await expect(client.request(createSocialMediaMutation, variables)).rejects.toThrow(
'"url" must be a valid uri',
)
})
})
})
describe('authenticated', () => {
describe('update social media', () => {
beforeEach(async () => {
headers = await login({
email: 'test@example.org',
password: '1234',
})
client = new GraphQLClient(host, {
headers,
})
})
headers = await login(ownerParams)
client = new GraphQLClient(host, { headers })
it('creates social media with correct URL', async () => {
const variables = {
url: 'http://nsosp.org',
}
await expect(client.request(mutationC, variables)).resolves.toEqual(
expect.objectContaining({
CreateSocialMedia: {
id: expect.any(String),
url: 'http://nsosp.org',
},
}),
)
})
it('deletes social media', async () => {
const creationVariables = {
url: 'http://nsosp.org',
}
const { CreateSocialMedia } = await client.request(mutationC, creationVariables)
const { CreateSocialMedia } = await client.request(createSocialMediaMutation, { url })
const { id } = CreateSocialMedia
const deletionVariables = {
id,
}
const expected = {
DeleteSocialMedia: {
id: id,
url: 'http://nsosp.org',
},
}
await expect(client.request(mutationD, deletionVariables)).resolves.toEqual(expected)
variables = { url: newUrl, id }
mutation = updateSocialMediaMutation
})
it('rejects empty string', async () => {
const variables = {
url: '',
}
await expect(client.request(mutationC, variables)).rejects.toThrow(
'"url" is not allowed to be empty',
)
describe('unauthenticated', () => {
it('throws authorization error', async () => {
client = new GraphQLClient(host)
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
})
})
it('validates URLs', async () => {
const variables = {
url: 'not-a-url',
}
describe('authenticated as other user', () => {
// TODO: make sure it throws an authorization error
})
await expect(client.request(mutationC, variables)).rejects.toThrow(
'"url" must be a valid uri',
)
describe('authenticated as owner', () => {
it('updates social media', async () => {
const expected = { UpdateSocialMedia: { ...variables } }
await expect(client.request(mutation, variables)).resolves.toEqual(
expect.objectContaining(expected),
)
})
describe('given a non-existent id', () => {
// TODO: make sure it throws an error
})
})
})
describe('delete social media', () => {
beforeEach(async () => {
headers = await login(ownerParams)
client = new GraphQLClient(host, { headers })
const { CreateSocialMedia } = await client.request(createSocialMediaMutation, { url })
const { id } = CreateSocialMedia
variables = { id }
mutation = deleteSocialMediaMutation
})
describe('unauthenticated', () => {
it('throws authorization error', async () => {
client = new GraphQLClient(host)
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
})
})
describe('authenticated as other user', () => {
// TODO: make sure it throws an authorization error
})
describe('authenticated as owner', () => {
beforeEach(async () => {
headers = await login(ownerParams)
client = new GraphQLClient(host, { headers })
})
it('deletes social media', async () => {
const expected = {
DeleteSocialMedia: {
id: variables.id,
url: url,
},
}
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
})
})
})
})

View File

@ -79,7 +79,7 @@ Then('I should be on the {string} page', page => {
})
When('I add a social media link', () => {
cy.get("input[name='social-media']")
cy.get('input#addSocialMedia')
.type('https://freeradical.zone/peter-pan')
.get('button')
.contains('Add link')
@ -98,7 +98,7 @@ Then('the new social media link shows up on the page', () => {
Given('I have added a social media link', () => {
cy.openPage('/settings/my-social-media')
.get("input[name='social-media']")
.get('input#addSocialMedia')
.type('https://freeradical.zone/peter-pan')
.get('button')
.contains('Add link')
@ -121,3 +121,34 @@ Then('it gets deleted successfully', () => {
cy.get('.iziToast-message')
.should('contain', 'Deleted social media')
})
When('I start editing a social media link', () => {
cy.get("a[name='edit']")
.click()
})
Then('I can cancel editing', () => {
cy.get('button#cancel')
.click()
.get('input#editSocialMedia')
.should('have.length', 0)
})
When('I edit and save the link', () => {
cy.get('input#editSocialMedia')
.clear()
.type('https://freeradical.zone/tinkerbell')
.get('button')
.contains('Save')
.click()
})
Then('the new url is displayed', () => {
cy.get("a[href='https://freeradical.zone/tinkerbell']")
.should('have.length', 1)
})
Then('the old url is not displayed', () => {
cy.get("a[href='https://freeradical.zone/peter-pan']")
.should('have.length', 0)
})

View File

@ -15,7 +15,7 @@ Feature: List Social Media Accounts
Then it gets saved successfully
And the new social media link shows up on the page
Scenario: Other user's viewing my Social Media
Scenario: Other users viewing my Social Media
Given I have added a social media link
When people visit my profile page
Then they should be able to see my social media links
@ -27,3 +27,16 @@ Feature: List Social Media Accounts
Given I have added a social media link
When I delete a social media link
Then it gets deleted successfully
Scenario: Editing Social Media
Given I am on the "settings" page
And I click on the "Social media" link
Then I should be on the "/settings/my-social-media" page
Given I have added a social media link
When I start editing a social media link
Then I can cancel editing
When I start editing a social media link
And I edit and save the link
Then it gets saved successfully
And the new url is displayed
But the old url is not displayed

View File

@ -168,7 +168,8 @@
},
"social-media": {
"name": "Soziale Medien",
"placeholder": "Füge eine Social-Media URL hinzu",
"placeholder": "Deine Social-Media URL",
"requireUnique": "Dieser Link existiert bereits",
"submit": "Link hinzufügen",
"successAdd": "Social-Media hinzugefügt. Profil aktualisiert!",
"successDelete": "Social-Media gelöscht. Profil aktualisiert!"
@ -286,6 +287,7 @@
"reportContent": "Melden",
"validations": {
"email": "muss eine gültige E-Mail Adresse sein",
"url": "muss eine gültige URL sein",
"verification-code": "muss genau 6 Buchstaben lang sein"
}
},

View File

@ -169,7 +169,8 @@
},
"social-media": {
"name": "Social media",
"placeholder": "Add social media url",
"placeholder": "Your social media url",
"requireUnique": "You added this url already",
"submit": "Add link",
"successAdd": "Added social media. Updated user profile!",
"successDelete": "Deleted social media. Updated user profile!"
@ -288,6 +289,7 @@
"reportContent": "Report",
"validations": {
"email": "must be a valid email address",
"url": "must be a valid URL",
"verification-code": "must be 6 characters long"
}
},

View File

@ -101,6 +101,7 @@
"eslint-plugin-promise": "~4.2.1",
"eslint-plugin-standard": "~4.0.0",
"eslint-plugin-vue": "~5.2.3",
"flush-promises": "^1.0.2",
"fuse.js": "^3.4.5",
"jest": "~24.8.0",
"node-sass": "~4.12.0",

View File

@ -1,4 +1,5 @@
import { mount, createLocalVue } from '@vue/test-utils'
import flushPromises from 'flush-promises'
import MySocialMedia from './my-social-media.vue'
import Vuex from 'vuex'
import Styleguide from '@human-connection/styleguide'
@ -12,23 +13,17 @@ localVue.use(Filters)
describe('my-social-media.vue', () => {
let wrapper
let store
let mocks
let getters
let input
let submitBtn
const socialMediaUrl = 'https://freeradical.zone/@mattwr18'
const newSocialMediaUrl = 'https://twitter.com/mattwr18'
const faviconUrl = 'https://freeradical.zone/favicon.ico'
beforeEach(() => {
mocks = {
$t: jest.fn(),
$apollo: {
mutate: jest
.fn()
.mockRejectedValue({ message: 'Ouch!' })
.mockResolvedValueOnce({
data: { CreateSocialMeda: { id: 's1', url: socialMediaUrl } },
}),
mutate: jest.fn(),
},
$toast: {
error: jest.fn(),
@ -43,79 +38,161 @@ describe('my-social-media.vue', () => {
})
describe('mount', () => {
let form, input, submitButton
const Wrapper = () => {
store = new Vuex.Store({
const store = new Vuex.Store({
getters,
})
return mount(MySocialMedia, { store, mocks, localVue })
}
it('renders', () => {
wrapper = Wrapper()
expect(wrapper.contains('div')).toBe(true)
})
describe('given currentUser has a social media account linked', () => {
describe('adding social media link', () => {
beforeEach(() => {
getters = {
'auth/user': () => {
return {
socialMedia: [{ id: 's1', url: socialMediaUrl }],
}
},
}
})
it("displays a link to the currentUser's social media", () => {
wrapper = Wrapper()
const socialMediaLink = wrapper.find('a').attributes().href
expect(socialMediaLink).toBe(socialMediaUrl)
form = wrapper.find('form')
input = wrapper.find('input#addSocialMedia')
submitButton = wrapper.find('button')
})
beforeEach(() => {
mocks = {
$t: jest.fn(),
$apollo: {
mutate: jest
.fn()
.mockRejectedValue({ message: 'Ouch!' })
.mockResolvedValueOnce({
data: { DeleteSocialMeda: { id: 's1', url: socialMediaUrl } },
}),
},
$toast: {
error: jest.fn(),
success: jest.fn(),
},
}
getters = {
'auth/user': () => {
return {
socialMedia: [{ id: 's1', url: socialMediaUrl }],
}
},
}
it('requires the link to be a valid url', () => {
input.setValue('some value')
form.trigger('submit')
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
})
it('displays a trash sympol after a social media and allows the user to delete it', () => {
wrapper = Wrapper()
const deleteSelector = wrapper.find({ name: 'delete' })
expect(deleteSelector).toEqual({ selector: 'Component' })
const icon = wrapper.find({ name: 'trash' })
icon.trigger('click')
expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1)
it('displays an error message when not saved successfully', async () => {
mocks.$apollo.mutate.mockRejectedValue({ message: 'Ouch!' })
input.setValue(newSocialMediaUrl)
form.trigger('submit')
await flushPromises()
expect(mocks.$toast.error).toHaveBeenCalledTimes(1)
})
describe('success', () => {
beforeEach(() => {
mocks.$apollo.mutate.mockResolvedValue({
data: { CreateSocialMedia: { id: 's2', url: newSocialMediaUrl } },
})
input.setValue(newSocialMediaUrl)
form.trigger('submit')
})
it('sends the new url to the backend', () => {
const expected = expect.objectContaining({
variables: { url: newSocialMediaUrl },
})
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected)
})
it('displays a success message', async () => {
await flushPromises()
expect(mocks.$toast.success).toHaveBeenCalledTimes(1)
})
it('clears the form', async () => {
await flushPromises()
expect(input.value).toBe(undefined)
expect(submitButton.vm.$attrs.disabled).toBe(true)
})
})
})
describe('currentUser does not have a social media account linked', () => {
it('allows a user to add a social media link', () => {
describe('given existing social media links', () => {
beforeEach(() => {
getters = {
'auth/user': () => ({
socialMedia: [{ id: 's1', url: socialMediaUrl }],
}),
}
wrapper = Wrapper()
input = wrapper.find({ name: 'social-media' })
input.element.value = socialMediaUrl
input.trigger('input')
submitBtn = wrapper.find('.ds-button')
submitBtn.trigger('click')
expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1)
form = wrapper.find('form')
})
describe('for each link it', () => {
it('displays the favicon', () => {
expect(wrapper.find(`img[src="${faviconUrl}"]`).exists()).toBe(true)
})
it('displays the url', () => {
expect(wrapper.find(`a[href="${socialMediaUrl}"]`).exists()).toBe(true)
})
it('displays the edit button', () => {
expect(wrapper.find('a[name="edit"]').exists()).toBe(true)
})
it('displays the delete button', () => {
expect(wrapper.find('a[name="delete"]').exists()).toBe(true)
})
})
it('does not accept a duplicate url', () => {
input = wrapper.find('input#addSocialMedia')
input.setValue(socialMediaUrl)
form.trigger('submit')
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
})
describe('editing social media link', () => {
beforeEach(() => {
const editButton = wrapper.find('a[name="edit"]')
editButton.trigger('click')
input = wrapper.find('input#editSocialMedia')
})
it('disables adding new links while editing', () => {
const addInput = wrapper.find('input#addSocialMedia')
expect(addInput.exists()).toBe(false)
})
it('sends the new url to the backend', () => {
const expected = expect.objectContaining({
variables: { id: 's1', url: newSocialMediaUrl },
})
input.setValue(newSocialMediaUrl)
form.trigger('submit')
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected)
})
it('allows the user to cancel editing', () => {
const cancelButton = wrapper.find('button#cancel')
cancelButton.trigger('click')
expect(wrapper.find('input#editSocialMedia').exists()).toBe(false)
})
})
describe('deleting social media link', () => {
beforeEach(() => {
const deleteButton = wrapper.find('a[name="delete"]')
deleteButton.trigger('click')
})
it('sends the link id to the backend', () => {
const expected = expect.objectContaining({
variables: { id: 's1' },
})
expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1)
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected)
})
it('displays a success message', async () => {
await flushPromises()
expect(mocks.$toast.success).toHaveBeenCalledTimes(1)
})
})
})
})

View File

@ -1,49 +1,90 @@
<template>
<ds-card :header="$t('settings.social-media.name')">
<ds-space v-if="socialMediaLinks" margin-top="base" margin="x-small">
<ds-list>
<ds-list-item v-for="link in socialMediaLinks" :key="link.id">
<a :href="link.url" target="_blank">
<img :src="link.favicon | proxyApiUrl" alt="Social Media link" width="16" height="16" />
{{ link.url }}
</a>
&nbsp;&nbsp;
<span class="layout-leave-active">|</span>
&nbsp;&nbsp;
<ds-icon name="edit" class="layout-leave-active" />
<a name="delete" @click="handleDeleteSocialMedia(link)">
<ds-icon name="trash" />
</a>
</ds-list-item>
</ds-list>
</ds-space>
<ds-space margin-top="base">
<div>
<ds-input
v-model="value"
:placeholder="$t('settings.social-media.placeholder')"
name="social-media"
:schema="{ type: 'url' }"
/>
</div>
<ds-space margin-top="base">
<div>
<ds-button primary @click="handleAddSocialMedia">
{{ $t('settings.social-media.submit') }}
</ds-button>
</div>
<ds-form
v-model="formData"
:schema="formSchema"
@input="handleInput"
@input-valid="handleInputValid"
@submit="handleSubmitSocialMedia"
>
<ds-card :header="$t('settings.social-media.name')">
<ds-space v-if="socialMediaLinks" margin-top="base" margin="x-small">
<ds-list>
<ds-list-item v-for="link in socialMediaLinks" :key="link.id" class="list-item--high">
<ds-input
v-if="editingLink.id === link.id"
id="editSocialMedia"
model="socialMediaUrl"
type="text"
:placeholder="$t('settings.social-media.placeholder')"
/>
<template v-else>
<a :href="link.url" target="_blank">
<img :src="link.favicon" alt="Link:" height="16" width="16" />
{{ link.url }}
</a>
<span class="divider">|</span>
<a name="edit" @click="handleEditSocialMedia(link)">
<ds-icon
:aria-label="$t('actions.edit')"
class="icon-button"
name="edit"
:title="$t('actions.edit')"
/>
</a>
<a name="delete" @click="handleDeleteSocialMedia(link)">
<ds-icon
:aria-label="$t('actions.delete')"
class="icon-button"
name="trash"
:title="$t('actions.delete')"
/>
</a>
</template>
</ds-list-item>
</ds-list>
</ds-space>
</ds-space>
</ds-card>
<ds-space margin-top="base">
<ds-input
v-if="!editingLink.id"
id="addSocialMedia"
model="socialMediaUrl"
type="text"
:placeholder="$t('settings.social-media.placeholder')"
/>
<ds-space margin-top="base">
<ds-button primary :disabled="disabled">
{{ editingLink.id ? $t('actions.save') : $t('settings.social-media.submit') }}
</ds-button>
<ds-button v-if="editingLink.id" id="cancel" ghost @click="handleCancel()">
{{ $t('actions.cancel') }}
</ds-button>
</ds-space>
</ds-space>
</ds-card>
</ds-form>
</template>
<script>
import unionBy from 'lodash/unionBy'
import gql from 'graphql-tag'
import { mapGetters, mapMutations } from 'vuex'
export default {
data() {
return {
value: '',
formData: {
socialMediaUrl: '',
},
formSchema: {
socialMediaUrl: {
type: 'url',
message: this.$t('common.validations.url'),
},
},
disabled: true,
editingLink: {},
}
},
computed: {
@ -51,11 +92,10 @@ export default {
currentUser: 'auth/user',
}),
socialMediaLinks() {
const domainRegex = /^(?:https?:\/\/)?(?:[^@\n])?(?:www\.)?([^:/\n?]+)/g
const { socialMedia = [] } = this.currentUser
return socialMedia.map(socialMedia => {
const { id, url } = socialMedia
const matches = url.match(/^(?:https?:\/\/)?(?:[^@\n])?(?:www\.)?([^:/\n?]+)/g)
const [domain] = matches || []
return socialMedia.map(({ id, url }) => {
const [domain] = url.match(domainRegex) || []
const favicon = domain ? `${domain}/favicon.ico` : null
return { id, url, favicon }
})
@ -65,39 +105,28 @@ export default {
...mapMutations({
setCurrentUser: 'auth/SET_USER',
}),
handleAddSocialMedia() {
this.$apollo
.mutate({
mutation: gql`
mutation($url: String!) {
CreateSocialMedia(url: $url) {
id
url
}
}
`,
variables: {
url: this.value,
},
update: (store, { data }) => {
const socialMedia = [...this.currentUser.socialMedia, data.CreateSocialMedia]
this.setCurrentUser({
...this.currentUser,
socialMedia,
})
},
})
.then(() => {
this.$toast.success(this.$t('settings.social-media.successAdd'))
this.value = ''
})
.catch(error => {
this.$toast.error(error.message)
})
handleCancel() {
this.editingLink = {}
this.formData.socialMediaUrl = ''
this.disabled = true
},
handleDeleteSocialMedia(link) {
this.$apollo
.mutate({
handleEditSocialMedia(link) {
this.editingLink = link
this.formData.socialMediaUrl = link.url
},
handleInput(data) {
this.disabled = true
},
handleInputValid(data) {
if (data.socialMediaUrl.length < 1) {
this.disabled = true
} else {
this.disabled = false
}
},
async handleDeleteSocialMedia(link) {
try {
await this.$apollo.mutate({
mutation: gql`
mutation($id: ID!) {
DeleteSocialMedia(id: $id) {
@ -119,19 +148,83 @@ export default {
})
},
})
.then(() => {
this.$toast.success(this.$t('settings.social-media.successDelete'))
})
.catch(error => {
this.$toast.error(error.message)
this.$toast.success(this.$t('settings.social-media.successDelete'))
} catch (err) {
this.$toast.error(err.message)
}
},
async handleSubmitSocialMedia() {
const isEditing = !!this.editingLink.id
const url = this.formData.socialMediaUrl
const duplicateUrl = this.socialMediaLinks.find(link => link.url === url)
if (duplicateUrl && duplicateUrl.id !== this.editingLink.id) {
return this.$toast.error(this.$t('settings.social-media.requireUnique'))
}
let mutation = gql`
mutation($url: String!) {
CreateSocialMedia(url: $url) {
id
url
}
}
`
const variables = { url }
let successMessage = this.$t('settings.social-media.successAdd')
if (isEditing) {
mutation = gql`
mutation($id: ID!, $url: String!) {
UpdateSocialMedia(id: $id, url: $url) {
id
url
}
}
`
variables.id = this.editingLink.id
successMessage = this.$t('settings.data.success')
}
try {
await this.$apollo.mutate({
mutation,
variables,
update: (store, { data }) => {
const newSocialMedia = isEditing ? data.UpdateSocialMedia : data.CreateSocialMedia
this.setCurrentUser({
...this.currentUser,
socialMedia: unionBy([newSocialMedia], this.currentUser.socialMedia, 'id'),
})
},
})
this.$toast.success(successMessage)
this.formData.socialMediaUrl = ''
this.disabled = true
this.editingLink = {}
} catch (err) {
this.$toast.error(err.message)
}
},
},
}
</script>
<style lang="scss">
.layout-leave-active {
.divider {
opacity: 0.4;
padding: 0 $space-small;
}
.icon-button {
cursor: pointer;
}
.list-item--high {
.ds-list-item-prefix {
align-self: center;
}
}
</style>

View File

@ -4911,6 +4911,11 @@ flatten@^1.0.2:
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782"
integrity sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=
flush-promises@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/flush-promises/-/flush-promises-1.0.2.tgz#4948fd58f15281fed79cbafc86293d5bb09b2ced"
integrity sha512-G0sYfLQERwKz4+4iOZYQEZVpOt9zQrlItIxQAAYAWpfby3gbHrx0osCHz5RLl/XoXevXk0xoN4hDFky/VV9TrA==
flush-write-stream@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8"