@praxium/sdk 0.5.91 → 0.6.97

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
@@ -2,6 +2,8 @@
2
2
 
3
3
  Official TypeScript SDK for the [Praxium](https://praxium.nl) platform API. Build tenant websites that display practice data (team, services, FAQ, opening hours) with full locale support.
4
4
 
5
+ Learn more on the [Praxium developer platform](https://platform.praxium.nl).
6
+
5
7
  - [Quick Start](#quick-start) — Installation and usage examples
6
8
  - [Configuration](#configuration) — Environment variables and tenant routing
7
9
  - [Available Methods](#available-methods) — All API endpoints
@@ -31,6 +33,7 @@ const client = createPraxiumClient({
31
33
  const hours = await client.getOpeningHours()
32
34
  const team = await client.getTeamMembers()
33
35
  const faq = await client.getFaq()
36
+ // FAQ category names, questions, and answers are strings in the requested locale.
34
37
  ```
35
38
 
36
39
  All methods return data directly or throw a typed `PraxiumError` on failure (see [Error Handling](#error-handling)).
@@ -39,6 +42,10 @@ All methods return data directly or throw a typed `PraxiumError` on failure (see
39
42
 
40
43
  Pass `locale: '*'` to receive all translations as objects. Resolve them at render time with `localizeText()`:
41
44
 
45
+ When a method has a different response shape in multilingual mode, the SDK exposes that shape with a `Multilingual*` type. For example, `getTeamMembers()` returns `MultilingualTeamMember[]`, and `getFaq()` returns `MultilingualFaqContent`.
46
+
47
+ `localizeText(text, locale)` resolves values in this order: requested locale, then English, then the first available value, then an empty string.
48
+
42
49
  ```typescript
43
50
  import { createPraxiumClient, localizeText } from '@praxium/sdk'
44
51
 
@@ -60,6 +67,9 @@ const team = await client.getTeamMembers()
60
67
 
61
68
  const label = localizeText(team[0].customFields[0].label, 'nl') // → "Specialisatie"
62
69
  const value = localizeText(team[0].customFields[0].value, 'nl') // → "Sport"
70
+
71
+ const faq = await client.getFaq()
72
+ const question = localizeText(faq[0].faqs[0].question, 'nl')
63
73
  ```
64
74
 
65
75
  ### Contact Form
@@ -78,6 +88,24 @@ const result = await client.submitContactForm({
78
88
  // → { success: true, emailStatus: 'sent' }
79
89
  ```
80
90
 
91
+ ### Multiple Locations
92
+
93
+ Practices with more than one location expose each location under a stable slug. List them with `getLocations()`, then pass `{ location: slug }` to any location-aware method to narrow the response:
94
+
95
+ ```typescript
96
+ const locations = await client.getLocations()
97
+ // → [{ slug: 'centrum', name: 'Centrum', kind: 'PHYSICAL',
98
+ // phone: '...', email: '...', fullAddress: '...',
99
+ // openingHours: { schedule: [...], globalNote: null } }, ...]
100
+
101
+ // Per-location data:
102
+ const hours = await client.getOpeningHours({ location: 'zuid' })
103
+ const team = await client.getTeamMembers({ location: 'zuid' })
104
+ const services = await client.getBookableServices({ location: 'zuid' })
105
+ ```
106
+
107
+ Without the filter, single-value methods (`getContactDetails`, `getLocation`, `getOpeningHours`, `getSocialLinks`) return the practice's main location, and collection methods (`getTeamMembers`, `getFaq`, `getServiceVariants`, `getBookableServices`) return the practice-wide data set. An unknown slug — or one outside your API key's location scope — throws `PraxiumNotFoundError` (HTTP 404).
108
+
81
109
  ## Configuration
82
110
 
83
111
  Your website needs two environment variables to connect to the Praxium platform, plus an optional third for webhook-based cache revalidation:
@@ -106,21 +134,24 @@ PRAXIUM_WEBHOOK_SECRET="your-webhook-secret-from-admin-portal" # optional
106
134
 
107
135
  ## Available Methods
108
136
 
137
+ Methods marked with `options?` accept an optional `{ location: slug }` filter — see [Multiple Locations](#multiple-locations).
138
+
109
139
  | Method | Description |
110
140
  |--------|-------------|
111
- | `getOpeningHours()` | Weekly opening schedule |
112
- | `getTeamMembers()` | Staff members with photos and custom fields |
113
- | `getContactDetails()` | Practice contact information |
114
- | `getLocation()` | Address and map coordinates |
141
+ | `getLocations(options?)` | Practice locations with address, contact, and weekly hours |
142
+ | `getOpeningHours(options?)` | Weekly opening schedule |
143
+ | `getTeamMembers(options?)` | Staff members with photos and custom fields |
144
+ | `getContactDetails(options?)` | Practice contact information |
145
+ | `getLocation(options?)` | Address and map coordinates |
115
146
  | `getBusinessName()` | Practice display name |
116
- | `getSocialLinks()` | Social media URLs |
117
- | `getFaq()` | FAQ grouped by category |
118
- | `getServiceVariants()` | Service pricing and variants |
147
+ | `getSocialLinks(options?)` | Social media URLs |
148
+ | `getFaq(options?)` | FAQ grouped by category |
149
+ | `getServiceVariants(options?)` | Service pricing and variants |
119
150
  | `getInsuranceInfo()` | Accepted insurance providers |
120
151
  | `getFeatures()` | Practice features and amenities |
121
152
  | `getPaymentMethods()` | Accepted payment methods |
122
153
  | `getPolicyInfo()` | Practice policies |
123
- | `getBookableServices()` | Services available for online booking |
154
+ | `getBookableServices(options?)` | Services available for online booking |
124
155
  | `submitContactForm(body)` | Submit a contact form |
125
156
 
126
157
  ## Error Handling
package/dist/index.d.ts CHANGED
@@ -145,6 +145,92 @@ type Location = {
145
145
  */
146
146
  accessibility: string | null;
147
147
  } | null;
148
+ /**
149
+ * Practice locations ordered by display order, clamped to the API key location scope. Filter with ?location={slug} (unknown or out-of-scope slugs return 404).
150
+ */
151
+ type Locations = Array<PublicLocationDetail>;
152
+ /**
153
+ * One practice location: identity, address, contact, and weekly hours
154
+ */
155
+ type PublicLocationDetail = {
156
+ /**
157
+ * URL-safe location identifier — the value the `location` query parameter accepts on every public endpoint
158
+ */
159
+ slug: string;
160
+ /**
161
+ * Location display name (localized)
162
+ */
163
+ name: string;
164
+ /**
165
+ * Location kind (e.g. PHYSICAL, MOBILE, VIRTUAL)
166
+ */
167
+ kind: string;
168
+ /**
169
+ * Location phone number
170
+ */
171
+ phone: string;
172
+ /**
173
+ * WhatsApp contact number
174
+ */
175
+ whatsappPhone: string;
176
+ /**
177
+ * Location email address
178
+ */
179
+ email: string;
180
+ /**
181
+ * Street address
182
+ */
183
+ address: string;
184
+ /**
185
+ * City name
186
+ */
187
+ city: string;
188
+ /**
189
+ * Postal/ZIP code
190
+ */
191
+ postalCode: string;
192
+ /**
193
+ * Country name (localized)
194
+ */
195
+ country: string;
196
+ /**
197
+ * Complete formatted address
198
+ */
199
+ fullAddress: string;
200
+ /**
201
+ * GPS latitude coordinate
202
+ */
203
+ latitude: number | null;
204
+ /**
205
+ * GPS longitude coordinate
206
+ */
207
+ longitude: number | null;
208
+ /**
209
+ * Google Maps URL for this location
210
+ */
211
+ googleMapsUrl: string | null;
212
+ /**
213
+ * Parking information (localized)
214
+ */
215
+ parkingInfo: string | null;
216
+ /**
217
+ * Accessibility information (localized)
218
+ */
219
+ accessibility: string | null;
220
+ /**
221
+ * Weekly opening hours for this location
222
+ */
223
+ openingHours: {
224
+ /**
225
+ * Weekly schedule (7 entries)
226
+ */
227
+ schedule: Array<PublicDaySchedule>;
228
+ /**
229
+ * General note about opening hours
230
+ */
231
+ globalNote: string | null;
232
+ };
233
+ };
148
234
  /**
149
235
  * Practice business name
150
236
  */
@@ -239,7 +325,7 @@ type FaqGroup = {
239
325
  faqs: Array<FaqItem>;
240
326
  };
241
327
  /**
242
- * FAQ category with bilingual name
328
+ * FAQ category
243
329
  */
244
330
  type FaqCategory = {
245
331
  /**
@@ -247,11 +333,9 @@ type FaqCategory = {
247
333
  */
248
334
  id: string;
249
335
  /**
250
- * Category name per locale
336
+ * Category name resolved to requested locale
251
337
  */
252
- name: {
253
- [key: string]: string;
254
- };
338
+ name: string;
255
339
  /**
256
340
  * Display order (ascending)
257
341
  */
@@ -266,17 +350,13 @@ type FaqItem = {
266
350
  */
267
351
  id: string;
268
352
  /**
269
- * Question text per locale
353
+ * Question text resolved to requested locale
270
354
  */
271
- question: {
272
- [key: string]: string;
273
- };
355
+ question: string;
274
356
  /**
275
- * Answer text per locale (may contain HTML)
357
+ * Answer text resolved to requested locale (may contain HTML)
276
358
  */
277
- answer: {
278
- [key: string]: string;
279
- };
359
+ answer: string;
280
360
  /**
281
361
  * Parent category UUID
282
362
  */
@@ -659,6 +739,39 @@ type MultilingualTeamMember = {
659
739
  /** Custom fields with multilingual labels and values */
660
740
  customFields: Array<MultilingualCustomField>;
661
741
  };
742
+ /** FAQ category in locale='*' mode. */
743
+ type MultilingualFaqCategory = {
744
+ /** Category UUID */
745
+ id: string;
746
+ /** Category name in all available locales */
747
+ name: MultilingualText;
748
+ /** Display order (ascending) */
749
+ order: number;
750
+ };
751
+ /** FAQ item in locale='*' mode. */
752
+ type MultilingualFaqItem = {
753
+ /** FAQ item UUID */
754
+ id: string;
755
+ /** Question text in all available locales */
756
+ question: MultilingualText;
757
+ /** Answer text in all available locales (may contain HTML) */
758
+ answer: MultilingualText;
759
+ /** Parent category UUID */
760
+ categoryId: string;
761
+ /** Display order within category (ascending) */
762
+ order: number;
763
+ /** Whether this FAQ item is currently visible */
764
+ visible: boolean;
765
+ };
766
+ /** FAQ category group in locale='*' mode. */
767
+ type MultilingualFaqGroup = {
768
+ /** The FAQ category */
769
+ category: MultilingualFaqCategory;
770
+ /** FAQ items in this category */
771
+ faqs: Array<MultilingualFaqItem>;
772
+ };
773
+ /** FAQ content in locale='*' mode. */
774
+ type MultilingualFaqContent = Array<MultilingualFaqGroup>;
662
775
 
663
776
  /**
664
777
  * Supported locale codes for server-side content resolution.
@@ -688,29 +801,49 @@ interface PraxiumClientConfig {
688
801
  /** Locale for server-side content resolution */
689
802
  locale: ClientLocale;
690
803
  }
804
+ /**
805
+ * Options accepted by every location-aware read method.
806
+ *
807
+ * Multi-location practices expose each location under a stable slug
808
+ * (see `getLocations()` — the `slug` field of each entry). Passing
809
+ * `{ location: slug }` narrows the response to that location, within
810
+ * the location scope of your API key. An unknown or out-of-scope slug
811
+ * rejects with `PraxiumNotFoundError` (HTTP 404).
812
+ *
813
+ * Omitting the filter keeps the default behavior: the practice's main
814
+ * location for single-value endpoints (contact details, address,
815
+ * opening hours, social links), and the practice-wide data set for
816
+ * collections (team, FAQ, services, prices).
817
+ */
818
+ interface LocationFilterOptions {
819
+ /** Location slug (e.g., 'zuid') from `getLocations()`. */
820
+ location?: string;
821
+ }
691
822
  /** Shared methods available on both client types */
692
823
  interface PraxiumClientBase {
693
- getOpeningHours: () => Promise<OpeningHours>;
694
- getContactDetails: () => Promise<ContactDetails>;
695
- getLocation: () => Promise<Location>;
824
+ getOpeningHours: (options?: LocationFilterOptions) => Promise<OpeningHours>;
825
+ getContactDetails: (options?: LocationFilterOptions) => Promise<ContactDetails>;
826
+ getLocation: (options?: LocationFilterOptions) => Promise<Location>;
827
+ getLocations: (options?: LocationFilterOptions) => Promise<Locations>;
696
828
  getBusinessName: () => Promise<BusinessName>;
697
- getSocialLinks: () => Promise<SocialLinks>;
698
- getFaq: () => Promise<FaqContent>;
699
- getServiceVariants: () => Promise<PricingVariants>;
829
+ getSocialLinks: (options?: LocationFilterOptions) => Promise<SocialLinks>;
830
+ getServiceVariants: (options?: LocationFilterOptions) => Promise<PricingVariants>;
700
831
  getInsuranceInfo: () => Promise<InsuranceList>;
701
832
  getFeatures: () => Promise<FeatureList>;
702
833
  getPaymentMethods: () => Promise<PaymentMethodList>;
703
834
  getPolicyInfo: () => Promise<PolicyList>;
704
- getBookableServices: () => Promise<BookableServices>;
835
+ getBookableServices: (options?: LocationFilterOptions) => Promise<BookableServices>;
705
836
  submitContactForm: (body: SubmitContactFormData['body']) => Promise<ContactFormResult>;
706
837
  }
707
838
  /** Client for locale-specific responses (locale='nl', 'en', etc.) */
708
839
  interface PraxiumClient extends PraxiumClientBase {
709
- getTeamMembers: () => Promise<TeamMembers>;
840
+ getFaq: (options?: LocationFilterOptions) => Promise<FaqContent>;
841
+ getTeamMembers: (options?: LocationFilterOptions) => Promise<TeamMembers>;
710
842
  }
711
843
  /** Client for multilingual responses (locale='*') */
712
844
  interface MultilingualPraxiumClient extends PraxiumClientBase {
713
- getTeamMembers: () => Promise<MultilingualTeamMember[]>;
845
+ getFaq: (options?: LocationFilterOptions) => Promise<MultilingualFaqContent>;
846
+ getTeamMembers: (options?: LocationFilterOptions) => Promise<MultilingualTeamMember[]>;
714
847
  }
715
848
  /** Multilingual mode: returns MultilingualTeamMember[] (use localizeText() helper for resolution) */
716
849
  declare function createPraxiumClient(config: PraxiumClientConfig & {
@@ -863,4 +996,4 @@ declare function getCustomField(member: MultilingualTeamMember, identifier: stri
863
996
  */
864
997
  declare function localizeText(text: MultilingualText | null | undefined, locale: SupportedLocale): string;
865
998
 
866
- export { API_KEY_PREFIX, API_KEY_PREFIX_WITH_SEPARATOR, API_KEY_SEPARATOR, type ApiError, type BilingualText, type BookableService, type BookableServices, type BookableVariantInfo, type BusinessName, type ClientLocale, type ContactDetails, type ContactFormResult, type CustomField, type FaqCategory, type FaqContent, type FaqGroup, type FaqItem, type FeatureItem, type FeatureList, type InsuranceInfo, type InsuranceList, type Location, type MultilingualCustomField, type MultilingualPraxiumClient, type MultilingualTeamMember, type MultilingualText, type OpeningHours, type PaymentMethod, type PaymentMethodList, type PolicyInfo, type PolicyList, PraxiumAuthError, type PraxiumClient, type PraxiumClientConfig, PraxiumError, PraxiumForbiddenError, PraxiumNotFoundError, PraxiumRateLimitError, PraxiumValidationError, type PricingVariant, type PricingVariants, type PublicDaySchedule, type PublicTeamMember, type ServiceCategoryInfo, type SocialLinks, type SupportedLocale, type TeamMembers, type ValidationDetail, createPraxiumClient, extractTenantSlugFromApiKey, getCustomField, getCustomFieldValue, localizeText };
999
+ export { API_KEY_PREFIX, API_KEY_PREFIX_WITH_SEPARATOR, API_KEY_SEPARATOR, type ApiError, type BilingualText, type BookableService, type BookableServices, type BookableVariantInfo, type BusinessName, type ClientLocale, type ContactDetails, type ContactFormResult, type CustomField, type FaqCategory, type FaqContent, type FaqGroup, type FaqItem, type FeatureItem, type FeatureList, type InsuranceInfo, type InsuranceList, type Location, type LocationFilterOptions, type Locations, type MultilingualCustomField, type MultilingualFaqCategory, type MultilingualFaqContent, type MultilingualFaqGroup, type MultilingualFaqItem, type MultilingualPraxiumClient, type MultilingualTeamMember, type MultilingualText, type OpeningHours, type PaymentMethod, type PaymentMethodList, type PolicyInfo, type PolicyList, PraxiumAuthError, type PraxiumClient, type PraxiumClientConfig, PraxiumError, PraxiumForbiddenError, PraxiumNotFoundError, PraxiumRateLimitError, PraxiumValidationError, type PricingVariant, type PricingVariants, type PublicDaySchedule, type PublicLocationDetail, type PublicTeamMember, type ServiceCategoryInfo, type SocialLinks, type SupportedLocale, type TeamMembers, type ValidationDetail, createPraxiumClient, extractTenantSlugFromApiKey, getCustomField, getCustomFieldValue, localizeText };
package/dist/index.js CHANGED
@@ -827,6 +827,11 @@ var getLocation = (options) => (options.client ?? client).get({
827
827
  url: "/api/{tenantSlug}/location",
828
828
  ...options
829
829
  });
830
+ var getLocations = (options) => (options.client ?? client).get({
831
+ security: [{ scheme: "bearer", type: "http" }],
832
+ url: "/api/{tenantSlug}/locations",
833
+ ...options
834
+ });
830
835
  var getBusinessName = (options) => (options.client ?? client).get({
831
836
  security: [{ scheme: "bearer", type: "http" }],
832
837
  url: "/api/{tenantSlug}/business",
@@ -973,33 +978,42 @@ function createPraxiumClient(config) {
973
978
  return request;
974
979
  });
975
980
  const path = { tenantSlug };
981
+ const baseRequest = { client: clientInstance, path };
982
+ const requestOptions = (options) => ({
983
+ ...baseRequest,
984
+ ...options?.location !== void 0 && {
985
+ query: { location: options.location }
986
+ }
987
+ });
976
988
  const baseMethods = {
977
989
  // ─── Layout Endpoints ───
978
- getOpeningHours: () => getOpeningHours({ client: clientInstance, path }).then(unwrapOrThrow),
979
- getContactDetails: () => getContactDetails({ client: clientInstance, path }).then(unwrapOrThrow),
980
- getLocation: () => getLocation({ client: clientInstance, path }).then(unwrapOrThrow),
981
- getBusinessName: () => getBusinessName({ client: clientInstance, path }).then(unwrapOrThrow),
982
- getSocialLinks: () => getSocialLinks({ client: clientInstance, path }).then(unwrapOrThrow),
990
+ getOpeningHours: (options) => getOpeningHours(requestOptions(options)).then(unwrapOrThrow),
991
+ getContactDetails: (options) => getContactDetails(requestOptions(options)).then(unwrapOrThrow),
992
+ getLocation: (options) => getLocation(requestOptions(options)).then(unwrapOrThrow),
993
+ getLocations: (options) => getLocations(requestOptions(options)).then(unwrapOrThrow),
994
+ getBusinessName: () => getBusinessName(baseRequest).then(unwrapOrThrow),
995
+ getSocialLinks: (options) => getSocialLinks(requestOptions(options)).then(unwrapOrThrow),
983
996
  // ─── Content Endpoints ───
984
- getFaq: () => getFaq({ client: clientInstance, path }).then(unwrapOrThrow),
985
- getServiceVariants: () => getServiceVariants({ client: clientInstance, path }).then(unwrapOrThrow),
986
- getInsuranceInfo: () => getInsuranceInfo({ client: clientInstance, path }).then(unwrapOrThrow),
987
- getFeatures: () => getFeatures({ client: clientInstance, path }).then(unwrapOrThrow),
988
- getPaymentMethods: () => getPaymentMethods({ client: clientInstance, path }).then(unwrapOrThrow),
989
- getPolicyInfo: () => getPolicyInfo({ client: clientInstance, path }).then(unwrapOrThrow),
990
- getBookableServices: () => getBookableServices({ client: clientInstance, path }).then(unwrapOrThrow),
997
+ getServiceVariants: (options) => getServiceVariants(requestOptions(options)).then(unwrapOrThrow),
998
+ getInsuranceInfo: () => getInsuranceInfo(baseRequest).then(unwrapOrThrow),
999
+ getFeatures: () => getFeatures(baseRequest).then(unwrapOrThrow),
1000
+ getPaymentMethods: () => getPaymentMethods(baseRequest).then(unwrapOrThrow),
1001
+ getPolicyInfo: () => getPolicyInfo(baseRequest).then(unwrapOrThrow),
1002
+ getBookableServices: (options) => getBookableServices(requestOptions(options)).then(unwrapOrThrow),
991
1003
  // ─── Contact Form ───
992
- submitContactForm: (body) => submitContactForm({ client: clientInstance, path, body }).then(unwrapOrThrow)
1004
+ submitContactForm: (body) => submitContactForm({ ...baseRequest, body }).then(unwrapOrThrow)
993
1005
  };
994
1006
  if (config.locale === "*") {
995
1007
  return {
996
1008
  ...baseMethods,
997
- getTeamMembers: () => getTeamMembers({ client: clientInstance, path }).then(unwrapOrThrow)
1009
+ getFaq: (options) => getFaq(requestOptions(options)).then(unwrapOrThrow),
1010
+ getTeamMembers: (options) => getTeamMembers(requestOptions(options)).then(unwrapOrThrow)
998
1011
  };
999
1012
  }
1000
1013
  return {
1001
1014
  ...baseMethods,
1002
- getTeamMembers: () => getTeamMembers({ client: clientInstance, path }).then(unwrapOrThrow)
1015
+ getFaq: (options) => getFaq(requestOptions(options)).then(unwrapOrThrow),
1016
+ getTeamMembers: (options) => getTeamMembers(requestOptions(options)).then(unwrapOrThrow)
1003
1017
  };
1004
1018
  }
1005
1019
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@praxium/sdk",
3
- "version": "0.5.91",
3
+ "version": "0.6.97",
4
4
  "description": "Official TypeScript SDK for the Praxium platform API",
5
5
  "type": "module",
6
6
  "exports": {