diff --git a/cypress/support/step_definitions/Post.Comment/I_should_see_my_comment.js b/cypress/support/step_definitions/Post.Comment/I_should_see_my_comment.js
index 707a7397f..332379dcc 100644
--- a/cypress/support/step_definitions/Post.Comment/I_should_see_my_comment.js
+++ b/cypress/support/step_definitions/Post.Comment/I_should_see_my_comment.js
@@ -8,6 +8,6 @@ defineStep('I should see my comment', () => {
.get('.profile-avatar img')
.should('have.attr', 'src')
.and('contain', 'https://') // some url
- .get('.user-teaser > .info > .text')
+ .get('.user-teaser .info > .text')
.should('contain', 'today at')
})
diff --git a/webapp/assets/_new/styles/tokens.scss b/webapp/assets/_new/styles/tokens.scss
index 578484355..42c01d3a8 100644
--- a/webapp/assets/_new/styles/tokens.scss
+++ b/webapp/assets/_new/styles/tokens.scss
@@ -150,7 +150,7 @@ $font-size-xx-large: 2rem;
$font-size-x-large: 1.5rem;
$font-size-large: 1.25rem;
$font-size-base: 1rem;
-$font-size-body: 15px;
+$font-size-body: 0.938rem;
$font-size-small: 0.8rem;
$font-size-x-small: 0.7rem;
$font-size-xx-small: 0.6rem;
@@ -359,37 +359,37 @@ $media-query-medium: (min-width: 768px);
$media-query-large: (min-width: 1024px);
$media-query-x-large: (min-width: 1200px);
-/**
+/**
* @tokens Background Images
*/
-/**
+/**
* @tokens Header Color
*/
$color-header-background: $color-neutral-100;
-/**
+/**
* @tokens Footer Color
*/
$color-footer-background: $color-neutral-100;
$color-footer-link: $color-primary;
-/**
+/**
* @tokens Locale Menu Color
*/
$color-locale-menu: $text-color-soft;
-/**
+/**
* @tokens Donation Bar Color
*/
$color-donation-bar: $color-primary;
$color-donation-bar-light: $color-primary-light;
-/**
+/**
* @tokens Toast Color
*/
diff --git a/webapp/assets/styles/main.scss b/webapp/assets/styles/main.scss
index b726758c7..4fba0b5e0 100644
--- a/webapp/assets/styles/main.scss
+++ b/webapp/assets/styles/main.scss
@@ -52,11 +52,6 @@ $easeOut: cubic-bezier(0.19, 1, 0.22, 1);
background: #fff;
}
-body.dropdown-open {
- height: 100vh;
- overflow: hidden;
-}
-
blockquote {
display: block;
padding: 15px 20px 15px 45px;
@@ -140,6 +135,10 @@ hr {
opacity: 1;
transition-delay: 0;
transition: opacity 80ms ease-out;
+
+ @media(hover: none) {
+ pointer-events: all;
+ }
}
}
@@ -155,7 +154,6 @@ hr {
[class$='menu-popover'] {
min-width: 130px;
- a,
button {
display: flex;
align-content: center;
@@ -179,4 +177,4 @@ hr {
.dropdown-arrow {
font-size: $font-size-xx-small;
-}
\ No newline at end of file
+}
diff --git a/webapp/components/Dropdown.vue b/webapp/components/Dropdown.vue
index 7e4d21223..dd2b4a822 100644
--- a/webapp/components/Dropdown.vue
+++ b/webapp/components/Dropdown.vue
@@ -43,32 +43,12 @@ export default {
},
watch: {
isPopoverOpen: {
- immediate: true,
handler(isOpen) {
- try {
- if (isOpen) {
- this.$nextTick(() => {
- setTimeout(() => {
- const paddingRightStyle = `${
- window.innerWidth - document.documentElement.clientWidth
- }px`
- const navigationElement = document.querySelector('.main-navigation')
- document.body.style.paddingRight = paddingRightStyle
- document.body.classList.add('dropdown-open')
- if (navigationElement) {
- navigationElement.style.paddingRight = paddingRightStyle
- }
- }, 20)
- })
- } else {
- const navigationElement = document.querySelector('.main-navigation')
- document.body.style.paddingRight = null
- document.body.classList.remove('dropdown-open')
- if (navigationElement) {
- navigationElement.style.paddingRight = null
- }
- }
- } catch (err) {}
+ if (isOpen) {
+ document.body.classList.add('dropdown-open')
+ } else {
+ document.body.classList.remove('dropdown-open')
+ }
},
},
},
diff --git a/webapp/components/Notification/Notification.vue b/webapp/components/Notification/Notification.vue
index a657b10ba..d83995b9b 100644
--- a/webapp/components/Notification/Notification.vue
+++ b/webapp/components/Notification/Notification.vue
@@ -4,6 +4,7 @@
{{ $t(`notifications.reason.${notification.reason}`) }}
diff --git a/webapp/components/NotificationMenu/NotificationMenu.vue b/webapp/components/NotificationMenu/NotificationMenu.vue
index 276da8490..576abb213 100644
--- a/webapp/components/NotificationMenu/NotificationMenu.vue
+++ b/webapp/components/NotificationMenu/NotificationMenu.vue
@@ -127,7 +127,7 @@ export default {
apollo: {
notifications: {
query() {
- return notificationQuery(this.$i18n)
+ return notificationQuery()
},
variables() {
return {
diff --git a/webapp/components/NotificationsTable/NotificationsTable.spec.js b/webapp/components/NotificationsTable/NotificationsTable.spec.js
index 0d3560787..5fbfc338a 100644
--- a/webapp/components/NotificationsTable/NotificationsTable.spec.js
+++ b/webapp/components/NotificationsTable/NotificationsTable.spec.js
@@ -88,7 +88,7 @@ describe('NotificationsTable.vue', () => {
})
it('renders the author', () => {
- const userinfo = firstRowNotification.find('.user-teaser > .info')
+ const userinfo = firstRowNotification.find('.user-teaser .info')
expect(userinfo.text()).toContain(postNotification.from.author.name)
})
@@ -121,7 +121,7 @@ describe('NotificationsTable.vue', () => {
})
it('renders the author', () => {
- const userinfo = secondRowNotification.find('.user-teaser > .info')
+ const userinfo = secondRowNotification.find('.user-teaser .info')
expect(userinfo.text()).toContain(commentNotification.from.author.name)
})
diff --git a/webapp/components/UserTeaser/LocationInfo.spec.js b/webapp/components/UserTeaser/LocationInfo.spec.js
new file mode 100644
index 000000000..2b100e66d
--- /dev/null
+++ b/webapp/components/UserTeaser/LocationInfo.spec.js
@@ -0,0 +1,31 @@
+import { render } from '@testing-library/vue'
+import LocationInfo from './LocationInfo.vue'
+
+const localVue = global.localVue
+
+describe('LocationInfo', () => {
+ const Wrapper = ({ withDistance }) => {
+ return render(LocationInfo, {
+ localVue,
+ propsData: {
+ locationData: {
+ name: 'Paris',
+ distanceToMe: withDistance ? 100 : null,
+ },
+ },
+ mocks: {
+ $t: jest.fn((t) => t),
+ },
+ })
+ }
+
+ it('renders with distance', () => {
+ const wrapper = Wrapper({ withDistance: true })
+ expect(wrapper.container).toMatchSnapshot()
+ })
+
+ it('renders without distance', () => {
+ const wrapper = Wrapper({ withDistance: false })
+ expect(wrapper.container).toMatchSnapshot()
+ })
+})
diff --git a/webapp/components/UserTeaser/LocationInfo.vue b/webapp/components/UserTeaser/LocationInfo.vue
new file mode 100644
index 000000000..67dc46c27
--- /dev/null
+++ b/webapp/components/UserTeaser/LocationInfo.vue
@@ -0,0 +1,44 @@
+
+
+
+
+ {{ locationData.name }}
+
+
{{ distance }}
+
+
+
+
+
+
diff --git a/webapp/components/UserTeaser/UserTeaser.spec.js b/webapp/components/UserTeaser/UserTeaser.spec.js
index 354308109..8a67285ac 100644
--- a/webapp/components/UserTeaser/UserTeaser.spec.js
+++ b/webapp/components/UserTeaser/UserTeaser.spec.js
@@ -1,113 +1,250 @@
-import { mount, RouterLinkStub } from '@vue/test-utils'
+import { render, screen, fireEvent } from '@testing-library/vue'
+import { RouterLinkStub } from '@vue/test-utils'
import UserTeaser from './UserTeaser.vue'
import Vuex from 'vuex'
const localVue = global.localVue
-const filter = jest.fn((str) => str)
-localVue.filter('truncate', filter)
+// Mock Math.random, used in Dropdown
+Object.assign(Math, {
+ random: () => 0,
+})
+
+const waitForPopover = async () => await new Promise((resolve) => setTimeout(resolve, 1000))
+
+let mockIsTouchDevice
+jest.mock('../utils/isTouchDevice', () => ({
+ isTouchDevice: jest.fn(() => mockIsTouchDevice),
+}))
+
+const userTilda = {
+ name: 'Tilda Swinton',
+ slug: 'tilda-swinton',
+ id: 'user1',
+ avatar: '/avatars/tilda-swinton',
+ badgeVerification: {
+ id: 'bv1',
+ icon: '/icons/verified',
+ description: 'Verified',
+ isDefault: false,
+ },
+ badgeTrophiesSelected: [
+ {
+ id: 'trophy1',
+ icon: '/icons/trophy1',
+ description: 'Trophy 1',
+ isDefault: false,
+ },
+ {
+ id: 'trophy2',
+ icon: '/icons/trophy2',
+ description: 'Trophy 2',
+ isDefault: false,
+ },
+ {
+ id: 'empty',
+ icon: '/icons/empty',
+ description: 'Empty',
+ isDefault: true,
+ },
+ ],
+}
describe('UserTeaser', () => {
- let propsData
- let mocks
- let stubs
- let getters
+ const Wrapper = ({
+ isModerator = false,
+ withLinkToProfile = true,
+ onTouchScreen = false,
+ withAvatar = true,
+ user = userTilda,
+ withPopoverEnabled = true,
+ }) => {
+ mockIsTouchDevice = onTouchScreen
- beforeEach(() => {
- propsData = {}
-
- mocks = {
- $t: jest.fn(),
- }
- stubs = {
- NuxtLink: RouterLinkStub,
- }
- getters = {
- 'auth/user': () => {
- return {}
+ const store = new Vuex.Store({
+ getters: {
+ 'auth/user': () => {
+ return {}
+ },
+ 'auth/isModerator': () => isModerator,
},
- 'auth/isModerator': () => false,
- }
+ })
+ return render(UserTeaser, {
+ localVue,
+ store,
+ propsData: {
+ user,
+ linkToProfile: withLinkToProfile,
+ showAvatar: withAvatar,
+ showPopover: withPopoverEnabled,
+ },
+ stubs: {
+ NuxtLink: RouterLinkStub,
+ 'user-teaser-popover': true,
+ 'v-popover': true,
+ 'client-only': true,
+ },
+ mocks: {
+ $t: jest.fn((t) => t),
+ },
+ })
+ }
+
+ it('renders anonymous user', () => {
+ const wrapper = Wrapper({ user: null })
+ expect(wrapper.container).toMatchSnapshot()
})
- describe('mount', () => {
- const Wrapper = () => {
- const store = new Vuex.Store({
- getters,
+ describe('given an user', () => {
+ describe('without linkToProfile, on touch screen', () => {
+ let wrapper
+ beforeEach(() => {
+ wrapper = Wrapper({ withLinkToProfile: false, onTouchScreen: true })
})
- return mount(UserTeaser, { store, propsData, mocks, stubs, localVue })
- }
- it('renders anonymous user', () => {
- const wrapper = Wrapper()
- expect(wrapper.text()).toBe('')
- expect(mocks.$t).toHaveBeenCalledWith('profile.userAnonym')
+ it('renders', () => {
+ expect(wrapper.container).toMatchSnapshot()
+ })
+
+ describe('when clicking the user name', () => {
+ beforeEach(async () => {
+ const userName = screen.getByText('Tilda Swinton')
+ await fireEvent.click(userName)
+ await waitForPopover()
+ })
+
+ it('renders the popover', () => {
+ expect(wrapper.container).toMatchSnapshot()
+ })
+ })
+
+ describe('when clicking the user avatar', () => {
+ beforeEach(async () => {
+ const userAvatar = screen.getByAltText('Tilda Swinton')
+ await fireEvent.click(userAvatar)
+ await waitForPopover()
+ })
+
+ it('renders the popover', () => {
+ expect(wrapper.container).toMatchSnapshot()
+ })
+ })
})
- describe('given an user', () => {
+ describe('with linkToProfile, on touch screen', () => {
+ let wrapper
beforeEach(() => {
- propsData.user = {
- name: 'Tilda Swinton',
- slug: 'tilda-swinton',
- }
+ wrapper = Wrapper({ withLinkToProfile: true, onTouchScreen: true })
})
- it('renders user name', () => {
- const wrapper = Wrapper()
- expect(mocks.$t).not.toHaveBeenCalledWith('profile.userAnonym')
- expect(wrapper.text()).toMatch('Tilda Swinton')
+ it('renders', () => {
+ expect(wrapper.container).toMatchSnapshot()
})
- describe('user is deleted', () => {
- beforeEach(() => {
- propsData.user.deleted = true
+ describe('when clicking the user name', () => {
+ beforeEach(async () => {
+ const userName = screen.getByText('Tilda Swinton')
+ await fireEvent.click(userName)
})
+ it('renders the popover', () => {
+ expect(wrapper.container).toMatchSnapshot()
+ })
+ })
+ })
+
+ describe('without linkToProfile, on desktop', () => {
+ let wrapper
+ beforeEach(() => {
+ wrapper = Wrapper({ withLinkToProfile: false, onTouchScreen: false })
+ })
+
+ it('renders', () => {
+ expect(wrapper.container).toMatchSnapshot()
+ })
+
+ describe('when hovering the user name', () => {
+ beforeEach(async () => {
+ const userName = screen.getByText('Tilda Swinton')
+ await fireEvent.mouseOver(userName)
+ await waitForPopover()
+ })
+
+ it('renders the popover', () => {
+ expect(wrapper.container).toMatchSnapshot()
+ })
+ })
+
+ describe('when hovering the user avatar', () => {
+ beforeEach(async () => {
+ const userAvatar = screen.getByAltText('Tilda Swinton')
+ await fireEvent.mouseOver(userAvatar)
+ await waitForPopover()
+ })
+
+ it('renders the popover', () => {
+ expect(wrapper.container).toMatchSnapshot()
+ })
+ })
+ })
+
+ describe('with linkToProfile, on desktop', () => {
+ let wrapper
+ beforeEach(() => {
+ wrapper = Wrapper({ withLinkToProfile: true, onTouchScreen: false })
+ })
+
+ it('renders', () => {
+ expect(wrapper.container).toMatchSnapshot()
+ })
+
+ describe('when hovering the user name', () => {
+ beforeEach(async () => {
+ const userName = screen.getByText('Tilda Swinton')
+ await fireEvent.mouseOver(userName)
+ await waitForPopover()
+ })
+
+ it('renders the popover', () => {
+ expect(wrapper.container).toMatchSnapshot()
+ })
+ })
+ })
+
+ describe('avatar is disabled', () => {
+ it('does not render the avatar', () => {
+ const wrapper = Wrapper({ withAvatar: false })
+ expect(wrapper.container).toMatchSnapshot()
+ })
+ })
+
+ describe('user is deleted', () => {
+ it('renders anonymous user', () => {
+ const wrapper = Wrapper({ user: { ...userTilda, deleted: true } })
+ expect(wrapper.container).toMatchSnapshot()
+ })
+
+ describe('even if the current user is a moderator', () => {
it('renders anonymous user', () => {
- const wrapper = Wrapper()
- expect(wrapper.text()).not.toMatch('Tilda Swinton')
- expect(mocks.$t).toHaveBeenCalledWith('profile.userAnonym')
- })
-
- describe('even if the current user is a moderator', () => {
- beforeEach(() => {
- getters['auth/isModerator'] = () => true
- })
-
- it('renders anonymous user', () => {
- const wrapper = Wrapper()
- expect(wrapper.text()).not.toMatch('Tilda Swinton')
- expect(mocks.$t).toHaveBeenCalledWith('profile.userAnonym')
+ const wrapper = Wrapper({
+ user: { ...userTilda, deleted: true },
+ isModerator: true,
})
+ expect(wrapper.container).toMatchSnapshot()
})
})
+ })
- describe('user is disabled', () => {
- beforeEach(() => {
- propsData.user.disabled = true
- })
+ describe('user is disabled', () => {
+ it('renders anonymous user', () => {
+ const wrapper = Wrapper({ user: { ...userTilda, disabled: true } })
+ expect(wrapper.container).toMatchSnapshot()
+ })
- it('renders anonymous user', () => {
- const wrapper = Wrapper()
- expect(wrapper.text()).not.toMatch('Tilda Swinton')
- expect(mocks.$t).toHaveBeenCalledWith('profile.userAnonym')
- })
-
- describe('current user is a moderator', () => {
- beforeEach(() => {
- getters['auth/isModerator'] = () => true
- })
-
- it('renders user name', () => {
- const wrapper = Wrapper()
- expect(wrapper.text()).not.toMatch('Anonymous')
- expect(wrapper.text()).toMatch('Tilda Swinton')
- })
-
- it('has "disabled-content" class', () => {
- const wrapper = Wrapper()
- expect(wrapper.classes()).toContain('disabled-content')
- })
+ describe('current user is a moderator', () => {
+ it('renders user name', () => {
+ const wrapper = Wrapper({ user: { ...userTilda, disabled: true }, isModerator: true })
+ expect(wrapper.container).toMatchSnapshot()
})
})
})
diff --git a/webapp/components/UserTeaser/UserTeaser.vue b/webapp/components/UserTeaser/UserTeaser.vue
index a9e556bf4..4bc72576e 100644
--- a/webapp/components/UserTeaser/UserTeaser.vue
+++ b/webapp/components/UserTeaser/UserTeaser.vue
@@ -4,57 +4,34 @@
{{ $t('profile.userAnonym') }}
-
-
-
-
-
-
-
-
-
- {{ userSlug }}
- {{ userName }}
-
-
-
- {{ userSlug }}
- {{ userName }}
-
-
-
-
- {{ $t('group.in') }}
-
-
-
- {{ groupSlug }}
- {{ groupName }}
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
diff --git a/webapp/components/UserTeaser/__snapshots__/LocationInfo.spec.js.snap b/webapp/components/UserTeaser/__snapshots__/LocationInfo.spec.js.snap
new file mode 100644
index 000000000..50ce23f9a
--- /dev/null
+++ b/webapp/components/UserTeaser/__snapshots__/LocationInfo.spec.js.snap
@@ -0,0 +1,51 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`LocationInfo renders with distance 1`] = `
+
+
+
+
+
+
+
+ Paris
+
+
+
+
+ location.distance
+
+
+
+`;
+
+exports[`LocationInfo renders without distance 1`] = `
+
+
+
+
+
+
+
+ Paris
+
+
+
+
+
+
+`;
diff --git a/webapp/components/UserTeaser/__snapshots__/UserTeaser.spec.js.snap b/webapp/components/UserTeaser/__snapshots__/UserTeaser.spec.js.snap
new file mode 100644
index 000000000..b8fc6cae9
--- /dev/null
+++ b/webapp/components/UserTeaser/__snapshots__/UserTeaser.spec.js.snap
@@ -0,0 +1,1136 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`UserTeaser given an user avatar is disabled does not render the avatar 1`] = `
+
+`;
+
+exports[`UserTeaser given an user user is deleted even if the current user is a moderator renders anonymous user 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ profile.userAnonym
+
+
+
+`;
+
+exports[`UserTeaser given an user user is deleted renders anonymous user 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ profile.userAnonym
+
+
+
+`;
+
+exports[`UserTeaser given an user user is disabled current user is a moderator renders user name 1`] = `
+
+`;
+
+exports[`UserTeaser given an user user is disabled renders anonymous user 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ profile.userAnonym
+
+
+
+`;
+
+exports[`UserTeaser given an user with linkToProfile, on desktop renders 1`] = `
+
+`;
+
+exports[`UserTeaser given an user with linkToProfile, on desktop when hovering the user name renders the popover 1`] = `
+
+`;
+
+exports[`UserTeaser given an user with linkToProfile, on touch screen renders 1`] = `
+
+
+
+
+
+
+
+ TS
+
+
+
+
+
+
+
+
+
+
+
+
+ @tilda-swinton
+
+
+
+ Tilda Swinton
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`UserTeaser given an user with linkToProfile, on touch screen when clicking the user name renders the popover 1`] = `
+
+
+
+
+
+
+
+ TS
+
+
+
+
+
+
+
+
+
+
+
+
+ @tilda-swinton
+
+
+
+ Tilda Swinton
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`UserTeaser given an user without linkToProfile, on desktop renders 1`] = `
+
+
+
+
+
+
+
+ TS
+
+
+
+
+
+
+
+
+
+
+
+
+ @tilda-swinton
+
+
+
+ Tilda Swinton
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`UserTeaser given an user without linkToProfile, on desktop when hovering the user avatar renders the popover 1`] = `
+
+
+
+
+
+
+
+ TS
+
+
+
+
+
+
+
+
+
+
+
+
+ @tilda-swinton
+
+
+
+ Tilda Swinton
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`UserTeaser given an user without linkToProfile, on desktop when hovering the user name renders the popover 1`] = `
+
+
+
+
+
+
+
+ TS
+
+
+
+
+
+
+
+
+
+
+
+
+ @tilda-swinton
+
+
+
+ Tilda Swinton
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`UserTeaser given an user without linkToProfile, on touch screen renders 1`] = `
+
+
+
+
+
+
+
+ TS
+
+
+
+
+
+
+
+
+
+
+
+
+ @tilda-swinton
+
+
+
+ Tilda Swinton
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`UserTeaser given an user without linkToProfile, on touch screen when clicking the user avatar renders the popover 1`] = `
+
+
+
+
+
+
+
+ TS
+
+
+
+
+
+
+
+
+
+
+
+
+ @tilda-swinton
+
+
+
+ Tilda Swinton
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`UserTeaser given an user without linkToProfile, on touch screen when clicking the user name renders the popover 1`] = `
+
+
+
+
+
+
+
+ TS
+
+
+
+
+
+
+
+
+
+
+
+
+ @tilda-swinton
+
+
+
+ Tilda Swinton
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`UserTeaser renders anonymous user 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ profile.userAnonym
+
+
+
+`;
diff --git a/webapp/components/UserTeaser/__snapshots__/UserTeaserHelper.spec.js.snap b/webapp/components/UserTeaser/__snapshots__/UserTeaserHelper.spec.js.snap
new file mode 100644
index 000000000..2257e8a51
--- /dev/null
+++ b/webapp/components/UserTeaser/__snapshots__/UserTeaserHelper.spec.js.snap
@@ -0,0 +1,19 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`UserTeaserHelper with linkToProfile and popover enabled, on touch screen renders button 1`] = `
+
+
+
+`;
+
+exports[`UserTeaserHelper with linkToProfile, on desktop renders link 1`] = `
+
+`;
+
+exports[`UserTeaserHelper without linkToProfile renders span 1`] = `
+
+
+
+`;
diff --git a/webapp/components/UserTeaser/__snapshots__/UserTeaserPopover.spec.js.snap b/webapp/components/UserTeaser/__snapshots__/UserTeaserPopover.spec.js.snap
new file mode 100644
index 000000000..3eab03611
--- /dev/null
+++ b/webapp/components/UserTeaser/__snapshots__/UserTeaserPopover.spec.js.snap
@@ -0,0 +1,547 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`UserTeaserPopover does not show badges when disabled 1`] = `
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
+
+ profile.followers
+
+
+
+
+
+
+
+
+ 0
+
+
+
+ common.post
+
+
+
+
+
+
+
+
+ 0
+
+
+
+ common.comment
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`UserTeaserPopover given a non-touch device does not show button when userLink is provided 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
+
+ profile.followers
+
+
+
+
+
+
+
+
+ 0
+
+
+
+ common.post
+
+
+
+
+
+
+
+
+ 0
+
+
+
+ common.comment
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`UserTeaserPopover given a touch device does not show button when userLink is not provided 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
+
+ profile.followers
+
+
+
+
+
+
+
+
+ 0
+
+
+
+ common.post
+
+
+
+
+
+
+
+
+ 0
+
+
+
+ common.comment
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`UserTeaserPopover given a touch device shows button when userLink is provided 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
+
+ profile.followers
+
+
+
+
+
+
+
+
+ 0
+
+
+
+ common.post
+
+
+
+
+
+
+
+
+ 0
+
+
+
+ common.comment
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`UserTeaserPopover shows badges when enabled 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
+
+ profile.followers
+
+
+
+
+
+
+
+
+ 0
+
+
+
+ common.post
+
+
+
+
+
+
+
+
+ 0
+
+
+
+ common.comment
+
+
+
+
+
+
+
+
+
+`;
diff --git a/webapp/components/utils/isTouchDevice.js b/webapp/components/utils/isTouchDevice.js
new file mode 100644
index 000000000..a6bc17752
--- /dev/null
+++ b/webapp/components/utils/isTouchDevice.js
@@ -0,0 +1,2 @@
+export const isTouchDevice = () =>
+ 'ontouchstart' in window || navigator.MaxTouchPoints > 0 || navigator.msMaxTouchPoints > 0
diff --git a/webapp/graphql/Fragments.js b/webapp/graphql/Fragments.js
index 77af830e8..e1704923f 100644
--- a/webapp/graphql/Fragments.js
+++ b/webapp/graphql/Fragments.js
@@ -17,9 +17,11 @@ export const locationFragment = (lang) => gql`
fragment location on User {
locationName
location {
+ id
name: name${lang}
lng
lat
+ distanceToMe
}
}
`
@@ -50,6 +52,19 @@ export const userCountsFragment = gql`
}
`
+export const userTeaserFragment = (lang) => gql`
+ ${badgesFragment}
+ ${locationFragment(lang)}
+
+ fragment userTeaser on User {
+ followedByCount
+ contributionsCount
+ commentedCount
+ ...badges
+ ...location
+ }
+`
+
export const postFragment = gql`
fragment post on Post {
id
diff --git a/webapp/graphql/User.js b/webapp/graphql/User.js
index 75342ef2a..7440b5051 100644
--- a/webapp/graphql/User.js
+++ b/webapp/graphql/User.js
@@ -7,6 +7,7 @@ import {
postFragment,
commentFragment,
groupFragment,
+ userTeaserFragment,
} from './Fragments'
export const profileUserQuery = (i18n) => {
@@ -125,7 +126,7 @@ export const mapUserQuery = (i18n) => {
`
}
-export const notificationQuery = (_i18n) => {
+export const notificationQuery = () => {
return gql`
${userFragment}
${commentFragment}
@@ -483,6 +484,18 @@ export const userDataQuery = (i18n) => {
`
}
+export const userTeaserQuery = (i18n) => {
+ const lang = i18n.locale().toUpperCase()
+ return gql`
+ ${userTeaserFragment(lang)}
+ query ($id: ID!) {
+ User(id: $id) {
+ ...userTeaser
+ }
+ }
+ `
+}
+
export const setTrophyBadgeSelected = gql`
mutation ($slot: Int!, $badgeId: ID) {
setTrophyBadgeSelected(slot: $slot, badgeId: $badgeId) {
diff --git a/webapp/locales/de.json b/webapp/locales/de.json
index df050b191..e2b641b08 100644
--- a/webapp/locales/de.json
+++ b/webapp/locales/de.json
@@ -636,6 +636,9 @@
"localeSwitch": {
"tooltip": "Sprache wählen"
},
+ "location": {
+ "distance": "{distance} km von mir entfernt"
+ },
"login": {
"email": "Deine E-Mail",
"failure": "Fehlerhafte E-Mail-Adresse oder Passwort.",
@@ -1173,5 +1176,10 @@
"newTermsAndConditions": "Neue Nutzungsbedingungen",
"termsAndConditionsNewConfirm": "Ich habe die neuen Nutzungsbedingungen durchgelesen und stimme zu.",
"termsAndConditionsNewConfirmText": "Bitte lies Dir die neuen Nutzungsbedingungen jetzt durch!"
+ },
+ "user-teaser": {
+ "popover": {
+ "open-profile": "Profil öffnen"
+ }
}
}
diff --git a/webapp/locales/en.json b/webapp/locales/en.json
index ecd0ec18d..ab34ba66a 100644
--- a/webapp/locales/en.json
+++ b/webapp/locales/en.json
@@ -636,6 +636,9 @@
"localeSwitch": {
"tooltip": "Choose language"
},
+ "location": {
+ "distance": "{distance} km away from me"
+ },
"login": {
"email": "Your E-mail",
"failure": "Incorrect email address or password.",
@@ -1173,5 +1176,10 @@
"newTermsAndConditions": "New Terms and Conditions",
"termsAndConditionsNewConfirm": "I have read and agree to the new terms of conditions.",
"termsAndConditionsNewConfirmText": "Please read the new terms of use now!"
+ },
+ "user-teaser": {
+ "popover": {
+ "open-profile": "Open profile"
+ }
}
}
diff --git a/webapp/locales/es.json b/webapp/locales/es.json
index 15096b9d8..b7d95d11c 100644
--- a/webapp/locales/es.json
+++ b/webapp/locales/es.json
@@ -636,6 +636,9 @@
"localeSwitch": {
"tooltip": null
},
+ "location": {
+ "distance": null
+ },
"login": {
"email": "Su correo electrónico",
"failure": "Dirección de correo electrónico o contraseña incorrecta.",
@@ -1173,5 +1176,10 @@
"newTermsAndConditions": "Nuevos términos de uso",
"termsAndConditionsNewConfirm": "He leído y acepto los nuevos términos de uso.",
"termsAndConditionsNewConfirmText": "¡Por favor, lea los nuevos términos de uso ahora!"
+ },
+ "user-teaser": {
+ "popover": {
+ "open-profile": null
+ }
}
}
diff --git a/webapp/locales/fr.json b/webapp/locales/fr.json
index 2da2a9801..37a182c28 100644
--- a/webapp/locales/fr.json
+++ b/webapp/locales/fr.json
@@ -636,6 +636,9 @@
"localeSwitch": {
"tooltip": null
},
+ "location": {
+ "distance": null
+ },
"login": {
"email": "Votre mail",
"failure": "Adresse mail ou mot de passe incorrect.",
@@ -1173,5 +1176,10 @@
"newTermsAndConditions": "Nouvelles conditions générales",
"termsAndConditionsNewConfirm": "J'ai lu et accepté les nouvelles conditions générales.",
"termsAndConditionsNewConfirmText": "Veuillez lire les nouvelles conditions d'utilisation dès maintenant !"
+ },
+ "user-teaser": {
+ "popover": {
+ "open-profile": null
+ }
}
}
diff --git a/webapp/locales/it.json b/webapp/locales/it.json
index 485abff3a..6b686502c 100644
--- a/webapp/locales/it.json
+++ b/webapp/locales/it.json
@@ -636,6 +636,9 @@
"localeSwitch": {
"tooltip": null
},
+ "location": {
+ "distance": null
+ },
"login": {
"email": "La tua email",
"failure": null,
@@ -1173,5 +1176,10 @@
"newTermsAndConditions": "Nuovi Termini e Condizioni",
"termsAndConditionsNewConfirm": "Ho letto e accetto le nuove condizioni generali di contratto.",
"termsAndConditionsNewConfirmText": "Si prega di leggere le nuove condizioni d'uso ora!"
+ },
+ "user-teaser": {
+ "popover": {
+ "open-profile": null
+ }
}
}
diff --git a/webapp/locales/nl.json b/webapp/locales/nl.json
index 40f9aca2e..714ed2b01 100644
--- a/webapp/locales/nl.json
+++ b/webapp/locales/nl.json
@@ -636,6 +636,9 @@
"localeSwitch": {
"tooltip": null
},
+ "location": {
+ "distance": null
+ },
"login": {
"email": "Uw E-mail",
"failure": null,
@@ -1173,5 +1176,10 @@
"newTermsAndConditions": null,
"termsAndConditionsNewConfirm": null,
"termsAndConditionsNewConfirmText": null
+ },
+ "user-teaser": {
+ "popover": {
+ "open-profile": null
+ }
}
}
diff --git a/webapp/locales/pl.json b/webapp/locales/pl.json
index ee332b84b..61a6acf24 100644
--- a/webapp/locales/pl.json
+++ b/webapp/locales/pl.json
@@ -636,6 +636,9 @@
"localeSwitch": {
"tooltip": null
},
+ "location": {
+ "distance": null
+ },
"login": {
"email": "Twój adres e-mail",
"failure": null,
@@ -1173,5 +1176,10 @@
"newTermsAndConditions": null,
"termsAndConditionsNewConfirm": null,
"termsAndConditionsNewConfirmText": null
+ },
+ "user-teaser": {
+ "popover": {
+ "open-profile": null
+ }
}
}
diff --git a/webapp/locales/pt.json b/webapp/locales/pt.json
index 54f9b5d99..80172daa3 100644
--- a/webapp/locales/pt.json
+++ b/webapp/locales/pt.json
@@ -636,6 +636,9 @@
"localeSwitch": {
"tooltip": null
},
+ "location": {
+ "distance": null
+ },
"login": {
"email": "Seu email",
"failure": "Endereço de e-mail ou senha incorretos.",
@@ -1173,5 +1176,10 @@
"newTermsAndConditions": "Novos Termos e Condições",
"termsAndConditionsNewConfirm": "Eu li e concordo com os novos termos de condições.",
"termsAndConditionsNewConfirmText": "Por favor, leia os novos termos de uso agora!"
+ },
+ "user-teaser": {
+ "popover": {
+ "open-profile": null
+ }
}
}
diff --git a/webapp/locales/ru.json b/webapp/locales/ru.json
index 4d2e2a357..f7956755c 100644
--- a/webapp/locales/ru.json
+++ b/webapp/locales/ru.json
@@ -636,6 +636,9 @@
"localeSwitch": {
"tooltip": null
},
+ "location": {
+ "distance": null
+ },
"login": {
"email": "Электронная почта",
"failure": "Неверный адрес электронной почты или пароль.",
@@ -1173,5 +1176,10 @@
"newTermsAndConditions": "Новые условия и положения",
"termsAndConditionsNewConfirm": "Я прочитал(а) и согласен(на) с новыми условиями.",
"termsAndConditionsNewConfirmText": "Пожалуйста, ознакомьтесь с новыми условиями использования!"
+ },
+ "user-teaser": {
+ "popover": {
+ "open-profile": null
+ }
}
}
diff --git a/webapp/pages/groups/_id/__snapshots__/_slug.spec.js.snap b/webapp/pages/groups/_id/__snapshots__/_slug.spec.js.snap
index cb43b8526..68c2b50ba 100644
--- a/webapp/pages/groups/_id/__snapshots__/_slug.spec.js.snap
+++ b/webapp/pages/groups/_id/__snapshots__/_slug.spec.js.snap
@@ -493,39 +493,52 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
class=""
placement="top-start"
>
-
-
+
-
-
- PL
-
-
-
-
-
-
-
-
-
-
-
+ PL
+
+
+
+
+
+
+
+
+
+
+
Peter Lustig
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
-
-
- JR
-
-
-
-
-
-
-
-
-
-
-
+ JR
+
+
+
+
+
+
+
+
+
+
+
Jenny Rostock
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
-
-
- BDB
-
-
-
-
-
-
-
-
-
-
-
+ BDB
+
+
+
+
+
+
+
+
+
+
+
Bob der Baumeister
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
-
-
- H
-
-
-
-
-
-
-
-
-
-
-
+ H
+
+
+
+
+
+
+
+
+
+
+
Huey
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
@@ -2304,39 +2364,52 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
class=""
placement="top-start"
>
-
-
+
-
-
- PL
-
-
-
-
-
-
-
-
-
-
-
+ PL
+
+
+
+
+
+
+
+
+
+
+
Peter Lustig
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
-
-
- JR
-
-
-
-
-
-
-
-
-
-
-
+ JR
+
+
+
+
+
+
+
+
+
+
+
Jenny Rostock
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
-
-
- BDB
-
-
-
-
-
-
-
-
-
-
-
+ BDB
+
+
+
+
+
+
+
+
+
+
+
Bob der Baumeister
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
-
-
- H
-
-
-
-
-
-
-
-
-
-
-
+ H
+
+
+
+
+
+
+
+
+
+
+
Huey
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
@@ -3197,39 +3317,52 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
class=""
placement="top-start"
>
-
-
+
-
-
- PL
-
-
-
-
-
-
-
-
-
-
-
+ PL
+
+
+
+
+
+
+
+
+
+
+
Peter Lustig
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
-
-
- JR
-
-
-
-
-
-
-
-
-
-
-
+ JR
+
+
+
+
+
+
+
+
+
+
+
Jenny Rostock
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
-
-
- BDB
-
-
-
-
-
-
-
-
-
-
-
+ BDB
+
+
+
+
+
+
+
+
+
+
+
Bob der Baumeister
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
-
-
- H
-
-
-
-
-
-
-
-
-
-
-
+ H
+
+
+
+
+
+
+
+
+
+
+
Huey
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
@@ -3956,39 +4136,52 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
class=""
placement="top-start"
>
-
-
+
-
-
- PL
-
-
-
-
-
-
-
-
-
-
-
+ PL
+
+
+
+
+
+
+
+
+
+
+
Peter Lustig
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
-
-
- JR
-
-
-
-
-
-
-
-
-
-
-
+ JR
+
+
+
+
+
+
+
+
+
+
+
Jenny Rostock
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
-
-
- BDB
-
-
-
-
-
-
-
-
-
-
-
+ BDB
+
+
+
+
+
+
+
+
+
+
+
Bob der Baumeister
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
-
-
- H
-
-
-
-
-
-
-
-
-
-
-
+ H
+
+
+
+
+
+
+
+
+
+
+
Huey
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
@@ -4713,39 +4953,52 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
class=""
placement="top-start"
>
-
-
+
-
-
- PL
-
-
-
-
-
-
-
-
-
-
-
+ PL
+
+
+
+
+
+
+
+
+
+
+
Peter Lustig
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
-
-
- JR
-
-
-
-
-
-
-
-
-
-
-
+ JR
+
+
+
+
+
+
+
+
+
+
+
Jenny Rostock
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
-
-
- BDB
-
-
-
-
-
-
-
-
-
-
-
+ BDB
+
+
+
+
+
+
+
+
+
+
+
Bob der Baumeister
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
-
-
- H
-
-
-
-
-
-
-
-
-
-
-
+ H
+
+
+
+
+
+
+
+
+
+
+
Huey
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
@@ -5539,39 +5839,52 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
class=""
placement="top-start"
>
-
-
+
-
-
- PL
-
-
-
-
-
-
-
-
-
-
-
+ PL
+
+
+
+
+
+
+
+
+
+
+
Peter Lustig
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
-
-
- JR
-
-
-
-
-
-
-
-
-
-
-
+ JR
+
+
+
+
+
+
+
+
+
+
+
Jenny Rostock
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
-
-
- BDB
-
-
-
-
-
-
-
-
-
-
-
+ BDB
+
+
+
+
+
+
+
+
+
+
+
Bob der Baumeister
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
-
-
- H
-
-
-
-
-
-
-
-
-
-
-
+ H
+
+
+
+
+
+
+
+
+
+
+
Huey
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
@@ -6479,39 +6839,52 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a hidde
class=""
placement="top-start"
>
-
-
+
-
-
- PL
-
-
-
-
-
-
-
-
-
-
-
+ PL
+
+
+
+
+
+
+
+
+
+
+
Peter Lustig
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
-
-
- JR
-
-
-
-
-
-
-
-
-
-
-
+ JR
+
+
+
+
+
+
+
+
+
+
+
Jenny Rostock
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
-
-
- BDB
-
-
-
-
-
-
-
-
-
-
-
+ BDB
+
+
+
+
+
+
+
+
+
+
+
Bob der Baumeister
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
-
-
- H
-
-
-
-
-
-
-
-
-
-
-
+ H
+
+
+
+
+
+
+
+
+
+
+
Huey
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
@@ -7393,39 +7813,52 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a hidde
class=""
placement="top-start"
>
-
-
+
-
-
- PL
-
-
-
-
-
-
-
-
-
-
-
+ PL
+
+
+
+
+
+
+
+
+
+
+
Peter Lustig
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
-
-
- JR
-
-
-
-
-
-
-
-
-
-
-
+ JR
+
+
+
+
+
+
+
+
+
+
+
Jenny Rostock
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
-
-
- BDB
-
-
-
-
-
-
-
-
-
-
-
+ BDB
+
+
+
+
+
+
+
+
+
+
+
Bob der Baumeister
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
-
-
- H
-
-
-
-
-
-
-
-
-
-
-
+ H
+
+
+
+
+
+
+
+
+
+
+
Huey
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+