@ossy/booking 1.16.3 → 3.0.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.
- package/README.md +4 -0
- package/package.json +9 -9
- package/src/Definition.js +1 -1
- package/src/bookings.component.jsx +25 -20
- package/src/public-booking.page.jsx +2 -1
- package/src/moduleStatus.js +0 -30
- package/src/stories/AvailabilityEditor.stories.jsx +0 -54
- package/src/stories/AvailabilitySetupPage.stories.jsx +0 -44
- package/src/stories/BookingCard.stories.jsx +0 -57
- package/src/stories/BookingList.stories.jsx +0 -30
- package/src/stories/BookingsComponent.stories.jsx +0 -31
- package/src/stories/BookingsPage.stories.jsx +0 -66
- package/src/stories/HeroCover.stories.jsx +0 -42
- package/src/stories/PublicBookingForm.stories.jsx +0 -66
- package/src/stories/PublicBookingPage.stories.jsx +0 -78
- package/src/stories/ServiceCard.stories.jsx +0 -52
- package/src/stories/ServiceDetail.stories.jsx +0 -52
- package/src/stories/ServiceForm.stories.jsx +0 -64
- package/src/stories/ServicesPage.stories.jsx +0 -53
- package/src/stories/decorators.jsx +0 -68
- package/src/stories/story-helpers.jsx +0 -85
package/README.md
CHANGED
|
@@ -67,3 +67,7 @@ Tasks write resources via `commitResource` / `mutateResource` from `@ossy/resour
|
|
|
67
67
|
| `booking-card.component.jsx` | `@ossy/booking/view/booking/card` |
|
|
68
68
|
|
|
69
69
|
See [SPEC.md](./SPEC.md) for the full product specification.
|
|
70
|
+
|
|
71
|
+
## Preview
|
|
72
|
+
|
|
73
|
+
Booking pages and flows are exercised in **`@ossy/app-test`** on the production build path. Enable the package in `devEntitlements`, then open Booking from the sidebar or `/packages/booking`. See [`packages/app-test/README.md`](../app-test/README.md).
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/booking",
|
|
3
3
|
"description": "Booking feature package — services, availability, and appointment management for Ossy providers",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "3.0.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"module": "./src/index.js",
|
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
"src": "./src"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@ossy/email": "^
|
|
18
|
-
"@ossy/event-store": "^
|
|
19
|
-
"@ossy/observability": "^
|
|
20
|
-
"@ossy/platform": "^
|
|
21
|
-
"@ossy/resources": "^
|
|
22
|
-
"@ossy/users": "^
|
|
23
|
-
"@ossy/workspaces": "^
|
|
17
|
+
"@ossy/email": "^3.0.1",
|
|
18
|
+
"@ossy/event-store": "^3.0.1",
|
|
19
|
+
"@ossy/observability": "^3.0.1",
|
|
20
|
+
"@ossy/platform": "^3.0.1",
|
|
21
|
+
"@ossy/resources": "^3.0.1",
|
|
22
|
+
"@ossy/users": "^3.0.1",
|
|
23
|
+
"@ossy/workspaces": "^3.0.1"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
26
|
"@ossy/app": ">=1.0.0",
|
|
@@ -37,5 +37,5 @@
|
|
|
37
37
|
"/src",
|
|
38
38
|
"README.md"
|
|
39
39
|
],
|
|
40
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "4a70ec216448680d2fddc57b2a8a0077489720ae"
|
|
41
41
|
}
|
package/src/Definition.js
CHANGED
|
@@ -1,33 +1,38 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { View, Text, Tags, Page } from '@ossy/design-system'
|
|
3
3
|
import { Definition } from './Definition.js'
|
|
4
|
-
import { moduleStatusTags } from './moduleStatus.js'
|
|
5
4
|
import { BookingList } from './BookingList.jsx'
|
|
6
5
|
|
|
7
6
|
export const metadata = {
|
|
8
7
|
id: 'booking/bookings',
|
|
9
8
|
}
|
|
10
9
|
|
|
11
|
-
export const BookingsContent = () =>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
10
|
+
export const BookingsContent = () => {
|
|
11
|
+
const statusTags = (Definition.status ?? [])
|
|
12
|
+
.map((s) => ({ beta: 'Beta', 'coming-soon': 'Coming Soon' }[s]))
|
|
13
|
+
.filter(Boolean)
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<Page>
|
|
17
|
+
<View gap="s">
|
|
18
|
+
<View layout="row" gap="s" alignItems="center">
|
|
19
|
+
<Text variant="heading-default" as="h1">{Definition.title} — Bookings</Text>
|
|
20
|
+
{statusTags.length > 0 && (
|
|
21
|
+
<Tags tags={statusTags} size="s" />
|
|
22
|
+
)}
|
|
23
|
+
</View>
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
<Text style={{ maxWidth: '400px' }}>
|
|
26
|
+
Incoming appointments from your clients.
|
|
27
|
+
</Text>
|
|
28
|
+
</View>
|
|
25
29
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
)
|
|
30
|
+
<View gap="s">
|
|
31
|
+
<Text variant="heading-secondary" as="h2">Upcoming</Text>
|
|
32
|
+
<BookingList bookings={[]} />
|
|
33
|
+
</View>
|
|
34
|
+
</Page>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
32
37
|
|
|
33
38
|
export default BookingsContent
|
|
@@ -165,6 +165,7 @@ function PendingScreen ({ slot, t, language }) {
|
|
|
165
165
|
export const metadata = {
|
|
166
166
|
id: 'public-booking',
|
|
167
167
|
public: true,
|
|
168
|
+
layout: '@ossy/app/layout/blank',
|
|
168
169
|
path: { sv: '/boka/:providerSlug', en: '/book/:providerSlug' },
|
|
169
170
|
}
|
|
170
171
|
|
|
@@ -254,7 +255,7 @@ export default function PublicBookingPage ({ providerSlug }) {
|
|
|
254
255
|
|
|
255
256
|
return (
|
|
256
257
|
<View surface="base" layout="off-center-s" inset="s" style={{ minHeight: '100dvh' }}>
|
|
257
|
-
<View inset="m" roundness="l" surface="primary"
|
|
258
|
+
<View inset="m" roundness="l" surface="primary" data-region="content" gap="l" style={{ maxWidth: 720, width: '100%' }}>
|
|
258
259
|
<View gap="xs" style={{ borderBottom: '1px solid var(--separator)', paddingBottom: 'var(--space-m)' }}>
|
|
259
260
|
<Text variant="heading-secondary" as="h2" text="publicBooking.title" />
|
|
260
261
|
<Text color="secondary" text="publicBooking.description" />
|
package/src/moduleStatus.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
export const MODULE_STATUS = {
|
|
2
|
-
beta: 'beta',
|
|
3
|
-
comingSoon: 'coming-soon',
|
|
4
|
-
stable: 'stable',
|
|
5
|
-
hidden: 'hidden',
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
const STATUS_TO_TAG_LABEL = {
|
|
9
|
-
[MODULE_STATUS.beta]: 'Beta',
|
|
10
|
-
[MODULE_STATUS.comingSoon]: 'Coming Soon',
|
|
11
|
-
[MODULE_STATUS.stable]: null,
|
|
12
|
-
[MODULE_STATUS.hidden]: null,
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function moduleStatuses(definition) {
|
|
16
|
-
const fromArray = Array.isArray(definition?.statuses) ? definition.statuses : []
|
|
17
|
-
const fromLegacy = definition?.status && typeof definition.status === 'string' ? [definition.status] : []
|
|
18
|
-
if (fromArray.length) return [...new Set(fromArray)]
|
|
19
|
-
if (fromLegacy.length) return [...new Set(fromLegacy)]
|
|
20
|
-
return []
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function moduleStatusTags(definition) {
|
|
24
|
-
const labels = []
|
|
25
|
-
for (const s of moduleStatuses(definition)) {
|
|
26
|
-
const label = STATUS_TO_TAG_LABEL[s]
|
|
27
|
-
if (label) labels.push(label)
|
|
28
|
-
}
|
|
29
|
-
return [...new Set(labels)]
|
|
30
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react'
|
|
2
|
-
import { View, Text } from '@ossy/design-system'
|
|
3
|
-
import { AvailabilityEditor } from '../AvailabilityEditor.jsx'
|
|
4
|
-
import { sampleAvailability } from './fixtures.js'
|
|
5
|
-
|
|
6
|
-
const savedPreviewStyle = {
|
|
7
|
-
margin: 0,
|
|
8
|
-
padding: 'var(--space-s)',
|
|
9
|
-
background: 'var(--color-secondary)',
|
|
10
|
-
color: 'var(--text-default-color, CanvasText)',
|
|
11
|
-
borderRadius: 'var(--space-xs)',
|
|
12
|
-
border: '1px solid var(--separator-primary)',
|
|
13
|
-
fontSize: 12,
|
|
14
|
-
overflow: 'auto',
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export default {
|
|
18
|
-
title: 'Booking/Components/AvailabilityEditor',
|
|
19
|
-
component: AvailabilityEditor,
|
|
20
|
-
parameters: {
|
|
21
|
-
layout: 'padded',
|
|
22
|
-
docs: {
|
|
23
|
-
story: {
|
|
24
|
-
inline: true,
|
|
25
|
-
height: 'auto',
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function AvailabilityEditorShell ({ availability = [] }) {
|
|
32
|
-
const [saved, setSaved] = useState(null)
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<View gap="m" style={{ maxWidth: 720, width: '100%' }}>
|
|
36
|
-
<AvailabilityEditor
|
|
37
|
-
availability={availability}
|
|
38
|
-
onSave={(windows) => setSaved(windows)}
|
|
39
|
-
/>
|
|
40
|
-
{saved && (
|
|
41
|
-
<View gap="xs">
|
|
42
|
-
<Text size="s" color="secondary">Saved windows (JSON)</Text>
|
|
43
|
-
<pre style={savedPreviewStyle}>{JSON.stringify(saved, null, 2)}</pre>
|
|
44
|
-
</View>
|
|
45
|
-
)}
|
|
46
|
-
</View>
|
|
47
|
-
)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export const Empty = () => <AvailabilityEditorShell />
|
|
51
|
-
|
|
52
|
-
export const WithExistingAvailability = () => (
|
|
53
|
-
<AvailabilityEditorShell availability={sampleAvailability} />
|
|
54
|
-
)
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import AvailabilitySetupPage from '../availability-setup.page.jsx'
|
|
3
|
-
import { withMockRouter, withMockSdk } from './decorators.jsx'
|
|
4
|
-
import { sampleAvailabilitySettings, sampleProviderContact } from './fixtures.js'
|
|
5
|
-
|
|
6
|
-
export default {
|
|
7
|
-
title: 'Booking/Pages/AvailabilitySetupPage',
|
|
8
|
-
component: AvailabilitySetupPage,
|
|
9
|
-
decorators: [withMockRouter, withMockSdk],
|
|
10
|
-
parameters: {
|
|
11
|
-
layout: 'fullscreen',
|
|
12
|
-
docs: {
|
|
13
|
-
story: {
|
|
14
|
-
inline: false,
|
|
15
|
-
height: '720px',
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export const LoadedSchedule = {
|
|
22
|
-
parameters: {
|
|
23
|
-
mockSdk: {},
|
|
24
|
-
},
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export const EmptySchedule = {
|
|
28
|
-
parameters: {
|
|
29
|
-
mockSdk: {
|
|
30
|
-
availability: null,
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export const WithBookingLink = {
|
|
36
|
-
args: {
|
|
37
|
-
workspaceId: sampleProviderContact.workspaceId,
|
|
38
|
-
},
|
|
39
|
-
parameters: {
|
|
40
|
-
mockSdk: {
|
|
41
|
-
availability: sampleAvailabilitySettings,
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { View } from '@ossy/design-system'
|
|
3
|
-
import { BookingCard } from '../booking-card.component.jsx'
|
|
4
|
-
import { sampleBookings } from './fixtures.js'
|
|
5
|
-
|
|
6
|
-
export default {
|
|
7
|
-
title: 'Booking/Components/BookingCard',
|
|
8
|
-
component: BookingCard,
|
|
9
|
-
parameters: {
|
|
10
|
-
layout: 'padded',
|
|
11
|
-
docs: {
|
|
12
|
-
story: {
|
|
13
|
-
inline: true,
|
|
14
|
-
height: 'auto',
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const cardShell = (booking, handlers = {}) => (
|
|
21
|
-
<View style={{ maxWidth: 560, width: '100%' }}>
|
|
22
|
-
<BookingCard booking={booking} {...handlers} />
|
|
23
|
-
</View>
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
const mockAction = (label) => async () => {
|
|
27
|
-
await new Promise((resolve) => setTimeout(resolve, 400))
|
|
28
|
-
// eslint-disable-next-line no-console
|
|
29
|
-
console.log(`[Storybook] ${label}`)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export const Pending = () =>
|
|
33
|
-
cardShell(sampleBookings[0], {
|
|
34
|
-
onConfirm: mockAction('confirm'),
|
|
35
|
-
onDecline: mockAction('decline'),
|
|
36
|
-
onView: mockAction('view'),
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
export const Confirmed = () =>
|
|
40
|
-
cardShell(sampleBookings[1], {
|
|
41
|
-
onCancel: mockAction('cancel'),
|
|
42
|
-
onView: mockAction('view'),
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
export const Cancelled = () => cardShell(sampleBookings[2])
|
|
46
|
-
|
|
47
|
-
export const WithMessage = () =>
|
|
48
|
-
cardShell(
|
|
49
|
-
{
|
|
50
|
-
...sampleBookings[1],
|
|
51
|
-
clientMessage:
|
|
52
|
-
'Please focus on onboarding improvements during our session. I have a few specific questions about team workflows.',
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
onCancel: mockAction('cancel'),
|
|
56
|
-
},
|
|
57
|
-
)
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { View } from '@ossy/design-system'
|
|
3
|
-
import { BookingList } from '../BookingList.jsx'
|
|
4
|
-
import { sampleBookings } from './fixtures.js'
|
|
5
|
-
|
|
6
|
-
export default {
|
|
7
|
-
title: 'Booking/Components/BookingList',
|
|
8
|
-
component: BookingList,
|
|
9
|
-
parameters: {
|
|
10
|
-
layout: 'padded',
|
|
11
|
-
docs: {
|
|
12
|
-
story: {
|
|
13
|
-
inline: true,
|
|
14
|
-
height: 'auto',
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const listShell = (bookings) => (
|
|
21
|
-
<View style={{ maxWidth: 640, width: '100%' }}>
|
|
22
|
-
<BookingList bookings={bookings} />
|
|
23
|
-
</View>
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
export const Empty = () => listShell([])
|
|
27
|
-
|
|
28
|
-
export const Populated = () => listShell(sampleBookings)
|
|
29
|
-
|
|
30
|
-
export const SinglePending = () => listShell([sampleBookings[0]])
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { View } from '@ossy/design-system'
|
|
3
|
-
import { BookingsContent } from '../bookings.component.jsx'
|
|
4
|
-
import { BookingList } from '../BookingList.jsx'
|
|
5
|
-
import { sampleBookings } from './fixtures.js'
|
|
6
|
-
|
|
7
|
-
export default {
|
|
8
|
-
title: 'Booking/Components/BookingsComponent',
|
|
9
|
-
component: BookingsContent,
|
|
10
|
-
parameters: {
|
|
11
|
-
layout: 'padded',
|
|
12
|
-
docs: {
|
|
13
|
-
story: {
|
|
14
|
-
inline: true,
|
|
15
|
-
height: 'auto',
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export const MarketingShell = () => <BookingsContent />
|
|
22
|
-
|
|
23
|
-
export const WithSampleBookings = () => (
|
|
24
|
-
<View
|
|
25
|
-
gap="m"
|
|
26
|
-
surface="primary"
|
|
27
|
-
style={{ padding: 'var(--space-m) var(--space-l)', maxWidth: 640, width: '100%' }}
|
|
28
|
-
>
|
|
29
|
-
<BookingList bookings={sampleBookings} />
|
|
30
|
-
</View>
|
|
31
|
-
)
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import BookingsPage from '../bookings.page.jsx'
|
|
3
|
-
import { withMockRouter, withMockSdk } from './decorators.jsx'
|
|
4
|
-
import { useSelectBookingsTab } from './story-helpers.jsx'
|
|
5
|
-
import { sampleBookings } from './fixtures.js'
|
|
6
|
-
|
|
7
|
-
export default {
|
|
8
|
-
title: 'Booking/Pages/BookingsPage',
|
|
9
|
-
component: BookingsPage,
|
|
10
|
-
decorators: [withMockRouter, withMockSdk],
|
|
11
|
-
parameters: {
|
|
12
|
-
layout: 'fullscreen',
|
|
13
|
-
docs: {
|
|
14
|
-
story: {
|
|
15
|
-
inline: false,
|
|
16
|
-
height: '640px',
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export const PendingTab = {
|
|
23
|
-
parameters: {
|
|
24
|
-
mockSdk: {},
|
|
25
|
-
},
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function ConfirmedTabStory () {
|
|
29
|
-
useSelectBookingsTab('confirmed')
|
|
30
|
-
return <BookingsPage />
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export const ConfirmedTab = {
|
|
34
|
-
render: () => <ConfirmedTabStory />,
|
|
35
|
-
parameters: {
|
|
36
|
-
mockSdk: {},
|
|
37
|
-
},
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function AllTabStory () {
|
|
41
|
-
useSelectBookingsTab('all')
|
|
42
|
-
return <BookingsPage />
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export const AllTab = {
|
|
46
|
-
render: () => <AllTabStory />,
|
|
47
|
-
parameters: {
|
|
48
|
-
mockSdk: {},
|
|
49
|
-
},
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export const EmptyPending = {
|
|
53
|
-
parameters: {
|
|
54
|
-
mockSdk: {
|
|
55
|
-
bookings: sampleBookings.filter((booking) => booking.status !== 'pending'),
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export const LoadError = {
|
|
61
|
-
parameters: {
|
|
62
|
-
mockSdk: {
|
|
63
|
-
'@ossy/booking/actions/list': () => Promise.reject(new Error('Failed to load bookings')),
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { HeroCover } from '../HeroCover.jsx'
|
|
3
|
-
import { sampleHeroCover } from './fixtures.js'
|
|
4
|
-
|
|
5
|
-
export default {
|
|
6
|
-
title: 'Booking/Components/HeroCover',
|
|
7
|
-
component: HeroCover,
|
|
8
|
-
parameters: {
|
|
9
|
-
layout: 'padded',
|
|
10
|
-
docs: {
|
|
11
|
-
story: {
|
|
12
|
-
inline: true,
|
|
13
|
-
height: 'auto',
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export const Default = () => <HeroCover {...sampleHeroCover} />
|
|
20
|
-
|
|
21
|
-
export const SingleAction = () => (
|
|
22
|
-
<HeroCover
|
|
23
|
-
{...sampleHeroCover}
|
|
24
|
-
actions={[{ label: 'Start booking setup', variant: 'cta' }]}
|
|
25
|
-
/>
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
export const CompactTitle = () => (
|
|
29
|
-
<HeroCover
|
|
30
|
-
title="Simple scheduling for small teams"
|
|
31
|
-
titleMaxWidth="640px"
|
|
32
|
-
text="Publish services, collect requests, and confirm appointments from one workspace."
|
|
33
|
-
actions={[{ label: 'Explore booking', variant: 'primary' }]}
|
|
34
|
-
/>
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
export const Minimal = () => (
|
|
38
|
-
<HeroCover
|
|
39
|
-
title="Booking"
|
|
40
|
-
text="Let clients request time with you online."
|
|
41
|
-
/>
|
|
42
|
-
)
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react'
|
|
2
|
-
import { Button, View, Form, FieldFactory } from '@ossy/design-system'
|
|
3
|
-
import publicBookingResource from '../public-booking-form.schema.js'
|
|
4
|
-
import { metadata as publicBookingFormMeta } from '../public-booking-form.form.js'
|
|
5
|
-
import { samplePublicBookingFormData } from './fixtures.js'
|
|
6
|
-
|
|
7
|
-
const dataPreviewStyle = {
|
|
8
|
-
margin: 0,
|
|
9
|
-
padding: 'var(--space-s)',
|
|
10
|
-
background: 'var(--color-secondary)',
|
|
11
|
-
color: 'var(--text-default-color, CanvasText)',
|
|
12
|
-
borderRadius: 'var(--space-xs)',
|
|
13
|
-
border: '1px solid var(--separator-primary)',
|
|
14
|
-
fontSize: 12,
|
|
15
|
-
overflow: 'auto',
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export default {
|
|
19
|
-
title: 'Booking/Forms/PublicBookingForm',
|
|
20
|
-
component: Form,
|
|
21
|
-
parameters: {
|
|
22
|
-
layout: 'padded',
|
|
23
|
-
docs: {
|
|
24
|
-
story: {
|
|
25
|
-
inline: true,
|
|
26
|
-
height: 'auto',
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function PublicBookingFormShell ({ initial = {} }) {
|
|
33
|
-
const [preview, setPreview] = useState(initial)
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<View
|
|
37
|
-
surface="primary"
|
|
38
|
-
inset="m"
|
|
39
|
-
roundness="m"
|
|
40
|
-
gap="m"
|
|
41
|
-
style={{ maxWidth: 480, width: '100%', fontFamily: 'system-ui, sans-serif' }}
|
|
42
|
-
>
|
|
43
|
-
<Form
|
|
44
|
-
id="storybook/booking/public-booking"
|
|
45
|
-
schemaId={publicBookingFormMeta.schemaId}
|
|
46
|
-
fields={publicBookingResource.fields}
|
|
47
|
-
defaultData={initial}
|
|
48
|
-
onChange={({ data }) => setPreview(data)}
|
|
49
|
-
onSubmit={(data) => setPreview(data)}
|
|
50
|
-
gap="m"
|
|
51
|
-
>
|
|
52
|
-
<FieldFactory />
|
|
53
|
-
<Button type="submit" variant="cta">
|
|
54
|
-
Request booking
|
|
55
|
-
</Button>
|
|
56
|
-
</Form>
|
|
57
|
-
<pre style={dataPreviewStyle}>{JSON.stringify(preview, null, 2)}</pre>
|
|
58
|
-
</View>
|
|
59
|
-
)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export const Empty = () => <PublicBookingFormShell />
|
|
63
|
-
|
|
64
|
-
export const Prefilled = () => (
|
|
65
|
-
<PublicBookingFormShell initial={samplePublicBookingFormData} />
|
|
66
|
-
)
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import PublicBookingPage from '../public-booking.page.jsx'
|
|
3
|
-
import { withMockSdk } from './decorators.jsx'
|
|
4
|
-
import { useAdvancePublicBookingStep } from './story-helpers.jsx'
|
|
5
|
-
import { sampleProviderContact } from './fixtures.js'
|
|
6
|
-
|
|
7
|
-
export default {
|
|
8
|
-
title: 'Booking/Pages/PublicBookingPage',
|
|
9
|
-
component: PublicBookingPage,
|
|
10
|
-
decorators: [withMockSdk],
|
|
11
|
-
parameters: {
|
|
12
|
-
layout: 'fullscreen',
|
|
13
|
-
docs: {
|
|
14
|
-
story: {
|
|
15
|
-
inline: false,
|
|
16
|
-
height: '720px',
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const pageArgs = {
|
|
23
|
-
providerSlug: sampleProviderContact.workspaceId,
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export const ServiceSelection = {
|
|
27
|
-
args: pageArgs,
|
|
28
|
-
parameters: {
|
|
29
|
-
mockSdk: {},
|
|
30
|
-
},
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function CalendarStepStory () {
|
|
34
|
-
useAdvancePublicBookingStep('pick-slot')
|
|
35
|
-
return <PublicBookingPage providerSlug={pageArgs.providerSlug} />
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export const CalendarAndSlots = {
|
|
39
|
-
render: () => <CalendarStepStory />,
|
|
40
|
-
args: pageArgs,
|
|
41
|
-
parameters: {
|
|
42
|
-
mockSdk: {},
|
|
43
|
-
},
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function ClientDetailsStepStory () {
|
|
47
|
-
useAdvancePublicBookingStep('fill-form')
|
|
48
|
-
return <PublicBookingPage providerSlug={pageArgs.providerSlug} />
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export const ClientDetailsForm = {
|
|
52
|
-
render: () => <ClientDetailsStepStory />,
|
|
53
|
-
args: pageArgs,
|
|
54
|
-
parameters: {
|
|
55
|
-
mockSdk: {},
|
|
56
|
-
},
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export const NoServices = {
|
|
60
|
-
args: pageArgs,
|
|
61
|
-
parameters: {
|
|
62
|
-
mockSdk: {
|
|
63
|
-
publicServices: {
|
|
64
|
-
services: [],
|
|
65
|
-
contact: sampleProviderContact,
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export const ServicesLoadError = {
|
|
72
|
-
args: pageArgs,
|
|
73
|
-
parameters: {
|
|
74
|
-
mockSdk: {
|
|
75
|
-
'@ossy/booking/actions/get-services': () => Promise.reject(new Error('Provider unavailable')),
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { View } from '@ossy/design-system'
|
|
3
|
-
import { ServiceCard } from '../ServiceCard.jsx'
|
|
4
|
-
import { sampleServices } from './fixtures.js'
|
|
5
|
-
|
|
6
|
-
export default {
|
|
7
|
-
title: 'Booking/Components/ServiceCard',
|
|
8
|
-
component: ServiceCard,
|
|
9
|
-
parameters: {
|
|
10
|
-
layout: 'padded',
|
|
11
|
-
docs: {
|
|
12
|
-
story: {
|
|
13
|
-
inline: true,
|
|
14
|
-
height: 'auto',
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const cardShell = (props) => (
|
|
21
|
-
<View style={{ maxWidth: 520, width: '100%' }}>
|
|
22
|
-
<ServiceCard {...props} />
|
|
23
|
-
</View>
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
export const Default = () => cardShell(sampleServices[0])
|
|
27
|
-
|
|
28
|
-
export const FreeService = () =>
|
|
29
|
-
cardShell({
|
|
30
|
-
...sampleServices[0],
|
|
31
|
-
name: 'Intro call',
|
|
32
|
-
price: 0,
|
|
33
|
-
duration: 30,
|
|
34
|
-
description: 'A short complimentary introduction.',
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
export const Inactive = () => cardShell(sampleServices[2])
|
|
38
|
-
|
|
39
|
-
export const LongDescription = () =>
|
|
40
|
-
cardShell({
|
|
41
|
-
...sampleServices[0],
|
|
42
|
-
description:
|
|
43
|
-
'A comprehensive first meeting covering goals, constraints, timeline, and deliverables. We leave with a shared understanding of scope and a proposed next step.',
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
export const AllVariants = () => (
|
|
47
|
-
<View gap="m" style={{ maxWidth: 520, width: '100%' }}>
|
|
48
|
-
{sampleServices.map((service) => (
|
|
49
|
-
<ServiceCard key={service.name} {...service} />
|
|
50
|
-
))}
|
|
51
|
-
</View>
|
|
52
|
-
)
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { View } from '@ossy/design-system'
|
|
3
|
-
import ServiceDetailComponent from '../service-detail.component.jsx'
|
|
4
|
-
import { sampleServiceResource } from './fixtures.js'
|
|
5
|
-
|
|
6
|
-
export default {
|
|
7
|
-
title: 'Booking/Components/ServiceDetail',
|
|
8
|
-
component: ServiceDetailComponent,
|
|
9
|
-
parameters: {
|
|
10
|
-
layout: 'padded',
|
|
11
|
-
docs: {
|
|
12
|
-
story: {
|
|
13
|
-
inline: true,
|
|
14
|
-
height: 'auto',
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const detailShell = (resource) => (
|
|
21
|
-
<View surface="primary" roundness="m" style={{ maxWidth: 560, width: '100%' }}>
|
|
22
|
-
<ServiceDetailComponent resource={resource} />
|
|
23
|
-
</View>
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
export const ActiveService = () => detailShell(sampleServiceResource)
|
|
27
|
-
|
|
28
|
-
export const FreeService = () =>
|
|
29
|
-
detailShell({
|
|
30
|
-
...sampleServiceResource,
|
|
31
|
-
name: 'Intro call',
|
|
32
|
-
content: {
|
|
33
|
-
...sampleServiceResource.content,
|
|
34
|
-
name: 'Intro call',
|
|
35
|
-
price: 0,
|
|
36
|
-
duration: 30,
|
|
37
|
-
description: 'A short complimentary introduction call.',
|
|
38
|
-
},
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
export const InactiveService = () =>
|
|
42
|
-
detailShell({
|
|
43
|
-
...sampleServiceResource,
|
|
44
|
-
content: {
|
|
45
|
-
...sampleServiceResource.content,
|
|
46
|
-
name: 'Legacy workshop',
|
|
47
|
-
active: false,
|
|
48
|
-
price: 0,
|
|
49
|
-
duration: 120,
|
|
50
|
-
description: 'No longer offered to new clients.',
|
|
51
|
-
},
|
|
52
|
-
})
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react'
|
|
2
|
-
import { Button, View, Form, FieldFactory } from '@ossy/design-system'
|
|
3
|
-
import serviceResource from '../service.schema.js'
|
|
4
|
-
import { metadata as serviceFormMeta } from '../service.form.js'
|
|
5
|
-
import { sampleServiceFormData } from './fixtures.js'
|
|
6
|
-
|
|
7
|
-
const dataPreviewStyle = {
|
|
8
|
-
margin: 0,
|
|
9
|
-
padding: 'var(--space-s)',
|
|
10
|
-
background: 'var(--color-secondary)',
|
|
11
|
-
color: 'var(--text-default-color, CanvasText)',
|
|
12
|
-
borderRadius: 'var(--space-xs)',
|
|
13
|
-
border: '1px solid var(--separator-primary)',
|
|
14
|
-
fontSize: 12,
|
|
15
|
-
overflow: 'auto',
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export default {
|
|
19
|
-
title: 'Booking/Forms/ServiceForm',
|
|
20
|
-
component: Form,
|
|
21
|
-
parameters: {
|
|
22
|
-
layout: 'padded',
|
|
23
|
-
docs: {
|
|
24
|
-
story: {
|
|
25
|
-
inline: true,
|
|
26
|
-
height: 'auto',
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function ServiceFormShell ({ initial = sampleServiceFormData }) {
|
|
33
|
-
const [preview, setPreview] = useState(initial)
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<View
|
|
37
|
-
surface="primary"
|
|
38
|
-
inset="m"
|
|
39
|
-
roundness="m"
|
|
40
|
-
gap="m"
|
|
41
|
-
style={{ maxWidth: 480, width: '100%', fontFamily: 'system-ui, sans-serif' }}
|
|
42
|
-
>
|
|
43
|
-
<Form
|
|
44
|
-
id="storybook/booking/service"
|
|
45
|
-
schemaId={serviceFormMeta.schemaId}
|
|
46
|
-
fields={serviceResource.fields}
|
|
47
|
-
defaultData={initial}
|
|
48
|
-
onChange={({ data }) => setPreview(data)}
|
|
49
|
-
onSubmit={(data) => setPreview(data)}
|
|
50
|
-
gap="m"
|
|
51
|
-
>
|
|
52
|
-
<FieldFactory />
|
|
53
|
-
<Button type="submit" variant="cta">
|
|
54
|
-
Save service
|
|
55
|
-
</Button>
|
|
56
|
-
</Form>
|
|
57
|
-
<pre style={dataPreviewStyle}>{JSON.stringify(preview, null, 2)}</pre>
|
|
58
|
-
</View>
|
|
59
|
-
)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export const Default = () => <ServiceFormShell />
|
|
63
|
-
|
|
64
|
-
export const Empty = () => <ServiceFormShell initial={{ active: true, currency: 'SEK' }} />
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import ServicesPage from '../services.page.jsx'
|
|
3
|
-
import { withMockSdk } from './decorators.jsx'
|
|
4
|
-
import { useOpenServicesCreateForm } from './story-helpers.jsx'
|
|
5
|
-
|
|
6
|
-
export default {
|
|
7
|
-
title: 'Booking/Pages/ServicesPage',
|
|
8
|
-
component: ServicesPage,
|
|
9
|
-
decorators: [withMockSdk],
|
|
10
|
-
parameters: {
|
|
11
|
-
layout: 'fullscreen',
|
|
12
|
-
docs: {
|
|
13
|
-
story: {
|
|
14
|
-
inline: false,
|
|
15
|
-
height: '640px',
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export const ListWithServices = {
|
|
22
|
-
parameters: {
|
|
23
|
-
mockSdk: {},
|
|
24
|
-
},
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export const EmptyList = {
|
|
28
|
-
parameters: {
|
|
29
|
-
mockSdk: {
|
|
30
|
-
services: [],
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function CreateFormOpenStory () {
|
|
36
|
-
useOpenServicesCreateForm()
|
|
37
|
-
return <ServicesPage />
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export const CreateFormOpen = {
|
|
41
|
-
render: () => <CreateFormOpenStory />,
|
|
42
|
-
parameters: {
|
|
43
|
-
mockSdk: {},
|
|
44
|
-
},
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export const LoadError = {
|
|
48
|
-
parameters: {
|
|
49
|
-
mockSdk: {
|
|
50
|
-
'@ossy/booking/actions/get-services': () => Promise.reject(new Error('Failed to load services')),
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createMockRouterDecorator,
|
|
3
|
-
createMockSdkDecorator,
|
|
4
|
-
resolveMockActionId,
|
|
5
|
-
} from '../../../../.storybook/decorators/index.jsx'
|
|
6
|
-
import {
|
|
7
|
-
buildSampleSlots,
|
|
8
|
-
sampleAvailabilitySettings,
|
|
9
|
-
sampleBookings,
|
|
10
|
-
samplePublicServicesResponse,
|
|
11
|
-
sampleServicesWithIds,
|
|
12
|
-
} from './fixtures.js'
|
|
13
|
-
|
|
14
|
-
export { resolveMockActionId }
|
|
15
|
-
|
|
16
|
-
export function createMockSdkHandlers (overrides = {}) {
|
|
17
|
-
return {
|
|
18
|
-
'@ossy/booking/actions/get-services': async (payload) => {
|
|
19
|
-
if (payload?.providerSlug) {
|
|
20
|
-
if ('publicServices' in overrides) return overrides.publicServices
|
|
21
|
-
return samplePublicServicesResponse
|
|
22
|
-
}
|
|
23
|
-
if ('services' in overrides) return overrides.services
|
|
24
|
-
return sampleServicesWithIds
|
|
25
|
-
},
|
|
26
|
-
'@ossy/booking/actions/list': async () => (
|
|
27
|
-
'bookings' in overrides ? overrides.bookings : sampleBookings
|
|
28
|
-
),
|
|
29
|
-
'@ossy/booking/actions/get-available-slots': async () => (
|
|
30
|
-
'slots' in overrides ? overrides.slots : buildSampleSlots()
|
|
31
|
-
),
|
|
32
|
-
'@ossy/booking/actions/get-availability': async () => (
|
|
33
|
-
'availability' in overrides ? overrides.availability : sampleAvailabilitySettings
|
|
34
|
-
),
|
|
35
|
-
'@ossy/booking/actions/create-service': async () => ({}),
|
|
36
|
-
'@ossy/booking/actions/update-service': async () => ({}),
|
|
37
|
-
'@ossy/booking/actions/delete-service': async () => ({}),
|
|
38
|
-
'@ossy/booking/actions/save-availability': async () => ({}),
|
|
39
|
-
'@ossy/booking/actions/create': async () => ({}),
|
|
40
|
-
'@ossy/booking/actions/confirm': async () => ({}),
|
|
41
|
-
'@ossy/booking/actions/decline': async () => ({}),
|
|
42
|
-
'@ossy/booking/actions/cancel': async () => ({}),
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const defaultMockRouter = {
|
|
47
|
-
href: 'http://localhost/en/bookings',
|
|
48
|
-
pages: [],
|
|
49
|
-
language: 'en',
|
|
50
|
-
defaultLanguage: 'en',
|
|
51
|
-
supportedLanguages: ['en', 'sv'],
|
|
52
|
-
params: {},
|
|
53
|
-
searchParams: {},
|
|
54
|
-
navigate: () => {},
|
|
55
|
-
getHref: ({ id, params = {} } = {}) => {
|
|
56
|
-
if (id === 'booking/booking-detail' && params.bookingId) {
|
|
57
|
-
return `/en/bookings/${params.bookingId}`
|
|
58
|
-
}
|
|
59
|
-
if (id === 'public-booking' && params.providerSlug) {
|
|
60
|
-
return `/en/book/${params.providerSlug}`
|
|
61
|
-
}
|
|
62
|
-
return `/en/${String(id).replace(/\//g, '-')}`
|
|
63
|
-
},
|
|
64
|
-
back: () => {},
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export const withMockSdk = createMockSdkDecorator(createMockSdkHandlers)
|
|
68
|
-
export const withMockRouter = createMockRouterDecorator(defaultMockRouter)
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { useEffect } from 'react'
|
|
2
|
-
|
|
3
|
-
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
4
|
-
|
|
5
|
-
async function waitForSelector (selector, { attempts = 30, interval = 100 } = {}) {
|
|
6
|
-
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
7
|
-
const element = document.querySelector(selector)
|
|
8
|
-
if (element) return element
|
|
9
|
-
await sleep(interval)
|
|
10
|
-
}
|
|
11
|
-
return null
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function useOpenServicesCreateForm () {
|
|
15
|
-
useEffect(() => {
|
|
16
|
-
let cancelled = false
|
|
17
|
-
|
|
18
|
-
async function openForm () {
|
|
19
|
-
await sleep(200)
|
|
20
|
-
if (cancelled) return
|
|
21
|
-
const button = await waitForSelector('[data-action="@ossy/booking/actions/open-create-service"]')
|
|
22
|
-
button?.click()
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
openForm()
|
|
26
|
-
return () => { cancelled = true }
|
|
27
|
-
}, [])
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function useSelectBookingsTab (tabId) {
|
|
31
|
-
useEffect(() => {
|
|
32
|
-
let cancelled = false
|
|
33
|
-
|
|
34
|
-
async function selectTab () {
|
|
35
|
-
await sleep(300)
|
|
36
|
-
if (cancelled) return
|
|
37
|
-
const tabsRoot = await waitForSelector('[data-tabs]')
|
|
38
|
-
if (!tabsRoot) return
|
|
39
|
-
const tabIndex = { pending: 0, confirmed: 1, all: 2 }[tabId] ?? 0
|
|
40
|
-
tabsRoot.querySelectorAll('button')[tabIndex]?.click()
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
selectTab()
|
|
44
|
-
return () => { cancelled = true }
|
|
45
|
-
}, [tabId])
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Advances PublicBookingPage through its wizard for calendar / form step stories.
|
|
50
|
-
* @param {'pick-slot' | 'fill-form'} targetStep
|
|
51
|
-
*/
|
|
52
|
-
export function useAdvancePublicBookingStep (targetStep) {
|
|
53
|
-
useEffect(() => {
|
|
54
|
-
let cancelled = false
|
|
55
|
-
|
|
56
|
-
async function advance () {
|
|
57
|
-
if (targetStep === 'pick-service') return
|
|
58
|
-
|
|
59
|
-
await sleep(300)
|
|
60
|
-
if (cancelled) return
|
|
61
|
-
|
|
62
|
-
const serviceButton = await waitForSelector('[data-action="public-booking/pick-service"]')
|
|
63
|
-
serviceButton?.click()
|
|
64
|
-
|
|
65
|
-
await sleep(150)
|
|
66
|
-
document.querySelector('[data-action="public-booking/continue"]')?.click()
|
|
67
|
-
|
|
68
|
-
if (targetStep === 'pick-slot') return
|
|
69
|
-
|
|
70
|
-
await sleep(400)
|
|
71
|
-
const dateButton = await waitForSelector('[data-action="public-booking/pick-date"]:not([disabled])')
|
|
72
|
-
dateButton?.click()
|
|
73
|
-
|
|
74
|
-
await sleep(200)
|
|
75
|
-
const slotButton = await waitForSelector('[data-action="public-booking/pick-slot"]')
|
|
76
|
-
slotButton?.click()
|
|
77
|
-
|
|
78
|
-
await sleep(150)
|
|
79
|
-
document.querySelector('[data-action="public-booking/continue"]')?.click()
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
advance()
|
|
83
|
-
return () => { cancelled = true }
|
|
84
|
-
}, [targetStep])
|
|
85
|
-
}
|