Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: badge in progress content component #5618

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
264 changes: 264 additions & 0 deletions src/components/MyKiva/BadgeInProgress.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
<template>
<div>
<div
class="tw-pt-1 tw-pb-1.5 tw-flex tw-flex-col md:tw-flex-row tw-items-center tw-justify-left tw-gap-3"
>
<BadgeContainer :status="BADGE_IN_PROGRESS" :shape="getBadgeShape()" class="tw-z-1">
<img
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dashed border is showing up inside the badge icon. It should show around the outside. Is this because of the maxheight override? Also, it would be helpful to add BadgeModal stories to cover the 5 different badge use cases, to ensure the badge icon looks correctly for each.

Related, this version of the BadgeModal requires there to be modal content to be complete compared to the mocks (modal without title, back button, etc.). Is that something you see as a follow-up ticket?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, badge image is old, new one looks good
image

And yeah, I think the other elements should be handled while integrating this in BadgeModal, I'll create a separate ticket to integrate them

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good regarding the follow-up ticket. Please include a link to the mocks and bullets for the specific requirements in the mocks that are still needed.

:src="badgeImage"
alt="Badge"
style="max-height: 133px;"
@load="isBadgeImageLoaded = true"
>
</BadgeContainer>
<div>
<h3>{{ badgeName }}</h3>
<p>{{ subHead }}</p>
</div>
</div>
<KvCarousel
ref="kvCarouselRef"
:key="carouselKey"
class="kv-carousel tw-w-full md:tw-block tw-hidden"
:embla-options="{
loop: false,
startIndex,
}"
:multiple-slides-visible="true"
slides-to-scroll="visible"
:slide-max-width="singleSlideWidth"
@interact-carousel="onInteractCarousel"
>
<template
v-for="(loanId, index) in augmentedLoanIds"
:key="`badge-in-progress-carousel-${index}-${loanId}`" #[`slide${index}`]
>
<KvClassicLoanCardContainer
:loan-id="loanId"
:show-tags="true"
:use-full-width="true"
:show-view-loan="true"
class="tw-h-full"
/>
</template>
<template v-if="showViewMoreCard" #[`${viewAllLoansSlideIndex}`]>
<div :key="`load-more-card`" class="tw-flex tw-justify-center tw-items-center tw-h-full">
<KvButton
class="tw-mx-1 tw-mb-3 tw-whitespace-nowrap"
variant="secondary"
@click="onLoadMore"
>
Load more
</KvButton>
</div>
</template>
</KvCarousel>
<div
class="md:tw-hidden tw-grid tw-justify-items-center tw-gap-3 md:tw-gap-y-4 md:tw-py-1.5"
>
<template
v-for="(loanId, index) in augmentedLoanIds"
:key="`badge-in-progress-carousel-${index}-${loanId}`"
>
<KvClassicLoanCardContainer
:loan-id="loanId"
:show-tags="true"
:show-view-loan="true"
class="tw-h-full"
/>
</template>
</div>
<div class="tw-mt-4 tw-flex tw-flex-col md:tw-hidden tw-justify-center tw-w-auto tw-mx-auto">
<KvButton
class="tw-mx-1 tw-mb-3 tw-whitespace-nowrap"
variant="secondary"
@click="onLoadMore"
>
Load more
</KvButton>
</div>
</div>
</template>

<script setup>

import KvClassicLoanCardContainer from '#src/components/LoanCards/KvClassicLoanCardContainer';
import KvCarousel from '@kiva/kv-components/vue/KvCarousel';
import KvButton from '@kiva/kv-components/vue/KvButton';
import {
computed, watch, ref, inject
} from 'vue';
import useBadgeModal, {
MOBILE_BREAKPOINT,
BADGE_IN_PROGRESS,
} from '#src/composables/useBadgeModal';
import { useRouter } from 'vue-router';
import useIsMobile from '#src/composables/useIsMobile';
import BadgeContainer from './BadgeContainer';

const props = defineProps({
/**
* {
* id: '',
* fields: {
* challengeName: '',
* shareFact: '',
* badgeImage: {
* fields: {
* file: {
* url: '',
* },
* },
* },
* },
* totalProgressToAchievement,
* tiers: [
* {
* target: 2,
* completedDate: null,
* },
* ],
* }
*/
badge: {
type: Object,
default: () => ({}),
},
isLoading: {
type: Boolean,
default: false
},
loanIds: {
type: Array,
default: () => ([])
},
loanLimit: {
type: Number,
default: 1
},
showViewMoreCard: {
type: Boolean,
default: false
}
});

const isLoading = computed(() => props.isLoading ?? false);
const loanIds = computed(() => props.loanIds ?? []);
const loanLimit = computed(() => props.loanLimit ?? 1);
const loanDisplayCount = ref(3);
const loadMoreClicked = ref(false);
const kvCarouselRef = ref(null);
const $kvTrackEvent = inject('$kvTrackEvent');
const carouselKey = ref('badge-in-progress-carousel');
const startIndex = ref(0);
const {
getBadgeShape,
getPrefilteredUrl,
} = useBadgeModal(props.badge);
const { isMobile } = useIsMobile(MOBILE_BREAKPOINT);

const router = useRouter();
const forceRerender = () => {
carouselKey.value += 1;
startIndex.value = 1;
};

watch(loanIds, async ids => {
if (!isLoading.value) {
const defaultInitialCount = 3;
const loansCount = ids.length;
loanDisplayCount.value = loansCount > defaultInitialCount ? defaultInitialCount : loansCount;
loadMoreClicked.value = loansCount === loanDisplayCount.value;
kvCarouselRef.value?.reInitVisible();
}
}, { immediate: true });

const augmentedLoanIds = computed(() => {
if (isLoading.value) {
return [...Array(6).fill(0)];
}
return [...loanIds.value].splice(0, loanDisplayCount.value);
});

const viewAllLoansSlideIndex = computed(() => {
const slideIndex = augmentedLoanIds?.value?.length ?? 0;
return `slide${slideIndex + 1}`;
});

const singleSlideWidth = computed(() => {
const viewportWidth = typeof window !== 'undefined' ? window.innerWidth : 1024;
// Handle tiny screens
if (viewportWidth < 414) {
return `${viewportWidth - 80}px`;
}
if (viewportWidth >= 414 && viewportWidth < 768) return '278px';
if (viewportWidth >= 768 && viewportWidth < 1024) return '328px';
return '328px';
});

const onInteractCarousel = interaction => {
$kvTrackEvent('carousel', 'click-carousel-horizontal-scroll', interaction);
};

const badgeName = computed(() => props.badge?.fields?.challengeName ?? '');
// TODO: Replace once the work of using the icon of the correct in-progress badge level is complete
const badgeImage = computed(() => props.badge?.fields?.badgeImage?.fields?.file?.url ?? '');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will need to change once we update to use all badge icons, this needs to be the badge of the current level. I'm creating utils to get the correct tier. Please add a TODO here to use the icon of the correct in-progress badge level.

const badgeLevel = computed(() => props.badge?.fields?.level ?? '');
const totalProgress = computed(() => props.badge?.totalProgressToAchievement ?? 0);
const tiers = computed(() => props.badge?.tiers ?? []);

const sortedTiers = computed(() => {
const defaultTiers = [...tiers.value];
return defaultTiers.sort((a, b) => a.target - b.target);
});

const currentTier = computed(() => {
return sortedTiers.value?.find(tier => !tier?.completedDate) ?? null;
});

const target = computed(() => {
return currentTier.value?.target ?? 0;
});

const subHead = computed(() => `${totalProgress.value} of ${target.value} loans completed`);

const onLoadMore = () => {
let initialLabelString = 'Scroll to';
if (isMobile.value) {
initialLabelString = 'Load';
}
if (!loadMoreClicked.value) {
$kvTrackEvent('portfolio', 'click', `${initialLabelString} more loans`, badgeName.value, badgeLevel.value);

loanDisplayCount.value = loanLimit.value;
loadMoreClicked.value = true;
return forceRerender();
}

$kvTrackEvent(
'portfolio',
'click',
`${initialLabelString} more loans on loan finding page`,
badgeName.value,
badgeLevel.value
);

router.push(`lend/filter?${getPrefilteredUrl()}`);
};

</script>

<style lang="postcss" scoped>

.kv-carousel >>> div[aria-label*=screen] {
@apply tw-invisible;
}

.badge-container {
width: 175px;

@screen md {
width: 220px;
}
}
</style>
28 changes: 27 additions & 1 deletion src/composables/useBadgeModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ export const ID_US_ECONOMIC_EQUALITY = 'us-economic-equality';
export const ID_CLIMATE_ACTION = 'climate-action';
export const ID_REFUGEE_EQUALITY = 'refugee-equality';
export const ID_BASIC_NEEDS = 'basic-needs';
// TODO: CLIMATE_ACTION_FILTER and BASIC_NEEDS_FILTER require special params to be correctly implemented
export const US_ECONOMIC_EQUALITY_FILTER = 'country=PR,US';
export const CLIMATE_ACTION_FILTER = 'attribute=32&tag=9,8';
export const REFUGEE_EQUALITY_FILTER = 'attribute=28';
export const WOMENS_EQUALITY_FILTER = 'gender=female';
export const BASIC_NEEDS_FILTER = 'sector=6,10&attribute=8';

/**
* General utilities for the MyKiva badge modal
Expand Down Expand Up @@ -179,7 +185,27 @@ export default function useBadgeModal(currentBadge) {
}
};

/**
* Gets the URL params of the badge to be used in lend/filter
* @returns The URL params
*/
const getPrefilteredUrl = () => {
christian14b marked this conversation as resolved.
Show resolved Hide resolved
switch (badge.value.id) {
case ID_WOMENS_EQUALITY:
return WOMENS_EQUALITY_FILTER;
case ID_US_ECONOMIC_EQUALITY:
return US_ECONOMIC_EQUALITY_FILTER;
case ID_CLIMATE_ACTION:
return CLIMATE_ACTION_FILTER;
case ID_REFUGEE_EQUALITY:
return REFUGEE_EQUALITY_FILTER;
case ID_BASIC_NEEDS:
default:
return BASIC_NEEDS_FILTER;
}
};

return {
getTierPositions, getLineComponent, getLineStyle, getBadgeShape
getTierPositions, getLineComponent, getLineStyle, getBadgeShape, getPrefilteredUrl
};
}
14 changes: 14 additions & 0 deletions test/unit/specs/composables/useBadgeModal.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import useBadgeModal,
ID_CLIMATE_ACTION,
ID_REFUGEE_EQUALITY,
ID_BASIC_NEEDS,
US_ECONOMIC_EQUALITY_FILTER,
WOMENS_EQUALITY_FILTER,
} from '#src/composables/useBadgeModal';
import LineLarge from '#src/assets/images/my-kiva/journey-line-large.svg';
import LineMedium from '#src/assets/images/my-kiva/journey-line-medium.svg';
Expand Down Expand Up @@ -151,4 +153,16 @@ describe('useBadgeModal.js', () => {
expect(getBadgeShape()).toEqual(BADGE_SHAPE_CIRCLE);
});
});

describe('getPrefilteredUrl', () => {
it('should return expected shape for us-economic-equality', () => {
const { getPrefilteredUrl } = useBadgeModal({ id: ID_US_ECONOMIC_EQUALITY });
expect(getPrefilteredUrl()).toEqual(US_ECONOMIC_EQUALITY_FILTER);
});

it('should return expected shape for women-equality', () => {
const { getPrefilteredUrl } = useBadgeModal({ id: ID_WOMENS_EQUALITY });
expect(getPrefilteredUrl()).toEqual(WOMENS_EQUALITY_FILTER);
});
});
});
Loading