Skip to content

Commit

Permalink
Wall of Love
Browse files Browse the repository at this point in the history
  • Loading branch information
Lindsey Zylstra committed Nov 14, 2024
1 parent 542c7ce commit 1c8b752
Show file tree
Hide file tree
Showing 10 changed files with 329 additions and 2 deletions.
13 changes: 13 additions & 0 deletions assets/svg/placeholder-avatar.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions assets/svg/star.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions components/Base/Block.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ const components: Record<BlockType, ReturnType<typeof resolveComponent>> = {
block_carousel_cards: resolveComponent('BlockCarouselCard'),
block_masonry_grid: resolveComponent('BlockMasonryGrid'),
block_masonry_grid_card: resolveComponent('BlockMasonryGridCard'),
block_wall_of_love: resolveComponent('BlockWallOfLove'),
testimonials: resolveComponent('BlockTestimonials'),
block_code: resolveComponent('BlockCode'),
block_columns: resolveComponent('BlockColumns'),
block_cta: resolveComponent('BlockCta'),
Expand Down
158 changes: 158 additions & 0 deletions components/Block/WallOfLove/Testimonials.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
<script setup lang="ts">
import type { BlockProps } from '../types';
const { $directus, $readItem } = useNuxtApp();
interface TestimonialsProps extends BlockProps {
uuid: string;
}
const props = defineProps<TestimonialsProps>();
import placeholderAvatar from '~/assets/svg/placeholder-avatar.svg';
import starIcon from '~/assets/svg/star.svg';
const {
public: { directusUrl },
} = useRuntimeConfig();
const { data: block } = useAsyncData(`testimonials-${props.uuid}`, () =>
$directus.request(
$readItem('testimonials', props.uuid, {
fields: ['id', 'company', 'name', 'role', 'quote', 'logo', 'avatar', 'avatar_url'],
}),
),
);
const avatarImageUrl = computed(() => {
if (block.value) {
if (block.value.avatar_url) {
return block.value.avatar_url;
} else if (block.value.avatar) {
const url = new URL(`/assets/${block.value.avatar}`, directusUrl as string);
return url.toString();
}
}
return placeholderAvatar;
});
const logoImageUrl = computed(() => {
if (block.value && block.value.logo) {
const url = new URL(`/assets/${block.value.logo}`, directusUrl as string);
return url.toString();
}
return null;
});
</script>

<template>
<div v-if="block" class="testimonial-card">
<div class="header">
<img :src="avatarImageUrl" alt="Avatar" class="avatar" />
<div class="info">
<strong>{{ block.name }}</strong>
<div class="role">{{ block.role }}</div>
</div>
</div>
<BaseText class="quote" :content="block.quote" align="start" color="foreground"></BaseText>
<div class="footer">
<img v-if="logoImageUrl" :src="logoImageUrl" alt="Company Logo" class="company-logo" />
<div v-else class="logo-placeholder"></div>
<div class="stars">
<img v-for="index in 5" :key="index" :src="starIcon" alt="Star" class="star-icon" />
</div>
</div>
</div>
</template>

<style lang="scss" scoped>
.testimonial-card {
border-radius: var(--Large, 12px);
border: 1px solid var(--Border-Normal, #d3dae4);
background: #fff;
display: flex;
min-width: 358px;
min-height: 330px;
padding: 32px;
flex-direction: column;
gap: 32px;
.header {
display: flex;
align-items: center;
margin-bottom: 12px;
justify-content: center;
.avatar {
width: 50px;
height: 50px;
border-radius: 50%;
margin-right: 12px;
}
.info {
display: flex;
flex-direction: column;
strong {
font-size: 1.1em;
}
.role {
font-size: 0.9em;
color: #666;
}
}
}
.quote {
margin-bottom: 12px;
font-style: italic;
flex-grow: 1;
:deep(> *) {
quotes: auto;
&::before {
content: open-quote;
position: absolute;
translate: -0.7ch 0;
}
&::after {
content: close-quote;
}
}
}
.footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-top: 16px;
.company-logo {
max-height: 40px;
max-width: 200px;
width: auto;
}
.logo-placeholder {
width: 40px;
height: 40px;
border-radius: 4px;
}
.stars {
display: flex;
gap: 4px;
.star-icon {
width: 16px;
height: 16px;
}
}
}
}
</style>
117 changes: 117 additions & 0 deletions components/Block/WallOfLove/WallOfLove.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import Testimonials from './Testimonials.vue';
const { $directus, $readItem } = useNuxtApp();
const props = defineProps<{ uuid: string }>();
const { data: block } = useAsyncData(`wall-of-love-${props.uuid}`, () =>
$directus.request(
$readItem('block_wall_of_love', props.uuid, {
fields: ['id', 'heading', { testimonials: ['testimonials_id'] }],
}),
),
);
const showAll = ref(false);
const displayedTestimonials = computed(() => {
if (!block.value || !block.value.testimonials) return [];
return showAll.value ? block.value.testimonials : block.value.testimonials.slice(0, 6);
});
const toggleShowAll = () => {
showAll.value = !showAll.value;
};
</script>

<template>
<div v-if="block" class="wall-of-love">
<h2 class="wall-heading">{{ block.heading }}</h2>
<div class="testimonial-container">
<Testimonials
v-for="testimonial in displayedTestimonials"
:key="testimonial.testimonials_id"
:uuid="testimonial.testimonials_id"
class="testimonial-item"
/>
<!-- Fading effect -->
<div v-if="!showAll && block.testimonials && block.testimonials.length > 6" class="fade-out"></div>
</div>

<BaseButton
v-if="block.testimonials && block.testimonials.length > 6"
size="large"
type="button"
color="secondary"
icon="arrow_forward"
class="see-more-button"
outline
:label="showAll ? 'See Less Testimonials' : 'See More Testimonials'"
@click="toggleShowAll"
/>
</div>
</template>

<style lang="scss" scoped>
.wall-of-love {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
.wall-heading {
margin-bottom: 16px;
text-align: center;
font-size: 1.5em;
font-weight: bold;
}
.testimonial-container {
margin-top: 32px;
display: flex;
flex-wrap: wrap;
gap: 16px;
justify-content: center;
width: 100%;
position: relative;
}
.testimonial-item {
flex: 1 1 calc(33.333% - 16px);
min-width: 300px;
box-sizing: border-box;
}
@media (max-width: 768px) {
.testimonial-item {
flex: 1 1 calc(50% - 16px);
}
}
@media (max-width: 480px) {
.testimonial-item {
flex: 1 1 100%;
}
}
.fade-out {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 450px;
background: linear-gradient(to bottom, transparent, rgba(255, 255, 255, 0.9));
pointer-events: none;
z-index: 1;
}
.see-more-button {
margin: 16px auto;
align-self: center;
position: relative;
z-index: 2;
}
}
</style>
11 changes: 11 additions & 0 deletions types/schema/blocks/block-testimonials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface BlockTestimonials {
id: string;
sort: number;
name: string;
company: string | null;
role: string | null;
quote: string;
logo: string | File | null;
avatar: string | File | null;
avatar_url: string | null;
}
11 changes: 11 additions & 0 deletions types/schema/blocks/block-wall-of-love.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface BlockWallOfLove {
id: string;
heading: string | null;
testimonials: BlockWallOfLoveTestimonials[];
}

export interface BlockWallOfLoveTestimonials {
id: string;
block_wall_of_love_id: string;
testimonials_id: string;
}
10 changes: 8 additions & 2 deletions types/schema/blocks/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import type { BlockCarousel } from './block-carousel.js';
import type { BlockCarouselCards } from './block-carousel-cards.js';
import type { BlockMasonryGrid } from './block-masonry-grid.js';
import type { BlockMasonryGridCard } from './block-masonry-grid-card.js';
import type { BlockWallOfLove } from './block-wall-of-love.js';
import type { BlockTestimonials } from './block-testimonials.js';

export type BlockType =
| 'block_accordion_group'
Expand Down Expand Up @@ -64,7 +66,9 @@ export type BlockType =
| 'block_carousel'
| 'block_carousel_cards'
| 'block_masonry_grid'
| 'block_masonry_grid_card';
| 'block_masonry_grid_card'
| 'testimonials'
| 'block_wall_of_love';

export type Block =
| BlockAccordion
Expand Down Expand Up @@ -99,4 +103,6 @@ export type Block =
| BlockCarousel
| BlockCarouselCards
| BlockMasonryGrid
| BlockMasonryGridCard;
| BlockMasonryGridCard
| BlockTestimonials
| BlockWallOfLove;
2 changes: 2 additions & 0 deletions types/schema/blocks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@ export type * from './block-carousel.js';
export type * from './block-carousel-cards.js';
export type * from './block-masonry-grid.js';
export type * from './block-masonry-grid-card.js';
export type * from './block-testimonials.js';
export type * from './block-wall-of-love.js';
4 changes: 4 additions & 0 deletions types/schema/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ import type {
BlockCarouselCards,
BlockMasonryGrid,
BlockMasonryGridCard,
BlockTestimonials,
BlockWallOfLove,
} from './blocks/index.js';
import type {
Form,
Expand Down Expand Up @@ -139,6 +141,8 @@ export interface Schema {
block_carousel_cards: BlockCarouselCards[];
block_masonry_grid: BlockMasonryGrid[];
block_masonry_grid_card: BlockMasonryGridCard[];
block_wall_of_love: BlockWallOfLove[];
testimonials: BlockTestimonials[];

// Meta
globals: Globals;
Expand Down

0 comments on commit 1c8b752

Please sign in to comment.