@ossy/booking 1.15.5 → 1.15.7

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 CHANGED
@@ -19,6 +19,18 @@ Three resource schemas are registered under the `@ossy/booking` category:
19
19
  - `@ossy/booking/booking` — a confirmed appointment
20
20
  - `@ossy/booking/availability` — recurring weekly availability window
21
21
 
22
+ ## Resource view slots
23
+
24
+ Register custom UI for resource types via `*.component.jsx` with `metadata.id` matching the slot key (see `@ossy/design-system` [SLOTS.md](../design-system/docs/SLOTS.md)):
25
+
26
+ | Component | Slot key |
27
+ |-----------|----------|
28
+ | `service-detail.component.jsx` | `resource:booking/service/detail` |
29
+ | `service-card-slot.component.jsx` | `resource:booking/service/card` |
30
+ | `booking-card.component.jsx` | `resource:booking/booking/card` |
31
+
32
+ Dedicated pages (`services.page.jsx`, `booking-detail.page.jsx`, etc.) continue to work alongside generic `@ossy/resources` slot hosts.
33
+
22
34
  ## What still needs implementation
23
35
 
24
36
  - **Slot generation** — derive available time slots from `availability` records minus existing `booking` records
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 consultants",
4
- "version": "1.15.5",
4
+ "version": "1.15.7",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
7
7
  "module": "./src/index.js",
@@ -14,11 +14,11 @@
14
14
  "src": "./src"
15
15
  },
16
16
  "dependencies": {
17
- "@ossy/email": "^1.5.5",
18
- "@ossy/event-store": "^1.7.5",
19
- "@ossy/observability": "^1.7.5",
20
- "@ossy/platform": "^1.38.5",
21
- "@ossy/resources": "^1.11.5"
17
+ "@ossy/email": "^1.5.7",
18
+ "@ossy/event-store": "^1.7.7",
19
+ "@ossy/observability": "^1.7.7",
20
+ "@ossy/platform": "^1.38.7",
21
+ "@ossy/resources": "^1.11.7"
22
22
  },
23
23
  "peerDependencies": {
24
24
  "@ossy/design-system": ">=1.0.0",
@@ -34,5 +34,5 @@
34
34
  "/src",
35
35
  "README.md"
36
36
  ],
37
- "gitHead": "97d02b0762f2c0461abb1e6753fc3aaad30ee93f"
37
+ "gitHead": "f65df0cd6da864c314c4f424112e05e8e2dec0ff"
38
38
  }
@@ -1,5 +1,5 @@
1
1
  import React, { useState } from 'react'
2
- import { View, Title, Text, Button, Input, Switch } from '@ossy/design-system'
2
+ import { View, Title, Text, Button, Input } from '@ossy/design-system'
3
3
 
4
4
  const DAY_LABELS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
5
5
 
@@ -54,10 +54,14 @@ export const AvailabilityEditor = ({ availability = [], onSave }) => {
54
54
  roundness="m"
55
55
  inset="s"
56
56
  >
57
- <Switch
58
- checked={days[idx].enabled}
59
- onChange={(enabled) => update(idx, { enabled })}
60
- />
57
+ <label style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }}>
58
+ <input
59
+ type="checkbox"
60
+ checked={days[idx].enabled}
61
+ onChange={e => update(idx, { enabled: e.target.checked })}
62
+ style={{ width: 16, height: 16, accentColor: 'var(--color-accent)' }}
63
+ />
64
+ </label>
61
65
  <Text weight="medium" style={{ width: '2.5rem', flexShrink: 0 }}>{label}</Text>
62
66
 
63
67
  {days[idx].enabled ? (
@@ -93,7 +97,7 @@ export const AvailabilityEditor = ({ availability = [], onSave }) => {
93
97
  </View>
94
98
 
95
99
  {/* TODO: wire to booking.set-availability action */}
96
- <Button variant="primary" onPress={handleSave}>Save availability</Button>
100
+ <Button variant="primary" onClick={handleSave}>Save availability</Button>
97
101
  </View>
98
102
  )
99
103
  }
@@ -22,7 +22,7 @@ export const BookingForm = ({ workspaceId }) => {
22
22
  <Title variant="secondary">Choose a service</Title>
23
23
  {/* TODO: replace with real services loaded from API */}
24
24
  <Text color="secondary">Loading services for workspace {workspaceId}…</Text>
25
- <Button variant="secondary" onPress={() => setStep('pick-slot')}>
25
+ <Button variant="secondary" onClick={() => setStep('pick-slot')}>
26
26
  Continue (placeholder)
27
27
  </Button>
28
28
  </View>
@@ -36,8 +36,8 @@ export const BookingForm = ({ workspaceId }) => {
36
36
  {/* TODO: render a slot grid derived from availability minus existing bookings */}
37
37
  <Text color="secondary">Available slots will appear here.</Text>
38
38
  <View layout="row" gap="s">
39
- <Button variant="ghost" onPress={() => setStep('pick-service')}>Back</Button>
40
- <Button variant="secondary" onPress={() => setStep('enter-details')}>
39
+ <Button variant="ghost" onClick={() => setStep('pick-service')}>Back</Button>
40
+ <Button variant="secondary" onClick={() => setStep('enter-details')}>
41
41
  Continue (placeholder)
42
42
  </Button>
43
43
  </View>
@@ -63,9 +63,9 @@ export const BookingForm = ({ workspaceId }) => {
63
63
  placeholder="jane@example.com"
64
64
  />
65
65
  <View layout="row" gap="s">
66
- <Button variant="ghost" onPress={() => setStep('pick-slot')}>Back</Button>
66
+ <Button variant="ghost" onClick={() => setStep('pick-slot')}>Back</Button>
67
67
  {/* TODO: submit booking action then redirect to Stripe */}
68
- <Button variant="primary" onPress={() => setStep('confirm')}>
68
+ <Button variant="primary" onClick={() => setStep('confirm')}>
69
69
  Confirm &amp; Pay
70
70
  </Button>
71
71
  </View>
@@ -0,0 +1,99 @@
1
+ import React from 'react'
2
+ import { Title, Button, View, PageSection } from '@ossy/design-system'
3
+
4
+ const Cover = ({
5
+ title,
6
+ titleMaxWidth = '1100px',
7
+ text,
8
+ actions = [],
9
+ }) => (
10
+ <View
11
+ gap="l"
12
+ placeContent="center"
13
+ layout="column"
14
+ justifyContent="center"
15
+ style={{
16
+ height: '100%',
17
+ borderRadius: 'var(--space-m)',
18
+ padding: 'var(--space-xl) var(--space-s)',
19
+ }}
20
+ >
21
+ <View gap="m" alignItems="center">
22
+ <Title as="h1" variant="display" style={{ maxWidth: titleMaxWidth, textWrap: 'balance' }}>
23
+ {title}
24
+ </Title>
25
+
26
+ <Title as="h2" variant="title-tertiary" style={{ maxWidth: '800px' }}>
27
+ {text}
28
+ </Title>
29
+ </View>
30
+
31
+ <View gap="m" layout="row" justifyContent="center" style={{ flexWrap: 'wrap' }}>
32
+ {actions.map(({ label, ...props }) => (
33
+ <Button {...props} key={label}>
34
+ {label}
35
+ </Button>
36
+ ))}
37
+ </View>
38
+ </View>
39
+ )
40
+
41
+ export const HeroCover = ({
42
+ title,
43
+ titleMaxWidth = '1100px',
44
+ text,
45
+ actions = [],
46
+
47
+ id = 'hero-cover',
48
+ surfaceAs = 'div',
49
+ maxWidth = 'l',
50
+ surface = 'hero',
51
+ ...props
52
+ }) => {
53
+ const { style: passthroughStyle, ...pageSectionProps } = props
54
+
55
+ return (
56
+ <PageSection
57
+ id={id}
58
+ surfaceAs={surfaceAs}
59
+ maxWidth={maxWidth}
60
+ surface={surface}
61
+ {...pageSectionProps}
62
+ data-rounded
63
+ data-component="@ossy/website-ossy/cover"
64
+ style={{
65
+ backgroundAttachment: 'scroll',
66
+ boxShadow: 'inset 0 1px 0 color-mix(in srgb, var(--foreground) 6%, transparent)',
67
+ ...passthroughStyle,
68
+ }}
69
+ >
70
+ <style href="@ossy/website-ossy/cover" precedence="high">
71
+ {`
72
+ [data-component="@ossy/website-ossy/cover"] {
73
+ gap: var(--space-m);
74
+ min-height: min(42vh, 420px);
75
+ height: auto;
76
+ }
77
+
78
+ [data-rounded] {
79
+ border-radius: var(--space-m);
80
+ overflow: hidden;
81
+ }
82
+
83
+ @media (min-width: 900px) {
84
+ [data-component="@ossy/website-ossy/cover"] {
85
+ min-height: min(46vh, 520px);
86
+ }
87
+ }
88
+ `}
89
+ </style>
90
+
91
+ <Cover
92
+ title={title}
93
+ titleMaxWidth={titleMaxWidth}
94
+ text={text}
95
+ actions={actions}
96
+ />
97
+ </PageSection>
98
+ )
99
+ }
@@ -0,0 +1,72 @@
1
+ import React from 'react'
2
+ import {
3
+ Title,
4
+ Text,
5
+ View,
6
+ Icon,
7
+ PageSection,
8
+ MarkdownViewer,
9
+ } from '@ossy/design-system'
10
+ import { HeroCover } from './HeroCover.jsx'
11
+
12
+ export default ({
13
+ cover,
14
+ features,
15
+ }) => (
16
+ <PageSection maxWidth="xl" gap="l">
17
+ <View gap="l" surface="primary" style={{ padding: 'var(--space-m) var(--space-l)', height: '100%', overflowY: 'auto' }}>
18
+ <HeroCover {...cover} />
19
+
20
+ <View gap="m" inset="s">
21
+ <Title variant="secondary">
22
+ Översikt
23
+ </Title>
24
+
25
+ <View gap="m" layout="row-wrap">
26
+ {features.map(x => (
27
+ <View
28
+ key={x.title}
29
+ roundness="m"
30
+ surface="primary"
31
+ inset="m"
32
+ gap="m"
33
+ style={{
34
+ width: 200,
35
+ border: '1px solid var(--separator)',
36
+ }}
37
+ >
38
+ <View justifyContent="center" alignItems="center">
39
+ <Icon name={x.icon} size="m" />
40
+ </View>
41
+ <Text variant="small" style={{ fontWeight: 'bold', textAlign: 'center' }}>{x.title}</Text>
42
+ <Text variant="small" style={{ textAlign: 'center' }}>{x.text}</Text>
43
+ </View>
44
+ ))}
45
+ </View>
46
+ </View>
47
+
48
+ <View gap="m" inset="s">
49
+ <Title variant="secondary">
50
+ Funktioner
51
+ </Title>
52
+
53
+ {features.map(x => (
54
+ <View key={x.title} roundness="m" gap="m" inset="l" style={{ border: '1px solid var(--separator)' }}>
55
+ <View layout="row" gap="s" alignItems="center">
56
+ <View justifyContent="center" alignItems="center">
57
+ <Icon name={x.icon} size="m" />
58
+ </View>
59
+ <Title variant="tertiary">
60
+ {x.title}
61
+ </Title>
62
+ </View>
63
+
64
+ <Text>{x.text}</Text>
65
+
66
+ {x.content && <MarkdownViewer children={x.content} />}
67
+ </View>
68
+ ))}
69
+ </View>
70
+ </View>
71
+ </PageSection>
72
+ )