Skip to content

Commit

Permalink
TV Content Page Updates (#148)
Browse files Browse the repository at this point in the history
* wip

* Update async data call in all.vue

* Remove unused imports in tv.ts

* Update TV episode component to use TV URL for image source and format TV date

* Add formatTvDate function to utils/dates.ts

* Refactor formatDate function to use formatTvDate

* Remove unused import in index.vue

* cleanup

* Add schema.org markup for video metadata

* Add website.svg social icon

* Add provider option to DirectusImageProps and update Nuxt Image configuration

* Add TVByline and TVReactions components

* Add visitor ID tracking for TV pages

* fix popper hydration issues

* update TVShow to accept URL

* Add recommendations section to TV episode page

* fix css warnings

* fix padding for transcript

* add divider between content and meta
  • Loading branch information
bryantgillespie committed Mar 29, 2024
1 parent 8063379 commit 0cc4117
Show file tree
Hide file tree
Showing 9 changed files with 614 additions and 39 deletions.
1 change: 1 addition & 0 deletions assets/svg/social/website.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 5 additions & 2 deletions components/Base/DirectusImage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ export interface DirectusImageProps {
width?: number;
height?: number;
provider?: string;
}
defineProps<DirectusImageProps>();
withDefaults(defineProps<DirectusImageProps>(), {
provider: 'directus',
});
</script>

<template>
<NuxtImg :src="uuid" :alt="alt" :width="width" :height="height" format="auto" loading="lazy" />
<NuxtImg :src="uuid" :alt="alt" :width="width" :height="height" format="auto" loading="lazy" :provider />
</template>
2 changes: 1 addition & 1 deletion components/Block/Showcase.vue
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ loop();
.block-showcase {
@container (width > 50rem) {
display: grid;
align-items: start;
align-items: flex-start;
grid-template-columns: repeat(v-bind(sections), 1fr);
gap: var(--space-8);
}
Expand Down
98 changes: 98 additions & 0 deletions components/Tv/TVByline.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<script setup lang="ts">
interface BaseBylineProps {
name?: string;
title?: string;
image?: string;
links?: [
{
service: string;
url: string;
},
];
}
defineProps<BaseBylineProps>();
</script>

<template>
<address rel="author" class="base-byline">
<BaseDirectusImage
v-if="image"
:width="44"
:height="44"
class="avatar"
:uuid="image"
:alt="name ?? ''"
provider="directusTv"
/>
<div>
<p v-if="name" class="name">{{ name }}</p>
<p v-if="title" class="title">{{ title }}</p>
<div v-if="links" class="share-icons">
<template v-for="{ service, url } in links" :key="service">
<NuxtLink :href="url" target="_blank">
<img :src="dynamicAsset(`/svg/social/${service}.svg`)" :alt="service" />
</NuxtLink>
</template>
</div>
</div>
</address>
</template>
<style scoped lang="scss">
.base-byline {
--color: var(--foreground);
--title-color: var(--gray-400);
--text-shadow: none;
color: var(--color);
display: flex;
gap: var(--space-2);
font-style: normal;
align-items: flex-start;
.avatar {
border-radius: var(--rounded-full);
inline-size: var(--space-11);
block-size: var(--space-11);
object-fit: cover;
background: var(--gray-100);
}
div {
text-shadow: var(--text-shadow);
}
.name,
.title {
margin: 0;
}
.title {
color: var(--title-color);
font-size: var(--font-size-sm);
line-height: var(--line-height-sm);
text-wrap: balance;
}
.share-icons {
display: flex;
align-items: center;
gap: var(--space-5);
img {
margin-block-start: var(--space-1);
width: var(--space-6);
height: auto;
filter: brightness(1);
transition: filter var(--duration-150) var(--ease-out);
&:hover {
transition: none;
filter: brightness(50);
}
}
}
}
</style>
239 changes: 239 additions & 0 deletions components/Tv/TVReactions.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
<script setup>
import { createItem, updateItem } from '@directus/sdk';
const { $directusTv } = useNuxtApp();
const ariaId = useId();
const props = defineProps({
episodeId: {
type: String,
required: true,
},
});
const visitorId = useCookie('visitor_id');
const loading = ref(false);
const error = ref(null);
const success = ref(false);
const reaction = reactive({
comments: '',
});
const textarea = ref(null);
const isOpen = ref(false);
const ratingMessages = {
dislike: 'Woof! 🤦‍♂️ How can we do better?',
like: 'Nice! 👍 Anything we can improve upon?',
love: `Awesome! The whole team is rejoicing in celebration! 🥳🎉🎊 Anything you'd like to say to them?`,
};
async function submitReaction(type) {
isOpen.value = true;
loading.value = true;
if (type) {
reaction.type = type;
}
try {
if (reaction.id) {
const response = await $directusTv.request(
updateItem('tv_episode_reactions', reaction.id, {
episode: props.episodeId,
type: reaction.type,
comments: reaction.comments,
visitor_id: visitorId.value,
}),
);
if (response.comments) {
success.value = true;
await setTimeout(() => {
isOpen.value = false;
}, 2000);
}
} else {
const data = await $directusTv.request(
createItem('tv_episode_reactions', {
episode: props.episodeId,
type: reaction.type,
comments: reaction.comments,
visitor_id: visitorId.value,
}),
);
reaction.id = data.id;
}
} catch (e) {
error.value = e;
} finally {
loading.value = false;
}
}
watch(isOpen, (value) => {
if (value) {
setTimeout(() => {
textarea.value.focus();
}, 100);
}
});
onKeyStroke('Escape', () => {
isOpen.value = false;
});
</script>
<template>
<div>
<VDropdown :aria-id class="reactions" :triggers="[]" :shown="isOpen" :auto-hide="false">
<button
v-tooltip="`I do not like this`"
class="feedback-button"
:aria-pressed="reaction.type === 'dislike'"
@click="() => submitReaction('dislike')"
>
<BaseIcon name="thumb_down" />
</button>
<button
v-tooltip="'I like this'"
class="feedback-button"
:aria-pressed="reaction.type === 'like'"
@click="() => submitReaction('like')"
>
<BaseIcon name="thumb_up" />
</button>
<button
v-tooltip="'I freaking love this'"
:aria-pressed="reaction.type === 'love'"
class="feedback-button"
@click="() => submitReaction('love')"
>
<BaseIcon name="favorite" />
</button>
<template #popper>
<ThemeProvider variant="dark">
<div class="popover">
<template v-if="!success">
<p>{{ ratingMessages[reaction.type] }}</p>
<textarea ref="textarea" v-model="reaction.comments" class="input" placeholder="Give us your feedback" />
<BaseButton
type="button"
:loading="loading"
:disabled="loading"
color="primary"
label="Send Your Feedback"
@click="submitReaction(reaction.type)"
/>
</template>
<template v-else-if="error">
<p>Whoops! There was an error submitting your feedback.</p>
</template>
<template v-else-if="success">
<p>Thank you for your feedback!</p>
</template>
<button class="close-button" @click="() => (isOpen = false)">
<BaseIcon name="close" />
</button>
</div>
</ThemeProvider>
</template>
</VDropdown>
</div>
</template>

<style lang="scss" scoped>
.reactions {
display: flex;
align-content: center;
border-radius: var(--rounded-full);
background: var(--gray-100);
padding: var(--space-1);
gap: var(--space-1);
}
.feedback-button {
transition: background-color 0.2s;
background: none;
border: none;
cursor: pointer;
padding: var(--space-2);
&:hover {
background: var(--gray-200);
}
border-radius: var(--rounded-full);
&[aria-pressed='true'] {
background: var(--primary-500);
color: var(--white);
}
}
.popover {
position: relative;
width: 350px;
padding: var(--space-5);
background-color: var(--gray-100);
border-radius: var(--rounded-xl);
border: 2px solid var(--gray-200);
display: flex;
flex-direction: column;
gap: var(--space-2);
box-shadow: var(--shadow-lg);
button {
width: auto;
}
.close-button {
position: absolute;
top: var(--space-2);
right: var(--space-2);
background: none;
border: none;
cursor: pointer;
padding: var(--space-2);
border-radius: var(--rounded-full);
&:hover {
background: var(--gray-200);
}
}
}
.input {
color: var(--gray-900);
width: 100%;
height: 100px;
border-radius: 4px;
padding: 0.375rem 0.75rem;
background-color: var(--gray-50);
border: 1px solid var(--gray-200);
&:focus {
border-color: var(--primary);
outline: none;
box-shadow: 0px 0px var(--space-1) 0px var(--primary-100);
}
}
</style>

<style lang="css">
.v-popper--theme-dropdown {
.v-popper__inner {
background-color: transparent !important;
border: none !important;
border-radius: 6px;
}
.v-popper__arrow-container {
.v-popper__arrow-outer {
border-color: #334155;
}
.v-popper__arrow-inner {
border-color: #334155;
}
}
}
</style>
3 changes: 2 additions & 1 deletion components/Tv/TVShow.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<NuxtLink :to="`/tv/${slug}`" class="show">
<NuxtLink :to="url ? url : `/tv/${slug}`" class="show">
<div class="tile" :style="`background-image: url(${directusUrl}/assets/${tile}?width=600)`">
<span v-if="overlay">{{ overlay }}</span>
</div>
Expand All @@ -16,6 +16,7 @@ const {
const directusUrl = process.env.DIRECTUS_TV_URL || tvUrl;
defineProps({
url: String,
slug: String,
tile: String,
title: String,
Expand Down
9 changes: 9 additions & 0 deletions layouts/tv.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@
useHead({
bodyAttrs: { class: 'tv' },
});
// Generate a unique visitor ID for each user of TV pages to track reactions
const visitorId = useCookie('visitor_id');
if (!visitorId.value) {
const id = useId();
visitorId.value = id;
}
</script>

<template>
Expand Down
Loading

0 comments on commit 0cc4117

Please sign in to comment.