@seamapi/react 1.48.0 → 1.49.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 (123) hide show
  1. package/README.md +1 -1
  2. package/dist/elements.js +4653 -4626
  3. package/dist/elements.js.map +1 -1
  4. package/dist/index.css +587 -2
  5. package/dist/index.css.map +1 -1
  6. package/dist/index.min.css +1 -1
  7. package/dist/index.min.css.map +1 -1
  8. package/lib/dates.d.ts +9 -0
  9. package/lib/dates.js +15 -0
  10. package/lib/dates.js.map +1 -1
  11. package/lib/icons/ArrowRight.d.ts +2 -0
  12. package/lib/icons/ArrowRight.js +7 -0
  13. package/lib/icons/ArrowRight.js.map +1 -0
  14. package/lib/icons/ChevronWide.d.ts +2 -0
  15. package/lib/icons/ChevronWide.js +5 -0
  16. package/lib/icons/ChevronWide.js.map +1 -0
  17. package/lib/icons/Copy.js +2 -4
  18. package/lib/icons/Copy.js.map +1 -1
  19. package/lib/icons/TemperatureAdd.d.ts +2 -0
  20. package/lib/icons/TemperatureAdd.js +7 -0
  21. package/lib/icons/TemperatureAdd.js.map +1 -0
  22. package/lib/icons/TemperatureSubtract.d.ts +2 -0
  23. package/lib/icons/TemperatureSubtract.js +7 -0
  24. package/lib/icons/TemperatureSubtract.js.map +1 -0
  25. package/lib/seam/SeamProvider.d.ts +2 -2
  26. package/lib/seam/SeamProvider.js.map +1 -1
  27. package/lib/seam/access-codes/use-generate-access-code-code.d.ts +6 -0
  28. package/lib/seam/access-codes/use-generate-access-code-code.js +13 -0
  29. package/lib/seam/access-codes/use-generate-access-code-code.js.map +1 -0
  30. package/lib/seam/components/AccessCodeDetails/AccessCodeDetails.js +9 -2
  31. package/lib/seam/components/AccessCodeDetails/AccessCodeDetails.js.map +1 -1
  32. package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleCard.d.ts +6 -0
  33. package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleCard.js +41 -0
  34. package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleCard.js.map +1 -0
  35. package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDetails.d.ts +7 -0
  36. package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDetails.js +32 -0
  37. package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDetails.js.map +1 -0
  38. package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDeviceBar.d.ts +4 -0
  39. package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDeviceBar.js +29 -0
  40. package/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDeviceBar.js.map +1 -0
  41. package/lib/seam/components/ClimateSettingScheduleTable/ClimateSettingScheduleRow.js +2 -2
  42. package/lib/seam/components/ClimateSettingScheduleTable/ClimateSettingScheduleRow.js.map +1 -1
  43. package/lib/seam/components/ClimateSettingScheduleTable/{ClimateSettingScheduleDetails.d.ts → ClimateSettingScheduleRowDetails.d.ts} +1 -1
  44. package/lib/seam/components/ClimateSettingScheduleTable/{ClimateSettingScheduleDetails.js → ClimateSettingScheduleRowDetails.js} +2 -2
  45. package/lib/seam/components/ClimateSettingScheduleTable/ClimateSettingScheduleRowDetails.js.map +1 -0
  46. package/lib/seam/components/ClimateSettingScheduleTable/ClimateSettingScheduleTable.d.ts +3 -1
  47. package/lib/seam/components/ClimateSettingScheduleTable/ClimateSettingScheduleTable.js +20 -3
  48. package/lib/seam/components/ClimateSettingScheduleTable/ClimateSettingScheduleTable.js.map +1 -1
  49. package/lib/seam/use-seam-client.js +2 -2
  50. package/lib/seam/use-seam-client.js.map +1 -1
  51. package/lib/ui/AccessCodeForm/AccessCodeFormDatePicker.js +1 -1
  52. package/lib/ui/AccessCodeForm/AccessCodeFormDatePicker.js.map +1 -1
  53. package/lib/ui/Menu/Menu.js +5 -5
  54. package/lib/ui/Menu/Menu.js.map +1 -1
  55. package/lib/ui/Switch/Switch.d.ts +8 -0
  56. package/lib/ui/Switch/Switch.js +15 -0
  57. package/lib/ui/Switch/Switch.js.map +1 -0
  58. package/lib/ui/TextField/TextField.js +1 -3
  59. package/lib/ui/TextField/TextField.js.map +1 -1
  60. package/lib/ui/clipboard.js +1 -1
  61. package/lib/ui/clipboard.js.map +1 -1
  62. package/lib/ui/layout/AccordionRow.d.ts +7 -0
  63. package/lib/ui/layout/AccordionRow.js +8 -0
  64. package/lib/ui/layout/AccordionRow.js.map +1 -0
  65. package/lib/ui/layout/DetailRow.d.ts +6 -0
  66. package/lib/ui/layout/DetailRow.js +5 -0
  67. package/lib/ui/layout/DetailRow.js.map +1 -0
  68. package/lib/ui/layout/DetailSection.d.ts +7 -0
  69. package/lib/ui/layout/DetailSection.js +6 -0
  70. package/lib/ui/layout/DetailSection.js.map +1 -0
  71. package/lib/ui/layout/DetailSectionGroup.d.ts +2 -0
  72. package/lib/ui/layout/DetailSectionGroup.js +5 -0
  73. package/lib/ui/layout/DetailSectionGroup.js.map +1 -0
  74. package/lib/ui/thermostat/TemperatureControl.d.ts +11 -0
  75. package/lib/ui/thermostat/TemperatureControl.js +42 -0
  76. package/lib/ui/thermostat/TemperatureControl.js.map +1 -0
  77. package/lib/ui/use-current-time.d.ts +2 -0
  78. package/lib/ui/use-current-time.js +12 -0
  79. package/lib/ui/use-current-time.js.map +1 -0
  80. package/lib/version.d.ts +1 -1
  81. package/lib/version.js +1 -1
  82. package/package.json +7 -7
  83. package/src/lib/dates.ts +17 -0
  84. package/src/lib/element.tsx +6 -4
  85. package/src/lib/icons/ArrowRight.tsx +36 -0
  86. package/src/lib/icons/ChevronWide.tsx +21 -0
  87. package/src/lib/icons/Copy.tsx +6 -21
  88. package/src/lib/icons/TemperatureAdd.tsx +38 -0
  89. package/src/lib/icons/TemperatureSubtract.tsx +35 -0
  90. package/src/lib/seam/SeamProvider.tsx +2 -3
  91. package/src/lib/seam/access-codes/use-generate-access-code-code.ts +38 -0
  92. package/src/lib/seam/components/AccessCodeDetails/AccessCodeDetails.tsx +23 -2
  93. package/src/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleCard.tsx +101 -0
  94. package/src/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDetails.element.ts +13 -0
  95. package/src/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDetails.tsx +103 -0
  96. package/src/lib/seam/components/ClimateSettingScheduleDetails/ClimateSettingScheduleDeviceBar.tsx +60 -0
  97. package/src/lib/seam/components/ClimateSettingScheduleTable/ClimateSettingScheduleRow.tsx +2 -2
  98. package/src/lib/seam/components/ClimateSettingScheduleTable/{ClimateSettingScheduleDetails.tsx → ClimateSettingScheduleRowDetails.tsx} +1 -1
  99. package/src/lib/seam/components/ClimateSettingScheduleTable/ClimateSettingScheduleTable.element.ts +2 -0
  100. package/src/lib/seam/components/ClimateSettingScheduleTable/ClimateSettingScheduleTable.tsx +37 -2
  101. package/src/lib/seam/use-seam-client.ts +2 -2
  102. package/src/lib/ui/AccessCodeForm/AccessCodeFormDatePicker.tsx +2 -2
  103. package/src/lib/ui/Menu/Menu.tsx +5 -6
  104. package/src/lib/ui/Switch/Switch.tsx +38 -0
  105. package/src/lib/ui/TextField/TextField.tsx +1 -3
  106. package/src/lib/ui/clipboard.ts +1 -1
  107. package/src/lib/ui/layout/AccordionRow.tsx +36 -0
  108. package/src/lib/ui/layout/DetailRow.tsx +17 -0
  109. package/src/lib/ui/layout/DetailSection.tsx +25 -0
  110. package/src/lib/ui/layout/DetailSectionGroup.tsx +7 -0
  111. package/src/lib/ui/thermostat/TemperatureControl.tsx +121 -0
  112. package/src/lib/ui/use-current-time.ts +16 -0
  113. package/src/lib/version.ts +1 -1
  114. package/src/styles/_access-code-details.scss +42 -1
  115. package/src/styles/_climate-setting-schedule-details.scss +44 -0
  116. package/src/styles/_colors.scss +4 -0
  117. package/src/styles/_layout.scss +146 -0
  118. package/src/styles/_main.scss +4 -0
  119. package/src/styles/_seam-table.scss +8 -0
  120. package/src/styles/_switch.scss +53 -0
  121. package/src/styles/_thermostat.scss +387 -2
  122. package/src/styles/_tooltip.scss +3 -0
  123. package/lib/seam/components/ClimateSettingScheduleTable/ClimateSettingScheduleDetails.js.map +0 -1
package/lib/version.js CHANGED
@@ -1,3 +1,3 @@
1
- const seamapiReactVersion = '1.48.0';
1
+ const seamapiReactVersion = '1.49.0';
2
2
  export default seamapiReactVersion;
3
3
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seamapi/react",
3
- "version": "1.48.0",
3
+ "version": "1.49.0",
4
4
  "description": "Seam Components.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -122,7 +122,7 @@
122
122
  "@tanstack/react-query": "^4.28.0",
123
123
  "classnames": "^2.3.2",
124
124
  "luxon": "^3.3.0",
125
- "seamapi": "^8.6.3",
125
+ "seamapi": "^8.9.0",
126
126
  "uuid": "^9.0.0"
127
127
  },
128
128
  "devDependencies": {
@@ -133,7 +133,7 @@
133
133
  "@mui/icons-material": "^5.11.16",
134
134
  "@mui/material": "^5.12.2",
135
135
  "@rxfork/r2wc-react-to-web-component": "^2.3.0",
136
- "@seamapi/fake-seam-connect": "^1.6.0",
136
+ "@seamapi/fake-seam-connect": "^1.9.0",
137
137
  "@storybook/addon-designs": "^7.0.1",
138
138
  "@storybook/addon-essentials": "^7.0.2",
139
139
  "@storybook/addon-links": "^7.0.2",
@@ -159,11 +159,11 @@
159
159
  "eslint-config-standard": "^17.1.0",
160
160
  "eslint-config-standard-jsx": "^11.0.0",
161
161
  "eslint-config-standard-react": "^13.0.0",
162
- "eslint-config-standard-with-typescript": "^37.0.0",
162
+ "eslint-config-standard-with-typescript": "^39.0.0",
163
163
  "eslint-plugin-simple-import-sort": "^10.0.0",
164
164
  "eslint-plugin-storybook": "^0.6.11",
165
- "eslint-plugin-unused-imports": "^2.0.0",
166
- "execa": "^7.1.1",
165
+ "eslint-plugin-unused-imports": "^3.0.0",
166
+ "execa": "^8.0.1",
167
167
  "fast-glob": "^3.2.12",
168
168
  "http-proxy-middleware": "^2.0.6",
169
169
  "isomorphic-fetch": "^3.0.0",
@@ -182,7 +182,7 @@
182
182
  "tsc-alias": "^1.8.5",
183
183
  "tsconfig-paths-webpack-plugin": "^4.0.1",
184
184
  "tsx": "^3.12.7",
185
- "typescript": "^5.1.0",
185
+ "typescript": "~5.1.0",
186
186
  "vite": "^4.2.1",
187
187
  "vite-tsconfig-paths": "^4.0.8",
188
188
  "zustand-hoist": "^1.0.0"
package/src/lib/dates.ts CHANGED
@@ -138,3 +138,20 @@ export const createIsoDate = (date: string, timezone: string): string => {
138
138
  */
139
139
  export const getTimezoneFromIsoDate = (date: string): string | null =>
140
140
  DateTime.fromISO(date).zoneName
141
+
142
+ /**
143
+ * Takes an ISO datetime string (2023-07-20T00:00:00.000-07:00) and returns a string like
144
+ * (Jul 20, 12:00 AM PDT).
145
+ *
146
+ * @param date
147
+ * @returns string
148
+ *
149
+ */
150
+ export const formatDateAndTime = (date: string): string =>
151
+ DateTime.fromISO(date).toLocaleString({
152
+ month: 'short',
153
+ day: 'numeric',
154
+ hour: 'numeric',
155
+ minute: '2-digit',
156
+ timeZoneName: 'short',
157
+ })
@@ -28,8 +28,10 @@ export type ElementProps<T> = Record<
28
28
  'string' | 'number' | 'boolean' | 'array' | 'function' | 'json' | 'object'
29
29
  >
30
30
 
31
- type ProviderProps = SeamProviderPropsWithPublishableKey &
32
- SeamProviderPropsWithClientSessionToken
31
+ type ProviderProps = Omit<
32
+ SeamProviderPropsWithPublishableKey & SeamProviderPropsWithClientSessionToken,
33
+ 'children'
34
+ >
33
35
 
34
36
  const providerProps: ElementProps<ProviderProps> = {
35
37
  publishableKey: 'string',
@@ -53,7 +55,7 @@ export const defineCustomElement = ({
53
55
  ...providerProps,
54
56
  },
55
57
  })
56
- globalThis?.customElements?.define(name, element)
58
+ globalThis.customElements?.define(name, element)
57
59
  }
58
60
 
59
61
  function withProvider<P extends JSX.IntrinsicAttributes>(
@@ -82,7 +84,7 @@ function withProvider<P extends JSX.IntrinsicAttributes>(
82
84
  disableFontInjection={
83
85
  disableFontInjection ?? globalThis.disableSeamFontInjection
84
86
  }
85
- unminifiyCss={unminifiyCss ?? globalThis?.unminifiySeamCss}
87
+ unminifiyCss={unminifiyCss ?? globalThis.unminifiySeamCss}
86
88
  >
87
89
  <Component {...(props as P)} />
88
90
  </SeamProvider>
@@ -0,0 +1,36 @@
1
+ /*
2
+ * Automaically generated by SVGR from assets/icons/*.svg.
3
+ * Do not edit this file or add other components to this directory.
4
+ */
5
+ import type { SVGProps } from 'react'
6
+ export function ArrowRightIcon(props: SVGProps<SVGSVGElement>): JSX.Element {
7
+ return (
8
+ <svg
9
+ xmlns='http://www.w3.org/2000/svg'
10
+ width={18}
11
+ height={19}
12
+ fill='none'
13
+ {...props}
14
+ >
15
+ <mask
16
+ id='arrow-right_svg__a'
17
+ width={18}
18
+ height={19}
19
+ x={0}
20
+ y={0}
21
+ maskUnits='userSpaceOnUse'
22
+ style={{
23
+ maskType: 'alpha',
24
+ }}
25
+ >
26
+ <path fill='#D9D9D9' d='M0 .5h18v18H0z' />
27
+ </mask>
28
+ <g mask='url(#arrow-right_svg__a)'>
29
+ <path
30
+ fill='#B9BEC5'
31
+ d='m10.5 14-1.05-1.088 2.663-2.662H3v-1.5h9.113L9.45 6.088 10.5 5 15 9.5 10.5 14Z'
32
+ />
33
+ </g>
34
+ </svg>
35
+ )
36
+ }
@@ -0,0 +1,21 @@
1
+ /*
2
+ * Automaically generated by SVGR from assets/icons/*.svg.
3
+ * Do not edit this file or add other components to this directory.
4
+ */
5
+ import type { SVGProps } from 'react'
6
+ export function ChevronWideIcon(props: SVGProps<SVGSVGElement>): JSX.Element {
7
+ return (
8
+ <svg
9
+ xmlns='http://www.w3.org/2000/svg'
10
+ width={25}
11
+ height={24}
12
+ fill='none'
13
+ {...props}
14
+ >
15
+ <path
16
+ fill='#D5D8DC'
17
+ d='m22.123 8-10 10-10-10 1.775-1.775 8.225 8.225 8.225-8.225L22.123 8Z'
18
+ />
19
+ </svg>
20
+ )
21
+ }
@@ -7,30 +7,15 @@ export function CopyIcon(props: SVGProps<SVGSVGElement>): JSX.Element {
7
7
  return (
8
8
  <svg
9
9
  xmlns='http://www.w3.org/2000/svg'
10
- width={18}
11
- height={18}
10
+ width={25}
11
+ height={25}
12
12
  fill='none'
13
13
  {...props}
14
14
  >
15
- <mask
16
- id='copy_svg__a'
17
- width={18}
18
- height={18}
19
- x={0}
20
- y={0}
21
- maskUnits='userSpaceOnUse'
22
- style={{
23
- maskType: 'alpha',
24
- }}
25
- >
26
- <path fill='#D9D9D9' d='M0 0h18v18H0z' />
27
- </mask>
28
- <g mask='url(#copy_svg__a)'>
29
- <path
30
- fill='#007AFC'
31
- d='M3.75 16.5c-.413 0-.766-.147-1.06-.44A1.446 1.446 0 0 1 2.25 15V4.5h1.5V15H12v1.5H3.75Zm3-3c-.412 0-.766-.147-1.059-.44A1.445 1.445 0 0 1 5.25 12V3c0-.413.147-.766.441-1.06.293-.293.647-.44 1.059-.44h6.75c.412 0 .766.147 1.06.44.293.294.44.647.44 1.06v9c0 .412-.147.766-.44 1.06-.294.293-.648.44-1.06.44H6.75Zm0-1.5h6.75V3H6.75v9Z'
32
- />
33
- </g>
15
+ <path
16
+ fill='#232B3A'
17
+ d='M5.84 22.988c-.55 0-1.02-.195-1.412-.587a1.928 1.928 0 0 1-.587-1.413v-14h2v14h11v2h-11Zm4-4c-.55 0-1.02-.195-1.411-.587a1.927 1.927 0 0 1-.588-1.413v-12c0-.55.196-1.02.588-1.413a1.925 1.925 0 0 1 1.412-.587h9c.55 0 1.02.196 1.413.587.391.392.587.863.587 1.413v12c0 .55-.196 1.021-.587 1.413a1.928 1.928 0 0 1-1.413.587h-9Zm0-2h9v-12h-9v12Z'
18
+ />
34
19
  </svg>
35
20
  )
36
21
  }
@@ -0,0 +1,38 @@
1
+ /*
2
+ * Automaically generated by SVGR from assets/icons/*.svg.
3
+ * Do not edit this file or add other components to this directory.
4
+ */
5
+ import type { SVGProps } from 'react'
6
+ export function TemperatureAddIcon(
7
+ props: SVGProps<SVGSVGElement>
8
+ ): JSX.Element {
9
+ return (
10
+ <svg
11
+ xmlns='http://www.w3.org/2000/svg'
12
+ width={18}
13
+ height={18}
14
+ fill='none'
15
+ {...props}
16
+ >
17
+ <mask
18
+ id='temperature-add_svg__a'
19
+ width={18}
20
+ height={18}
21
+ x={0}
22
+ y={0}
23
+ maskUnits='userSpaceOnUse'
24
+ style={{
25
+ maskType: 'alpha',
26
+ }}
27
+ >
28
+ <path fill='#2196F3' d='M0 0h18v18H0z' />
29
+ </mask>
30
+ <g mask='url(#temperature-add_svg__a)'>
31
+ <path
32
+ fill='#9DA1A9'
33
+ d='M8.25 14.25v-4.5h-4.5v-1.5h4.5v-4.5h1.5v4.5h4.5v1.5h-4.5v4.5h-1.5Z'
34
+ />
35
+ </g>
36
+ </svg>
37
+ )
38
+ }
@@ -0,0 +1,35 @@
1
+ /*
2
+ * Automaically generated by SVGR from assets/icons/*.svg.
3
+ * Do not edit this file or add other components to this directory.
4
+ */
5
+ import type { SVGProps } from 'react'
6
+ export function TemperatureSubtractIcon(
7
+ props: SVGProps<SVGSVGElement>
8
+ ): JSX.Element {
9
+ return (
10
+ <svg
11
+ xmlns='http://www.w3.org/2000/svg'
12
+ width={18}
13
+ height={18}
14
+ fill='none'
15
+ {...props}
16
+ >
17
+ <mask
18
+ id='temperature-subtract_svg__a'
19
+ width={18}
20
+ height={18}
21
+ x={0}
22
+ y={0}
23
+ maskUnits='userSpaceOnUse'
24
+ style={{
25
+ maskType: 'alpha',
26
+ }}
27
+ >
28
+ <path fill='#2196F3' d='M0 0h18v18H0z' />
29
+ </mask>
30
+ <g mask='url(#temperature-subtract_svg__a)'>
31
+ <path fill='#9DA1A9' d='M3.75 9.75v-1.5h10.5v1.5H3.75Z' />
32
+ </g>
33
+ </svg>
34
+ )
35
+ }
@@ -25,11 +25,10 @@ export interface SeamContext {
25
25
  clientSessionToken?: string | undefined
26
26
  }
27
27
 
28
- export type SeamProviderProps = PropsWithChildren<
28
+ export type SeamProviderProps =
29
29
  | SeamProviderPropsWithClient
30
30
  | SeamProviderPropsWithPublishableKey
31
31
  | SeamProviderPropsWithClientSessionToken
32
- >
33
32
 
34
33
  export interface SeamProviderPropsWithClient extends SeamProviderBaseProps {
35
34
  client: Seam
@@ -48,7 +47,7 @@ export interface SeamProviderPropsWithClientSessionToken
48
47
  clientSessionToken: string
49
48
  }
50
49
 
51
- interface SeamProviderBaseProps {
50
+ interface SeamProviderBaseProps extends PropsWithChildren {
52
51
  disableCssInjection?: boolean | undefined
53
52
  disableFontInjection?: boolean | undefined
54
53
  unminifiyCss?: boolean | undefined
@@ -0,0 +1,38 @@
1
+ import { useMutation, type UseMutationResult } from '@tanstack/react-query'
2
+ import type {
3
+ AccessCode,
4
+ AccessCodeGenerateCodeRequest,
5
+ AccessCodeGenerateCodeResponse,
6
+ SeamError,
7
+ } from 'seamapi'
8
+
9
+ import { NullSeamClientError, useSeamClient } from 'lib/seam/use-seam-client.js'
10
+
11
+ export type UseGenerateAccessCodeCodeParams = never
12
+ export type UseGenerateAccessCodeCodeData = Pick<
13
+ AccessCode,
14
+ 'code' | 'device_id'
15
+ >
16
+ export type UseGenerateAccessCodeCodeMutationParams =
17
+ AccessCodeGenerateCodeRequest
18
+
19
+ export function useGenerateAccessCodeCode(): UseMutationResult<
20
+ UseGenerateAccessCodeCodeData,
21
+ SeamError,
22
+ UseGenerateAccessCodeCodeMutationParams
23
+ > {
24
+ const { client } = useSeamClient()
25
+
26
+ return useMutation<
27
+ AccessCodeGenerateCodeResponse['generated_code'],
28
+ SeamError,
29
+ AccessCodeGenerateCodeRequest
30
+ >({
31
+ mutationFn: async (
32
+ mutationParams: UseGenerateAccessCodeCodeMutationParams
33
+ ) => {
34
+ if (client === null) throw new NullSeamClientError()
35
+ return await client.accessCodes.generateCode(mutationParams)
36
+ },
37
+ })
38
+ }
@@ -8,12 +8,15 @@ import type {
8
8
  DeviceError,
9
9
  } from 'seamapi'
10
10
 
11
+ import { CopyIcon } from 'lib/icons/Copy.js'
11
12
  import { useAccessCode } from 'lib/seam/access-codes/use-access-code.js'
12
13
  import { useDeleteAccessCode } from 'lib/seam/access-codes/use-delete-access-code.js'
13
14
  import { AccessCodeDevice } from 'lib/seam/components/AccessCodeDetails/AccessCodeDevice.js'
14
15
  import { DeviceDetails } from 'lib/seam/components/DeviceDetails/DeviceDetails.js'
15
16
  import { Alerts } from 'lib/ui/Alert/Alerts.js'
16
17
  import { Button } from 'lib/ui/Button.js'
18
+ import { copyToClipboard } from 'lib/ui/clipboard.js'
19
+ import { IconButton } from 'lib/ui/IconButton.js'
17
20
  import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
18
21
  import { useIsDateInPast } from 'lib/ui/use-is-date-in-past.js'
19
22
 
@@ -82,7 +85,16 @@ export function AccessCodeDetails({
82
85
  >
83
86
  <span className='seam-label'>{t.accessCode}</span>
84
87
  <h5 className='seam-access-code-name'>{name}</h5>
85
- <div className='seam-code'>{accessCode.code}</div>
88
+ <div className='seam-code'>
89
+ <span>{accessCode.code}</span>
90
+ <IconButton
91
+ onClick={() => {
92
+ void copyToClipboard(accessCode.code ?? '')
93
+ }}
94
+ >
95
+ <CopyIcon />
96
+ </IconButton>
97
+ </div>
86
98
  <div className='seam-duration'>
87
99
  <Duration accessCode={accessCode} />
88
100
  </div>
@@ -117,7 +129,16 @@ export function AccessCodeDetails({
117
129
  <div className='seam-details'>
118
130
  <div className='seam-row'>
119
131
  <div className='seam-heading'>{t.id}:</div>
120
- <div className='seam-content'>{accessCode.access_code_id}</div>
132
+ <div className='seam-content seam-code-id'>
133
+ <span>{accessCode.access_code_id}</span>
134
+ <IconButton
135
+ onClick={() => {
136
+ void copyToClipboard(accessCode.access_code_id)
137
+ }}
138
+ >
139
+ <CopyIcon />
140
+ </IconButton>
141
+ </div>
121
142
  </div>
122
143
  <div className='seam-row'>
123
144
  <div className='seam-heading'>{t.created}:</div>
@@ -0,0 +1,101 @@
1
+ import { DateTime } from 'luxon'
2
+ import type { ClimateSettingSchedule } from 'seamapi'
3
+
4
+ import { formatDateAndTime } from 'lib/dates.js'
5
+ import { ClimateSettingScheduleIcon } from 'lib/icons/ClimateSettingSchedule.js'
6
+ import { useClimateSettingSchedule } from 'lib/seam/thermostats/climate-setting-schedules/use-climate-setting-schedule.js'
7
+ import { DotDivider } from 'lib/ui/layout/DotDivider.js'
8
+ import { ClimateSettingStatus } from 'lib/ui/thermostat/ClimateSettingStatus.js'
9
+ import { useCurrentTime } from 'lib/ui/use-current-time.js'
10
+
11
+ import { ClimateSettingScheduleDeviceBar } from './ClimateSettingScheduleDeviceBar.js'
12
+
13
+ interface ClimateSettingScheduleCardProps {
14
+ climateSettingScheduleId: string
15
+ }
16
+
17
+ export function ClimateSettingScheduleCard({
18
+ climateSettingScheduleId,
19
+ }: ClimateSettingScheduleCardProps): JSX.Element {
20
+ return (
21
+ <div className='seam-climate-setting-schedule-card'>
22
+ <Content climateSettingScheduleId={climateSettingScheduleId} />
23
+ </div>
24
+ )
25
+ }
26
+
27
+ function Content(props: {
28
+ climateSettingScheduleId: string
29
+ }): JSX.Element | null {
30
+ const { climateSettingScheduleId } = props
31
+
32
+ const { climateSettingSchedule } = useClimateSettingSchedule(
33
+ climateSettingScheduleId
34
+ )
35
+
36
+ if (climateSettingSchedule == null) {
37
+ return null
38
+ }
39
+
40
+ const name = climateSettingSchedule.name ?? t.fallbackName
41
+
42
+ return (
43
+ <div className='seam-climate-setting-schedule-content'>
44
+ <div className='seam-climate-setting-schedule-summary-container'>
45
+ <div className='seam-climate-setting-schedule-icon-block'>
46
+ <ClimateSettingScheduleIcon />
47
+ </div>
48
+ <div className='seam-climate-setting-schedule-summary'>
49
+ <h5 className='seam-climate-setting-schedule-heading'>{name}</h5>
50
+ <div className='seam-climate-setting-schedule-subheading'>
51
+ <ClimateSettingStatus climateSetting={climateSettingSchedule} />
52
+ <DotDivider />
53
+ <ClimateSettingScheduleTiming
54
+ climateSettingSchedule={climateSettingSchedule}
55
+ />
56
+ </div>
57
+ </div>
58
+ </div>
59
+ <ClimateSettingScheduleDeviceBar
60
+ deviceId={climateSettingSchedule.device_id}
61
+ />
62
+ </div>
63
+ )
64
+ }
65
+
66
+ function ClimateSettingScheduleTiming(props: {
67
+ climateSettingSchedule: ClimateSettingSchedule
68
+ }): JSX.Element | null {
69
+ const { climateSettingSchedule } = props
70
+
71
+ const currentTime = useCurrentTime()
72
+
73
+ if (currentTime === null) return null
74
+
75
+ const startTime = DateTime.fromISO(climateSettingSchedule.schedule_starts_at)
76
+ const endTime = DateTime.fromISO(climateSettingSchedule.schedule_ends_at)
77
+
78
+ if (currentTime < startTime)
79
+ return (
80
+ <span>
81
+ {t.starts}{' '}
82
+ {formatDateAndTime(climateSettingSchedule.schedule_starts_at)}
83
+ </span>
84
+ )
85
+
86
+ if (startTime <= currentTime && currentTime <= endTime)
87
+ return (
88
+ <span>
89
+ {t.ends} {formatDateAndTime(climateSettingSchedule.schedule_starts_at)}
90
+ </span>
91
+ )
92
+
93
+ return <span>{t.expired}</span>
94
+ }
95
+
96
+ const t = {
97
+ starts: 'Starts',
98
+ ends: 'Ends',
99
+ expired: 'Expired',
100
+ fallbackName: 'Climate Setting Schedule',
101
+ }
@@ -0,0 +1,13 @@
1
+ import type { ElementProps } from 'lib/element.js'
2
+
3
+ import type { ClimateSettingScheduleDetailsProps } from './ClimateSettingScheduleDetails.js'
4
+
5
+ export const name = 'seam-climate-setting-schedule-details'
6
+
7
+ export const props: ElementProps<ClimateSettingScheduleDetailsProps> = {
8
+ climateSettingScheduleId: 'string',
9
+ onBack: 'object',
10
+ className: 'string',
11
+ }
12
+
13
+ export { ClimateSettingScheduleDetails as Component } from './ClimateSettingScheduleDetails.js'
@@ -0,0 +1,103 @@
1
+ import classNames from 'classnames'
2
+
3
+ import { formatDateAndTime } from 'lib/dates.js'
4
+ import { ArrowRightIcon } from 'lib/icons/ArrowRight.js'
5
+ import { useClimateSettingSchedule } from 'lib/seam/thermostats/climate-setting-schedules/use-climate-setting-schedule.js'
6
+ import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
7
+ import { DetailRow } from 'lib/ui/layout/DetailRow.js'
8
+ import { DetailSection } from 'lib/ui/layout/DetailSection.js'
9
+ import { DetailSectionGroup } from 'lib/ui/layout/DetailSectionGroup.js'
10
+ import { ClimateSettingStatus } from 'lib/ui/thermostat/ClimateSettingStatus.js'
11
+
12
+ import { ClimateSettingScheduleCard } from './ClimateSettingScheduleCard.js'
13
+
14
+ export interface ClimateSettingScheduleDetailsProps {
15
+ climateSettingScheduleId: string
16
+ onBack?: () => void
17
+ className?: string
18
+ }
19
+
20
+ export function ClimateSettingScheduleDetails({
21
+ climateSettingScheduleId,
22
+ onBack,
23
+ className,
24
+ }: ClimateSettingScheduleDetailsProps): JSX.Element | null {
25
+ const { climateSettingSchedule } = useClimateSettingSchedule(
26
+ climateSettingScheduleId
27
+ )
28
+
29
+ if (climateSettingSchedule == null) {
30
+ return null
31
+ }
32
+
33
+ const isManualOverrideAllowed =
34
+ climateSettingSchedule.manual_override_allowed ?? false
35
+
36
+ return (
37
+ <div
38
+ className={classNames('seam-climate-setting-schedule-details', className)}
39
+ >
40
+ <ContentHeader title={t.climateSettingSchedule} onBack={onBack} />
41
+ <div className='seam-climate-setting-schedule-details-content'>
42
+ <ClimateSettingScheduleCard
43
+ climateSettingScheduleId={climateSettingScheduleId}
44
+ />
45
+ <div className='seam-default-setting-message-container'>
46
+ <span className='seam-default-setting-message'>
47
+ {t.defaultSettingMessagePart1}{' '}
48
+ <span className='seam-default-setting-text'>
49
+ {t.defaultSetting}
50
+ </span>{' '}
51
+ {t.defaultSettingMessagePart2}
52
+ </span>
53
+ </div>
54
+ <DetailSectionGroup>
55
+ <DetailSection>
56
+ <DetailRow label={t.startEndTime}>
57
+ <span className='seam-climate-setting-details-value seam-climate-setting-details-schedule-range'>
58
+ {`${formatDateAndTime(
59
+ climateSettingSchedule.schedule_starts_at
60
+ )}`}
61
+ <ArrowRightIcon />
62
+ {`${formatDateAndTime(
63
+ climateSettingSchedule.schedule_ends_at
64
+ )}`}
65
+ </span>
66
+ </DetailRow>
67
+ <DetailRow label={t.climateSetting}>
68
+ <ClimateSettingStatus
69
+ climateSetting={climateSettingSchedule}
70
+ iconPlacement='right'
71
+ />
72
+ </DetailRow>
73
+ <DetailRow label={t.allowManualOverride}>
74
+ <span className='seam-climate-setting-details-value'>
75
+ {isManualOverrideAllowed ? t.on : t.off}
76
+ </span>
77
+ </DetailRow>
78
+ </DetailSection>
79
+ <DetailSection>
80
+ <DetailRow label={t.creationDate}>
81
+ <div className='seam-creation-date'>
82
+ {formatDateAndTime(climateSettingSchedule.created_at)}
83
+ </div>
84
+ </DetailRow>
85
+ </DetailSection>
86
+ </DetailSectionGroup>
87
+ </div>
88
+ </div>
89
+ )
90
+ }
91
+
92
+ const t = {
93
+ climateSettingSchedule: 'Climate setting schedule',
94
+ startEndTime: 'Start/End Time',
95
+ climateSetting: 'Climate setting',
96
+ allowManualOverride: 'Allow manual override',
97
+ creationDate: 'Creation date',
98
+ defaultSettingMessagePart1: 'Thermostat will return to its',
99
+ defaultSetting: 'default setting',
100
+ defaultSettingMessagePart2: 'at end time.',
101
+ on: 'On',
102
+ off: 'Off',
103
+ }
@@ -0,0 +1,60 @@
1
+ import { isThermostatDevice, type ThermostatDevice } from 'seamapi'
2
+
3
+ import { ChevronWideIcon } from 'lib/icons/ChevronWide.js'
4
+ import { useDevice } from 'lib/seam/devices/use-device.js'
5
+ import { DeviceImage } from 'lib/ui/device/DeviceImage.js'
6
+ import { ClimateSettingStatus } from 'lib/ui/thermostat/ClimateSettingStatus.js'
7
+
8
+ export function ClimateSettingScheduleDeviceBar({
9
+ deviceId,
10
+ }: {
11
+ deviceId: string
12
+ }): JSX.Element | null {
13
+ const { isLoading, device } = useDevice({
14
+ device_id: deviceId,
15
+ })
16
+
17
+ if (isLoading) {
18
+ return null
19
+ }
20
+
21
+ if (device == null) {
22
+ return null
23
+ }
24
+
25
+ if (!isThermostatDevice(device)) {
26
+ return null
27
+ }
28
+
29
+ return <Content device={device} />
30
+ }
31
+
32
+ function Content(props: { device: ThermostatDevice }): JSX.Element {
33
+ const { device } = props
34
+
35
+ return (
36
+ <div className='seam-climate-setting-schedule-device-bar'>
37
+ <div className='seam-climate-setting-schedule-device-content'>
38
+ <div className='seam-device-image'>
39
+ <DeviceImage device={device} />
40
+ </div>
41
+ <div className='seam-climate-setting-schedule-device-details'>
42
+ <div className='seam-device-name'>{device.properties.name}</div>
43
+ <div className='seam-device-current-climate-setting'>
44
+ {t.currentClimate}:
45
+ <ClimateSettingStatus
46
+ climateSetting={device.properties.current_climate_setting}
47
+ />
48
+ </div>
49
+ </div>
50
+ </div>
51
+ <div className='seam-climate-setting-schedule-device-chevron'>
52
+ <ChevronWideIcon />
53
+ </div>
54
+ </div>
55
+ )
56
+ }
57
+
58
+ const t = {
59
+ currentClimate: 'Current climate',
60
+ }