@kiva/kv-components 3.48.0 → 3.48.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -181,7 +181,7 @@
181
181
  >
182
182
  <kv-loan-progress-group
183
183
  id="loanProgress"
184
- :money-left="unreservedAmount"
184
+ :money-left="`${unreservedAmount}`"
185
185
  :progress-percent="fundraisingPercent"
186
186
  class="tw-text-black"
187
187
  />
@@ -220,7 +220,8 @@
220
220
  </template>
221
221
 
222
222
  <script>
223
- import { mdiMapMarker } from '@mdi/js';
223
+ import { loanCardComputedProperties, loanCardMethods } from '../utils/loanCard';
224
+
224
225
  import KvLoanUse from './KvLoanUse.vue';
225
226
  import KvBorrowerImage from './KvBorrowerImage.vue';
226
227
  import KvLoanProgressGroup from './KvLoanProgressGroup.vue';
@@ -231,12 +232,6 @@ import KvLoanTag from './KvLoanTag.vue';
231
232
  import KvMaterialIcon from './KvMaterialIcon.vue';
232
233
  import KvLoadingPlaceholder from './KvLoadingPlaceholder.vue';
233
234
 
234
- const LSE_LOAN_KEY = 'N/A';
235
- const ECO_FRIENDLY_KEY = 'ECO-FRIENDLY';
236
- const SUSTAINABLE_AG_KEY = 'SUSTAINABLE AG';
237
- const SINGLE_PARENT_KEY = 'SINGLE PARENT';
238
- const REFUGEE_KEY = 'REFUGEES/DISPLACED';
239
-
240
235
  export default {
241
236
  name: 'KvClassicLoanCard',
242
237
  components: {
@@ -263,10 +258,6 @@ export default {
263
258
  type: Boolean,
264
259
  default: false,
265
260
  },
266
- useFullWidth: {
267
- type: Boolean,
268
- default: false,
269
- },
270
261
  showTags: {
271
262
  type: Boolean,
272
263
  default: false,
@@ -279,10 +270,6 @@ export default {
279
270
  type: Boolean,
280
271
  default: false,
281
272
  },
282
- largeCard: {
283
- type: Boolean,
284
- default: false,
285
- },
286
273
  isAdding: {
287
274
  type: Boolean,
288
275
  default: false,
@@ -335,80 +322,77 @@ export default {
335
322
  type: Boolean,
336
323
  default: false,
337
324
  },
325
+ customCallouts: {
326
+ type: Array,
327
+ default: () => ([]),
328
+ },
329
+ useFullWidth: {
330
+ type: Boolean,
331
+ default: false,
332
+ },
333
+ largeCard: {
334
+ type: Boolean,
335
+ default: false,
336
+ },
338
337
  },
339
- data() {
338
+ setup(props) {
339
+ const {
340
+ allDataLoaded,
341
+ borrowerName,
342
+ city,
343
+ countryName,
344
+ distributionModel,
345
+ formattedLocation,
346
+ fundraisingPercent,
347
+ hasProgressData,
348
+ imageHash,
349
+ isLoading,
350
+ loanAmount,
351
+ loanBorrowerCount,
352
+ loanCallouts,
353
+ loanStatus,
354
+ loanUse,
355
+ mdiMapMarker,
356
+ readMorePath,
357
+ state,
358
+ tag,
359
+ unreservedAmount,
360
+ } = loanCardComputedProperties(props);
361
+
362
+ const {
363
+ clickReadMore,
364
+ showLoanDetails,
365
+ } = loanCardMethods(props);
366
+
340
367
  return {
368
+ allDataLoaded,
369
+ borrowerName,
370
+ city,
371
+ countryName,
372
+ distributionModel,
373
+ formattedLocation,
374
+ fundraisingPercent,
375
+ hasProgressData,
376
+ imageHash,
377
+ isLoading,
378
+ loanAmount,
379
+ loanBorrowerCount,
380
+ loanCallouts,
381
+ loanStatus,
382
+ loanUse,
341
383
  mdiMapMarker,
384
+ readMorePath,
385
+ state,
386
+ tag,
387
+ unreservedAmount,
388
+ clickReadMore,
389
+ showLoanDetails,
342
390
  };
343
391
  },
344
392
  computed: {
345
- tag() {
346
- return this.externalLinks ? 'a' : 'router-link';
347
- },
348
- readMorePath() {
349
- return this.customLoanDetails ? '' : `/lend/${this.loanId}`;
350
- },
351
- isLoading() {
352
- return !this.loanId || !this.loan;
353
- },
354
393
  cardWidth() {
355
394
  return this.useFullWidth ? '100%' : '374px';
356
395
  },
357
- borrowerName() {
358
- return this.loan?.name || '';
359
- },
360
- countryName() {
361
- return this.loan?.geocode?.country?.name || '';
362
- },
363
- city() {
364
- return this.loan?.geocode?.city || '';
365
- },
366
- state() {
367
- return this.loan?.geocode?.state || '';
368
- },
369
- distributionModel() {
370
- return this.loan?.distributionModel || '';
371
- },
372
- imageHash() {
373
- return this.loan?.image?.hash ?? '';
374
- },
375
- hasProgressData() {
376
- // Local resolver values for the progress bar load client-side
377
- return typeof this.loan?.unreservedAmount !== 'undefined'
378
- && typeof this.loan?.fundraisingPercent !== 'undefined';
379
- },
380
- allDataLoaded() {
381
- return !this.isLoading && this.hasProgressData;
382
- },
383
- fundraisingPercent() {
384
- return this.loan?.fundraisingPercent ?? 0;
385
- },
386
- unreservedAmount() {
387
- return this.loan?.unreservedAmount ?? '0';
388
- },
389
- formattedLocation() {
390
- if (this.distributionModel === 'direct') {
391
- const formattedString = `${this.city}, ${this.state}, ${this.countryName}`;
392
- return formattedString;
393
- }
394
- if (this.countryName === 'Puerto Rico') {
395
- const formattedString = `${this.city}, PR`;
396
- return formattedString;
397
- }
398
- return this.countryName;
399
- },
400
- loanUse() {
401
- return this.loan?.use ?? '';
402
- },
403
- loanStatus() {
404
- return this.loan?.status ?? '';
405
- },
406
- loanAmount() {
407
- return this.loan?.loanAmount ?? '0';
408
- },
409
- loanBorrowerCount() {
410
- return this.loan?.borrowerCount ?? 0;
411
- },
412
396
  imageAspectRatio() {
413
397
  if (this.largeCard) {
414
398
  return 5 / 8;
@@ -430,95 +414,12 @@ export default {
430
414
  { width: 335, viewSize: 375 },
431
415
  ];
432
416
  },
433
- loanCallouts() {
434
- const callouts = [];
435
- const activityName = this.loan?.activity?.name ?? '';
436
- const sectorName = this.loan?.sector?.name ?? '';
437
- const tags = this.loan?.tags?.filter((tag) => tag.charAt(0) === '#')
438
- .map((tag) => tag.substring(1)) ?? [];
439
- const themes = this.loan?.themes ?? [];
440
- const categories = {
441
- ecoFriendly: !!tags // eslint-disable-next-line max-len
442
- .filter((t) => t.toUpperCase() === ECO_FRIENDLY_KEY || t.toUpperCase() === SUSTAINABLE_AG_KEY).length,
443
- refugeesIdps: !!themes.filter((t) => t.toUpperCase() === REFUGEE_KEY).length,
444
- singleParents: !!tags.filter((t) => t.toUpperCase() === SINGLE_PARENT_KEY).length,
445
- };
446
-
447
- const isLseLoan = this.loan?.partnerName?.toUpperCase().includes(LSE_LOAN_KEY);
448
-
449
- // P1 Category
450
- // Exp limited to: Eco-friendly, Refugees and IDPs, Single Parents
451
- // Tag as first option for LSE loans
452
- if (isLseLoan && tags.length) {
453
- const position = Math.floor(Math.random() * tags.length);
454
- const tag = tags[position];
455
- callouts.push(tag);
456
- }
457
-
458
- if (!this.categoryPageName) {
459
- if (categories.ecoFriendly // eslint-disable-next-line max-len
460
- && !callouts.find((c) => c.toUpperCase() === ECO_FRIENDLY_KEY || c.toUpperCase() === SUSTAINABLE_AG_KEY)) {
461
- callouts.push('Eco-friendly');
462
- } else if (categories.refugeesIdps) {
463
- callouts.push('Refugees and IDPs');
464
- } else if (categories.singleParents
465
- && !callouts.find((c) => c.toUpperCase() === SINGLE_PARENT_KEY)) {
466
- callouts.push('Single Parent');
467
- }
468
- }
469
-
470
- // P2 Activity
471
- if (activityName && this.categoryPageName?.toUpperCase() !== activityName.toUpperCase()) {
472
- callouts.push(activityName);
473
- }
474
-
475
- // P3 Sector
476
- if (sectorName
477
- && (activityName.toUpperCase() !== sectorName.toUpperCase())
478
- && (sectorName.toUpperCase() !== this.categoryPageName?.toUpperCase())
479
- && callouts.length < 2) {
480
- callouts.push(sectorName);
481
- }
482
-
483
- // P4 Tag
484
- if (!!tags.length && callouts.length < 2) {
485
- const position = Math.floor(Math.random() * tags.length);
486
- const tag = tags[position];
487
- if (!callouts.filter((c) => c.toUpperCase() === tag.toUpperCase()).length) {
488
- callouts.push(tag);
489
- }
490
- }
491
-
492
- // P5 Theme
493
- if (!!themes.length && callouts.length < 2) {
494
- const position = Math.floor(Math.random() * themes.length);
495
- const theme = themes[position];
496
- if (!callouts.filter((c) => c.toUpperCase() === theme.toUpperCase()).length
497
- && theme.toUpperCase() !== this.categoryPageName?.toUpperCase()) {
498
- callouts.push(theme);
499
- }
500
- }
501
-
502
- // Only show one callout for LSE loans
503
- if (isLseLoan && callouts.length > 1) return [callouts.shift()];
504
- return callouts;
505
- },
506
- },
507
- methods: {
508
- showLoanDetails(e) {
509
- if (this.customLoanDetails) {
510
- e.preventDefault();
511
- this.$emit('show-loan-details');
512
- }
513
- },
514
- clickReadMore(target) {
515
- this.kvTrackFunction('Lending', 'click-Read more', target, this.loanId);
516
- },
517
417
  },
518
418
  };
519
419
  </script>
520
420
 
521
421
  <style lang="postcss" scoped>
422
+ /** Shared with KvWideLoanCard */
522
423
  .loan-card-use:hover,
523
424
  .loan-card-use:focus {
524
425
  @apply tw-text-primary;
@@ -0,0 +1,429 @@
1
+ <template>
2
+ <div
3
+ class="
4
+ tw-flex
5
+ tw-flex-row
6
+ tw-flex-wrap
7
+ tw-bg-white
8
+ tw-rounded
9
+ tw-w-full
10
+ tw-pb-1
11
+ tw-p-1
12
+ tw-gap-1
13
+ tw-items-center
14
+ "
15
+ :class="{'tw-pointer-events-none' : isLoading }"
16
+ style="box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);"
17
+ >
18
+ <div
19
+ class="loan-card-active-hover tw-flex-1 tw-min-w-[275px] md:tw-min-w-[320px] md:tw-max-w-[320px]"
20
+ >
21
+ <!-- Borrower image -->
22
+ <kv-loading-placeholder
23
+ v-if="isLoading"
24
+ class="tw-mb-1 tw-w-full tw-rounded"
25
+ :style="{ height: '15rem' }"
26
+ />
27
+ <div
28
+ v-else
29
+ class="tw-relative tw-w-full"
30
+ @click="showLoanDetails"
31
+ >
32
+ <component
33
+ :is="tag"
34
+ :to="readMorePath"
35
+ :href="readMorePath"
36
+ class="tw-flex"
37
+ aria-label="Borrower image"
38
+ @click="clickReadMore('Photo')"
39
+ >
40
+ <kv-borrower-image
41
+ class="
42
+ tw-relative
43
+ tw-w-full
44
+ tw-bg-black
45
+ tw-rounded
46
+ "
47
+ :alt="`Photo of ${borrowerName}`"
48
+ :aspect-ratio="imageAspectRatio"
49
+ :default-image="{ width: imageDefaultWidth }"
50
+ :hash="imageHash"
51
+ :images="imageSizes"
52
+ :photo-path="photoPath"
53
+ />
54
+
55
+ <div v-if="countryName">
56
+ <p
57
+ class="
58
+ tw-absolute
59
+ tw-bottom-1
60
+ tw-left-1
61
+ tw-text-primary
62
+ tw-bg-white
63
+ tw-rounded
64
+ tw-p-1
65
+ tw-mb-0
66
+ tw-mr-2
67
+ tw-text-h4
68
+ tw-inline-flex
69
+ tw-items-center"
70
+ style="padding: 2px 6px; text-transform: capitalize;"
71
+ >
72
+ <kv-material-icon
73
+ class="tw-h-2 tw-w-2"
74
+ :icon="mdiMapMarker"
75
+ />
76
+ {{ formattedLocation }}
77
+ </p>
78
+ </div>
79
+ </component>
80
+ <kv-loan-bookmark
81
+ v-if="!isVisitor"
82
+ :loan-id="loanId"
83
+ :is-bookmarked="isBookmarked"
84
+ class="tw-absolute tw-right-1"
85
+ style="top: -6px;"
86
+ data-testid="loan-card-bookmark"
87
+ @toggle-bookmark="$emit('toggle-bookmark')"
88
+ />
89
+ </div>
90
+ </div>
91
+
92
+ <div
93
+ class="tw-flex tw-flex-col tw-flex-1 tw-px-1"
94
+ >
95
+ <h3 class="tw-hidden md:tw-inline-block">
96
+ {{ borrowerName }}
97
+ </h3>
98
+ <!-- Loan tag -->
99
+ <component
100
+ :is="tag"
101
+ :to="readMorePath"
102
+ :href="readMorePath"
103
+ class="tw-flex hover:tw-no-underline focus:tw-no-underline"
104
+ aria-label="Loan tag"
105
+ @click="clickReadMore('Tag')"
106
+ >
107
+ <kv-loan-tag
108
+ v-if="showTags && !isLoading"
109
+ :loan="loan"
110
+ :kv-track-function="kvTrackFunction"
111
+ />
112
+ </component>
113
+
114
+ <component
115
+ :is="tag"
116
+ :to="readMorePath"
117
+ :href="readMorePath"
118
+ class="loan-card-use tw-text-primary"
119
+ aria-label="Loan use"
120
+ @click="clickReadMore('Use')"
121
+ >
122
+ <!-- Loan use -->
123
+ <div class="tw-pt-1">
124
+ <div
125
+ v-if="isLoading"
126
+ class="tw-w-full"
127
+ style="height: 5.5rem;"
128
+ >
129
+ <div
130
+ v-for="(_n, i) in [...Array(4)]"
131
+ :key="i"
132
+ class="tw-h-2 tw-mb-1"
133
+ >
134
+ <kv-loading-placeholder />
135
+ </div>
136
+ </div>
137
+ <div v-else>
138
+ <kv-loan-use
139
+ :use="loanUse"
140
+ :loan-amount="loanAmount"
141
+ :status="loanStatus"
142
+ :borrower-count="loanBorrowerCount"
143
+ :name="borrowerName"
144
+ :distribution-model="distributionModel"
145
+ />
146
+ </div>
147
+ </div>
148
+ </component>
149
+ <!-- Loan call outs -->
150
+ <kv-loading-placeholder
151
+ v-if="isLoading || typeof loanCallouts === 'undefined'"
152
+ class="tw-mt-1.5 tw-mb-1"
153
+ :style="{ width: '60%', height: '1.75rem', 'border-radius': '500rem' }"
154
+ />
155
+
156
+ <kv-loan-callouts
157
+ v-else
158
+ :callouts="loanCallouts"
159
+ class="loan-callouts tw-my-1.5"
160
+ />
161
+
162
+ <div class="tw-flex tw-flex-row tw-justify-between tw-gap-1 md:tw-gap-3 ">
163
+ <!-- Fundraising -->
164
+ <div
165
+ v-if="!hasProgressData"
166
+ class="tw-w-full tw-pt-1 tw-pr-1"
167
+ >
168
+ <kv-loading-placeholder
169
+ class="tw-mb-0.5"
170
+ :style="{ width: '70%', height: '1.3rem' }"
171
+ />
172
+
173
+ <kv-loading-placeholder
174
+ class="tw-rounded"
175
+ :style="{ width: '70%', height: '0.5rem' }"
176
+ />
177
+ </div>
178
+ <component
179
+ :is="tag"
180
+ v-if="unreservedAmount > 0"
181
+ :to="readMorePath"
182
+ :href="readMorePath"
183
+ class="loan-card-progress tw-mt-1 tw-flex-grow"
184
+ aria-label="Loan progress"
185
+ @click="clickReadMore('Progress')"
186
+ >
187
+ <kv-loan-progress-group
188
+ id="loanProgress"
189
+ :money-left="`${unreservedAmount}`"
190
+ :progress-percent="fundraisingPercent"
191
+ class="tw-text-black"
192
+ />
193
+ </component>
194
+
195
+ <!-- CTA Button -->
196
+ <kv-loading-placeholder
197
+ v-if="!allDataLoaded"
198
+ class="tw-rounded tw-self-start"
199
+ :style="{ width: '9rem', height: '3rem' }"
200
+ />
201
+
202
+ <kv-lend-cta
203
+ v-else
204
+ :loan="loan"
205
+ :basket-items="basketItems"
206
+ :is-loading="isLoading"
207
+ :is-adding="isAdding"
208
+ :enable-five-dollars-notes="enableFiveDollarsNotes"
209
+ :five-dollars-selected="fiveDollarsSelected"
210
+ :kv-track-function="kvTrackFunction"
211
+ :show-view-loan="showViewLoan"
212
+ :custom-loan-details="customLoanDetails"
213
+ :external-links="externalLinks"
214
+ :route="route"
215
+ :user-balance="userBalance"
216
+ :get-cookie="getCookie"
217
+ :set-cookie="setCookie"
218
+ class="tw-mt-auto tw-self-end"
219
+ :class="{'tw-flex-grow' : unreservedAmount === 0, 'tw-flex-shrink-0' : unreservedAmount > 0}"
220
+ @add-to-basket="$emit('add-to-basket', $event)"
221
+ @show-loan-details="clickReadMore('ViewLoan')"
222
+ />
223
+ </div>
224
+ </div>
225
+ </div>
226
+ </template>
227
+
228
+ <script>
229
+ import { loanCardComputedProperties, loanCardMethods } from '../utils/loanCard';
230
+ import KvLoanUse from './KvLoanUse.vue';
231
+ import KvBorrowerImage from './KvBorrowerImage.vue';
232
+ import KvLoanProgressGroup from './KvLoanProgressGroup.vue';
233
+ import KvLoanCallouts from './KvLoanCallouts.vue';
234
+ import KvLendCta from './KvLendCta.vue';
235
+ import KvLoanBookmark from './KvLoanBookmark.vue';
236
+ import KvLoanTag from './KvLoanTag.vue';
237
+ import KvMaterialIcon from './KvMaterialIcon.vue';
238
+ import KvLoadingPlaceholder from './KvLoadingPlaceholder.vue';
239
+
240
+ export default {
241
+ name: 'KvWideLoanCard',
242
+ components: {
243
+ KvBorrowerImage,
244
+ KvLoadingPlaceholder,
245
+ KvLoanUse,
246
+ KvLoanProgressGroup,
247
+ KvMaterialIcon,
248
+ KvLendCta,
249
+ KvLoanTag,
250
+ KvLoanCallouts,
251
+ KvLoanBookmark,
252
+ },
253
+ props: {
254
+ loanId: {
255
+ type: Number,
256
+ default: undefined,
257
+ },
258
+ loan: {
259
+ type: Object,
260
+ default: null,
261
+ },
262
+ customLoanDetails: {
263
+ type: Boolean,
264
+ default: false,
265
+ },
266
+ showTags: {
267
+ type: Boolean,
268
+ default: false,
269
+ },
270
+ categoryPageName: {
271
+ type: String,
272
+ default: '',
273
+ },
274
+ enableFiveDollarsNotes: {
275
+ type: Boolean,
276
+ default: false,
277
+ },
278
+ isAdding: {
279
+ type: Boolean,
280
+ default: false,
281
+ },
282
+ isVisitor: {
283
+ type: Boolean,
284
+ default: true,
285
+ },
286
+ basketItems: {
287
+ type: Array,
288
+ default: () => ([]),
289
+ },
290
+ isBookmarked: {
291
+ type: Boolean,
292
+ default: false,
293
+ },
294
+ kvTrackFunction: {
295
+ type: Function,
296
+ required: true,
297
+ },
298
+ photoPath: {
299
+ type: String,
300
+ required: true,
301
+ },
302
+ showViewLoan: {
303
+ type: Boolean,
304
+ default: false,
305
+ },
306
+ externalLinks: {
307
+ type: Boolean,
308
+ default: false,
309
+ },
310
+ route: {
311
+ type: Object,
312
+ default: undefined,
313
+ },
314
+ userBalance: {
315
+ type: String,
316
+ default: undefined,
317
+ },
318
+ getCookie: {
319
+ type: Function,
320
+ default: undefined,
321
+ },
322
+ setCookie: {
323
+ type: Function,
324
+ default: undefined,
325
+ },
326
+ fiveDollarsSelected: {
327
+ type: Boolean,
328
+ default: false,
329
+ },
330
+ customCallouts: {
331
+ type: Array,
332
+ default: () => ([]),
333
+ },
334
+ },
335
+ setup(props) {
336
+ const {
337
+ allDataLoaded,
338
+ borrowerName,
339
+ city,
340
+ countryName,
341
+ distributionModel,
342
+ formattedLocation,
343
+ fundraisingPercent,
344
+ hasProgressData,
345
+ imageHash,
346
+ isLoading,
347
+ loanAmount,
348
+ loanBorrowerCount,
349
+ loanCallouts,
350
+ loanStatus,
351
+ loanUse,
352
+ mdiMapMarker,
353
+ readMorePath,
354
+ state,
355
+ tag,
356
+ unreservedAmount,
357
+ } = loanCardComputedProperties(props);
358
+
359
+ const {
360
+ clickReadMore,
361
+ showLoanDetails,
362
+ } = loanCardMethods(props);
363
+
364
+ return {
365
+ allDataLoaded,
366
+ borrowerName,
367
+ city,
368
+ countryName,
369
+ distributionModel,
370
+ formattedLocation,
371
+ fundraisingPercent,
372
+ hasProgressData,
373
+ imageHash,
374
+ isLoading,
375
+ loanAmount,
376
+ loanBorrowerCount,
377
+ loanCallouts,
378
+ loanStatus,
379
+ loanUse,
380
+ mdiMapMarker,
381
+ readMorePath,
382
+ state,
383
+ tag,
384
+ unreservedAmount,
385
+ clickReadMore,
386
+ showLoanDetails,
387
+ };
388
+ },
389
+ computed: {
390
+ cardWidth() {
391
+ return '374px';
392
+ },
393
+ imageAspectRatio() {
394
+ return 1;
395
+ },
396
+ imageDefaultWidth() {
397
+ return 320;
398
+ },
399
+ imageSizes() {
400
+ return [
401
+ { width: this.imageDefaultWidth, viewSize: 1024 },
402
+ { width: this.imageDefaultWidth, viewSize: 768 },
403
+ { width: 416, viewSize: 480 },
404
+ { width: 374, viewSize: 414 },
405
+ { width: 335, viewSize: 375 },
406
+ ];
407
+ },
408
+ },
409
+ };
410
+ </script>
411
+
412
+ <style lang="postcss" scoped>
413
+ /** Shared with KvClassicLoanCard */
414
+ .loan-card-use:hover,
415
+ .loan-card-use:focus {
416
+ @apply tw-text-primary;
417
+ }
418
+ .loan-card-active-hover:hover .loan-card-use {
419
+ @apply tw-underline;
420
+ }
421
+ .loan-card-progress:hover,
422
+ .loan-card-progress:focus {
423
+ @apply tw-no-underline;
424
+ }
425
+ /** Unique to this loan card */
426
+ .loan-callouts >>> div{
427
+ @apply tw-flex-wrap tw-h-auto;
428
+ }
429
+ </style>
@@ -60,3 +60,9 @@ const props = {
60
60
  };
61
61
 
62
62
  export const Default = story(props);
63
+
64
+ export const Square = story({
65
+ ...props,
66
+ aspectRatio: 1,
67
+ defaultImage: { width: 300 },
68
+ });