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

kiva / ui / 15935961384

27 Jun 2025 09:06PM UTC coverage: 51.428% (+0.2%) from 51.236%
15935961384

Pull #6108

github

web-flow
Merge 78e5d5de3 into 25cec199c
Pull Request #6108: fix: mp-1721 / Make Nav Header Sticky

1804 of 3739 branches covered (48.25%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

73 existing lines in 2 files now uncovered.

2643 of 4908 relevant lines covered (53.85%)

275.43 hits per line

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

27.08
/src/components/WwwFrame/TheHeader.vue
1
<template>
2
        <header
3
                class="tw-transition-all tw-duration-1000 tw-ease-in-out"
4
                :class="isInExperimentPages & enableAddToBasketExp ? 'tw-sticky tw-top-0' : ''"
2!
5
                style="z-index: 999;"
6
        >
7
                <nav
8
                        aria-label="Primary navigation"
9
                        class="tw-bg-primary tw-border-b tw-border-tertiary tw-relative"
10
                >
11
                        <kv-page-container>
12
                                <!-- minimal header -->
13
                                <template v-if="minimal">
14
                                        <div class="tw-flex tw-justify-center">
15
                                                <a
16
                                                        class="header__button tw-inline-flex"
17
                                                        :href="homePagePath"
18
                                                        data-testid="header-home"
19
                                                        v-kv-track-event="['TopNav','click-Logo']"
20
                                                >
21
                                                        <kiva-logo class="tw-w-6 tw-text-brand" style="transform: translateY(-0.1875rem);" />
22
                                                        <span class="tw-sr-only">Kiva Home</span>
×
23
                                                </a>
24
                                        </div>
25
                                </template>
26

27
                                <!-- Corporate header for /cc pages -->
28
                                <template v-else-if="corporate">
29
                                        <div
30
                                                class="
1✔
31
                                                tw-flex tw-gap-2.5 lg:tw-gap-6 tw-items-center align-middle"
32
                                        >
33
                                                <campaign-logo-group
34
                                                        class="tw-h-2.5 lg:tw-h-3.5"
35
                                                        :corporate-logo-url="corporateLogoUrl"
36
                                                        :logo-height="logoHeight"
37
                                                        :logo-classes="logoClasses"
38
                                                />
39
                                                <div class="tw-flex-1"></div>
×
40
                                                <span
UNCOV
41
                                                        @click="$emit('show-basket')"
×
42
                                                        data-testid="header-basket"
43
                                                        class="header__button header__basket tw-cursor-pointer"
44
                                                        v-kv-track-event="['TopNav','click-Basket']"
45
                                                        :style="isBasketLoading ? {
×
46
                                                                display: 'var(--ui-data-corporate-basket-count-display, inline-flex)',
47
                                                        } : {
48
                                                                display: hasBasket ? 'inline-flex' : 'none'
49
                                                        }"
50
                                                >
51
                                                        <span class="tw-bg-secondary tw-rounded-sm tw-py-0.5 tw-px-1 tw-mr-1">
1✔
52
                                                                <div v-if="isBasketLoading" class="tw-w-1 tw-h-3">
53
                                                                        <kv-loading-placeholder />
54
                                                                </div>
55
                                                                <template v-else>
56
                                                                        {{ basketCount - lcaLoanCount }}
57
                                                                </template>
58
                                                        </span>
×
59
                                                        Basket
60
                                                </span>
61
                                                <my-kiva-button
62
                                                        v-show="!isVisitor"
63
                                                        :id="myKivaMenuId"
64
                                                        class="header__button header__portfolio"
65
                                                        :balance="balance"
66
                                                        :is-user-data-loading="isUserDataLoading"
67
                                                        :profile-pic="profilePic"
68
                                                        :profile-pic-id="profilePicId"
69
                                                />
70
                                                <kv-button
71
                                                        variant="secondary"
72
                                                        v-show="isVisitor"
73
                                                        class="tw-bg-white tw-whitespace-nowrap"
74
                                                        :to="loginUrl"
75
                                                        data-testid="header-log-in"
76
                                                        v-kv-track-event="['TopNav','click-Sign-in']"
77
                                                >
78
                                                        Log in
79
                                                </kv-button>
×
80
                                        </div>
81
                                </template>
82

83
                                <!-- Default Header -->
84
                                <template v-else>
85
                                        <div
86
                                                class="header
87
                                                        tw-grid xl:tw-gap-x-4 tw-items-center"
88
                                                :class="{
2✔
89
                                                        'tw-gap-x-1 ': isMobile,
90
                                                        'tw-gap-x-2.5': !isMobile,
91
                                                        'header--mobile-open': searchOpen || isVisitor,
92
                                                        'header--tablet-open': openTabletVariant,
93
                                                }"
94
                                        >
95
                                                <!-- Logo -->
96
                                                <div class="header__logo">
1✔
97
                                                        <a
98
                                                                class="header__button tw-inline-flex"
99
                                                                :href="homePagePath"
100
                                                                data-testid="header-home"
101
                                                                v-kv-track-event="['TopNav','click-Logo']"
102
                                                        >
103
                                                                <kiva-logo class="tw-w-6 tw-text-brand" style="transform: translateY(-0.1875rem);" />
104
                                                                <span class="tw-sr-only">Kiva Home</span>
2✔
105
                                                        </a>
106
                                                </div>
107

108
                                                <!-- Lend -->
109
                                                <router-link
110
                                                        :id="lendMenuId"
111
                                                        to="/lend-by-category"
112
                                                        data-testid="header-lend"
113
                                                        class="header__button header__lend tw-inline-flex"
114
                                                        v-kv-track-event="['TopNav','click-Lend']"
115
                                                        @pointerenter.stop="onLendLinkPointerEnter"
116
                                                        @pointerleave.stop="onLendLinkPointerLeave"
117
                                                        @pointerup.stop="onLendLinkPointerUp"
118
                                                        @click.stop="onLendLinkClick"
119
                                                >
120
                                                        <span class="tw-flex tw-items-center">Lend
1!
121
                                                                <kv-material-icon
122
                                                                        class="tw-w-3 tw-h-3 tw-transition-transform tw-duration-300"
123
                                                                        :icon="mdiChevronDown"
124
                                                                        :class="{'tw-rotate-180' : isLendMenuVisible}"
125
                                                                />
126
                                                        </span>
127
                                                </router-link>
128

129
                                                <transition name="kvfastfade">
130
                                                        <div
131
                                                                v-show="isLendMenuVisible"
132
                                                                class="
133
                                                                        tw-absolute tw-left-0 tw-right-0 tw-top-8 md:tw-top-9 tw-z-dropdown
134
                                                                        tw-bg-primary tw-border-b tw-border-tertiary"
135
                                                                data-testid="header-lend-dropdown-list"
136
                                                                style="margin-top: 1px;"
137
                                                        >
138
                                                                <kv-page-container>
139
                                                                        <the-lend-menu
140
                                                                                ref="lendMenu"
141
                                                                                @pointerenter="onLendMenuPointerEnter"
142
                                                                                @pointerleave="onLendMenuPointerLeave"
143
                                                                        />
144
                                                                </kv-page-container>
145
                                                        </div>
146
                                                </transition>
147

148
                                                <!-- Search -->
149
                                                <div
150
                                                        v-if="!hideSearchInHeader"
151
                                                        data-testid="header-search-area"
152
                                                        id="top-nav-search-area"
153
                                                        class="
154
                                                                header__search
155
                                                                tw-py-1.5 md:py-0
156
                                                                tw--mx-2.5 tw-px-2 md:tw-mx-0 md:tw-px-0
157
                                                                tw-border-t tw-border-tertiary md:tw-border-t-0
158
                                                                lg:tw-block
159
                                                        "
160
                                                        :class="{
7!
161
                                                                'tw-hidden': !searchOpen || isVisitor,
162
                                                                'md:tw-hidden': hasBasket && isVisitor && !searchOpen || !searchOpen,
163
                                                                'md:tw-block': searchOpen || !isVisitor,
164
                                                                'md:!tw-block': searchOpen && hasBasket && balance || !hasBasket,
165
                                                                'lg:tw-block': hasBasket,
166
                                                        }"
167
                                                >
168
                                                        <search-bar ref="search" />
169
                                                </div>
170

171
                                                <div
172
                                                        class="header__right-side
173
                                                tw-flex tw-justify-end xl:tw-gap-4 align-middle"
174
                                                        :class="{
175
                                                                'tw-gap-1': isMobile,
176
                                                                'tw-gap-2.5': !isMobile,
177
                                                        }"
178
                                                >
179
                                                        <!-- Mobile Search Toggle -->
180
                                                        <button
181
                                                                class="header__button header__search-icon tw-inline-flex"
182
                                                                :class="{
2✔
183
                                                                        '!tw-hidden': isVisitor,
184
                                                                        'md:!tw-hidden': !hasBasket,
185
                                                                        'md:!tw-inline-flex lg:!tw-hidden': isVisitor && hasBasket,
186
                                                                        'lg:!tw-hidden': !isVisitor,
187
                                                                }"
188
                                                                v-show="!hideSearchInHeader"
189
                                                                data-testid="header-mobile-search-toggle"
190
                                                                :aria-expanded="searchOpen ? 'true' : 'false'"
1!
191
                                                                :aria-pressed="searchOpen ? 'true' : 'false'"
1!
192
                                                                aria-controls="top-nav-search-area"
UNCOV
193
                                                                @click="toggleMobileSearch"
×
194
                                                                v-kv-track-event="['TopNav','click-search-toggle']"
195
                                                        >
196
                                                                <kv-material-icon class="tw-w-3 tw-h-3" :icon="mdiMagnify" />
197
                                                        </button>
198

199
                                                        <!-- Borrow -->
200
                                                        <router-link
201
                                                                v-show="!isMobile"
202
                                                                to="/borrow"
203
                                                                data-testid="header-borrow"
204
                                                                class="header__borrow"
205
                                                                :class="{
206
                                                                        'tw-hidden': !isVisitor,
207
                                                                        'header__button !tw-hidden md:!tw-flex': isVisitor
208
                                                                }"
209
                                                                v-kv-track-event="['TopNav','click-Borrow']"
210
                                                        >
211
                                                                Borrow
212
                                                        </router-link>
×
213

214
                                                        <!-- Teams -->
215
                                                        <teams-menu
216
                                                                v-if="!isVisitor && teamsMenuEnabled"
1!
217
                                                                class="tw-hidden lg:tw-block"
218
                                                                :teams="teams"
219
                                                        />
220

221
                                                        <!-- About -->
222
                                                        <div class="tw-group" :class="{ 'tw-hidden md:tw-block': !isVisitor }">
223
                                                                <router-link
224
                                                                        :id="aboutMenuId"
225
                                                                        to="/about"
226
                                                                        data-testid="header-about"
227
                                                                        class="header__about header__button tw-inline-flex"
228
                                                                        v-kv-track-event="['TopNav','click-About']"
229
                                                                >
230
                                                                        <span class="tw-flex">
1!
231
                                                                                About
232
                                                                                <kv-material-icon
233
                                                                                        class="tw-w-3 tw-h-3
234
                                                                                        tw-transition-transform tw-duration-300 group-hover:tw-rotate-180"
235
                                                                                        :icon="mdiChevronDown"
236
                                                                                />
237
                                                                        </span>
238
                                                                </router-link>
239
                                                                <kv-dropdown
240
                                                                        :controller="aboutMenuId"
241
                                                                        class="dropdown-list"
242
                                                                        data-testid="header-about-dropdown-list"
243
                                                                >
244
                                                                        <ul>
245
                                                                                <li>
246
                                                                                        <router-link
247
                                                                                                to="/about"
248
                                                                                                v-kv-track-event="['TopNav','click-About-About us']"
249
                                                                                        >
250
                                                                                                About us
251
                                                                                        </router-link>
×
252
                                                                                </li>
253
                                                                                <li>
254
                                                                                        <a
255
                                                                                                href="/about/partner-with-us"
1✔
256
                                                                                                v-kv-track-event="['TopNav','click-About-Partner with us']"
257
                                                                                        >
258
                                                                                                Partner with us
259
                                                                                        </a>
1✔
260
                                                                                </li>
261
                                                                                <li>
262
                                                                                        <a
263
                                                                                                href="/about/how"
1✔
264
                                                                                                v-kv-track-event="['TopNav','click-About-How Kiva works']"
265
                                                                                        >
266
                                                                                                How Kiva works
267
                                                                                        </a>
1✔
268
                                                                                </li>
269
                                                                                <li>
270
                                                                                        <router-link
271
                                                                                                to="/donate/supportus"
272
                                                                                                v-kv-track-event="['TopNav', 'click-Support-Kiva']"
273
                                                                                        >
274
                                                                                                Support Kiva
275
                                                                                        </router-link>
×
276
                                                                                </li>
277
                                                                                <li>
278
                                                                                        <router-link
279
                                                                                                to="/about/where-kiva-works"
280
                                                                                                v-kv-track-event="['TopNav','click-About-Where Kiva works']"
281
                                                                                        >
282
                                                                                                Where Kiva works
283
                                                                                        </router-link>
×
284
                                                                                </li>
285
                                                                                <li>
286
                                                                                        <router-link
287
                                                                                                to="/impact"
288
                                                                                                v-kv-track-event="['TopNav','click-About-Impact']"
289
                                                                                        >
290
                                                                                                Impact
291
                                                                                        </router-link>
×
292
                                                                                </li>
293
                                                                                <li>
294
                                                                                        <router-link
295
                                                                                                to="/about/leadership"
296
                                                                                                v-kv-track-event="['TopNav','click-About-Leadership']"
297
                                                                                        >
298
                                                                                                Leadership
299
                                                                                        </router-link>
×
300
                                                                                </li>
301
                                                                                <li>
302
                                                                                        <router-link
303
                                                                                                to="/about/finances"
304
                                                                                                v-kv-track-event="['TopNav','click-About-Finances']"
305
                                                                                        >
306
                                                                                                Finances
307
                                                                                        </router-link>
×
308
                                                                                </li>
309
                                                                                <li>
310
                                                                                        <a
311
                                                                                                href="/about/press-center"
1✔
312
                                                                                                v-kv-track-event="['TopNav','click-About-Press']"
313
                                                                                        >
314
                                                                                                Press
315
                                                                                        </a>
1✔
316
                                                                                </li>
317
                                                                                <li>
318
                                                                                        <router-link
319
                                                                                                to="/about/due-diligence"
320
                                                                                                v-kv-track-event="['TopNav','click-About-Due diligence']"
321
                                                                                        >
322
                                                                                                Due diligence
323
                                                                                        </router-link>
×
324
                                                                                </li>
325
                                                                        </ul>
326
                                                                </kv-dropdown>
327
                                                        </div>
328

329
                                                        <!-- Basket -->
330
                                                        <div
331
                                                                :style="isBasketLoading ? {
1!
332
                                                                        display: 'var(--ui-data-basket-count-display, flex)',
333
                                                                } : {
334
                                                                        display: hasBasket ? 'flex' : 'none'
335
                                                                }"
336
                                                        >
337
                                                                <router-link
338
                                                                        to="/basket"
339
                                                                        data-testid="header-basket"
340
                                                                        class="header__button header__basket"
341
                                                                        :class="{
342
                                                                                'tw-hidden md:tw-flex': !hasLargeBasket,
343
                                                                                'tw-flex': hasLargeBasket,
344
                                                                        }"
345
                                                                        v-kv-track-event="['TopNav','click-Basket']"
346
                                                                >
347
                                                                        <span class="tw-bg-secondary tw-rounded-sm tw-py-0.5 tw-px-1 tw-mr-1">
1✔
348
                                                                                <div v-if="isBasketLoading" class="tw-w-1 tw-h-3">
349
                                                                                        <kv-loading-placeholder />
350
                                                                                </div>
351
                                                                                <template v-else>
352
                                                                                        {{ basketNumber }}
353
                                                                                </template>
354
                                                                        </span>
355
                                                                        <span class="tw-hidden md:tw-flex">Basket</span>
×
356
                                                                </router-link>
357

358
                                                                <!-- Mobile Basket -->
359
                                                                <router-link
360
                                                                        to="/basket"
361
                                                                        data-testid="header-basket"
362
                                                                        class="tw-flex tw-items-center tw-relative md:tw-hidden tw-text-eco-green-4"
363
                                                                        v-kv-track-event="['TopNav','click-Basket']"
364
                                                                >
365
                                                                        <span
366
                                                                                class="tw-absolute tw-w-4 tw-h-4 tw-pt-0.5
1✔
367
                                                                                        tw-flex tw-items-center tw-justify-center
368
                                                                                        tw-text-white tw-text-small tw-font-medium"
369
                                                                        >
370
                                                                                <div v-if="isBasketLoading" class="tw-w-1 tw-h-1.5">
371
                                                                                        <kv-loading-placeholder />
372
                                                                                </div>
373
                                                                                <template v-else>
374
                                                                                        {{ basketCount }}
375
                                                                                </template>
376
                                                                        </span>
377
                                                                        <kv-material-icon
378
                                                                                :icon="mdiBriefcase"
379
                                                                                class="tw-inline-block tw-w-4 tw-h-4"
380
                                                                        />
381
                                                                </router-link>
382
                                                        </div>
383

384
                                                        <!-- Log in Link -->
385
                                                        <router-link
386
                                                                v-show="isVisitor"
387
                                                                class="header__button tw-bg-white tw-whitespace-nowrap tw-inline-flex"
388
                                                                :to="loginUrl"
389
                                                                data-testid="header-log-in"
390
                                                                v-kv-track-event="['TopNav','click-Sign-in']"
391
                                                        >
392
                                                                Log in
393
                                                        </router-link>
×
394

395
                                                        <!-- Support Kiva -->
396
                                                        <kv-button
397
                                                                variant="secondary"
398
                                                                v-show="!isMobile"
399
                                                                class="tw-hidden md:tw-block tw-bg-white tw-whitespace-nowrap"
400
                                                                href="/donate/supportus"
401
                                                                data-testid="header-support-kiva"
402
                                                                v-kv-track-event="['TopNav', 'click-Support-Kiva']"
403
                                                        >
404
                                                                Support Kiva
405
                                                        </kv-button>
1✔
406

407
                                                        <!-- Logged in Profile -->
408
                                                        <my-kiva-button
409
                                                                v-show="!isVisitor"
410
                                                                :id="myKivaMenuId"
411
                                                                class="header__button header__portfolio"
412
                                                                :balance="balance"
413
                                                                :is-user-data-loading="isUserDataLoading"
414
                                                                :profile-pic="profilePic"
415
                                                                :profile-pic-id="profilePicId"
416
                                                        />
417

418
                                                        <kv-dropdown
419
                                                                :controller="myKivaMenuId"
420
                                                                v-show="!isVisitor"
421
                                                                class="dropdown-list"
422
                                                                data-testid="header-my-kiva-dropdown-list"
423
                                                                id="my-kiva-dropdown"
424
                                                                ref="userDropdown"
425
                                                        >
426
                                                                <ul>
427
                                                                        <template v-if="isBorrower">
428
                                                                                <li>
429
                                                                                        <router-link
430
                                                                                                to="/my/borrower"
431
                                                                                                v-kv-track-event="['TopNav','click-Portfolio-My borrower dashboard']"
432
                                                                                        >
433
                                                                                                My borrower dashboard
434
                                                                                        </router-link>
×
435
                                                                                </li>
436
                                                                                <template v-if="loanId !== null">
437
                                                                                        <li>
438
                                                                                                <router-link
439
                                                                                                        :to="`/lend/${loanId}`"
440
                                                                                                        v-kv-track-event="['TopNav','click-Portfolio-My loan page']"
441
                                                                                                >
442
                                                                                                        My loan page
443
                                                                                                </router-link>
×
444
                                                                                        </li>
445
                                                                                        <li>
446
                                                                                                <router-link
447
                                                                                                        :to="`/lend-classic/${loanId}#loanComments`"
448
                                                                                                        v-kv-track-event="['TopNav','click-Portfolio-My Conversations']"
449
                                                                                                >
450
                                                                                                        My conversations
451
                                                                                                </router-link>
×
452
                                                                                        </li>
453
                                                                                </template>
454
                                                                        </template>
455
                                                                        <template v-if="isTrustee">
456
                                                                                <template v-if="!isBorrower">
457
                                                                                        <li>
458
                                                                                                <router-link
459
                                                                                                        :to="trusteeLoansUrl"
460
                                                                                                        v-kv-track-event="['TopNav','click-Portfolio-My Trustee loans']"
461
                                                                                                >
462
                                                                                                        My Trustee loans
463
                                                                                                </router-link>
×
464
                                                                                        </li>
465
                                                                                        <li>
466
                                                                                                <router-link
467
                                                                                                        :to="`/trustees/${trusteeId}`"
468
                                                                                                        v-kv-track-event="[
469
                                                                                                                'TopNav',
470
                                                                                                                'click-Portfolio-My public Trustee page'
471
                                                                                                        ]"
472
                                                                                                >
473
                                                                                                        My public Trustee page
474
                                                                                                </router-link>
×
475
                                                                                        </li>
476
                                                                                </template>
477
                                                                                <li>
478
                                                                                        <router-link
479
                                                                                                to="/my/trustee"
480
                                                                                                v-kv-track-event="['TopNav','click-Portfolio-My Trustee dashboard']"
481
                                                                                        >
482
                                                                                                My Trustee dashboard
483
                                                                                        </router-link>
×
484
                                                                                </li>
485
                                                                                <hr>
×
486
                                                                        </template>
487
                                                                        <li>
488
                                                                                <router-link
489
                                                                                        to="/portfolio"
490
                                                                                        v-kv-track-event="['TopNav','click-Portfolio-Portfolio']"
491
                                                                                >
492
                                                                                        Portfolio
493
                                                                                </router-link>
×
494
                                                                        </li>
495
                                                                        <li>
496
                                                                                <router-link
497
                                                                                        to="/teams/my-teams"
498
                                                                                        v-kv-track-event="['TopNav','click-Portfolio-My teams']"
499
                                                                                >
500
                                                                                        My teams
501
                                                                                </router-link>
×
502
                                                                        </li>
503
                                                                        <li>
504
                                                                                <router-link
505
                                                                                        to="/portfolio/donations"
506
                                                                                        v-kv-track-event="['TopNav','click-Portfolio-Donations']"
507
                                                                                >
508
                                                                                        Donations
509
                                                                                </router-link>
×
510
                                                                        </li>
511
                                                                        <li>
512
                                                                                <router-link
513
                                                                                        to="/settings"
514
                                                                                        v-kv-track-event="['TopNav','click-Portfolio-Settings']"
515
                                                                                >
516
                                                                                        Settings
517
                                                                                </router-link>
×
518
                                                                        </li>
519
                                                                        <li v-show="isMobile">
520
                                                                                <router-link
521
                                                                                        to="/donate/supportus"
522
                                                                                        v-kv-track-event="['TopNav','click-Support-Kiva']"
523
                                                                                >
524
                                                                                        Support Kiva
525
                                                                                </router-link>
×
526
                                                                        </li>
527
                                                                        <hr>
2✔
528
                                                                        <li>
529
                                                                                <router-link
530
                                                                                        to="/ui-logout"
531
                                                                                        v-kv-track-event="['TopNav','click-Portfolio-Sign out']"
532
                                                                                >
533
                                                                                        Sign out
534
                                                                                </router-link>
×
535
                                                                        </li>
536
                                                                </ul>
537
                                                        </kv-dropdown>
538
                                                </div>
539
                                        </div>
540
                                </template>
541
                        </kv-page-container>
542
                </nav>
543
                <promo-credit-banner v-if="!hidePromoCreditBanner" />
2✔
544
        </header>
545
</template>
546

547
<script>
548
import { defineAsyncComponent } from 'vue';
549
import {
550
        hasLentBeforeCookie,
551
        hasDepositBeforeCookie,
552
        userHasLentBefore,
553
        userHasDepositBefore,
554
} from '#src/util/optimizelyUserMetrics';
555
import { setHotJarUserAttributes } from '#src/util/hotJarUtils';
556
import headerQueryPrivate from '#src/graphql/query/wwwHeaderPrivate.graphql';
557
import headerQueryPublic from '#src/graphql/query/wwwHeaderPublic.graphql';
558
import KivaLogo from '#src/assets/inline-svgs/logos/kiva-logo.svg';
559
import KvDropdown from '#src/components/Kv/KvDropdown';
560
import {
561
        mdiChevronDown,
562
        mdiMagnify,
563
        mdiBriefcase,
564
} from '@mdi/js';
565
import CampaignLogoGroup from '#src/components/CorporateCampaign/CampaignLogoGroup';
566
import _throttle from 'lodash/throttle';
567
import numeral from 'numeral';
568
import MyKivaButton from '#src/components/WwwFrame/Header/MyKivaButton';
569
import TeamsMenu from '#src/components/WwwFrame/Header/TeamsMenu';
570
import { readBoolSetting } from '#src/util/settingsUtils';
571
import experimentVersionFragment from '#src/graphql/fragments/experimentVersion.graphql';
572
import addToBasketExpMixin from '#src/plugins/add-to-basket-exp-mixin';
573
import myKivaHomePageMixin from '#src/plugins/my-kiva-homepage-mixin';
574
import {
575
        KvButton, KvLoadingPlaceholder, KvMaterialIcon, KvPageContainer
576
} from '@kiva/kv-components';
577
import SearchBar from './SearchBar';
578
import PromoCreditBanner from './PromotionalBanner/Banners/PromoCreditBanner';
579

580
const COMMS_OPT_IN_EXP_KEY = 'opt_in_comms';
1✔
581

582
export default {
1✔
583
        name: 'TheHeader',
584
        components: {
585
                CampaignLogoGroup,
586
                KivaLogo,
587
                KvDropdown,
588
                KvLoadingPlaceholder,
589
                KvMaterialIcon,
590
                KvPageContainer,
591
                MyKivaButton,
592
                PromoCreditBanner,
593
                SearchBar,
594
                KvButton,
UNCOV
595
                TheLendMenu: defineAsyncComponent(() => import('#src/components/WwwFrame/LendMenu/TheLendMenu')),
×
596
                TeamsMenu,
597
        },
598
        inject: {
599
                apollo: { default: null },
600
                cookieStore: { default: null },
601
                kvAuth0: { default: null },
602
        },
603
        mixins: [addToBasketExpMixin, myKivaHomePageMixin],
604
        data() {
605
                return {
1✔
606
                        aboutMenuId: 'about-header-dropdown',
607
                        balance: 0,
608
                        basketCount: 0,
609
                        basketTotal: 0,
610
                        hasEverLoggedIn: false,
611
                        hideLendMenuTimeout: null,
612
                        isBasketLoading: false,
613
                        isBorrower: false,
614
                        isLendMenuDesired: false,
615
                        isLendMenuVisible: false,
616
                        isMobile: false,
617
                        isUserDataLoading: false,
618
                        lcaLoanCount: 0,
619
                        lendMenuId: 'lend-header-dropdown',
620
                        loanId: null,
621
                        mdiBriefcase,
622
                        mdiChevronDown,
623
                        mdiMagnify,
624
                        myKivaMenuId: 'my-kiva-header-dropdown',
625
                        profilePic: '',
626
                        profilePicId: null,
627
                        searchOpen: false,
628
                        teams: null,
629
                        teamsMenuEnabled: false,
630
                        trusteeId: null,
631
                        userId: null,
632
                };
633
        },
634
        emits: ['show-basket'],
635
        props: {
636
                hideSearchInHeader: {
637
                        type: Boolean,
638
                        default: false,
639
                },
640
                minimal: {
641
                        type: Boolean,
642
                        default: false
643
                },
644
                corporate: {
645
                        type: Boolean,
646
                        default: false
647
                },
648
                corporateLogoUrl: {
649
                        type: String,
650
                        default: ''
651
                },
652
                logoHeight: {
653
                        type: String,
654
                        default: '28',
655
                        required: false
656
                },
657
                logoClasses: {
658
                        type: String,
659
                        default: '',
660
                        required: false
661
                },
662
        },
663
        computed: {
664
                isVisitor() {
665
                        return !this.userId && !this.$renderConfig?.cdnNotedLoggedIn;
1✔
666
                },
667
                isTrustee() {
668
                        return !!this.trusteeId;
1✔
669
                },
670
                trusteeLoansUrl() {
UNCOV
671
                        return {
×
672
                                path: '/lend',
673
                                query: {
674
                                        trustee: this.trusteeId,
675
                                        status: 'fundraising',
676
                                        sortBy: 'newest',
677
                                }
678
                        };
679
                },
680
                hasBasket() {
681
                        if (this.corporate) {
1!
UNCOV
682
                                return this.basketCount - this.lcaLoanCount > 0;
×
683
                        }
684
                        return this.basketCount > 0;
1✔
685
                },
686
                hidePromoCreditBanner() {
687
                        // hide this banner on managed lending landing + checkout pages
688
                        const routeExclusions = ['/cc', '/checkout'];
1✔
689
                        const routePath = this.$route?.path;
1✔
690
                        const matchedRoutes = routeExclusions.filter(item => {
1✔
691
                                return routePath.indexOf(item) !== -1;
2✔
692
                        });
693
                        return matchedRoutes.length > 0;
1✔
694
                },
695
                loginUrl() {
696
                        if (this.$route.path === '/') {
1!
697
                                return '/ui-login';
1✔
698
                        }
UNCOV
699
                        return `/ui-login?doneUrl=${encodeURIComponent(this.$route.fullPath)}`;
×
700
                },
701
                openTabletVariant() {
702
                        return (this.hasBasket && this.isVisitor) || (this.hasBasket || this.balance);
1!
703
                },
704
                hasLargeBasket() {
705
                        return this.basketTotal > 500;
1✔
706
                },
707
                basketNumber() {
708
                        // Show basket $ total if basket is over $500 total
709
                        if (this.hasLargeBasket) {
×
UNCOV
710
                                return numeral(this.basketTotal).format('$0,0');
×
711
                        }
UNCOV
712
                        return this.basketCount;
×
713
                },
714
        },
715
        apollo: [
716
                {
717
                        query: headerQueryPublic,
718
                        preFetch: true,
719
                        result({ data }) {
UNCOV
720
                                this.teamsMenuEnabled = readBoolSetting(data, 'general.teamsMenuEnabled.value');
×
721
                        },
722
                },
723
                {
724
                        query: headerQueryPrivate,
725
                        preFetch: true,
726
                        shouldPreFetch(config, { renderConfig }) {
727
                                // Don't prefetch if using CDN caching
UNCOV
728
                                return !renderConfig.useCDNCaching;
×
729
                        },
730
                        result({ data }) {
731
                                this.isBasketLoading = false;
×
732
                                this.isUserDataLoading = false;
×
733
                                this.userId = data?.my?.userAccount?.id ?? null;
×
734
                                this.isBorrower = data?.my?.isBorrower ?? false;
×
735
                                this.loanId = data?.my?.mostRecentBorrowedLoan?.id ?? null;
×
736
                                this.trusteeId = data?.my?.trustee?.id ?? null;
×
737
                                this.basketCount = data?.shop?.nonTrivialItemCount ?? 0;
×
738
                                this.balance = Math.floor(data?.my?.userAccount?.balance ?? 0);
×
739
                                this.profilePic = data?.my?.lender?.image?.url ?? '';
×
740
                                this.profilePicId = data?.my?.lender?.image?.id ?? null;
×
741
                                this.hasEverLoggedIn = data?.hasEverLoggedIn;
×
742
                                this.basketTotal = numeral(data.shop?.basket?.totals?.itemTotal).value() || 0;
×
UNCOV
743
                                this.teams = data?.my?.teams ?? {};
×
744
                        },
745
                },
746
        ],
747
        created() {
748
                this.isBasketLoading = this.$renderConfig?.useCDNCaching ?? false;
1✔
749
                this.isUserDataLoading = this.$renderConfig?.useCDNCaching && this.$renderConfig?.cdnNotedLoggedIn;
1!
750
        },
751
        mounted() {
752
                const { version } = this.apollo.readFragment({
1✔
753
                        id: `Experiment:${COMMS_OPT_IN_EXP_KEY}`,
754
                        fragment: experimentVersionFragment,
755
                }) ?? {};
756

757
                if (version) {
1!
UNCOV
758
                        this.cookieStore.set(COMMS_OPT_IN_EXP_KEY, version, { path: '/' });
×
759
                }
760

761
                // MARS-194 User Metrics for Optimizely A/B experiment
762
                const hasLentBefore = this.cookieStore.get(hasLentBeforeCookie) === 'true';
1✔
763
                const hasDepositBefore = this.cookieStore.get(hasDepositBeforeCookie) === 'true';
1✔
764

765
                userHasLentBefore(hasLentBefore);
1✔
766
                userHasDepositBefore(hasDepositBefore);
1✔
767

768
                // MARS-246 Hotjar user attributes
769
                setHotJarUserAttributes({
1✔
770
                        userId: this.userId,
771
                        hasEverLoggedIn: this.hasEverLoggedIn,
772
                        hasLentBefore,
773
                        hasDepositBefore,
774
                });
775
                window.addEventListener('resize', this.determineIfMobile());
1✔
776
        },
777
        beforeUnmount() {
778
                window.removeEventListener('resize', this.determineIfMobile());
1✔
779
        },
780
        methods: {
781
                determineIfMobile() {
782
                        return _throttle(() => {
2✔
UNCOV
783
                                this.isMobile = document.documentElement.clientWidth < 735;
×
784
                        }, 200);
785
                },
786
                toggleLendMenu(immediate = false) {
×
UNCOV
787
                        const wasVisible = this.isLendMenuVisible;
×
788

UNCOV
789
                        if (immediate) {
×
790
                                // if touch, toggle immediately
791
                                this.isLendMenuVisible = !this.isLendMenuVisible;
×
UNCOV
792
                        } else if (!this.isLendMenuVisible) {
×
793
                                // if mouse and menu is hidden, show immediately
UNCOV
794
                                this.isLendMenuVisible = true;
×
795
                        } else {
796
                                // if mouse and menu is showing,
797
                                // wait a half second to hide in case is was an accidental mouseout
798
                                clearTimeout(this.hideLendMenuTimeout);
×
799
                                this.hideLendMenuTimeout = setTimeout(() => {
×
800
                                        if (!this.isLendMenuDesired) {
×
UNCOV
801
                                                this.isLendMenuVisible = false;
×
802
                                        }
803
                                }, 500);
804
                        }
805

806
                        // If the menu was previously hidden and is now visible, run onLoad
807
                        if (!wasVisible && this.isLendMenuVisible) {
×
UNCOV
808
                                this.$refs?.lendMenu?.onLoad?.();
×
809
                        }
810
                },
811
                onLendLinkPointerEnter(e) {
812
                        if (e.pointerType === 'touch') {
×
UNCOV
813
                                return;
×
814
                        }
815
                        this.isLendMenuDesired = true;
×
UNCOV
816
                        this.toggleLendMenu();
×
817
                },
818
                onLendLinkPointerLeave(e) {
819
                        if (e.pointerType === 'touch') {
×
UNCOV
820
                                return;
×
821
                        }
822
                        this.isLendMenuDesired = false;
×
UNCOV
823
                        this.toggleLendMenu();
×
824
                },
825
                onLendLinkPointerUp(e) {
826
                        if (e.pointerType === 'touch') {
×
UNCOV
827
                                this.toggleLendMenu(true);
×
828
                        } else {
UNCOV
829
                                this.$router.push({
×
830
                                        path: '/lend-by-category'
831
                                }).catch(() => {});
832
                        }
833
                },
834
                onLendLinkClick(e) {
835
                        if (e.pointerType === 'touch') {
×
UNCOV
836
                                return;
×
837
                        }
UNCOV
838
                        this.$router.push({
×
839
                                path: '/lend-by-category'
840
                        }).catch(() => {});
841
                },
842
                onLendMenuPointerEnter(e) {
843
                        if (e.pointerType === 'touch') {
×
UNCOV
844
                                return;
×
845
                        }
846
                        this.isLendMenuDesired = true;
×
UNCOV
847
                        this.toggleLendMenu();
×
848
                },
849
                onLendMenuPointerLeave(e) {
850
                        if (e.pointerType === 'touch') {
×
UNCOV
851
                                return;
×
852
                        }
853
                        this.isLendMenuDesired = false;
×
UNCOV
854
                        this.toggleLendMenu();
×
855
                },
856
                onLendMenuShow() {
UNCOV
857
                        this.$kvTrackEvent('TopNav', 'hover-Lend-menu', 'Lend');
×
858
                },
859
                onLendMenuHide() {
860
                        if (this.$refs.lendMenu) {
×
UNCOV
861
                                this.$refs.lendMenu.onClose();
×
862
                        }
863
                },
864
                addHashToRoute(hash) {
865
                        const route = { ...this.$route };
×
866
                        route.hash = hash;
×
UNCOV
867
                        return route;
×
868
                },
869
                loadLendInfo() {
870
                        if (this.$refs.lendMenu) {
×
UNCOV
871
                                this.$refs.lendMenu.onLoad();
×
872
                        }
873
                },
874
                toggleMobileSearch() {
875
                        this.searchOpen = !this.searchOpen;
×
876
                        document.activeElement.blur();
×
UNCOV
877
                        if (this.searchOpen) {
×
878
                                // wait one tick, then focus search input
879
                                this.$nextTick(() => {
×
UNCOV
880
                                        this.$refs.search.focus();
×
881
                                });
882
                        }
883
                },
884
                withinBoundaryCheck(event) {
885
                        const target = this.$refs?.lendMenu?.$el ?? null; // TODO, make this an argument instead of hardcoded
×
886
                        if (!target) return false;
×
887
                        const withinBoundary = event.composedPath().includes(target);
×
888
                        if (!withinBoundary) {
×
UNCOV
889
                                this.toggleLendMenu(true);
×
890
                        }
891
                },
892
        },
893
        watch: {
894
                isVisitor(newVal, oldVal) {
895
                        if (newVal !== oldVal && !newVal && this.$refs.userDropdown) {
×
896
                                this.$nextTick(() => {
×
UNCOV
897
                                        this.$refs.userDropdown.remakeDropdown();
×
898
                                });
899
                        }
900
                },
901
                isLendMenuVisible() {
902
                        setTimeout(() => {
×
903
                                if (this.isLendMenuVisible === true) {
×
904
                                        this.onLendMenuShow();
×
UNCOV
905
                                        document.addEventListener('pointerup', this.withinBoundaryCheck);
×
906
                                } else {
907
                                        this.onLendMenuHide();
×
UNCOV
908
                                        document.removeEventListener('pointerup', this.withinBoundaryCheck);
×
909
                                }
910
                        });
911
                },
912
                basketCount() {
913
                        // update leftover credit allocation loan count when basket count is updated
UNCOV
914
                        this.lcaLoanCount = this.cookieStore.get('lcaid') ? 1 : 0;
×
915
                }
916
        }
917
};
918
</script>
919

920
<style lang="postcss" scoped>
921
.header__button {
922
        @apply tw-items-center tw-flex-shrink-0;
923
        @apply tw-font-medium tw-text-primary hover:tw-text-action-highlight;
924
        @apply tw-no-underline hover:tw-no-underline focus:tw-no-underline;
925
        @apply tw-h-8 md:tw-h-9 tw-whitespace-nowrap tw-flex-shrink-0;
926
}
927

928
.dropdown-list {
929
        @apply tw-px-2 tw-rounded-b;
930
}
931

932
.dropdown-list a {
933
        @apply tw-font-medium tw-text-primary tw-block tw-w-full tw-py-1;
934
        @apply tw-no-underline hover:tw-underline hover:tw-text-action active:tw-text-action-highlight;
935
}
936

937
/* CSS grid areas to manage position changes across breakpoints without markup duplication */
938
.header__logo { grid-area: logo; }
939
.header__lend { grid-area: lend; }
940
.header__search { grid-area: search; }
941
.header__right-side { grid-area: right-side; }
942

943
.header {
944
        grid-template-areas: "logo lend right-side";
945
        grid-template-columns: 1fr auto auto;
946
}
947

948
.header--mobile-open {
949
        grid-template-areas:
950
                "logo lend right-side"
951
                "search search search";
952
        grid-template-columns: 1fr auto auto;
953
}
954

955
@screen md {
956
        .header {
957
                grid-template-areas: "logo lend search right-side";
958
                grid-template-columns: auto auto 1fr auto;
959
        }
960

961
        .header.header--tablet-open {
962
                grid-template-areas:
963
                        "logo lend right-side"
964
                        "search search search";
965
                grid-template-columns: 1fr auto auto;
966
        }
967
}
968

969
@screen lg {
970
        .header.header--tablet-open {
971
                grid-template-areas: "logo lend search right-side";
972
                grid-template-columns: auto auto 1fr auto;
973
        }
974
}
975

976
.bubble-count {
977
        @apply tw-bottom-0 tw-right-0 tw-absolute tw-rounded-full tw-w-2.5 tw-h-2.5 tw-text-white
978
                tw-text-center tw-text-small tw-bg-brand tw-z-3 tw-mr-0.5 md:tw-mr-1 tw-cursor-pointer;
979
}
980

981
.user-avatar :deep(img),
982
.user-avatar :deep(.tw-bg-brand) {
983
        @apply tw-w-4 tw-h-4 md:tw-w-5 md:tw-h-5;
984
}
985

986
</style>
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

© 2025 Coveralls, Inc