Skip to content

Commit

Permalink
feat: feature ideas page (#2420)
Browse files Browse the repository at this point in the history
* Contentful query feature ideas page

* Create feature ideas page component

* Refactor feature cards to a separate FeaureIdeas component

* Add vote button to feature ideas

* Update logic to expected api handling

* Move card style overrides to component

* Add no feature ideas error message

* Fix stylelint

* Add feature ideas link to hamburger menu

* Handle unpublished feature ideas

* Adjust spacing feature ideas error message

* Add unit tests

* Increase size limit

* Fix unit test

* Add feature toggle for feature ideas link sidebar navigation

* Add FeatureIdeas component to the styleguide

* chore: use i18n.tc for likes count; rename lang file key

* chore: linting

---------

Co-authored-by: Richard Doe <[email protected]>
  • Loading branch information
LeoniePeters and rwd authored Aug 20, 2024
1 parent 7a122f4 commit aa50071
Show file tree
Hide file tree
Showing 19 changed files with 1,230 additions and 134 deletions.
3 changes: 2 additions & 1 deletion packages/portal/docs/sample-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ export default {
newsletter: 'https://images.ctfassets.net/i01duvb6kq77/1tvbbzdiq0sc0hBjbqlihZ/a60ff9eb7d24df0b0a1e54ae19d3c3d9/il_newsletter.svg',
curate: 'https://images.ctfassets.net/i01duvb6kq77/1DxiDhy46cX5eBheNYFdP7/42518b79959f2ea5cd270f9cffa022b2/homepage_A_v4_blackline.svg',
audience: 'https://images.ctfassets.net/i01duvb6kq77/B0VJ9of5sryy5n34hS93I/991133dbbc2dd0f48fd215e3aa1c3c75/Audience.svg',
support: 'https://images.ctfassets.net/i01duvb6kq77/KIMCBhGmlm90mxG8hldb1/8339d01737e9456cd10715b40f6c1925/feedback.svg'
support: 'https://images.ctfassets.net/i01duvb6kq77/KIMCBhGmlm90mxG8hldb1/8339d01737e9456cd10715b40f6c1925/feedback.svg',
search: 'https://images.ctfassets.net/i01duvb6kq77/JX06hL2kGk2mqCJLsoJUh/4d8bad1506779f0029c00f5f522e8e00/search.svg'
},
imagesWithAttribution: [
{
Expand Down
5 changes: 3 additions & 2 deletions packages/portal/src/components/content/ContentCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,16 @@
data-qa="card title"
:lang="langAttribute(displayTitle.code)"
>
<SmartLink
<component
:is="url ? 'SmartLink' : 'div'"
:destination="url"
link-class="card-link"
:title="(variant === 'mosaic' && displayTitle) ? displayTitle.value : null"
>
<span>
{{ truncate(displayTitle.value, 90) }}
</span>
</SmartLink>
</component>
</b-card-title>
<b-card-text
v-if="hitText"
Expand Down
15 changes: 6 additions & 9 deletions packages/portal/src/components/content/ContentRichText.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
:class="{ 'mb-5': richTextIsCard }"
tag="section"
>
<b-col class="col-12 col-lg-9">
<b-col
class="col-12"
:class="richTextIsCard ? 'col-lg-6' : 'col-lg-9'"
>
<b-card
v-if="html && richTextIsCard"
class="rich-text-card"
>
<!-- eslint-disable vue/no-v-html -->
<div
Expand Down Expand Up @@ -59,14 +61,9 @@
}
.xxl-page {
.col-lg-9 {
@media (min-width: $bp-large) {
flex: 0 0 50%;
max-width: 50%;
}
}
.rich-text-card {
.col,
.card {
max-width: $max-text-column-width;
}
}
Expand Down
15 changes: 8 additions & 7 deletions packages/portal/src/components/error/ErrorMessage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@
class="mt-4"
>
<!-- eslint-disable vue/no-v-html -->
<h1
<component
:is="titleTag"
v-if="title"
class="mb-4"
class="title mb-4"
v-html="title"
/>
<!-- eslint-enable vue/no-v-html -->
Expand Down Expand Up @@ -62,13 +63,13 @@
type: Boolean,
default: true
},
gridless: {
type: Boolean,
default: true
},
fullHeight: {
type: Boolean,
default: true
},
titleTag: {
type: String,
default: 'h1'
}
},
Expand Down Expand Up @@ -171,7 +172,7 @@
max-width: calc(1.5 * 35rem);
}
h1 {
.title {
color: $mediumgrey;
font-weight: 700;
font-size: 2rem;
Expand Down
251 changes: 251 additions & 0 deletions packages/portal/src/components/generic/FeatureIdeas.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
<template>
<div>
<ErrorMessage
v-if="$fetchState.error"
:error="$fetchState.error"
:full-height="false"
:show-message="false"
title-tag="h2"
data-qa="error message"
class="feature-ideas-error-container"
/>
<template v-else>
<ContentCard
v-for="(feature, index) in features"
:key="index"
:title="feature.name"
:texts="[feature.text]"
:image-url="feature.image?.image.url"
:image-content-type="feature.image?.image.contentType"
:image-width="feature.image?.image.width"
:image-height="feature.image?.image.height"
variant="list"
data-qa="feature idea card"
>
<template #footer>
<b-button
class="vote-button d-inline-flex align-items-center text-uppercase mt-auto mr-auto"
:class="{ voted: hasVotedOnFeature(feature.sys.id) }"
variant="light-flat"
:aria-label="$t('actions.vote')"
@click="voteOnFeature(feature.sys.id)"
>
<span
class="mr-1"
:class="feature.voted ? 'icon-thumbsup' : 'icon-thumbsup-outlined'"
/>
{{ $tc('likes.count', voteCountOnFeature(feature.sys.id)) }}
</b-button>
</template>
</ContentCard>
</template>
</div>
</template>

<script>
import keycloakMixin from '@/mixins/keycloak';
import ContentCard from '@/components/content/ContentCard';
export default {
name: 'FeatureIdeas',
components: {
ContentCard,
ErrorMessage: () => import('@/components/error/ErrorMessage')
},
mixins: [keycloakMixin],
props: {
features: {
type: Array,
default: () => []
}
},
data() {
return {
votesOnFeatures: {}
};
},
fetch() {
if (this.features.length < 1) {
const error = new Error('No feature ideas');
error.code = 'noFeatureIdeas';
this.$error(error);
}
// TODO: fetch the votes for each feature and remove mock functionality
// const featureIds = this.features.map((feature) => feature.sys.id).join(',');
// this.votesOnFeatures = await this.$axios.$get(`/api/votes/${featureIds}`);
for (const feature of this.features) {
const featureVotes = { count: Math.floor(Math.random() * 99), currentlyVotedOn: false };
this.votesOnFeatures[feature.sys.id] = featureVotes;
}
},
computed: {
userId() {
return this.$auth.user?.sub;
}
},
methods: {
voteOnFeature(featureId) {
// TODO: Implement voting on feature and remove mock functionality
// await this.$axios.$post(`/api/vote/${featureId}`);
console.log('Voting on feature', featureId);
if (this.$auth.loggedIn) {
if (this.hasVotedOnFeature(featureId)) {
this.votesOnFeatures[featureId].count = (this.votesOnFeatures[featureId]?.count || 0) - 1;
this.votesOnFeatures[featureId].currentlyVotedOn = false;
} else {
this.votesOnFeatures[featureId].count = (this.votesOnFeatures[featureId]?.count || 0) + 1;
this.votesOnFeatures[featureId].currentlyVotedOn = true;
}
} else {
this.keycloakLogin();
}
},
voteCountOnFeature(featureId) {
return this.votesOnFeatures[featureId]?.count;
},
hasVotedOnFeature(featureId) {
return this.votesOnFeatures[featureId]?.currentlyVotedOn;
}
}
};
</script>
<style lang="scss" scoped>
@import '@europeana/style/scss/variables';
.feature-ideas-error-container {
::v-deep .error-explanation {
flex-direction: column;
padding-top: 1rem;
padding-bottom: 1rem;
section {
width: 100%;
}
img {
max-width: 250px;
@media (min-width: $bp-4k) {
max-width: calc(1.5 * 250px);
}
}
.title {
color: $greyblack;
font-size: $font-size-medium;
font-weight: 600;
margin-bottom: 1rem !important;
@media (min-width: $bp-small) {
font-size: $font-size-large;
}
@media (min-width: $bp-4k) {
font-size: $font-size-large-4k;
}
}
p {
margin-bottom: 1rem;
}
}
}
::v-deep .content-card.list-card {
max-width: none;
.card-img {
background-color: $bodygrey;
img {
width: 80px;
height: auto;
margin: 0 auto;
@media (min-width: $bp-4k) {
width: 120px;
}
}
}
.card-wrapper {
&:hover {
box-shadow: none;
}
.title-texts-wrapper {
max-height: none;
margin-bottom: 0.5rem;
}
.card-text p {
display: inline;
-webkit-line-clamp: none;
}
}
}
.vote-button {
&:hover,
&.voted {
.icon-thumbsup-outlined::before {
content: '\e921';
}
}
&.voted {
color: $blue;
}
.icon-thumbsup-outlined,
.icon-thumbsup {
font-size: 1.125rem;
@media (min-width: $bp-4k) {
font-size: 1.5rem;
}
}
}
::v-deep .container.error-container {
max-width: none;
}
</style>
<docs lang="md">
```jsx
<FeatureIdeas :features="[
{ name: 'Feature 1',
text: 'Feature 1 description. Description of the feature. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.',
image: {
image: {
url: illustrations.search,
contentType: 'image/svg+xml',
}
},
sys: { id: '1' }
},
{ name: 'Feature 2',
text: 'Feature 2 description. Description of the feature. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.',
image: {
image: {
url: illustrations.search,
contentType: 'image/svg+xml',
}
},
sys: { id: '2' }
}
]" />
```
</docs>
3 changes: 2 additions & 1 deletion packages/portal/src/components/page/PageNavigation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@
{ url: '/europeana-classroom', text: this.$t('header.navigation.europeanaClassroom') },
{ url: '/about-us', text: this.$t('header.navigation.about') },
{ url: '/help', text: this.$t('header.navigation.help') }
];
].concat(this.$features.featureIdeas ? [{ url: '/feature-ideas', text: this.$t('header.navigation.featureIdeas') }] : []);
},
links() {
return this.mainNavigation.concat(this.sidebarNav ? this.sidebarNavigation : []);
Expand All @@ -146,6 +146,7 @@
case ('/account/login'):
case ('/account/logout'):
case ('/account/settings'):
case ('/feature-ideas'):
className = `icon-${url.split('/').pop()}`;
break;
case ('/'):
Expand Down
2 changes: 1 addition & 1 deletion packages/portal/src/components/search/SearchInterface.vue
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@
<ErrorMessage
v-if="$fetchState.error"
:error="$fetchState.error"
:gridless="false"
:full-height="false"
:show-message="showErrorMessage"
title-tag="h2"
/>
<template
v-else
Expand Down
1 change: 1 addition & 0 deletions packages/portal/src/features/toggles.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export default [
{ name: 'acceptEntityRecommendations' },
{ name: 'acceptSetRecommendations' },
{ name: 'eventLogging' },
{ name: 'featureIdeas' },
{ name: 'jiraServiceDeskFeedbackForm' },
{ name: 'mockTrendingItems' },
{ name: 'rejectEntityRecommendations' },
Expand Down
Loading

0 comments on commit aa50071

Please sign in to comment.