@ossy/booking 1.15.6 → 1.16.0

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.
@@ -0,0 +1,48 @@
1
+ import React from 'react'
2
+ import { View, Title, Text, Badge } from '@ossy/design-system'
3
+
4
+ export const metadata = { id: 'resource:booking/service/card' }
5
+
6
+ const formatPrice = (cents, currency) => {
7
+ if (cents === 0) return 'Free'
8
+ return new Intl.NumberFormat('sv-SE', { style: 'currency', currency }).format(cents / 100)
9
+ }
10
+
11
+ const formatDuration = (minutes) => {
12
+ if (!minutes) return '—'
13
+ if (minutes < 60) return `${minutes} min`
14
+ const h = Math.floor(minutes / 60)
15
+ const m = minutes % 60
16
+ return m === 0 ? `${h}h` : `${h}h ${m}min`
17
+ }
18
+
19
+ /** Booking service card view — registered at `resource:booking/service/card`. */
20
+ export default function ServiceCardSlotComponent ({ resource }) {
21
+ if (!resource) return null
22
+
23
+ const { name, duration, price, currency = 'SEK', description, active } = resource.content || {}
24
+
25
+ return (
26
+ <View
27
+ surface="secondary"
28
+ roundness="m"
29
+ inset="m"
30
+ gap="xs"
31
+ layout="row"
32
+ justifyContent="space-between"
33
+ alignItems="flex-start"
34
+ >
35
+ <View gap="xs" style={{ flex: 1 }}>
36
+ <View layout="row" gap="s" alignItems="center">
37
+ <Title variant="secondary">{resource.name || name}</Title>
38
+ {active === false && <Badge label="Inactive" variant="neutral" size="s" />}
39
+ </View>
40
+ {description && <Text size="s" color="secondary">{description}</Text>}
41
+ </View>
42
+ <View gap="xs" alignItems="flex-end" style={{ flexShrink: 0 }}>
43
+ <Text weight="medium">{formatPrice(price, currency)}</Text>
44
+ <Text size="s" color="secondary">{formatDuration(duration)}</Text>
45
+ </View>
46
+ </View>
47
+ )
48
+ }
@@ -0,0 +1,38 @@
1
+ import React from 'react'
2
+ import { View, Title, Text, Badge } from '@ossy/design-system'
3
+
4
+ export const metadata = { id: 'resource:booking/service/detail' }
5
+
6
+ const formatPrice = (cents, currency) => {
7
+ if (cents === 0) return 'Free'
8
+ return new Intl.NumberFormat('sv-SE', { style: 'currency', currency }).format(cents / 100)
9
+ }
10
+
11
+ const formatDuration = (minutes) => {
12
+ if (!minutes) return '—'
13
+ if (minutes < 60) return `${minutes} min`
14
+ const h = Math.floor(minutes / 60)
15
+ const m = minutes % 60
16
+ return m === 0 ? `${h}h` : `${h}h ${m}min`
17
+ }
18
+
19
+ /** Booking service detail view — registered at `resource:booking/service/detail`. */
20
+ export default function ServiceDetailComponent ({ resource }) {
21
+ if (!resource) return null
22
+
23
+ const { name, duration, price, currency = 'SEK', description, active } = resource.content || {}
24
+
25
+ return (
26
+ <View stack gap="m" inset="m">
27
+ <View layout="row" gap="s" alignItems="center">
28
+ <Title>{resource.name || name}</Title>
29
+ {active === false && <Badge label="Inactive" variant="neutral" size="s" />}
30
+ </View>
31
+ {description && <Text color="secondary">{description}</Text>}
32
+ <View layout="row" gap="l">
33
+ <Text weight="medium">{formatPrice(price, currency)}</Text>
34
+ <Text color="secondary">{formatDuration(duration)}</Text>
35
+ </View>
36
+ </View>
37
+ )
38
+ }
@@ -1,5 +1,5 @@
1
1
  import React from 'react'
2
- import { View, Title, Text, Button, Tags } from '@ossy/design-system'
2
+ import { View, Title, Text, Button, Tags, useLocale } from '@ossy/design-system'
3
3
  import { Definition } from './Definition.js'
4
4
  import { moduleStatusTags } from './moduleStatus.js'
5
5
  import { ServiceCard } from './ServiceCard.jsx'
@@ -12,53 +12,54 @@ export const metadata = {
12
12
  },
13
13
  }
14
14
 
15
- const ServicesPage = () => (
16
- <View
17
- gap="m"
18
- surface="primary"
19
- style={{ padding: 'var(--space-m) var(--space-l)', height: '100%', overflowY: 'auto' }}
20
- >
21
- <View inset="s" gap="s">
22
- <View layout="row" justifyContent="space-between" alignItems="center" style={{ flexShrink: 0 }}>
23
- <View layout="row" gap="s" alignItems="center">
24
- <Title>{Definition.title} Services</Title>
25
- {moduleStatusTags(Definition).length > 0 && (
26
- <Tags tags={moduleStatusTags(Definition)} size="s" />
27
- )}
15
+ const ServicesPage = () => {
16
+ const { t } = useLocale()
17
+ return (
18
+ <View
19
+ gap="m"
20
+ surface="primary"
21
+ style={{ padding: 'var(--space-m) var(--space-l)', height: '100%', overflowY: 'auto' }}
22
+ >
23
+ <View inset="s" gap="s">
24
+ <View layout="row" justifyContent="space-between" alignItems="center" style={{ flexShrink: 0 }}>
25
+ <View layout="row" gap="s" alignItems="center">
26
+ <Title>{Definition.title} {t('booking.services.titleSuffix')}</Title>
27
+ {moduleStatusTags(Definition).length > 0 && (
28
+ <Tags tags={moduleStatusTags(Definition)} size="s" />
29
+ )}
30
+ </View>
31
+ <Button variant="primary" size="s">{t('booking.services.addService')}</Button>
28
32
  </View>
29
- {/* TODO: wire to booking.create-service action */}
30
- <Button variant="primary" size="s">Add service</Button>
31
- </View>
32
33
 
33
- <Text style={{ maxWidth: '400px' }}>
34
- Define what you offer — name, duration, and price. Clients will see these when booking.
35
- </Text>
36
- </View>
34
+ <Text style={{ maxWidth: '400px' }}>
35
+ {t('booking.services.description')}
36
+ </Text>
37
+ </View>
37
38
 
38
- <View gap="s" inset="s">
39
- <Title variant="secondary">Your services</Title>
39
+ <View gap="s" inset="s">
40
+ <Title variant="secondary">{t('booking.services.yourServices')}</Title>
40
41
 
41
- {/* TODO: load services via booking.get-services view */}
42
- <View gap="s">
43
- <ServiceCard
44
- name="Intro call"
45
- duration={30}
46
- price={0}
47
- currency="SEK"
48
- description="A free 30-minute intro to see if we're a good fit."
49
- active
50
- />
51
- <ServiceCard
52
- name="Strategy session"
53
- duration={60}
54
- price={150000}
55
- currency="SEK"
56
- description="Deep-dive strategy session."
57
- active
58
- />
42
+ <View gap="s">
43
+ <ServiceCard
44
+ name={t('booking.services.example.introCall.name')}
45
+ duration={30}
46
+ price={0}
47
+ currency="SEK"
48
+ description={t('booking.services.example.introCall.description')}
49
+ active
50
+ />
51
+ <ServiceCard
52
+ name={t('booking.services.example.strategy.name')}
53
+ duration={60}
54
+ price={150000}
55
+ currency="SEK"
56
+ description={t('booking.services.example.strategy.description')}
57
+ active
58
+ />
59
+ </View>
59
60
  </View>
60
61
  </View>
61
- </View>
62
- )
62
+ )
63
+ }
63
64
 
64
65
  export default ServicesPage
@@ -0,0 +1,213 @@
1
+ {
2
+ "booking.home.documentTitle": "Bokningskalender – Ossy",
3
+ "booking.home.cover.title": "Låt kunder boka tid direkt",
4
+ "booking.home.cover.text": "Dela en länk. Kunder väljer tid. Du fokuserar på jobbet.",
5
+ "booking.home.cover.ctaPrimary": "Kom igång gratis",
6
+ "booking.home.cover.ctaSecondary": "Se ett exempel",
7
+ "booking.home.features.page.title": "Din bokningssida",
8
+ "booking.home.features.page.text": "En personlig sida på ossy.se/boka/ditt-namn. Dela länken med kunder – det är allt som krävs.",
9
+ "booking.home.features.confirmations.title": "Automatiska bekräftelser",
10
+ "booking.home.features.confirmations.text": "Kunden får en bekräftelse med kalenderinbjudan direkt. Inga manuella steg från din sida.",
11
+ "booking.home.features.availability.title": "Hantera din tid",
12
+ "booking.home.features.availability.text": "Ange när du är tillgänglig. Vi visar bara lediga tider – aldrig dubbelbokade.",
13
+ "booking.home.features.email.title": "E-postpingpong",
14
+ "booking.home.features.email.text": "Slipp fram och tillbaka i flera dagar bara för att hitta ett gemensamt tidslucka.",
15
+ "booking.home.features.doubleBook.title": "Inga dubbelbokningar",
16
+ "booking.home.features.doubleBook.text": "En kalender här, en länk där – vi håller koll så samma tid inte bokas två gånger.",
17
+ "booking.sales.overview": "Översikt",
18
+ "booking.sales.features": "Funktioner",
19
+ "booking.bookings.documentTitle": "Bokningar",
20
+ "booking.bookings.titleSuffix": "Bokningar",
21
+ "booking.bookings.description": "Hantera bokningsförfrågningar och bekräftade möten.",
22
+ "booking.bookings.tab.pending": "Förfrågningar",
23
+ "booking.bookings.tab.confirmed": "Bekräftade",
24
+ "booking.bookings.tab.all": "Alla",
25
+ "booking.bookings.empty.pending.heading": "Inga förfrågningar",
26
+ "booking.bookings.empty.pending.body": "Du har inga väntande bokningsförfrågningar just nu.",
27
+ "booking.bookings.empty.confirmed.heading": "Inga bekräftade bokningar",
28
+ "booking.bookings.empty.confirmed.body": "Bekräftade bokningar visas här.",
29
+ "booking.bookings.empty.all.heading": "Inga bokningar",
30
+ "booking.bookings.empty.all.body": "Bokningar från dina klienter visas här.",
31
+ "booking.bookings.loading": "Laddar…",
32
+ "booking.bookings.errorStatus": "Fel {status}",
33
+ "booking.bookings.errorConfirm": "Bekräftning misslyckades",
34
+ "booking.bookings.errorDecline": "Avböjning misslyckades",
35
+ "booking.bookings.errorCancel": "Avbokning misslyckades",
36
+ "booking.bookingDetail.documentTitle": "Bokningsdetaljer",
37
+ "booking.bookingDetail.back": "← Alla bokningar",
38
+ "booking.bookingDetail.loading": "Laddar…",
39
+ "booking.bookingDetail.notFound": "Bokningen hittades inte",
40
+ "booking.bookingDetail.errorStatus": "Fel {status}",
41
+ "booking.bookingDetail.status.pending": "Väntar på bekräftelse",
42
+ "booking.bookingDetail.status.confirmed": "Bekräftad",
43
+ "booking.bookingDetail.status.cancelled": "Avbokad",
44
+ "booking.bookingDetail.label.dateTime": "Datum och tid",
45
+ "booking.bookingDetail.label.duration": "Längd",
46
+ "booking.bookingDetail.label.message": "Meddelande",
47
+ "booking.bookingDetail.durationMinutes": "{minutes} minuter",
48
+ "booking.bookingDetail.durationHours": "{hours} timme",
49
+ "booking.bookingDetail.durationHoursMinutes": "{hours} timme {minutes} min",
50
+ "booking.bookingDetail.manageRequest": "Hantera förfrågan",
51
+ "booking.bookingDetail.manageBooking": "Hantera bokning",
52
+ "booking.bookingDetail.confirm": "Bekräfta bokning",
53
+ "booking.bookingDetail.confirming": "Bekräftar…",
54
+ "booking.bookingDetail.errorConfirm": "Bekräftning misslyckades",
55
+ "booking.bookingDetail.decline": "Avböj förfrågan",
56
+ "booking.bookingDetail.declineTitle": "Avböj bokningsförfrågan",
57
+ "booking.bookingDetail.declinePlaceholder": "T.ex. Tyvärr är den begärda tiden inte längre tillgänglig…",
58
+ "booking.bookingDetail.declineReasonHint": "Anledning (valfritt — skickas till klienten)",
59
+ "booking.bookingDetail.declineConfirm": "Bekräfta avböjning",
60
+ "booking.bookingDetail.declining": "Avböjer…",
61
+ "booking.bookingDetail.errorDecline": "Avböjning misslyckades",
62
+ "booking.bookingDetail.cancelMeeting": "Avboka möte",
63
+ "booking.bookingDetail.cancelTitle": "Avboka bekräftad bokning",
64
+ "booking.bookingDetail.cancelBody": "Klienten meddelas via e-post. Åtgärden kan inte ångras.",
65
+ "booking.bookingDetail.cancelConfirm": "Bekräfta avbokning",
66
+ "booking.bookingDetail.cancelling": "Avbokar…",
67
+ "booking.bookingDetail.errorCancel": "Avbokning misslyckades",
68
+ "booking.bookingDetail.cancelButton": "Avbryt",
69
+ "booking.availability.documentTitle": "Tillgänglighet",
70
+ "booking.availability.titleSuffix": "Tillgänglighet",
71
+ "booking.availability.description": "Ange din veckotillgänglighet. Kunder kan bara boka tider inom dessa fönster.",
72
+ "booking.availabilitySetup.documentTitle": "Tillgänglighet",
73
+ "booking.availabilitySetup.title": "Tillgänglighet",
74
+ "booking.availabilitySetup.description": "Ange när du är tillgänglig för bokningar. Klienter kan bara boka tider inom dessa fönster.",
75
+ "booking.availabilitySetup.loading": "Laddar…",
76
+ "booking.availabilitySetup.weeklySchedule": "Veckoschema",
77
+ "booking.availabilitySetup.day.monday": "Måndag",
78
+ "booking.availabilitySetup.day.tuesday": "Tisdag",
79
+ "booking.availabilitySetup.day.wednesday": "Onsdag",
80
+ "booking.availabilitySetup.day.thursday": "Torsdag",
81
+ "booking.availabilitySetup.day.friday": "Fredag",
82
+ "booking.availabilitySetup.day.saturday": "Lördag",
83
+ "booking.availabilitySetup.day.sunday": "Söndag",
84
+ "booking.availabilitySetup.unavailable": "Inte tillgänglig",
85
+ "booking.availabilitySetup.sessionDuration": "Sessionslängd",
86
+ "booking.availabilitySetup.sessionDurationHint": "Välj de sessionslängder klienter kan välja bland.",
87
+ "booking.availabilitySetup.durationMin": "{minutes} min",
88
+ "booking.availabilitySetup.bufferLabel": "Bufferttid mellan bokningar",
89
+ "booking.availabilitySetup.bufferNone": "Ingen bufferttid",
90
+ "booking.availabilitySetup.bufferMinutes": "{minutes} minuter",
91
+ "booking.availabilitySetup.bufferHint": "Automatisk paustid som läggs till efter varje bokning.",
92
+ "booking.availabilitySetup.timezone": "Tidszon",
93
+ "booking.availabilitySetup.save": "Spara tillgänglighet",
94
+ "booking.availabilitySetup.saving": "Sparar…",
95
+ "booking.availabilitySetup.errorSave": "Kunde inte spara ({status})",
96
+ "booking.availabilitySetup.savedTitle": "Tillgänglighet sparad",
97
+ "booking.availabilitySetup.savedBody": "Din bokningslänk är redo att delas med klienter.",
98
+ "booking.availabilitySetup.copyLink": "Kopiera länk",
99
+ "booking.availabilitySetup.copied": "Kopierad!",
100
+ "booking.services.documentTitle": "Tjänster",
101
+ "booking.services.titleSuffix": "Tjänster",
102
+ "booking.services.description": "Definiera vad du erbjuder — namn, längd och pris. Kunder ser detta vid bokning.",
103
+ "booking.services.addService": "Lägg till tjänst",
104
+ "booking.services.yourServices": "Dina tjänster",
105
+ "booking.services.example.introCall.name": "Introduktionssamtal",
106
+ "booking.services.example.introCall.description": "Ett gratis 30-minuters intro för att se om vi passar ihop.",
107
+ "booking.services.example.strategy.name": "Strategimöte",
108
+ "booking.services.example.strategy.description": "Fördjupat strategimöte.",
109
+ "booking.book.documentTitle": "Boka ett möte",
110
+ "booking.book.title": "Boka ett möte",
111
+ "booking.book.description": "Välj en tid som passar dig och bekräfta din bokning.",
112
+ "booking.book.form.chooseService": "Välj en tjänst",
113
+ "booking.book.form.loadingServices": "Laddar tjänster för workspace {workspaceId}…",
114
+ "booking.book.form.continuePlaceholder": "Fortsätt (platshållare)",
115
+ "booking.book.form.pickTime": "Välj en tid",
116
+ "booking.book.form.slotsPlaceholder": "Tillgängliga tider visas här.",
117
+ "booking.book.form.back": "Tillbaka",
118
+ "booking.book.form.yourDetails": "Dina uppgifter",
119
+ "booking.book.form.nameLabel": "Namn",
120
+ "booking.book.form.namePlaceholder": "Anna Svensson",
121
+ "booking.book.form.emailLabel": "E-post",
122
+ "booking.book.form.emailPlaceholder": "anna@exempel.se",
123
+ "booking.book.form.confirmPay": "Bekräfta och betala",
124
+ "booking.book.form.confirmed": "Bokning bekräftad!",
125
+ "booking.book.form.checkEmail": "Kolla din e-post för en bekräftelse.",
126
+ "booking.availabilityEditor.save": "Spara tillgänglighet",
127
+ "booking.availabilityEditor.unavailable": "Inte tillgänglig",
128
+ "booking.availabilityEditor.to": "till",
129
+ "booking.availabilityEditor.buffer": "Buffert",
130
+ "booking.availabilityEditor.min": "min",
131
+ "booking.availabilityEditor.day.sun": "Sön",
132
+ "booking.availabilityEditor.day.mon": "Mån",
133
+ "booking.availabilityEditor.day.tue": "Tis",
134
+ "booking.availabilityEditor.day.wed": "Ons",
135
+ "booking.availabilityEditor.day.thu": "Tor",
136
+ "booking.availabilityEditor.day.fri": "Fre",
137
+ "booking.availabilityEditor.day.sat": "Lör",
138
+ "publicBooking.documentTitle": "Boka en tid",
139
+ "publicBooking.title": "Boka en tid",
140
+ "publicBooking.description": "Välj ett datum och en tid som passar dig.",
141
+ "publicBooking.loadingSlots": "Laddar tider…",
142
+ "publicBooking.slotsError": "Kunde inte ladda tillgängliga tider.",
143
+ "publicBooking.noSlots": "Inga tillgängliga tider för valt datum.",
144
+ "publicBooking.back": "← Tillbaka",
145
+ "publicBooking.selectedTime": "Vald tid: {datetime}",
146
+ "publicBooking.durationMin": "({minutes} min)",
147
+ "publicBooking.continue": "Fortsätt →",
148
+ "publicBooking.pickDate": "Välj ett markerat datum i kalendern",
149
+ "publicBooking.form.name": "Ditt namn",
150
+ "publicBooking.form.namePlaceholder": "Anna Svensson",
151
+ "publicBooking.form.email": "E-postadress",
152
+ "publicBooking.form.emailPlaceholder": "anna@exempel.se",
153
+ "publicBooking.form.message": "Meddelande (valfritt)",
154
+ "publicBooking.form.messagePlaceholder": "Berätta gärna vad du vill prata om...",
155
+ "publicBooking.form.submit": "Skicka förfrågan",
156
+ "publicBooking.form.submitting": "Skickar…",
157
+ "publicBooking.form.error": "Bokning misslyckades ({status})",
158
+ "publicBooking.pending.title": "Din förfrågan är skickad!",
159
+ "publicBooking.pending.body": "Konsulten bekräftar inom kort. Du får ett mejl med kalenderinbjudan när bokningen är bekräftad.",
160
+ "publicBooking.pending.requestedTime": "Önskad tid",
161
+ "publicBooking.pending.duration": "{minutes} minuter",
162
+ "publicBooking.month.january": "Januari",
163
+ "publicBooking.month.february": "Februari",
164
+ "publicBooking.month.march": "Mars",
165
+ "publicBooking.month.april": "April",
166
+ "publicBooking.month.may": "Maj",
167
+ "publicBooking.month.june": "Juni",
168
+ "publicBooking.month.july": "Juli",
169
+ "publicBooking.month.august": "Augusti",
170
+ "publicBooking.month.september": "September",
171
+ "publicBooking.month.october": "Oktober",
172
+ "publicBooking.month.november": "November",
173
+ "publicBooking.month.december": "December",
174
+ "publicBooking.weekday.sun": "Sön",
175
+ "publicBooking.weekday.mon": "Mån",
176
+ "publicBooking.weekday.tue": "Tis",
177
+ "publicBooking.weekday.wed": "Ons",
178
+ "publicBooking.weekday.thu": "Tor",
179
+ "publicBooking.weekday.fri": "Fre",
180
+ "publicBooking.weekday.sat": "Lör",
181
+ "publicBooking.weekdayLong.sun": "Söndag",
182
+ "publicBooking.weekdayLong.mon": "Måndag",
183
+ "publicBooking.weekdayLong.tue": "Tisdag",
184
+ "publicBooking.weekdayLong.wed": "Onsdag",
185
+ "publicBooking.weekdayLong.thu": "Torsdag",
186
+ "publicBooking.weekdayLong.fri": "Fredag",
187
+ "publicBooking.weekdayLong.sat": "Lördag",
188
+ "booking.card.status.pending": "Väntar",
189
+ "booking.card.status.confirmed": "Bekräftad",
190
+ "booking.card.status.cancelled": "Avbokad",
191
+ "booking.card.view": "Visa →",
192
+ "booking.card.confirm": "Bekräfta",
193
+ "booking.card.decline": "Avböj",
194
+ "booking.card.cancel": "Avboka",
195
+ "booking.card.waiting": "Väntar…",
196
+ "booking.card.errorGeneric": "Något gick fel",
197
+ "booking/create.label": "Skapa bokning",
198
+ "booking/create.description": "Skapa en ny bokningsförfrågan från en klient",
199
+ "booking/get-available-slots.label": "Hämta tillgängliga tider",
200
+ "booking/get-available-slots.description": "Lista bokningsbara tider för en konsult",
201
+ "booking/get-availability.label": "Hämta tillgänglighet",
202
+ "booking/get-availability.description": "Ladda veckotillgänglighet för workspace",
203
+ "booking/save-availability.label": "Spara tillgänglighet",
204
+ "booking/save-availability.description": "Spara veckotillgänglighet och bokningsinställningar",
205
+ "booking/list.label": "Lista bokningar",
206
+ "booking/list.description": "Lista alla bokningar för aktuellt workspace",
207
+ "booking/decline.label": "Avböj bokning",
208
+ "booking/decline.description": "Avböj en väntande bokningsförfrågan",
209
+ "booking/confirm.label": "Bekräfta bokning",
210
+ "booking/confirm.description": "Bekräfta en väntande bokningsförfrågan",
211
+ "booking/cancel.label": "Avboka bokning",
212
+ "booking/cancel.description": "Avboka en bekräftad bokning"
213
+ }