@tfehotels/tfe-gatsby-library 1.0.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.
Files changed (84) hide show
  1. package/README.md +41 -0
  2. package/lib/index.browser.cjs +2 -0
  3. package/lib/index.browser.cjs.map +1 -0
  4. package/lib/index.browser.d.ts +640 -0
  5. package/lib/index.browser.esm.js +2 -0
  6. package/lib/index.browser.esm.js.map +1 -0
  7. package/lib/index.node.cjs +2 -0
  8. package/lib/index.node.cjs.map +1 -0
  9. package/lib/index.node.d.ts +116 -0
  10. package/lib/index.node.esm.js +2 -0
  11. package/lib/index.node.esm.js.map +1 -0
  12. package/package.json +84 -0
  13. package/src/browser/api/button/index.tsx +16 -0
  14. package/src/browser/api/button/types.ts +7 -0
  15. package/src/browser/api/checkbox/index.tsx +74 -0
  16. package/src/browser/api/checkbox/types.tsx +21 -0
  17. package/src/browser/api/country-prefix/index.tsx +150 -0
  18. package/src/browser/api/country-prefix/types.tsx +17 -0
  19. package/src/browser/api/form/index.tsx +330 -0
  20. package/src/browser/api/form/types.ts +31 -0
  21. package/src/browser/api/google-places/index.tsx +90 -0
  22. package/src/browser/api/google-places/types.ts +24 -0
  23. package/src/browser/api/icon/index.tsx +26 -0
  24. package/src/browser/api/icon/types.tsx +24 -0
  25. package/src/browser/api/image/index.tsx +91 -0
  26. package/src/browser/api/image/types.ts +11 -0
  27. package/src/browser/api/input/index.tsx +44 -0
  28. package/src/browser/api/input/types.ts +10 -0
  29. package/src/browser/api/link/index.tsx +54 -0
  30. package/src/browser/api/link/types.ts +24 -0
  31. package/src/browser/api/linklist/index.tsx +17 -0
  32. package/src/browser/api/linklist/types.ts +6 -0
  33. package/src/browser/api/select/index.tsx +99 -0
  34. package/src/browser/api/select/types.ts +24 -0
  35. package/src/browser/api/svg/index.tsx +8 -0
  36. package/src/browser/api/svg/types.ts +8 -0
  37. package/src/browser/api/text/index.tsx +14 -0
  38. package/src/browser/api/text/types.ts +12 -0
  39. package/src/browser/api/textarea/index.tsx +12 -0
  40. package/src/browser/api/title/index.tsx +19 -0
  41. package/src/browser/api/title/types.ts +10 -0
  42. package/src/browser/api/types.ts +245 -0
  43. package/src/browser/carousel/buttons/index.tsx +81 -0
  44. package/src/browser/carousel/buttons/types.ts +15 -0
  45. package/src/browser/carousel/dots/index.tsx +53 -0
  46. package/src/browser/carousel/dots/types.ts +14 -0
  47. package/src/browser/carousel/index.tsx +131 -0
  48. package/src/browser/carousel/types.ts +21 -0
  49. package/src/browser/markdown/index.tsx +41 -0
  50. package/src/browser/markdown/types.ts +11 -0
  51. package/src/browser/modal/index.tsx +35 -0
  52. package/src/browser/modal/types.ts +9 -0
  53. package/src/browser/spinner/index.tsx +19 -0
  54. package/src/browser/spinner/types.ts +5 -0
  55. package/src/browser/toast/index.tsx +84 -0
  56. package/src/browser/use_viewport/index.tsx +34 -0
  57. package/src/browser/use_viewport/types.ts +5 -0
  58. package/src/browser/utils/animation.ts +12 -0
  59. package/src/browser/utils/booking_engine.ts +180 -0
  60. package/src/browser/utils/eclub.ts +30 -0
  61. package/src/browser/utils/forms.ts +76 -0
  62. package/src/browser/utils/hotel.ts +25 -0
  63. package/src/browser/utils/image.ts +63 -0
  64. package/src/browser/utils/location.ts +213 -0
  65. package/src/browser/utils/notifications.tsx +25 -0
  66. package/src/browser/utils/number.ts +6 -0
  67. package/src/browser/utils/requests.ts +2 -0
  68. package/src/browser/utils/search.ts +25 -0
  69. package/src/browser/utils/string.ts +9 -0
  70. package/src/browser/utils/types.ts +106 -0
  71. package/src/browser/utils/url.ts +116 -0
  72. package/src/browser/utils/viewport.ts +59 -0
  73. package/src/index.browser.ts +103 -0
  74. package/src/index.node.ts +16 -0
  75. package/src/node/api/index.ts +174 -0
  76. package/src/node/api/types.ts +19 -0
  77. package/src/node/build/index.ts +142 -0
  78. package/src/node/build/types.ts +5 -0
  79. package/src/node/config/index.ts +149 -0
  80. package/src/node/form/index.ts +23 -0
  81. package/src/node/form/types.ts +3 -0
  82. package/src/node/property/index.ts +78 -0
  83. package/src/node/property/types.ts +25 -0
  84. package/src/node/url/index.ts +8 -0
@@ -0,0 +1,174 @@
1
+ import { join_paths } from "../url";
2
+ import { ImageType, PageTagsType, QueryParamsType } from "./types";
3
+ const { getImage, withArtDirection } = module.require("gatsby-plugin-image");
4
+
5
+ export const tagExists = (
6
+ pageTags: PageTagsType,
7
+ ...tags: string[]
8
+ ): boolean => {
9
+ for (const tag of Object.keys(pageTags)) {
10
+ if (tags.includes(tag)) {
11
+ return true;
12
+ }
13
+ }
14
+ return false;
15
+ };
16
+
17
+ export const api_link = (
18
+ apiPath: string = "",
19
+ queryParams: QueryParamsType | null = null
20
+ ): string =>
21
+ `${join_paths(
22
+ `${process.env.API_PROTOCOL}://${process.env.API_HOST}`,
23
+ apiPath
24
+ )}${queryParams ? `?${new URLSearchParams(queryParams).toString()}` : ""}`;
25
+
26
+ export const buildImage = (img: ImageType) => {
27
+ if (!img) {
28
+ return undefined;
29
+ }
30
+
31
+ let fallback = {
32
+ src: img.url,
33
+ sizes: "100vw",
34
+ srcSet: "",
35
+ };
36
+ let sources = [];
37
+ let placeholder = { fallback: img.url };
38
+ const sizes = [img.width, 360, 769, 1024, 1216, 1408].sort((a, b) => a - b);
39
+
40
+ if (img.optimized) {
41
+ fallback.src = img.url + img.name + "-" + img.width + "w.jpg";
42
+ placeholder.fallback = img.url + img.name + "-20w.jpg";
43
+ sources.push({ sizes: "100vw", srcSet: "", type: "image/avif" });
44
+ sources.push({ sizes: "100vw", srcSet: "", type: "image/webp" });
45
+ for (let index in sizes) {
46
+ if (sizes[index] > img.width) {
47
+ break;
48
+ }
49
+ let image_url =
50
+ (index === "0" ? "" : ",") + img.url + img.name + "-" + sizes[index];
51
+ fallback.srcSet += image_url + "w.jpg " + sizes[index] + "w";
52
+ sources[0].srcSet += image_url + "w.avif " + sizes[index] + "w";
53
+ sources[1].srcSet += image_url + "w.webp " + sizes[index] + "w";
54
+ }
55
+ }
56
+
57
+ return getImage({
58
+ childImageSharp: {
59
+ gatsbyImageData: {
60
+ backgroundColor: "transparent",
61
+ height: img.height / img.width,
62
+ width: 1,
63
+ layout: "fullWidth",
64
+ placeholder: placeholder,
65
+ images: {
66
+ fallback: fallback,
67
+ sources: sources,
68
+ },
69
+ },
70
+ },
71
+ });
72
+ };
73
+
74
+ export const getImageData = (image: ImageType, mobileImage: ImageType) => {
75
+ const image_data = buildImage(image);
76
+ const mobile_image_data = buildImage(mobileImage);
77
+ return image_data && mobile_image_data // If mobile image exists, it must be added using Art Direction.
78
+ ? withArtDirection(image_data, [
79
+ {
80
+ media: "(max-width: 768px)",
81
+ image: mobile_image_data,
82
+ },
83
+ ])
84
+ : image_data;
85
+ };
86
+
87
+ export const extractForm = (formSection: any) => {
88
+ let form: any = {
89
+ settings: {
90
+ pk: 0,
91
+ name: "",
92
+ form_method: "post",
93
+ form_submit: api_link("/v1/project/request/contact"),
94
+ form_redirection: null,
95
+ form_ajax_request: null,
96
+ recaptcha_action: "",
97
+ recaptcha_v3: false,
98
+ },
99
+ items: [],
100
+ fields: {},
101
+ };
102
+
103
+ form.settings.recaptcha_v3 = !!formSection.recaptcha_v3;
104
+ form.settings.recaptcha_action =
105
+ formSection.data.recaptcha_action ?? formSection.name;
106
+ form.settings.pk = formSection.pk;
107
+ form.settings.name = formSection.name;
108
+ [
109
+ "form_submit",
110
+ "form_recipient",
111
+ "form_method",
112
+ "form_redirection",
113
+ "form_ajax_request",
114
+ "form_success_msg",
115
+ ].map((cfg) => {
116
+ if (formSection.hasOwnProperty(cfg)) {
117
+ // @ts-ignore
118
+ form.settings[cfg] = formSection[cfg];
119
+ }
120
+ });
121
+ const validTypes = new Set([
122
+ "checkbox",
123
+ "input",
124
+ "select",
125
+ "button",
126
+ "google-places",
127
+ "country-prefix",
128
+ "textarea",
129
+ "recaptcha2",
130
+ "title",
131
+ "text",
132
+ "link",
133
+ "image",
134
+ ]);
135
+ let uniqueId = 0;
136
+ const addItem = (parents: any, newItem: any): any => {
137
+ let item = form.items;
138
+ for (let key of parents) {
139
+ item = item[key];
140
+ if (!Array.isArray(item)) {
141
+ item = item.children;
142
+ }
143
+ }
144
+ item.push(newItem);
145
+ };
146
+ // Set Form fields:
147
+ const setFormFields = (field: any, parents: any, k: number) => {
148
+ let fieldName =
149
+ field.input_name && !form.fields[field.input_name]
150
+ ? field.input_name
151
+ : field.name ?? `${field.type}${uniqueId++}`;
152
+
153
+ if ("section" === field.type) {
154
+ const sectionName = field.name ?? `sec_${parents.length}-${k}`;
155
+ addItem(parents, { name: sectionName, children: [] });
156
+ field.children.map((field: any, k2: any) =>
157
+ setFormFields(field, [...parents, k], k2)
158
+ );
159
+ } else if (validTypes.has(field.type)) {
160
+ addItem(parents, fieldName);
161
+ form.fields[fieldName] = field;
162
+ // Pre-process images (fixes flickering issue)
163
+ if (field.type === "image") {
164
+ form.fields[fieldName].imageData = getImageData(
165
+ field.image,
166
+ field.mobile_image
167
+ );
168
+ }
169
+ }
170
+ };
171
+
172
+ formSection.children.map((field: any, k: any) => setFormFields(field, [], k));
173
+ return form;
174
+ };
@@ -0,0 +1,19 @@
1
+ export interface PageTagsType {
2
+ [k: string]: string;
3
+ }
4
+
5
+ export interface QueryParamsType {
6
+ [key: string]: string;
7
+ }
8
+
9
+ export interface ImageType {
10
+ type: "image";
11
+ url: string;
12
+ optimized: boolean;
13
+ name: string;
14
+ alt: string | null;
15
+ caption: string | null;
16
+ width: number;
17
+ height: number;
18
+ extension: string;
19
+ }
@@ -0,0 +1,142 @@
1
+ import { Actions } from "gatsby";
2
+ import { RedirectionType } from "./types";
3
+ const fs = module.require("fs");
4
+
5
+ // Build page elements (only for the ones having a build.js)
6
+ export const build = async (page: any) => {
7
+ let body = [];
8
+ for (const element of page.body) {
9
+ body.push(
10
+ await buildElement(element, {
11
+ slug: page.slug,
12
+ tags: page.tags,
13
+ data: page.data,
14
+ })
15
+ );
16
+ }
17
+ page.body = body;
18
+ return page;
19
+ };
20
+
21
+ /**
22
+ * Generate API pages
23
+ *
24
+ * @param actions Gatsby actions: https://www.gatsbyjs.com/docs/reference/config-files/actions/
25
+ */
26
+ export const createAPIPages = async (actions: Actions) => {
27
+ if (!process.env.GATSBY_PROJECT_ROOT) {
28
+ throw new Error(
29
+ 'Please define "process.env.GATSBY_PROJECT_ROOT = __dirname" within the ./gatsby-config.js file.'
30
+ );
31
+ }
32
+ const { createPage, createRedirect } = actions;
33
+ // Generate Pages
34
+ const jsonDoc = JSON.parse(
35
+ fs.readFileSync(
36
+ `${process.env.GATSBY_PROJECT_ROOT}/src/data/pages/full-content.json`,
37
+ `utf-8`
38
+ )
39
+ );
40
+ let excluded = []; // List of excluded pages
41
+ for (const page of jsonDoc) {
42
+ if (page.is_hidden) {
43
+ excluded.push(page.slug);
44
+ }
45
+ // Skip disabled pages
46
+ if (!page.is_active) {
47
+ continue;
48
+ }
49
+ // Skip when "DEV_PAGE" is defined on "./.env.development" and doesn't match current page:
50
+ if (
51
+ process.env.DEV_PAGE &&
52
+ page.id !== parseInt(`${process.env.DEV_PAGE}`)
53
+ ) {
54
+ continue;
55
+ }
56
+
57
+ createPage({
58
+ path: page.slug,
59
+ component: require.resolve(
60
+ `${process.env.GATSBY_PROJECT_ROOT}/src/components/page/index.tsx`
61
+ ),
62
+ context: await build(page),
63
+ });
64
+ }
65
+
66
+ createAPIRedirections(createRedirect);
67
+ // Generate the file "./src/data/excluded.json" containing the pages which should be excluded on the sitemap and disallowed within robots.txt.
68
+ fs.writeFileSync(
69
+ `${process.env.GATSBY_PROJECT_ROOT}/src/data/pages/excluded.json`,
70
+ JSON.stringify(excluded)
71
+ );
72
+ };
73
+
74
+ /**
75
+ * Generate API redirections
76
+ */
77
+ export const createAPIRedirections = (
78
+ createRedirect: Actions["createRedirect"]
79
+ ) => {
80
+ const redirections = JSON.parse(
81
+ fs.readFileSync(
82
+ `${process.env.GATSBY_PROJECT_ROOT}/src/data/pages/redirections.json`,
83
+ `utf-8`
84
+ )
85
+ );
86
+ redirections.forEach((redirect: RedirectionType) => {
87
+ createRedirect({
88
+ fromPath: redirect.page,
89
+ toPath: redirect.redirection,
90
+ isPermanent: redirect.type === "301",
91
+ force: true,
92
+ });
93
+ });
94
+ };
95
+
96
+ /**
97
+ * Function executed after creating pages to perform following tasks:
98
+ * - Creation of "robots.txt"
99
+ */
100
+ export const postBuild = () => {
101
+ const excludedPages = JSON.parse(
102
+ fs.readFileSync(
103
+ `${process.env.GATSBY_PROJECT_ROOT}/src/data/pages/excluded.json`,
104
+ `utf-8`
105
+ )
106
+ );
107
+
108
+ const robotsTxt = [
109
+ "User-agent: *",
110
+ `${excludedPages.length ? excludedPages.map((slug: string) => `Disallow: ${slug}`).join("\n") : "Allow: /"}`,
111
+ `Sitemap: ${process.env.PROTOCOL}://${process.env.HOST}/sitemap/sitemap-index.xml`,
112
+ `Host: ${process.env.PROTOCOL}://${process.env.HOST}`,
113
+ ];
114
+
115
+ fs.writeFileSync(
116
+ `${process.env.GATSBY_PROJECT_ROOT}/public/robots.txt`,
117
+ robotsTxt.join("\n")
118
+ );
119
+ };
120
+
121
+ /**
122
+ * Builds API Element (usually for "section" and "data" elements)
123
+ * This generates a initial data structure for the section/data to avoid processing the React component
124
+ * initial state on every single render.
125
+ *
126
+ * @param element API element
127
+ * @param context
128
+ * @returns
129
+ */
130
+ export const buildElement = async (element: any, context: any) => {
131
+ const path = `${process.env.GATSBY_PROJECT_ROOT}/src/components/element/${element.type}/${element.name}/build.js`;
132
+ if (fs.existsSync(path)) {
133
+ const module = await import(path);
134
+ if (module?.build) {
135
+ return await module.build(element, context);
136
+ }
137
+ // Show warning on development:
138
+ } else if (process.env.ENV === "development") {
139
+ console.warn(`build.js file not found: ${path}`);
140
+ }
141
+ return element;
142
+ };
@@ -0,0 +1,5 @@
1
+ export interface RedirectionType {
2
+ type: string;
3
+ page: string;
4
+ redirection: string;
5
+ }
@@ -0,0 +1,149 @@
1
+ export const defaultGatsbyConfig = (config: any = {}, plugins: any[] = []) => {
2
+ if (!process.env.GATSBY_PROJECT_ROOT) {
3
+ throw new Error(
4
+ 'Please define "process.env.GATSBY_PROJECT_ROOT = __dirname" within the ./gatsby-config.js file.'
5
+ );
6
+ }
7
+
8
+ return {
9
+ siteMetadata: {
10
+ title: process.env.SITE_TITLE,
11
+ description: process.env.SITE_DESCRIPTION,
12
+ siteUrl: `${process.env.PROTOCOL}://${process.env.HOST}`,
13
+ image: process.env.SITE_IMAGE,
14
+ protocol: process.env.PROTOCOL,
15
+ domain: process.env.HOST,
16
+ },
17
+ plugins: [
18
+ `gatsby-plugin-image`,
19
+ "gatsby-plugin-use-query-params",
20
+ "gatsby-plugin-svgr",
21
+ `gatsby-transformer-sharp`,
22
+ "gatsby-plugin-sass",
23
+ {
24
+ resolve: `gatsby-plugin-manifest`,
25
+ options: {
26
+ name: process.env.PROJECT_NAME,
27
+ short_name: process.env.PROJECT,
28
+ lang: process.env.PROJECT_DEFAULT_LANGUAGE,
29
+ start_url: `/`,
30
+ display: `standalone`,
31
+ icon: `static/icons/favicon.png`,
32
+ },
33
+ },
34
+ {
35
+ resolve: `gatsby-plugin-sharp`,
36
+ options: {
37
+ defaults: {
38
+ formats: [`auto`, `webp`, "avif"],
39
+ placeholder: `blurred`,
40
+ quality: 80,
41
+ breakpoints: [360, 769, 1024, 1216, 1408],
42
+ backgroundColor: `transparent`,
43
+ tracedSVGOptions: {},
44
+ blurredOptions: {},
45
+ jpgOptions: {},
46
+ pngOptions: {},
47
+ webpOptions: {},
48
+ avifOptions: {},
49
+ },
50
+ },
51
+ },
52
+ {
53
+ resolve: "gatsby-source-apiserver",
54
+ options: {
55
+ typePrefix: "API__",
56
+ method: "get",
57
+ allowCache: false,
58
+ headers: {
59
+ "Content-Type": "application/json",
60
+ },
61
+ data: {}, // Request body
62
+ params: {},
63
+ verboseOutput: false,
64
+ // enableDevRefresh: true,
65
+ entitiesArray: [
66
+ {
67
+ url: `${process.env.API_PROTOCOL}://${process.env.API_HOST}/v1/${process.env.PROJECT}/full-content/`,
68
+ headers: {
69
+ "Content-Type": "application/json",
70
+ },
71
+ name: "full-content",
72
+ skipCreateNode: true,
73
+ localSave: true,
74
+ path: `${process.env.GATSBY_PROJECT_ROOT}/src/data/pages/`,
75
+ },
76
+ {
77
+ url: `${process.env.API_PROTOCOL}://${process.env.API_HOST}/v1/${process.env.PROJECT}/redirection/`,
78
+ headers: {
79
+ "Content-Type": "application/json",
80
+ },
81
+ name: "redirections",
82
+ skipCreateNode: true,
83
+ localSave: true,
84
+ path: `${process.env.GATSBY_PROJECT_ROOT}/src/data/pages/`,
85
+ },
86
+ ],
87
+ },
88
+ },
89
+ {
90
+ resolve: `gatsby-plugin-s3`,
91
+ options: {
92
+ bucketName: process.env.S3_BUCKET,
93
+ protocol: process.env.PROTOCOL,
94
+ hostname: process.env.HOST,
95
+ generateRoutingRules: true,
96
+ generateRedirectObjectsForPermanentRedirects: true,
97
+ acl: "private",
98
+ },
99
+ },
100
+ {
101
+ resolve: `gatsby-plugin-sitemap`,
102
+ options: {
103
+ excludes: ["/404/"],
104
+ query: `
105
+ {
106
+ allSitePage {
107
+ nodes {
108
+ path
109
+ pageContext
110
+ }
111
+ }
112
+ }
113
+ `,
114
+ resolveSiteUrl: () => `${process.env.PROTOCOL}://${process.env.HOST}`,
115
+ filterPages: (page: any) => page.is_hidden,
116
+ serialize: (page: any, { resolvePagePath }: any) => {
117
+ return {
118
+ url: `${resolvePagePath(page)}`,
119
+ lastmod: page.pageContext.published_at,
120
+ };
121
+ },
122
+ },
123
+ },
124
+ {
125
+ resolve: "gatsby-plugin-google-tagmanager",
126
+ options: {
127
+ id: process.env.GTM_ID,
128
+ },
129
+ },
130
+ {
131
+ resolve: "gatsby-plugin-newrelic",
132
+ options: {
133
+ config: {
134
+ instrumentationType: "proAndSPA",
135
+ accountId: process.env.NEWRELIC_ACCOUNT_ID,
136
+ trustKey: process.env.NEWRELIC_TRUST_KEY,
137
+ agentID: process.env.NEWRELIC_AGENT_ID,
138
+ licenseKey: process.env.NEWRELIC_LICENSE_KEY,
139
+ applicationID: process.env.NEWRELIC_APP_ID,
140
+ beacon: "bam.nr-data.net",
141
+ errorBeacon: "bam.nr-data.net",
142
+ },
143
+ },
144
+ },
145
+ ...plugins,
146
+ ],
147
+ ...config,
148
+ };
149
+ };
@@ -0,0 +1,23 @@
1
+ import { NOptionsType } from "./types";
2
+
3
+ /**
4
+ *
5
+ * Creates a Object with numeric options for a Select input.
6
+ *
7
+ * @param n Number of options to create
8
+ * @param start The number from which the options start
9
+ * @param defaultOpt Default options to be added into the resulting object
10
+ * @returns Object with the created options, for instance: { "0": "0", "1": "1", ... }
11
+ *
12
+ */
13
+ export const nOptions = (
14
+ n: number,
15
+ start = 0,
16
+ defaultOpt = {}
17
+ ): NOptionsType => {
18
+ const obj: NOptionsType = { ...defaultOpt };
19
+ for (let i = start; i < start + n; i++) {
20
+ obj[i.toString()] = i.toString();
21
+ }
22
+ return obj;
23
+ };
@@ -0,0 +1,3 @@
1
+ export interface NOptionsType {
2
+ [key: string]: string;
3
+ }
@@ -0,0 +1,78 @@
1
+ const slugify = module.require("slugify");
2
+ import { HotelOptionsByCityType, HotelPagesType, CityKeyType } from "./types";
3
+
4
+ /**
5
+ *
6
+ * Returns Select options sorting by the cities with more hotels, otherwise alphabetically.
7
+ *
8
+ * For instance:
9
+ *
10
+ * [
11
+ * {
12
+ * label: 'Sydney',
13
+ * options: [
14
+ * { label: 'Vibe Hotel Sydney', value: "58443" },
15
+ * { label: 'Vibe Hotel Sydney Darling Harbour', value: "8565" }
16
+ * ]
17
+ * },
18
+ * {
19
+ * label: 'Mebourne',
20
+ * options: [
21
+ * { label: 'Vibe Hotel Melbourne', value: "9969" },
22
+ * ]
23
+ * },
24
+ * ]
25
+ */
26
+ export const hotelOptionsByCity = (
27
+ hotelPages: HotelPagesType
28
+ ): HotelOptionsByCityType[] => {
29
+ let hotelsByCity: Record<CityKeyType, { value: string; label: string }[]> = {
30
+ Sydney: [],
31
+ Melbourne: [],
32
+ Perth: [],
33
+ Hobart: [],
34
+ Canberra: [],
35
+ Adelaide: [],
36
+ "Gold Coast": [],
37
+ Darwin: [],
38
+ Singapore: [],
39
+ };
40
+ const propertyOrder = Object.entries(hotelPages).reduce(
41
+ (acc, [id, hotel]) => {
42
+ if (hotelsByCity[hotel.city]) {
43
+ hotelsByCity[hotel.city].push({ label: hotel.name, value: id });
44
+ acc[id] = hotel.sort;
45
+ }
46
+ return acc;
47
+ },
48
+ {} as { [k: string]: number }
49
+ );
50
+ return Object.entries(hotelsByCity).map(([city, options]) => {
51
+ return {
52
+ label: city,
53
+ options: options.sort(
54
+ (a, b) => propertyOrder[a.value] - propertyOrder[b.value]
55
+ ),
56
+ };
57
+ });
58
+ };
59
+
60
+ /**
61
+ * Joins the Property address
62
+ */
63
+ export const joinAddress = (addressParams: (string | null)[]) =>
64
+ addressParams
65
+ .reduce((acc: string[], param: string | null) => {
66
+ if (param) {
67
+ acc.push(param);
68
+ }
69
+ return acc;
70
+ }, [])
71
+ .join(", ");
72
+
73
+ export const hotel_page_link = (city: string, hotelName: string) =>
74
+ `/book-accommodation/${slugify(city, {
75
+ lower: true,
76
+ })}/${slugify(hotelName, {
77
+ lower: true,
78
+ }).slice(5)}`;
@@ -0,0 +1,25 @@
1
+ // Define a type for the city keys in `hotelsByCity`
2
+ export type CityKeyType =
3
+ | "Sydney"
4
+ | "Melbourne"
5
+ | "Perth"
6
+ | "Hobart"
7
+ | "Canberra"
8
+ | "Adelaide"
9
+ | "Gold Coast"
10
+ | "Darwin"
11
+ | "Singapore";
12
+
13
+ export interface HotelPagesType {
14
+ [hotel_code: string]: {
15
+ name: string;
16
+ city: CityKeyType;
17
+ sort: number;
18
+ link: string;
19
+ };
20
+ }
21
+
22
+ export interface HotelOptionsByCityType {
23
+ label: string;
24
+ options: { value: string; label: string }[];
25
+ }
@@ -0,0 +1,8 @@
1
+ export const join_paths = (path1: string, path2: string): string =>
2
+ `${path1.replace(new RegExp(`/$`), "")}/${path2.replace(
3
+ new RegExp(`^/`),
4
+ ""
5
+ )}`;
6
+
7
+ export const ssoURL = (path: string) =>
8
+ `${process.env.SABRE_SSO_PROTOCOL}://${process.env.SABRE_SSO_HOST}${path}`;