@graphcommerce/ecommerce-ui 8.1.0-canary.8 → 9.0.0-canary.100

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.
@@ -1,8 +1,8 @@
1
1
  import {
2
- Controller,
3
2
  ControllerProps,
4
3
  FieldError,
5
4
  FieldValues,
5
+ useController,
6
6
  } from '@graphcommerce/react-hook-form'
7
7
  import { i18n } from '@lingui/core'
8
8
  import {
@@ -16,8 +16,6 @@ import {
16
16
 
17
17
  export type SliderElementProps<T extends FieldValues> = Omit<SliderProps, 'control'> & {
18
18
  label?: string
19
- /** @deprecated Form value parsing should happen in the handleSubmit function of the form */
20
- parseError?: (error: FieldError) => string
21
19
  required?: boolean
22
20
  formControlProps?: FormControlProps
23
21
  } & Omit<ControllerProps<T>, 'render'>
@@ -27,39 +25,40 @@ export function SliderElement<TFieldValues extends FieldValues>({
27
25
  control,
28
26
  label,
29
27
  rules = {},
30
- parseError,
31
28
  required,
32
29
  formControlProps,
30
+ defaultValue,
31
+ disabled,
32
+ shouldUnregister,
33
33
  ...other
34
34
  }: SliderElementProps<TFieldValues>) {
35
35
  if (required && !rules.required) {
36
36
  rules.required = i18n._(/* i18n */ 'This field is required')
37
37
  }
38
+
39
+ const {
40
+ field,
41
+ fieldState: { invalid, error },
42
+ } = useController({
43
+ name,
44
+ control,
45
+ rules,
46
+ defaultValue,
47
+ disabled,
48
+ shouldUnregister,
49
+ })
50
+
51
+ const parsedHelperText = error ? error.message : null
52
+
38
53
  return (
39
- <Controller
40
- name={name}
41
- control={control}
42
- rules={rules}
43
- render={({ field, fieldState: { invalid, error } }) => {
44
- const parsedHelperText = error
45
- ? typeof parseError === 'function'
46
- ? parseError(error)
47
- : error.message
48
- : null
49
- return (
50
- <FormControl error={invalid} required={required} fullWidth {...formControlProps}>
51
- {label && (
52
- <FormLabel component='legend' error={invalid}>
53
- {label}
54
- </FormLabel>
55
- )}
56
- <Slider {...other} {...field} valueLabelDisplay={other.valueLabelDisplay || 'auto'} />
57
- {parsedHelperText && (
58
- <FormHelperText error={invalid}>{parsedHelperText}</FormHelperText>
59
- )}
60
- </FormControl>
61
- )
62
- }}
63
- />
54
+ <FormControl error={invalid} required={required} fullWidth {...formControlProps}>
55
+ {label && (
56
+ <FormLabel component='legend' error={invalid}>
57
+ {label}
58
+ </FormLabel>
59
+ )}
60
+ <Slider {...other} {...field} valueLabelDisplay={other.valueLabelDisplay || 'auto'} />
61
+ {parsedHelperText && <FormHelperText error={invalid}>{parsedHelperText}</FormHelperText>}
62
+ </FormControl>
64
63
  )
65
64
  }
@@ -1,4 +1,4 @@
1
- import { Controller, FieldValues, ControllerProps } from '@graphcommerce/react-hook-form'
1
+ import { FieldValues, ControllerProps, useController } from '@graphcommerce/react-hook-form'
2
2
  import { FormControlLabel, FormControlLabelProps, Switch } from '@mui/material'
3
3
 
4
4
  type IProps = Omit<FormControlLabelProps, 'control'>
@@ -8,18 +8,20 @@ export type SwitchElementProps<T extends FieldValues> = IProps & Omit<Controller
8
8
  export function SwitchElement<TFieldValues extends FieldValues>({
9
9
  name,
10
10
  control,
11
+ defaultValue,
12
+ disabled,
13
+ shouldUnregister,
14
+ rules,
11
15
  ...other
12
16
  }: SwitchElementProps<TFieldValues>) {
13
- return (
14
- <FormControlLabel
15
- control={
16
- <Controller
17
- name={name}
18
- control={control}
19
- render={({ field }) => <Switch {...field} checked={!!field.value} />}
20
- />
21
- }
22
- {...other}
23
- />
24
- )
17
+ const { field } = useController({
18
+ name,
19
+ control,
20
+ defaultValue,
21
+ disabled,
22
+ shouldUnregister,
23
+ rules,
24
+ })
25
+
26
+ return <FormControlLabel control={<Switch {...field} checked={!!field.value} />} {...other} />
25
27
  }
@@ -0,0 +1,23 @@
1
+ import { FieldValues, phonePattern } from '@graphcommerce/react-hook-form'
2
+ import { Trans, t } from '@lingui/macro'
3
+ import { TextFieldElement, TextFieldElementProps } from './TextFieldElement'
4
+
5
+ export type TelephoneElementProps<T extends FieldValues> = TextFieldElementProps<T>
6
+
7
+ export function TelephoneElement<TFieldValues extends FieldValues>(
8
+ props: TelephoneElementProps<TFieldValues>,
9
+ ): JSX.Element {
10
+ const { rules, ...rest } = props
11
+ return (
12
+ <TextFieldElement
13
+ type='text'
14
+ label={<Trans>Telephone</Trans>}
15
+ autoComplete='tel'
16
+ rules={{
17
+ pattern: { value: phonePattern, message: t`Invalid phone number` },
18
+ ...rules,
19
+ }}
20
+ {...rest}
21
+ />
22
+ )
23
+ }
@@ -1,6 +1,11 @@
1
1
  /* eslint-disable no-nested-ternary */
2
2
  import { InputCheckmark } from '@graphcommerce/next-ui'
3
- import { FieldValues, UseControllerProps, useController } from '@graphcommerce/react-hook-form'
3
+ import {
4
+ FieldValues,
5
+ UseControllerProps,
6
+ emailPattern,
7
+ useController,
8
+ } from '@graphcommerce/react-hook-form'
4
9
  import { i18n } from '@lingui/core'
5
10
  import { TextField, TextFieldProps } from '@mui/material'
6
11
 
@@ -22,7 +27,9 @@ export function TextFieldElement<TFieldValues extends FieldValues>({
22
27
  control,
23
28
  defaultValue,
24
29
  rules = validation,
30
+ shouldUnregister,
25
31
  showValid,
32
+ disabled,
26
33
  ...rest
27
34
  }: TextFieldElementProps<TFieldValues>): JSX.Element {
28
35
  if (required && !rules.required) {
@@ -31,9 +38,7 @@ export function TextFieldElement<TFieldValues extends FieldValues>({
31
38
 
32
39
  if (type === 'email' && !rules.pattern) {
33
40
  rules.pattern = {
34
- // eslint-disable-next-line no-useless-escape
35
- value:
36
- /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
41
+ value: emailPattern,
37
42
  message: i18n._(/* i18n */ 'Please enter a valid email address'),
38
43
  }
39
44
  }
@@ -41,7 +46,7 @@ export function TextFieldElement<TFieldValues extends FieldValues>({
41
46
  const {
42
47
  field: { onChange, ref, value = '', ...field },
43
48
  fieldState: { error },
44
- } = useController({ name, control, rules, defaultValue })
49
+ } = useController({ name, control, rules, defaultValue, shouldUnregister, disabled })
45
50
 
46
51
  return (
47
52
  <TextField
@@ -1,8 +1,8 @@
1
1
  import {
2
- Controller,
3
2
  ControllerProps,
4
3
  FieldError,
5
4
  FieldValues,
5
+ useController,
6
6
  } from '@graphcommerce/react-hook-form'
7
7
  import { i18n } from '@lingui/core'
8
8
  import {
@@ -25,8 +25,6 @@ type SingleToggleButtonProps = Omit<ToggleButtonProps, 'value' | 'children'> & {
25
25
  export type ToggleButtonGroupElementProps<T extends FieldValues> = ToggleButtonGroupProps & {
26
26
  required?: boolean
27
27
  label?: string
28
- /** @deprecated Form value parsing should happen in the handleSubmit function of the form */
29
- parseError?: (error: FieldError) => string
30
28
  options: SingleToggleButtonProps[]
31
29
  formLabelProps?: FormLabelProps
32
30
  helperText?: string
@@ -39,9 +37,11 @@ export function ToggleButtonGroupElement<TFieldValues extends FieldValues = Fiel
39
37
  rules = {},
40
38
  required,
41
39
  options = [],
42
- parseError,
43
40
  helperText,
44
41
  formLabelProps,
42
+ defaultValue,
43
+ disabled,
44
+ shouldUnregister,
45
45
  ...toggleButtonGroupProps
46
46
  }: ToggleButtonGroupElementProps<TFieldValues>) {
47
47
  if (required && !rules.required) {
@@ -49,50 +49,51 @@ export function ToggleButtonGroupElement<TFieldValues extends FieldValues = Fiel
49
49
  }
50
50
 
51
51
  const isRequired = required || !!rules?.required
52
+
53
+ const {
54
+ field: { value, onChange, onBlur },
55
+ fieldState: { invalid, error },
56
+ } = useController({
57
+ name,
58
+ control,
59
+ rules,
60
+ defaultValue,
61
+ disabled,
62
+ shouldUnregister,
63
+ })
64
+
65
+ const renderHelperText = error ? error.message : helperText
66
+
52
67
  return (
53
- <Controller
54
- name={name}
55
- control={control}
56
- rules={rules}
57
- render={({ field: { value, onChange, onBlur }, fieldState: { invalid, error } }) => {
58
- const renderHelperText = error
59
- ? typeof parseError === 'function'
60
- ? parseError(error)
61
- : error.message
62
- : helperText
63
- return (
64
- <FormControl error={invalid} required={isRequired}>
65
- {label && (
66
- <FormLabel
67
- {...formLabelProps}
68
- error={invalid}
69
- required={isRequired}
70
- sx={{ mb: 1, ...formLabelProps?.sx }}
71
- >
72
- {label}
73
- </FormLabel>
74
- )}
75
- <ToggleButtonGroup
76
- {...toggleButtonGroupProps}
77
- value={value}
78
- onBlur={onBlur}
79
- onChange={(event, val) => {
80
- onChange(val)
81
- if (typeof toggleButtonGroupProps.onChange === 'function') {
82
- toggleButtonGroupProps.onChange(event, val)
83
- }
84
- }}
85
- >
86
- {options.map(({ label, id, ...toggleProps }) => (
87
- <ToggleButton value={id} {...toggleProps} key={id}>
88
- {label}
89
- </ToggleButton>
90
- ))}
91
- </ToggleButtonGroup>
92
- {renderHelperText && <FormHelperText>{renderHelperText}</FormHelperText>}
93
- </FormControl>
94
- )
95
- }}
96
- />
68
+ <FormControl error={invalid} required={isRequired}>
69
+ {label && (
70
+ <FormLabel
71
+ {...formLabelProps}
72
+ error={invalid}
73
+ required={isRequired}
74
+ sx={{ mb: 1, ...formLabelProps?.sx }}
75
+ >
76
+ {label}
77
+ </FormLabel>
78
+ )}
79
+ <ToggleButtonGroup
80
+ {...toggleButtonGroupProps}
81
+ value={value}
82
+ onBlur={onBlur}
83
+ onChange={(event, val) => {
84
+ onChange(val)
85
+ if (typeof toggleButtonGroupProps.onChange === 'function') {
86
+ toggleButtonGroupProps.onChange(event, val)
87
+ }
88
+ }}
89
+ >
90
+ {options.map(({ label, id, ...toggleProps }) => (
91
+ <ToggleButton value={id} {...toggleProps} key={id}>
92
+ {label}
93
+ </ToggleButton>
94
+ ))}
95
+ </ToggleButtonGroup>
96
+ {renderHelperText && <FormHelperText>{renderHelperText}</FormHelperText>}
97
+ </FormControl>
97
98
  )
98
99
  }
@@ -1,13 +1,16 @@
1
+ export * from './ActionCardListForm'
1
2
  export * from './AutoCompleteElement'
2
3
  export * from './CheckboxButtonGroup'
3
4
  export * from './CheckboxElement'
5
+ export * from './EmailElement'
4
6
  export * from './MultiSelectElement'
7
+ export * from './NumberFieldElement'
5
8
  export * from './PasswordElement'
6
9
  export * from './PasswordRepeatElement'
7
- export * from './NumberFieldElement'
8
- export * from './SliderElement'
9
- export * from './SwitchElement'
10
10
  export * from './RadioButtonGroup'
11
11
  export * from './SelectElement'
12
+ export * from './SliderElement'
13
+ export * from './SwitchElement'
14
+ export * from './TelephoneElement'
12
15
  export * from './TextFieldElement'
13
16
  export * from './ToggleButtonGroup'
@@ -0,0 +1,11 @@
1
+ import { styled, Tooltip, tooltipClasses } from '@mui/material'
2
+
3
+ export const LightTooltip = styled<typeof Tooltip>(({ className, ...props }) => (
4
+ <Tooltip {...props} classes={{ popper: className }} />
5
+ ))(({ theme }) => ({
6
+ [`& .${tooltipClasses.tooltip}`]: {
7
+ backgroundColor: theme.palette.common.white,
8
+ color: theme.palette.text.primary,
9
+ boxShadow: theme.shadows[1],
10
+ },
11
+ }))
@@ -0,0 +1,148 @@
1
+ import { PreviewData } from '@graphcommerce/graphql'
2
+ import {
3
+ Button,
4
+ IconSvg,
5
+ MessageSnackbar,
6
+ iconChevronRight,
7
+ iconClose,
8
+ iconContrast,
9
+ iconInfo,
10
+ iconRefresh,
11
+ } from '@graphcommerce/next-ui'
12
+ import { FormAutoSubmit, FormPersist, FormProvider, useForm } from '@graphcommerce/react-hook-form'
13
+ import { Box, IconButton } from '@mui/material'
14
+ import { useRouter } from 'next/router'
15
+ import { TextFieldElement } from '../FormComponents'
16
+ import { LightTooltip } from './LightTooltip'
17
+ import { PreviewModeActions } from './PreviewModeActions'
18
+ import { PreviewModeToolbar } from './PreviewModeToolbar'
19
+ import { previewModeDefaults } from './previewModeDefaults'
20
+
21
+ export function getPreviewUrl() {
22
+ const url = new URL(window.location.href)
23
+ url.pathname = '/api/preview'
24
+ ;[...url.searchParams.entries()].forEach(([key]) => url.searchParams.delete(key))
25
+ return url
26
+ }
27
+
28
+ function PreviewModeEnabled() {
29
+ const form = useForm<{ secret: string; previewData: PreviewData }>({
30
+ defaultValues: { previewData: previewModeDefaults() },
31
+ })
32
+
33
+ const submit = form.handleSubmit((formValues) => {
34
+ const url = getPreviewUrl()
35
+ url.searchParams.append('action', 'update')
36
+
37
+ Object.entries(formValues).forEach(([key, value]) => {
38
+ url.searchParams.append(key, JSON.stringify(value))
39
+ })
40
+
41
+ window.location.href = url.toString()
42
+ })
43
+
44
+ const exitHandler = form.handleSubmit(() => {
45
+ const url = getPreviewUrl()
46
+ url.searchParams.append('action', 'exit')
47
+
48
+ window.location.href = url.toString()
49
+ })
50
+
51
+ const revalidateHandler = form.handleSubmit((formValues) => {
52
+ const url = getPreviewUrl()
53
+ Object.entries(formValues).forEach(([key, value]) => {
54
+ url.searchParams.append(key, `${value}`)
55
+ })
56
+ url.searchParams.append('action', 'revalidate')
57
+ window.location.href = url.toString()
58
+ })
59
+
60
+ return (
61
+ <FormProvider {...form}>
62
+ <MessageSnackbar
63
+ variant='pill'
64
+ severity='warning'
65
+ disableBackdropClick
66
+ disableClose
67
+ open
68
+ icon={iconContrast}
69
+ onClose={() => {}}
70
+ action={
71
+ <>
72
+ <PreviewModeActions />
73
+ <LightTooltip title='Revalidate / Regenerate Page' placement='top'>
74
+ <IconButton color='secondary' type='submit' onClick={revalidateHandler}>
75
+ <IconSvg src={iconRefresh} />
76
+ </IconButton>
77
+ </LightTooltip>
78
+ <LightTooltip title='Stop preview mode' placement='top'>
79
+ <IconButton color='secondary' type='submit' onClick={exitHandler}>
80
+ <IconSvg src={iconClose} />
81
+ </IconButton>
82
+ </LightTooltip>
83
+ </>
84
+ }
85
+ >
86
+ <Box sx={{ display: 'grid', gridAutoFlow: 'column', placeItems: 'center', gap: 4 }}>
87
+ <Box sx={{ display: 'flex', placeItems: 'center' }}>
88
+ Preview Mode{' '}
89
+ <LightTooltip title='You are currently viewing the website in Preview Mode (caches are disabled).'>
90
+ <IconButton size='small'>
91
+ <IconSvg src={iconInfo} />
92
+ </IconButton>
93
+ </LightTooltip>
94
+ </Box>
95
+ <PreviewModeToolbar />
96
+ </Box>
97
+ </MessageSnackbar>
98
+ <FormPersist form={form} name='PreviewModePreviewData' />
99
+ <FormAutoSubmit control={form.control} submit={submit} />
100
+ </FormProvider>
101
+ )
102
+ }
103
+
104
+ function PreviewModeDisabled() {
105
+ const form = useForm<{ secret: string }>({})
106
+
107
+ const submit = form.handleSubmit((formValues) => {
108
+ const url = getPreviewUrl()
109
+ url.searchParams.append('action', 'enable')
110
+
111
+ Object.entries(formValues).forEach(([key, value]) => {
112
+ url.searchParams.append(key, typeof value === 'string' ? value : JSON.stringify(value))
113
+ })
114
+
115
+ window.location.href = url.toString()
116
+ })
117
+
118
+ return (
119
+ <FormProvider {...form}>
120
+ <MessageSnackbar
121
+ variant='pill'
122
+ severity='warning'
123
+ disableBackdropClick
124
+ disableClose
125
+ open
126
+ icon={iconContrast}
127
+ onClose={() => {}}
128
+ action={
129
+ <IconButton color='secondary' type='submit' onClick={submit}>
130
+ <IconSvg src={iconChevronRight} />
131
+ </IconButton>
132
+ }
133
+ >
134
+ <Box sx={{ display: 'grid', gridAutoFlow: 'column', placeItems: 'center', gap: 4 }}>
135
+ <Box sx={{ display: 'flex', placeItems: 'center' }}>Preview Mode</Box>
136
+ <TextFieldElement control={form.control} name='secret' label='Secret' />
137
+ </Box>
138
+ </MessageSnackbar>
139
+ <FormPersist form={form} name='PreviewModePreviewData' />
140
+ </FormProvider>
141
+ )
142
+ }
143
+
144
+ export function PreviewMode() {
145
+ const router = useRouter()
146
+
147
+ return router.isPreview ? <PreviewModeEnabled /> : <PreviewModeDisabled />
148
+ }
@@ -0,0 +1,6 @@
1
+ export type PreviewModeActionsProps = Record<string, unknown>
2
+
3
+ export function PreviewModeActions(props: PreviewModeActionsProps) {
4
+ const {} = props
5
+ return <></>
6
+ }
@@ -0,0 +1,6 @@
1
+ export type PreviewModeToolbarProps = Record<string, unknown>
2
+
3
+ export function PreviewModeToolbar(props: PreviewModeToolbarProps) {
4
+ const {} = props
5
+ return null
6
+ }
@@ -0,0 +1,5 @@
1
+ export * from './PreviewMode'
2
+ export * from './PreviewModeActions'
3
+ export * from './PreviewModeToolbar'
4
+ export * from './usePreviewModeForm'
5
+ export * from './previewModeDefaults'
@@ -0,0 +1,5 @@
1
+ import { PreviewData } from '@graphcommerce/graphql'
2
+
3
+ export function previewModeDefaults(): PreviewData {
4
+ return {}
5
+ }
@@ -0,0 +1,6 @@
1
+ import { PreviewData } from '@graphcommerce/graphql'
2
+ import { useFormContext } from '@graphcommerce/react-hook-form'
3
+
4
+ export function usePreviewModeForm() {
5
+ return useFormContext<{ previewData: PreviewData }>()
6
+ }
@@ -2,3 +2,4 @@ export * from './ComposedSubmitButton'
2
2
  export * from './ApolloError'
3
3
  export * from './WaitForQueries'
4
4
  export * from './FormComponents'
5
+ export * from './PreviewMode'
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@graphcommerce/ecommerce-ui",
3
3
  "homepage": "https://www.graphcommerce.org/",
4
4
  "repository": "github:graphcommerce-org/graphcommerce",
5
- "version": "8.1.0-canary.8",
5
+ "version": "9.0.0-canary.100",
6
6
  "sideEffects": false,
7
7
  "prettier": "@graphcommerce/prettier-config-pwa",
8
8
  "eslintConfig": {
@@ -12,12 +12,12 @@
12
12
  }
13
13
  },
14
14
  "peerDependencies": {
15
- "@graphcommerce/eslint-config-pwa": "^8.1.0-canary.8",
16
- "@graphcommerce/graphql": "^8.1.0-canary.8",
17
- "@graphcommerce/next-ui": "^8.1.0-canary.8",
18
- "@graphcommerce/prettier-config-pwa": "^8.1.0-canary.8",
19
- "@graphcommerce/react-hook-form": "^8.1.0-canary.8",
20
- "@graphcommerce/typescript-config-pwa": "^8.1.0-canary.8",
15
+ "@graphcommerce/eslint-config-pwa": "^9.0.0-canary.100",
16
+ "@graphcommerce/graphql": "^9.0.0-canary.100",
17
+ "@graphcommerce/next-ui": "^9.0.0-canary.100",
18
+ "@graphcommerce/prettier-config-pwa": "^9.0.0-canary.100",
19
+ "@graphcommerce/react-hook-form": "^9.0.0-canary.100",
20
+ "@graphcommerce/typescript-config-pwa": "^9.0.0-canary.100",
21
21
  "@lingui/core": "^4.2.1",
22
22
  "@lingui/macro": "^4.2.1",
23
23
  "@lingui/react": "^4.2.1",
@@ -0,0 +1,38 @@
1
+ import type { PagesProps } from '@graphcommerce/framer-next-pages'
2
+ import type { PluginConfig, PluginProps } from '@graphcommerce/next-config'
3
+ import dynamic from 'next/dynamic'
4
+ import { useEffect, useState } from 'react'
5
+
6
+ const PreviewMode = dynamic(
7
+ async () => (await import('../components/PreviewMode/PreviewMode')).PreviewMode,
8
+ {},
9
+ )
10
+
11
+ export const config: PluginConfig = {
12
+ type: 'component',
13
+ module: '@graphcommerce/framer-next-pages',
14
+ ifConfig: 'previewSecret',
15
+ }
16
+
17
+ export function FramerNextPages(props: PluginProps<PagesProps>) {
18
+ const { Prev, router, ...rest } = props
19
+ const [enabled, setEnabled] = useState(router.isPreview)
20
+
21
+ useEffect(() => {
22
+ const handler = (e: KeyboardEvent) => {
23
+ if (e.altKey && e.code === 'Backquote') {
24
+ setEnabled((prev) => !prev)
25
+ }
26
+ }
27
+
28
+ window.addEventListener('keydown', handler, false)
29
+ return () => window.removeEventListener('keydown', handler)
30
+ }, [])
31
+
32
+ return (
33
+ <>
34
+ <Prev {...rest} router={router} />
35
+ {enabled && <PreviewMode />}
36
+ </>
37
+ )
38
+ }
@@ -0,0 +1,60 @@
1
+ import { PreviewData } from '@graphcommerce/graphql'
2
+ import { NextApiRequest, NextApiResponse } from 'next'
3
+ import { previewModeDefaults } from '../components/PreviewMode/previewModeDefaults'
4
+
5
+ export default async function handler(req: NextApiRequest, res: NextApiResponse) {
6
+ const { action } = req.query
7
+
8
+ // const domain = req.url ? new URL(req.url) : undefined
9
+ const referer = req.headers.referer ? new URL(req.headers.referer) : undefined
10
+ const redirectTo =
11
+ req.headers.redirectTo ??
12
+ (referer && req.headers.host === referer.host ? referer.pathname : '/')
13
+
14
+ if (!action) {
15
+ res.status(400).json({ message: 'No action provided' })
16
+ res.end()
17
+ return
18
+ }
19
+
20
+ if (action === 'enable' && req.query.secret) {
21
+ if (req.query.secret !== import.meta.graphCommerce.previewSecret) {
22
+ // This secret should only be known to this API route and the CMS
23
+ res.status(401).json({ message: 'Invalid token' })
24
+ res.end()
25
+ return
26
+ }
27
+
28
+ res.setDraftMode({ enable: true })
29
+ const previewData = req.query.previewDat
30
+ ? (JSON.parse(`${req.query.previewData}`) as PreviewData)
31
+ : previewModeDefaults()
32
+
33
+ res.setPreviewData(previewData)
34
+ }
35
+
36
+ if (action === 'exit') {
37
+ res.setDraftMode({ enable: false })
38
+ res.clearPreviewData()
39
+ }
40
+
41
+ if (action === 'revalidate') {
42
+ const url = referer ? new URL(referer) : undefined
43
+
44
+ if (!url) {
45
+ res.status(401).json({ message: 'No referer header found' })
46
+ res.end()
47
+ return
48
+ }
49
+
50
+ await res.revalidate(url.pathname)
51
+ }
52
+
53
+ if (action === 'update' && req.preview && req.query.previewData) {
54
+ // todo we should probabaly validate this.
55
+ res.setPreviewData(JSON.parse(`${req.query.previewData}`) as PreviewData)
56
+ }
57
+
58
+ res.writeHead(307, { Location: redirectTo })
59
+ res.end()
60
+ }