• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kiva / ui / 29121608769

10 Jul 2026 08:30PM UTC coverage: 84.723% (-0.4%) from 85.167%
29121608769

push

github

web-flow
feat: borrower profile migration AD-1 (#7044)

* feat(borrower-profile): expand ALLOWED_LOAN_STATUSES to all loan statuses

Enable the borrower profile to handle all loan statuses instead of
redirecting unknown statuses to the legacy PHP page.

AD-218

* feat(borrower-profile): add status-aware behavior to BP child components

- LoanProgress: add payingBack repayment progress, status labels for
  terminal statuses, hide progress bar for non-active statuses, update
  /lend-classic/ links to /lend/
- LendCta: simplify state() to pass non-fundraising statuses through,
  invert showNonActionableLoanButton logic, default ctaButtonText to
  "Find another loan"
- LoanDetails: add terminal date display (expired, refunded, defaulted,
  ended) with formatted date row
- CommentsAndWhySpecial: add author role badges, borrower comment
  styling, admin delete button, subscribe/unsubscribe toggle

AD-218

* refactor(borrower-profile): extract FullBorrowerProfile and simplify page wrapper

- Rename FundedBorrowerProfile to MinimalBorrowerProfile, update links
  from /lend-classic/ to /lend/
- Extract full layout into FullBorrowerProfile.vue, deriving loan data
  from a single loan prop instead of many individual props
- Simplify BorrowerProfile.vue to a thin routing wrapper between full
  and minimal views based on status + privilege
- Add showFullView computed with isPrivileged and ?minimal=false support
- Remove legacy redirect logic (kvlendborrowerbeta cookie, MARS-358)
- Derive page meta from this.loan instead of separate data properties
- Add status-aware copy to MinimalBorrowerProfile for expired/refunded

AD-218

* feat(borrower-profile): add LoanTags component and GraphQL mutations

- Port TagSelector from rtp-nuxt, adapted for Options API
- Display applied tags with toggle for tag editing (logged-in only)
- Two separate queries: availableTags + loanTags
- GraphQL mutations: addOrRemoveTagOnLoan, deleteComment,
  subscribeToLoanComments, unsubscribeFromLoanCo... (continued)

5948 of 6607 branches covered (90.03%)

Branch coverage included in aggregate %.

2530 of 3158 new or added lines in 45 files covered. (80.11%)

30 existing lines in 6 files now uncovered.

48073 of 57155 relevant lines covered (84.11%)

31.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.97
/src/util/userPreferenceUtils.js
1
import { gql } from 'graphql-tag';
1✔
2
import setMyKivaGoalMutation from '#src/graphql/mutation/accountSettings/setMyKivaGoal.graphql';
1✔
3
import logReadQueryError from './logReadQueryError';
1✔
4

5
export const createUserPreferencesMutation = gql`
1✔
6
        mutation createUserPreferences($preferences: String) {
1✔
7
                my {
1✔
8
                        createUserPreferences(userPreferences: { preferences: $preferences }) {
1✔
9
                                id
1✔
10
                                preferences
1✔
11
                        }
1✔
12
                }
1✔
13
        }
1✔
14
`;
1✔
15

16
export const updateUserPreferencesMutation = gql`
1✔
17
        mutation UpdateUserPreferences(
1✔
18
                $updateUserPreferencesId: Int!,
1✔
19
                $preferences: String
1✔
20
        ) {
1✔
21
                my {
1✔
22
                        updateUserPreferences(id: $updateUserPreferencesId, userPreferences: {
1✔
23
                                preferences: $preferences
1✔
24
                }) {
1✔
25
                                id
1✔
26
                                preferences
1✔
27
                        }
1✔
28
                }
1✔
29
        }
1✔
30
`;
1✔
31

32
/**
1✔
33
 * Creates user preferences for the current user
1✔
34
 *
1✔
35
 * @param apollo The current Apollo client
1✔
36
 * @param newPreferences The new preferences to add
1✔
37
 * @returns The results of the mutation
1✔
38
 */
1✔
39
export const createUserPreferences = async (apollo, newPreferences) => {
1✔
40
        try {
3✔
41
                return await apollo.mutate({
3✔
42
                        mutation: createUserPreferencesMutation,
3✔
43
                        variables: { preferences: JSON.stringify(newPreferences) },
3✔
44
                });
3✔
45
        } catch (e) {
3✔
46
                logReadQueryError(e, 'userPreferenceUtils createUserPreferencesMutation');
1✔
47
        }
1✔
48
};
3✔
49

50
/**
1✔
51
 * Updates the user preferences for the current user
1✔
52
 *
1✔
53
 * @param apollo The current Apollo client
1✔
54
 * @param userPreferences The original user preferences
1✔
55
 * @param parsedPreferences The parsed user preferences
1✔
56
 * @param newPreferences The new preferences to add
1✔
57
 * @returns The updated user preferences
1✔
58
 */
1✔
59
export const updateUserPreferences = async (apollo, userPreferences, parsedPreferences, newPreferences) => {
1✔
60
        try {
3✔
61
                const mergedPreferences = { ...parsedPreferences, ...newPreferences };
3✔
62
                const preferences = JSON.stringify(mergedPreferences);
3✔
63
                return await apollo.mutate({
3✔
64
                        mutation: updateUserPreferencesMutation,
3✔
65
                        variables: {
3✔
66
                                updateUserPreferencesId: userPreferences.id,
3✔
67
                                preferences,
3✔
68
                        },
3✔
69
                });
3✔
70
        } catch (e) {
3✔
71
                logReadQueryError(e, 'userPreferenceUtils updateUserPreferencesMutation');
1✔
72
        }
1✔
73
};
3✔
74

75
/**
1✔
76
 * Read a single key out of a userPreferences node's JSON `preferences` blob.
1✔
77
 *
1✔
78
 * @param {{preferences?: string}|null} userPreferences
1✔
79
 * @param {string} key
1✔
80
 * @returns {*} the stored value, or null when absent/unparseable.
1✔
81
 */
1✔
82
export const getUserPreference = (userPreferences, key) => {
1✔
83
        if (!userPreferences?.preferences) return null;
8✔
84
        try {
6✔
85
                return JSON.parse(userPreferences.preferences)[key] ?? null;
8✔
86
        } catch {
8✔
87
                return null;
2✔
88
        }
2✔
89
};
8✔
90

91
/**
1✔
92
 * Upsert a single preference key for the current user: merge-update when a
1✔
93
 * record exists, otherwise create one.
1✔
94
 *
1✔
95
 * @param {object} apollo The current Apollo client
1✔
96
 * @param {{id?: number, preferences?: string}|null} userPreferences
1✔
97
 * @param {string} key
1✔
98
 * @param {*} value
1✔
99
 * @returns The result of the underlying create/update mutation
1✔
100
 */
1✔
101
export const setUserPreference = async (apollo, userPreferences, key, value) => {
1✔
102
        if (userPreferences?.id) {
2✔
103
                let parsed = {};
1✔
104
                try {
1✔
105
                        parsed = JSON.parse(userPreferences.preferences || '{}');
1!
106
                } catch {
1!
NEW
107
                        parsed = {};
×
NEW
108
                }
×
109
                return updateUserPreferences(apollo, userPreferences, parsed, { [key]: value });
1✔
110
        }
1✔
111
        return createUserPreferences(apollo, { [key]: value });
1✔
112
};
1✔
113

114
/**
1✔
115
 * Set a My Kiva goal using the setMyKivaGoal mutation
1✔
116
 *
1✔
117
 * @param apollo The current Apollo client
1✔
118
 * @param {Object} goalData The goal data to upsert
1✔
119
 * @param {string} goalData.category The goal category
1✔
120
 * @param {number} goalData.target The target number of loans
1✔
121
 * @param {string} goalData.dateStarted The date the goal was started (ISO-8601)
1✔
122
 * @param {string} goalData.status The goal status
1✔
123
 * @returns The result of the mutation
1✔
124
 */
1✔
125
export const setMyKivaGoal = async (apollo, {
1✔
126
        category, target, dateStarted, status
×
127
}) => {
×
128
        return apollo.mutate({
×
129
                mutation: setMyKivaGoalMutation,
×
130
                variables: {
×
131
                        category,
×
132
                        target,
×
133
                        dateStarted,
×
134
                        status,
×
135
                },
×
136
        });
×
137
};
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc