@licklist/design 0.69.3 → 0.69.4

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.
Files changed (28) hide show
  1. package/dist/events/edit-event-modal/utils/getDefaultProductSet.d.ts.map +1 -1
  2. package/dist/events/edit-event-modal/utils/getDefaultProductSet.js +3 -1
  3. package/dist/iframe/activity-card/ActivityCard.d.ts +5 -4
  4. package/dist/iframe/activity-card/ActivityCard.d.ts.map +1 -1
  5. package/dist/iframe/activity-card/ActivityCard.js +52 -40
  6. package/dist/product-set/card/ProductSetCard.d.ts.map +1 -1
  7. package/dist/product-set/card/ProductSetCard.js +10 -0
  8. package/dist/product-set/control/ProductSetControl.d.ts +4 -0
  9. package/dist/product-set/control/ProductSetControl.d.ts.map +1 -1
  10. package/dist/product-set/control/ProductSetControl.js +30 -3
  11. package/dist/product-set/control/ProductSetImageControl.d.ts +2 -0
  12. package/dist/product-set/control/ProductSetImageControl.d.ts.map +1 -0
  13. package/dist/product-set/control/ProductSetImageControl.js +279 -0
  14. package/dist/product-set/form/MobileFooter.d.ts.map +1 -1
  15. package/dist/styles/activity-card/{GridActivitiesCard.scss → ActivitiesCard.scss} +19 -5
  16. package/dist/styles/activity-card/ListActivitiesCard.scss +12 -42
  17. package/dist/styles/activity-card/_index.scss +1 -1
  18. package/package.json +2 -2
  19. package/src/events/edit-event-modal/utils/getDefaultProductSet.ts +2 -0
  20. package/src/iframe/activity-card/ActivityCard.tsx +35 -24
  21. package/src/product-set/card/ProductSetCard.tsx +15 -2
  22. package/src/product-set/control/ProductSetControl.tsx +52 -52
  23. package/src/product-set/control/ProductSetImageControl.tsx +97 -0
  24. package/src/product-set/form/MobileFooter.tsx +1 -3
  25. package/src/styles/activity-card/{GridActivitiesCard.scss → ActivitiesCard.scss} +19 -5
  26. package/src/styles/activity-card/ListActivitiesCard.scss +12 -42
  27. package/src/styles/activity-card/_index.scss +1 -1
  28. package/yarn.lock +57 -31
@@ -3,56 +3,26 @@
3
3
  .list-activity-card {
4
4
  display: flex;
5
5
  justify-content: space-between;
6
- align-items: center;
7
- cursor: pointer;
8
- height: 6rem;
9
- padding: 0.375rem;
10
- border: 2px solid transparent;
11
- border-radius: 0.5rem;
12
- background-color: $snippet-product-set-background-color;
13
- color: $snippet-product-set-font-color;
14
- transition: $color-transition;
6
+ gap: 0.5rem;
7
+ width: 100%;
15
8
 
16
- &.active {
17
- border: 2px solid #0e8be1;
18
- }
19
-
20
- p {
21
- margin-bottom: 0;
22
- }
23
-
24
- .description {
9
+ &-info {
25
10
  width: 70%;
26
11
  font-size: 0.81rem;
27
12
  display: flex;
28
13
  flex-direction: column;
14
+ align-items: start;
29
15
  }
30
16
 
31
- .title {
32
- font-weight: 600;
33
- font-size: 0.875rem;
34
- line-height: 1.5rem;
35
- }
36
-
37
- .image-container {
38
- display: flex;
39
- justify-content: start;
17
+ img {
18
+ max-width: 120px;
40
19
  height: 100%;
41
-
42
- .image {
43
- display: flex;
44
- align-items: start;
45
- width: 7rem;
46
- height: 4rem;
47
- margin-left: 3rem;
48
- background-repeat: no-repeat;
49
- background-size: cover;
50
- background-position: center;
51
- border-radius: 0.5rem;
52
- }
20
+ object-fit: cover;
21
+ aspect-ratio: 1.2/1;
22
+ border-radius: 0.5rem;
53
23
  }
54
- }
55
24
 
56
- .list-activity-card-hr {
57
- margin-top: 0;
25
+ &-hr {
26
+ margin-top: 0;
27
+ }
58
28
  }
@@ -1,2 +1,2 @@
1
- @import './GridActivitiesCard.scss';
1
+ @import './ActivitiesCard.scss';
2
2
  @import './ListActivitiesCard.scss';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@licklist/design",
3
- "version": "0.69.3",
3
+ "version": "0.69.4",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+ssh://git@bitbucket.org/artelogicsoft/licklist_design.git"
@@ -61,7 +61,7 @@
61
61
  "@dnd-kit/utilities": "2.0.0",
62
62
  "@fortawesome/fontawesome-svg-core": "1.2.34",
63
63
  "@fortawesome/free-solid-svg-icons": "5.15.2",
64
- "@licklist/core": "0.30.1",
64
+ "@licklist/core": "0.30.3",
65
65
  "@licklist/eslint-config": "0.5.6",
66
66
  "@licklist/plugins": "0.33.1",
67
67
  "@mantine/core": "6.0.22",
@@ -12,6 +12,8 @@ export const getDefaultValues = (
12
12
  ): ProductSetFormValues => ({
13
13
  id: productSet?.id ?? 0,
14
14
  name: productSet?.name ?? '',
15
+ description: productSet?.description ?? '',
16
+ image: productSet?.image,
15
17
  type: productSet?.type ?? DEFAULT_PRODUCT_SET_TYPE,
16
18
  termsAndConditions: productSet?.termsAndConditions ?? '',
17
19
  operationalCost: productSet?.operationalCost ?? DEFAULT_OPERATIONAL_COST_TYPE,
@@ -1,27 +1,29 @@
1
1
  import { ReactNode } from 'react'
2
2
  import clsx from 'clsx'
3
+ import { Image } from '@licklist/core/dist/DataMapper/Media/ImageDataMapper'
3
4
 
4
5
  export const LAYOUT_GRID = 'grid'
5
6
  export const LAYOUT_LIST = 'list'
6
7
 
7
- type Layout = typeof LAYOUT_GRID | typeof LAYOUT_LIST
8
-
9
8
  export type ActivityCardProps = {
10
9
  title: ReactNode
11
10
  duration: ReactNode
12
11
  price: ReactNode
12
+ description?: ReactNode
13
13
  onSelect: () => void
14
14
  isSelected: boolean
15
- layout?: Layout
15
+ layout?: typeof LAYOUT_GRID | typeof LAYOUT_LIST
16
16
  availableTimes?: string | null
17
+ image?: Image | null
17
18
  }
18
19
 
19
20
  export const ActivityCard = ({
20
21
  title,
21
22
  duration,
22
23
  price,
24
+ description,
23
25
  availableTimes,
24
- // image,
26
+ image,
25
27
  onSelect,
26
28
  isSelected,
27
29
  layout = LAYOUT_GRID,
@@ -30,39 +32,48 @@ export const ActivityCard = ({
30
32
  return (
31
33
  <button
32
34
  type='button'
33
- className={clsx('activity-card', isSelected && 'active')}
35
+ className={clsx('activity-card', { active: isSelected })}
34
36
  onClick={onSelect}
35
37
  >
36
- <div className={clsx('d-flex', 'flex-column')}>
38
+ {image && (
39
+ <img src={image.url} alt={typeof title === 'string' ? title : ''} />
40
+ )}
41
+
42
+ <div>
37
43
  <div className='activity-card-title'>{title}</div>
38
44
  {duration && <div>{duration}</div>}
39
-
40
45
  {price && <div>{price}</div>}
46
+ {description && (
47
+ <div className='mt-2 activity-card-description'>{description}</div>
48
+ )}
41
49
  </div>
42
50
  </button>
43
51
  )
44
52
  }
45
53
 
46
54
  return (
47
- <div role='button' onClick={onSelect} onKeyPress={onSelect} tabIndex={0}>
48
- <div
49
- className={clsx('list-activity-card', {
50
- active: isSelected,
51
- })}
52
- >
53
- <div className='description'>
54
- <div className='title mb-2'>{title}</div>
55
- {availableTimes && <div>{availableTimes}</div>}
56
- {duration && (
57
- <div>
58
- <p>{duration}</p>
59
- </div>
60
- )}
55
+ <button
56
+ type='button'
57
+ onClick={onSelect}
58
+ className={clsx('activity-card list-activity-card', {
59
+ active: isSelected,
60
+ })}
61
+ >
62
+ {image && (
63
+ <img src={image.url} alt={typeof title === 'string' ? title : ''} />
64
+ )}
61
65
 
62
- {price && <div>{price}</div>}
63
- </div>
66
+ <div className='activity-card-info'>
67
+ <div className='activity-card-title mb-2'>{title}</div>
68
+ {availableTimes && <div>{availableTimes}</div>}
69
+ {duration && <div>{duration}</div>}
70
+ {price && <div>{price}</div>}
71
+ {description && (
72
+ <div className='mt-2 activity-card-description'>{description}</div>
73
+ )}
64
74
  </div>
75
+
65
76
  <hr className='list-activity-card-hr' />
66
- </div>
77
+ </button>
67
78
  )
68
79
  }
@@ -79,7 +79,13 @@ export function ProductSetCard({
79
79
  >
80
80
  {hasPermission && (
81
81
  <>
82
- <ListGroup.Item action as='div'>
82
+ <ListGroup.Item
83
+ action
84
+ as='div'
85
+ role='button'
86
+ tabIndex={0}
87
+ onClick={(e) => e.stopPropagation()}
88
+ >
83
89
  {Boolean(onCopy) && (
84
90
  <ConfirmModal>
85
91
  {(confirm) => (
@@ -95,7 +101,14 @@ export function ProductSetCard({
95
101
  </ConfirmModal>
96
102
  )}
97
103
  </ListGroup.Item>
98
- <ListGroup.Item action className='text-danger' as='div'>
104
+ <ListGroup.Item
105
+ action
106
+ className='text-danger'
107
+ as='div'
108
+ role='button'
109
+ tabIndex={0}
110
+ onClick={(e) => e.stopPropagation()}
111
+ >
99
112
  {Boolean(onRemove) && (
100
113
  <ConfirmModal>
101
114
  {(confirm) => (
@@ -22,12 +22,11 @@ import HookFormService from '@licklist/plugins/dist/services/Form/HookFormServic
22
22
  import { ruleForUrlWithProtocol } from '@licklist/plugins/dist/validation/Rules/urlRule'
23
23
  import { WorkHour } from '@licklist/core/dist/DataMapper/Provider/WorkHourDataMapper'
24
24
  import { checkIfZoneCategory } from '@licklist/plugins'
25
+ import { Image } from '@licklist/core/dist/DataMapper/Media/ImageDataMapper'
25
26
  import { WarningMessage } from '../../static'
26
27
  import { SelectItem } from '../../types/generic/SelectItem'
27
28
  import {
28
29
  DateAndRecurrenceInput,
29
- // TODO: Show Date Component, when reccurent date bugs are fixed
30
- // DateAndRecurrenceInput,
31
30
  DateAndRecurrenceInputValues,
32
31
  } from './DateAndRecurrenceInput'
33
32
  import TutorialGifCard from './TutorialGifCard'
@@ -35,6 +34,7 @@ import { Step } from '../types'
35
34
  import { StepsControl } from '../form/StepsControl'
36
35
  import { Typeahead } from '../../typeahead'
37
36
  import { DateInput } from './DateInput'
37
+ import { ProductSetImageControl } from './ProductSetImageControl'
38
38
 
39
39
  const OPERATIONAL_COST_TITLES = {
40
40
  [OPERATIONAL_COST_PROVIDER]: 'operationalCostProvider',
@@ -47,6 +47,8 @@ const RELY_ON_PEOPLE_TYPE_TITLES = {
47
47
  [RELY_ON_PEOPLE_QUANTITY]: 'peopleQuantity',
48
48
  }
49
49
 
50
+ const MAX_DESCRIPTION_LENGTH = 100
51
+
50
52
  export interface TemplateItem {
51
53
  id: string
52
54
  value: EmailTemplate | SmsTemplate
@@ -55,6 +57,8 @@ export interface TemplateItem {
55
57
 
56
58
  export interface ProductSetControlValues extends DateAndRecurrenceInputValues {
57
59
  name: string
60
+ description?: string
61
+ image?: Image
58
62
  type: ProductSetType
59
63
  termsAndConditions: string
60
64
  thankYouPageUrl: string
@@ -65,6 +69,7 @@ export interface ProductSetControlValues extends DateAndRecurrenceInputValues {
65
69
  steps: Step[]
66
70
  emailTemplates?: TemplateItem[]
67
71
  smsTemplates?: TemplateItem[]
72
+ localImageBlobURL?: string
68
73
  }
69
74
 
70
75
  export interface ProductSetControlShared {
@@ -122,6 +127,7 @@ export function ProductSetControl({
122
127
  const steps = watch('steps')
123
128
 
124
129
  const nameId = useId()
130
+ const descriptionId = useId()
125
131
  const termsAndConditionsId = useId()
126
132
  const relyOnPeopleTypeId = useId()
127
133
 
@@ -174,6 +180,28 @@ export function ProductSetControl({
174
180
  </Form.Control.Feedback>
175
181
  </Form.Group>
176
182
 
183
+ <Form.Group controlId={descriptionId}>
184
+ <Form.Label>{t('Design:description')}</Form.Label>
185
+ <Form.Control
186
+ {...register('description', {
187
+ maxLength: {
188
+ value: MAX_DESCRIPTION_LENGTH,
189
+ message: t('Validation:fieldMaxLength', {
190
+ attribute: t('description'),
191
+ max: MAX_DESCRIPTION_LENGTH,
192
+ }),
193
+ },
194
+ })}
195
+ as='textarea'
196
+ isInvalid={Boolean(errors.description)}
197
+ />
198
+ <Form.Control.Feedback type='invalid'>
199
+ {errors.description?.message}
200
+ </Form.Control.Feedback>
201
+ </Form.Group>
202
+
203
+ <ProductSetImageControl />
204
+
177
205
  {showDateSelector ? (
178
206
  <DateInput
179
207
  workHours={isZoneAdded ? workHours : undefined}
@@ -207,27 +235,6 @@ export function ProductSetControl({
207
235
  </Col>
208
236
  <Col md={6} sm={12}>
209
237
  <div className='second-column'>
210
- {/* <Controller
211
- control={control}
212
- name="type"
213
- render={({ field }) => (
214
- <Form.Group className="d-flex flex-column flex-grow-1 align-items-start">
215
- <Form.Label>{t("Design:checkoutType")}</Form.Label>
216
- <Switch
217
- name={field.name}
218
- options={PRODUCT_SET_TYPES.map((type) => ({
219
- key: type,
220
- id: type,
221
- value: t(type),
222
- }))}
223
- value={field.value}
224
- onChange={field.onChange}
225
- disabled={isLoading}
226
- />
227
- </Form.Group>
228
- )}
229
- /> */}
230
-
231
238
  <Form.Group>
232
239
  <Form.Label className='mb-0'>
233
240
  {t('Design:operationalCost')}
@@ -364,35 +371,28 @@ export function ProductSetControl({
364
371
  <Controller
365
372
  control={control}
366
373
  name='fieldSetId'
367
- render={({ field }) => {
368
- const fieldValue = Number(field.value)
369
-
370
- return (
371
- <>
372
- <Form.Label>{t('Design:customFields')}</Form.Label>
373
- <Form.Control
374
- onChange={field.onChange}
375
- value={fieldValue}
376
- as='select'
377
- disabled={isOverrides}
378
- isInvalid={HookFormService.isInvalid(
379
- 'fieldSetId',
380
- errors,
381
- )}
382
- >
383
- <option value=''>{t('Design:choose')}</option>
384
- {fieldSets?.map((fieldSet) => (
385
- <option value={fieldSet.id} key={fieldSet.id}>
386
- {fieldSet.name}
387
- </option>
388
- ))}
389
- </Form.Control>
390
- <Form.Control.Feedback type='invalid'>
391
- {HookFormService.getErrors('fieldSetId', errors)}
392
- </Form.Control.Feedback>
393
- </>
394
- )
395
- }}
374
+ render={({ field }) => (
375
+ <>
376
+ <Form.Label>{t('Design:customFields')}</Form.Label>
377
+ <Form.Control
378
+ onChange={field.onChange}
379
+ value={Number(field.value)}
380
+ as='select'
381
+ disabled={isOverrides}
382
+ isInvalid={HookFormService.isInvalid('fieldSetId', errors)}
383
+ >
384
+ <option value=''>{t('Design:choose')}</option>
385
+ {fieldSets?.map((fieldSet) => (
386
+ <option value={fieldSet.id} key={fieldSet.id}>
387
+ {fieldSet.name}
388
+ </option>
389
+ ))}
390
+ </Form.Control>
391
+ <Form.Control.Feedback type='invalid'>
392
+ {HookFormService.getErrors('fieldSetId', errors)}
393
+ </Form.Control.Feedback>
394
+ </>
395
+ )}
396
396
  />
397
397
  </Form.Group>
398
398
 
@@ -0,0 +1,97 @@
1
+ import { useFormContext } from 'react-hook-form'
2
+ import { HookFormService, useImages } from '@licklist/plugins'
3
+ import { IMAGE_TYPE_IMAGE } from '@licklist/core/dist/DataMapper/Media/ImageDataMapper'
4
+ import { useCallback, useEffect } from 'react'
5
+ import { Form } from 'react-bootstrap'
6
+ import { FileUpload } from 'src/file-upload'
7
+ import { useTranslation } from 'react-i18next'
8
+ import { isEqual } from 'lodash'
9
+ import { ProductSetControlValues } from '..'
10
+
11
+ export const ProductSetImageControl = () => {
12
+ const { t } = useTranslation('Design')
13
+
14
+ const {
15
+ formState: { errors },
16
+ setValue,
17
+ watch,
18
+ clearErrors,
19
+ } = useFormContext<ProductSetControlValues>()
20
+ const currentProductSet: ProductSetControlValues = watch()
21
+
22
+ const {
23
+ images,
24
+ handleImageRemove,
25
+ handleImageUploading,
26
+ isLoading: isImageUploading,
27
+ setImages,
28
+ } = useImages(null)
29
+
30
+ const onFilesChange = useCallback(
31
+ async (files: File[]) => {
32
+ const uploadedFiles = await handleImageUploading(files, IMAGE_TYPE_IMAGE)
33
+
34
+ if (!uploadedFiles) return
35
+
36
+ setValue('localImageBlobURL', URL.createObjectURL(files[0]) as never)
37
+ },
38
+ // eslint-disable-next-line react-hooks/exhaustive-deps
39
+ [handleImageUploading],
40
+ )
41
+
42
+ const onImageRemove = useCallback(
43
+ (id, path) => {
44
+ handleImageRemove(id, path)
45
+ setValue('localImageBlobURL', undefined as never)
46
+ },
47
+ // eslint-disable-next-line react-hooks/exhaustive-deps
48
+ [handleImageRemove],
49
+ )
50
+
51
+ useEffect(() => {
52
+ if (!currentProductSet || !currentProductSet.image) return
53
+
54
+ setImages([
55
+ {
56
+ ...currentProductSet.image,
57
+ url:
58
+ currentProductSet.image.url || currentProductSet?.localImageBlobURL,
59
+ },
60
+ ])
61
+ // eslint-disable-next-line react-hooks/exhaustive-deps
62
+ }, [currentProductSet?.image])
63
+
64
+ useEffect(() => {
65
+ if (isEqual(images[0], currentProductSet?.image)) return
66
+
67
+ setValue('image', (images[0] || null) as never)
68
+
69
+ if (images[0]) {
70
+ clearErrors('image')
71
+ }
72
+ // eslint-disable-next-line react-hooks/exhaustive-deps
73
+ }, [images])
74
+
75
+ return (
76
+ <Form.Group controlId='name'>
77
+ <Form.Control
78
+ isInvalid={HookFormService.isInvalid('image', errors)}
79
+ hidden
80
+ />
81
+ <FileUpload
82
+ onFilesChange={onFilesChange}
83
+ allowedExtensions={['jpeg', 'jpg', 'png']}
84
+ subTitle='.jpeg .jpg .png'
85
+ enablePreview
86
+ onFileRemove={onImageRemove}
87
+ defaultFiles={images}
88
+ isLoading={isImageUploading}
89
+ withIcon
90
+ title={t('Design:addImage')}
91
+ />
92
+ <Form.Control.Feedback type='invalid'>
93
+ {HookFormService.getErrors('image', errors)}
94
+ </Form.Control.Feedback>
95
+ </Form.Group>
96
+ )
97
+ }
@@ -6,9 +6,7 @@ export function MobileFooter() {
6
6
 
7
7
  return (
8
8
  <div className='container product-set-mobile-footer'>
9
- <div
10
- className={`d-block d-sm-none bg-white border-top fixed-bottom py-4 px-5 d-flex justify-content-between flex-row-reverse`}
11
- >
9
+ <div className='d-block d-sm-none bg-white border-top fixed-bottom py-4 px-5 d-flex justify-content-between flex-row-reverse'>
12
10
  <Button type='submit'>{t('save')}</Button>
13
11
  </div>
14
12
  </div>
@@ -11,10 +11,7 @@
11
11
  background-color: #fff;
12
12
  font-size: 0.8rem;
13
13
  background-color: $snippet-product-set-background-color;
14
-
15
- div {
16
- text-align: left;
17
- }
14
+ text-align: left;
18
15
 
19
16
  &.active {
20
17
  border: 2px solid #0e8be1;
@@ -23,7 +20,24 @@
23
20
  &-title {
24
21
  font-weight: 600;
25
22
  line-height: 1.25rem;
26
- cursor: pointer;
27
23
  font-size: 0.875rem;
28
24
  }
25
+
26
+ img {
27
+ max-height: 100%;
28
+ max-width: 100%;
29
+ object-fit: cover;
30
+ aspect-ratio: 1.2/1;
31
+ margin-bottom: 0.8rem;
32
+ border-radius: 0.5rem;
33
+ }
34
+
35
+ &-description {
36
+ line-clamp: 5;
37
+ -webkit-line-clamp: 5;
38
+ overflow: hidden;
39
+ text-overflow: ellipsis;
40
+ display: -webkit-box;
41
+ -webkit-box-orient: vertical;
42
+ }
29
43
  }
@@ -3,56 +3,26 @@
3
3
  .list-activity-card {
4
4
  display: flex;
5
5
  justify-content: space-between;
6
- align-items: center;
7
- cursor: pointer;
8
- height: 6rem;
9
- padding: 0.375rem;
10
- border: 2px solid transparent;
11
- border-radius: 0.5rem;
12
- background-color: $snippet-product-set-background-color;
13
- color: $snippet-product-set-font-color;
14
- transition: $color-transition;
6
+ gap: 0.5rem;
7
+ width: 100%;
15
8
 
16
- &.active {
17
- border: 2px solid #0e8be1;
18
- }
19
-
20
- p {
21
- margin-bottom: 0;
22
- }
23
-
24
- .description {
9
+ &-info {
25
10
  width: 70%;
26
11
  font-size: 0.81rem;
27
12
  display: flex;
28
13
  flex-direction: column;
14
+ align-items: start;
29
15
  }
30
16
 
31
- .title {
32
- font-weight: 600;
33
- font-size: 0.875rem;
34
- line-height: 1.5rem;
35
- }
36
-
37
- .image-container {
38
- display: flex;
39
- justify-content: start;
17
+ img {
18
+ max-width: 120px;
40
19
  height: 100%;
41
-
42
- .image {
43
- display: flex;
44
- align-items: start;
45
- width: 7rem;
46
- height: 4rem;
47
- margin-left: 3rem;
48
- background-repeat: no-repeat;
49
- background-size: cover;
50
- background-position: center;
51
- border-radius: 0.5rem;
52
- }
20
+ object-fit: cover;
21
+ aspect-ratio: 1.2/1;
22
+ border-radius: 0.5rem;
53
23
  }
54
- }
55
24
 
56
- .list-activity-card-hr {
57
- margin-top: 0;
25
+ &-hr {
26
+ margin-top: 0;
27
+ }
58
28
  }
@@ -1,2 +1,2 @@
1
- @import './GridActivitiesCard.scss';
1
+ @import './ActivitiesCard.scss';
2
2
  @import './ListActivitiesCard.scss';