Skip to content

feat: [MP-2477] - new next steps page final sync#6711

Open
cristhianDt wants to merge 28 commits intomainfrom
feature/mp-2477-new-nextSteps-page-final-sync
Open

feat: [MP-2477] - new next steps page final sync#6711
cristhianDt wants to merge 28 commits intomainfrom
feature/mp-2477-new-nextSteps-page-final-sync

Conversation

@cristhianDt
Copy link
Contributor

@cristhianDt cristhianDt commented Mar 6, 2026

MP-2477

Summary

Create a new /mykiva/next-steps page, changes:

  1. refactor journeyCard
  2. moving out common methods, ref, computed to a new composable file
  3. create new Page component
  4. more unit tests
  5. fix some affected tests

https://kiva.atlassian.net/browse/MP-2477

Evidences

New Page with goal set

new.page.next.steps.with.goal.mov

New page without goal and full cards enabled

https://drive.google.com/file/d/19LvNVHOnJtUMW-XSZzdnKLR6d-rtH9zp/view?usp=sharing

No affected existing MyKiva page

https://drive.google.com/file/d/1O37jMjtOE_KXHY-4zqx0JJMa2m3ubBjg/view?usp=sharing

tiarascoleman and others added 23 commits February 24, 2026 22:53
…ature/mp-2477-new-nextSteps-page-second-alternative
hide non badges slides in second section
@cristhianDt cristhianDt requested a review from a team March 6, 2026 00:55
@cristhianDt cristhianDt added the b2c Sends B2C team a message in Slack on PR creation label Mar 6, 2026
@infante-jaime
Copy link
Contributor

Tested locally looking good

@roger-in-kiva
Copy link
Collaborator

I see this warn in console:
[Vue warn]: Property "hideGoalCard" was accessed during render but is not defined on instance.

:accepted="acceptedEmailMarketingUpdates"
:loans="loans"
:latest-loan="latestLoan"
@accept-email-updates="acceptedEmailMarketingUpdates = true"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Are we expecting some tracking here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We have, I'm not in front on my laptop but I think the recording is in the parent

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok, the recording is located in the component look:

v-kv-track-event="['portfolio', 'click', 'next-step-email-optin']"

};
const showSurveyCard = computed(() => checkShowSurveyCard({
showPostLendingNextStepsCards: props.showPostLendingNextStepsCards,
postLendingNextStepsEnable: props.postLendingNextStepsEnable,
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is probably no more needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So we are not using this flags anymore, is that?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah, that feat is rolled out to all users now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok, again, I would like this to be handled in a dedicated PR. I have already made many changes to match with main and make more changes that are not part of the requirement that could extend even more this ticket, I have to wait a week for other changes. let's leave this for a future task

storeGoalPreferences: goalData.storeGoalPreferences,
loadGoalData: goalData.loadGoalData,
trackingCategory: 'portfolio',
goalsV2Enabled: props.goalsV2Enabled,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Probably no more needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

same answer (a ticket to clean up this project)


<style lang="postcss" scoped>
.stats-wrapper {
height: auto;
Copy link
Collaborator

Choose a reason for hiding this comment

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

tw-h-auto

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed

@cristhianDt
Copy link
Contributor Author

I see this warn in console:

[Vue warn]: Property "hideGoalCard" was accessed during render but is not defined on instance.

Hi @roger-in-kiva could share me a screenshot and the whole error by slack , thanks

loans: mockLoans,
});

export const EmptyState = story({
Copy link
Collaborator

Choose a reason for hiding this comment

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

Duplicate story.

loans: mockLoans,
});

export const AllRegionsLent = story({
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we ever show this in app? Because the UI doesn't really make sense. If it's not used, we can remove. But if it's used, we need to chat with design about making this more usable.

Image

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well it's true but right now it is a probability, so I will start a new thread in our channel about , I mean if I should hide the module if all the regions were lent or not, agree?

Copy link
Collaborator

Choose a reason for hiding this comment

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

What happened previously? We can replicate that behavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

for the component a blank space but in the UI, I mean, the page we are hiding the section look this line

return this.isNextStepsExpEnabled && !this.postLendingNextStepsEnable && !this.userLentToAllRegions;
, as it's a storybook I would like to move forward in the meantime until talk with design

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah, I think there's no hidden state for the map. Although,I would show the region map and only hide cards/components that have a logic to hide after an action or logic. E.g. survey card

}
},
{
path: '/mykiva/next-steps',
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please also create a flux repo PR to add this URL to dev (see other recent PRs for where/how to do this).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I just want to have this PR ready, let me create the other for flux 👍

*/
export const getUrlParamsFromString = url => {
const urlSplit = url.split('?');
return urlSplit[1];
Copy link
Collaborator

Choose a reason for hiding this comment

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

Are we always guaranteed a value here or do we need a fallback?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

mmm I can added it but this is an old logic, just moved to this new util, let me check


if (urlParams && urlParams.includes(REFER_FRIEND_MODAL_KEY) && modalHandlers.openSharingModal) {
const paramsSplit = urlParams.split('=');
if (paramsSplit && paramsSplit[1] === 'true') {
Copy link
Collaborator

Choose a reason for hiding this comment

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

toLowerCase needed here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

same answer like my prev comment, I will check the old logic for that


const urlParams = getUrlParamsFromString(secondaryCtaUrl);

if (urlParams && urlParams.includes(JOURNEY_MODAL_KEY) && modalHandlers.updateJourney) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Need to handle casing here?

isTieredAchievementComplete,
getActiveTierData,
});
return buildUniversalOrderedSlides({
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we ever need to use universal ordered slides? Most of this carousel is being moved to the new page.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

probably not, in fact, I have another idea in mind for those components, but making changes over changes has already made me dedicate more time, I mean, syncs, etc. If it's not a problem, I'd like to create a task for that a kind of “tech enhancement"

* @param {Function} options.getActiveTierData - From useBadgeData
* @returns {Array} Achievement slide objects
*/
export const buildAchievementSlides = ({
Copy link
Collaborator

Choose a reason for hiding this comment

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

It seems like these two methods could live in the other util with the similar method buildUniversalOrderedSlides, if we indeed need both.

import { optimizeContentfulUrl } from '#src/util/imageUtils';
import { formatUiSetting } from '#src/util/contentfulUtils';

export const getRichTextContent = slide => slide?.fields?.richText?.content ?? [];
Copy link
Collaborator

Choose a reason for hiding this comment

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

Methods missing jsdoc comments like other util files in this changeset.

* @param {number|null} params.slidesNumber - Max slides to return (null = no limit)
* @returns {Array} Ordered sequence of slide objects
*/

Copy link
Collaborator

Choose a reason for hiding this comment

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

Remove extra newline.

* @returns {Array} Ordered sequence of slide objects
*/

export const buildUniversalOrderedSlides = ({
Copy link
Collaborator

Choose a reason for hiding this comment

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

See other comment, I'm not convinced we need this method now that the new page is built.

@@ -0,0 +1,255 @@
/**
Copy link
Collaborator

Choose a reason for hiding this comment

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

This file is name as a composable and in the composables folder, but it's actually a set of methods. Change to be a util file.

} from '#src/util/myKivaUtils';

let badgeDataInstance = null;
const getBadgeData = () => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This pattern is confusing. It means that this file, which is a util, is putting an instance of a. composable in memory. As far as I'm aware, that's not the intended pattern for composables and could be tough to maintain long-term, since you'll have to look at this file and the components to know where the composables are being instantiated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the idea is have just 1 instance of badgeData and used cross the Util (renamed), do you have any other ideas to suggest?

</WwwPage>
</template>

<script setup>
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is this second in many ways identical page needed? Could we instead revert most of the changes to MyKivaPage and add a prop to that page that loads different content with the same data loaded?

Copy link
Collaborator

Choose a reason for hiding this comment

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

In other words the new content here could be in a new component like MyKivaPageContent but with the same data loading page parent.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

that would mean modifying LendingStats to extract the information from there as well, and how would it determine which content to load? using the router?

Copy link
Collaborator

Choose a reason for hiding this comment

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

You could add a prop to MyKivaPage at the router level that would render one of the two templates. That prop could then also be provided to LendingStats This reduce the need for a good portion of the refactor. it would also remove the need for the in-memory storage added to the util file. Thoughts?

There have been a significant number of bugs lately in the MyKiva progress, and while we can explore this level of a refactor, it will take lots and lots and lots of functional testing with several different types of users to validate the functionality.

return (!userGoal.value || !userGoalAchieved.value) && !hideCompletedGoalCard.value;
});

onMounted(async () => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

We load most of this data on SSR on MyKivaPage, and we could either do the same here in an options API component, or like the other comment, utilize the same data-loading parent and remove a lot of duplicate from this component (and simplify the utils greatly).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, this relates to the discussion we had on Slack regarding the repositories. My plan is to shift the approach to use the Composition API, which will make it easier to interpret, migrate, and standardize across other repositories. I also think it's important to abstract the data loading and processing from the UI or template itself. The trend of having one large file with everything makes it difficult to maintain, especially as the project continues to grow.

I'm happy to close this PR and open a new one with a different approach if needed. However, it would be valuable to have a clear and defined vision for the future of our frontend repositories, so we can establish a unified standard—and why not work toward consolidating into a single frontend project if that is the case

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will work in a new alternative following your coments

Copy link
Collaborator

Choose a reason for hiding this comment

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

All good ideas, but we don't have the time or resources to do that level of tech debt refactor at this time in Kiva's life. Like mentioned in Slack, ui generally should remain Options API due to how preFetch works. There are instances where Composition API can be used, but it has to be components that are entirely FE.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Also a quick note that if we do in fact need the level of refactor in this PR for the new page, we can keep exploring that, I just think it's worth investing a little time today to explore if a simpler option is valid here. And we generally don't use in-memory "state" outside of composables and Apollo, so that would need extensive testing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah totally, okey, I created a draft with that alternative last week because that was one of my ideas as well let me work on that, but as I told you I wanted to switch from the API Options to the Composition API in the parent where we are loading the data

Copy link
Collaborator

Choose a reason for hiding this comment

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

Components that load data often need SSR, so they will need to stay Options API. I know it's annoying to have two Vue repos with different paradigms, but just part of working at an old company with only a handful of FE engineers 😿. Thanks for the flexibility in trying different options for this changeset!

Another good thing to consider in the meantime is putting up a smaller PR with aspects like pulling out the region component. This PR being 3K lines is really tough to get solid code reviews on.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In fact, my initial idea was to create at least two PRs to make it easier to review, but I didn't expect this ticket to take so long, depending on other changes, synchronizing with main, and again, it delayed me

Copy link
Contributor Author

Choose a reason for hiding this comment

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

so in the end I created just one PR (not the best idea of course)

Copy link
Collaborator

@dyersituations dyersituations left a comment

Choose a reason for hiding this comment

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

Overall nice work here. I'm not against changing the general pattern, but please dig into my comments around sharing the parent. If that seemed like a decent path forward, a new prop could be added to the parent, most of the file reverted here, and many of the utils removed (including instantiating the composable in memory in a JS file rather than a component).

this.goalModalHandlers.closeImpactInsightsModal(this.modalRefs);
},
openGoalModal(event) {
this.isUpdatingGoal = event?.updating || false;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@roger-in-kiva I synced my branch again with your recent changes, could you please check this file and useMyKivaJourneyData https://github.com/kiva/ui/pull/6711/changes#diff-2e1ede34c2bfa402da0a0903e75e34e01c2fef011d22fa2e366e2135441e7c0c

thank you

1. remove an empty state story for MyKivaRegionExperience.stories.js
2. css rules
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

b2c Sends B2C team a message in Slack on PR creation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants