2
0

Remember result in either local or session storage (#514)

Closes #513
This commit is contained in:
Baptiste Arnaud
2023-05-16 14:58:56 +02:00
committed by GitHub
parent 0ae2a4ba8b
commit 27b009dd76
11 changed files with 129 additions and 50 deletions

View File

@ -8,7 +8,7 @@ export const SwitchWithRelatedSettings = ({ children, ...props }: Props) => (
<Stack
borderWidth={props.initialValue ? 1 : undefined}
rounded="md"
p={props.initialValue ? '4' : undefined}
p={props.initialValue ? '3' : undefined}
spacing={4}
>
<SwitchWithLabel {...props} />

View File

@ -1,7 +1,17 @@
import { Flex, FormLabel, Stack, Switch, useDisclosure } from '@chakra-ui/react'
import {
Flex,
FormControl,
FormLabel,
HStack,
Stack,
Switch,
Tag,
useDisclosure,
Text,
} from '@chakra-ui/react'
import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
import { Plan } from '@typebot.io/prisma'
import { GeneralSettings } from '@typebot.io/schemas'
import { GeneralSettings, rememberUserStorages } from '@typebot.io/schemas'
import React from 'react'
import { isDefined } from '@typebot.io/lib'
import { SwitchWithLabel } from '@/components/inputs/SwitchWithLabel'
@ -9,6 +19,9 @@ import { ChangePlanModal } from '@/features/billing/components/ChangePlanModal'
import { LockTag } from '@/features/billing/components/LockTag'
import { isFreePlan } from '@/features/billing/helpers/isFreePlan'
import { useI18n } from '@/locales'
import { SwitchWithRelatedSettings } from '@/components/SwitchWithRelatedSettings'
import { DropdownList } from '@/components/DropdownList'
import { MoreInfoTooltip } from '@/components/MoreInfoTooltip'
type Props = {
generalSettings: GeneralSettings
@ -31,10 +44,13 @@ export const GeneralSettingsForm = ({
})
}
const handleNewResultOnRefreshChange = (isRememberSessionChecked: boolean) =>
const toggleRememberUser = (isEnabled: boolean) =>
onGeneralSettingsChange({
...generalSettings,
isNewResultOnRefreshEnabled: !isRememberSessionChecked,
rememberUser: {
...generalSettings.rememberUser,
isEnabled,
},
})
const handleInputPrefillChange = (isInputPrefillEnabled: boolean) =>
@ -49,6 +65,17 @@ export const GeneralSettingsForm = ({
isHideQueryParamsEnabled,
})
const updateRememberUserStorage = (
storage: NonNullable<GeneralSettings['rememberUser']>['storage']
) =>
onGeneralSettingsChange({
...generalSettings,
rememberUser: {
...generalSettings.rememberUser,
storage,
},
})
return (
<Stack spacing={6}>
<ChangePlanModal
@ -77,22 +104,46 @@ export const GeneralSettingsForm = ({
onCheckChange={handleInputPrefillChange}
moreInfoContent="Inputs are automatically pre-filled whenever their associated variable has a value"
/>
<SwitchWithLabel
label="Remember session"
initialValue={
isDefined(generalSettings.isNewResultOnRefreshEnabled)
? !generalSettings.isNewResultOnRefreshEnabled
: true
}
onCheckChange={handleNewResultOnRefreshChange}
moreInfoContent="If the user refreshes the page or opens the typebot again during the same session, his previous variables will be prefilled and his new answers will override the previous ones."
/>
<SwitchWithLabel
label="Hide query params on bot start"
initialValue={generalSettings.isHideQueryParamsEnabled ?? true}
onCheckChange={handleHideQueryParamsChange}
moreInfoContent="If your URL contains query params, they will be automatically hidden when the bot starts."
/>
<SwitchWithRelatedSettings
label={'Remember user'}
moreInfoContent="If enabled, user previous variables will be prefilled and his new answers will override the previous ones."
initialValue={
generalSettings.rememberUser?.isEnabled ??
(isDefined(generalSettings.isNewResultOnRefreshEnabled)
? !generalSettings.isNewResultOnRefreshEnabled
: false)
}
onCheckChange={toggleRememberUser}
>
<FormControl as={HStack} justifyContent="space-between">
<FormLabel mb="0">
Storage:&nbsp;
<MoreInfoTooltip>
<Stack>
<Text>
Choose <Tag size="sm">session</Tag> to remember the user as
long as he does not closes the tab or the browser.
</Text>
<Text>
Choose <Tag size="sm">local</Tag> to remember the user
forever.
</Text>
</Stack>
</MoreInfoTooltip>
</FormLabel>
<DropdownList
currentItem={generalSettings.rememberUser?.storage ?? 'session'}
onItemSelect={updateRememberUserStorage}
items={rememberUserStorages}
></DropdownList>
</FormControl>
</SwitchWithRelatedSettings>
</Stack>
)
}

View File

@ -8,8 +8,8 @@ The general settings represent the general behaviors of your typebot.
- **Typebot.io branding**: If enabled, will show a "Made with Typebot" badge displayed at the bottom of your bot.
- **Prefill input**: If enabled, the inputs will be automatically pre-filled whenever their associated variable has a value.
- **Remember session**: If enabled, when a user refreshes the page, its existing answers will be overwritten.
- **Hide query params on bot start**: If enabled, the query params will be hidden when the bot starts.
- **Remember user**: If enabled, user previous variables will be prefilled and his new answers will override the previous ones.
## Typing emulation

View File

@ -124,8 +124,11 @@ const startSession = async (startParams?: StartParams, userId?: string) => {
isPreview: startParams.isPreview || typeof startParams.typebot !== 'string',
typebotId: typebot.id,
prefilledVariables,
isNewResultOnRefreshEnabled:
typebot.settings.general.isNewResultOnRefreshEnabled ?? false,
isRememberUserEnabled:
typebot.settings.general.rememberUser?.isEnabled ??
(isDefined(typebot.settings.general.isNewResultOnRefreshEnabled)
? !typebot.settings.general.isNewResultOnRefreshEnabled
: false),
})
const startVariables =
@ -291,11 +294,11 @@ const getResult = async ({
isPreview,
resultId,
prefilledVariables,
isNewResultOnRefreshEnabled,
isRememberUserEnabled,
}: Pick<StartParams, 'isPreview' | 'resultId'> & {
typebotId: string
prefilledVariables: Variable[]
isNewResultOnRefreshEnabled: boolean
isRememberUserEnabled: boolean
}) => {
if (isPreview) return
const select = {
@ -305,7 +308,7 @@ const getResult = async ({
} satisfies Prisma.ResultSelect
const existingResult =
resultId && !isNewResultOnRefreshEnabled
resultId && isRememberUserEnabled
? ((await prisma.result.findFirst({
where: { id: resultId },
select,

View File

@ -21,7 +21,10 @@ test('Result should be overwritten on page refresh', async ({ page }) => {
...defaultSettings,
general: {
...defaultSettings.general,
isNewResultOnRefreshEnabled: false,
rememberUser: {
isEnabled: true,
storage: 'session',
},
},
},
...parseDefaultGroupWithBlock({

View File

@ -1,6 +1,6 @@
{
"name": "@typebot.io/js",
"version": "0.0.51",
"version": "0.0.52",
"description": "Javascript library to display typebots on your website",
"type": "module",
"main": "dist/index.js",

View File

@ -7,9 +7,9 @@ import { setIsMobile } from '@/utils/isMobileSignal'
import { BotContext, InitialChatReply, OutgoingLog } from '@/types'
import { ErrorMessage } from './ErrorMessage'
import {
getExistingResultIdFromSession,
setResultInSession,
} from '@/utils/sessionStorage'
getExistingResultIdFromStorage,
setResultInStorage,
} from '@/utils/storage'
import { setCssVariablesValue } from '@/utils/setCssVariablesValue'
import immutableCss from '../assets/immutable.css'
@ -52,7 +52,7 @@ export const Bot = (props: BotProps & { class?: string }) => {
isPreview: props.isPreview ?? false,
resultId: isNotEmpty(props.resultId)
? props.resultId
: getExistingResultIdFromSession(typebotIdFromProps),
: getExistingResultIdFromStorage(typebotIdFromProps),
startGroupId: props.startGroupId,
prefilledVariables: {
...prefilledVariables,
@ -76,7 +76,10 @@ export const Bot = (props: BotProps & { class?: string }) => {
if (!data) return setError(new Error("Error! Couldn't initiate the chat."))
if (data.resultId && typebotIdFromProps)
setResultInSession(typebotIdFromProps, data.resultId)
setResultInStorage(data.typebot.settings.general.rememberUser?.storage)(
typebotIdFromProps,
data.resultId
)
setInitialChatReply(data)
setCustomCss(data.typebot.theme.customCss ?? '')

View File

@ -1,20 +0,0 @@
const sessionStorageKey = 'resultId'
export const getExistingResultIdFromSession = (typebotId?: string) => {
if (!typebotId) return
try {
return (
sessionStorage.getItem(`${sessionStorageKey}-${typebotId}`) ?? undefined
)
} catch {
/* empty */
}
}
export const setResultInSession = (typebotId: string, resultId: string) => {
try {
return sessionStorage.setItem(`${sessionStorageKey}-${typebotId}`, resultId)
} catch {
/* empty */
}
}

View File

@ -0,0 +1,29 @@
const sessionStorageKey = 'resultId'
export const getExistingResultIdFromStorage = (typebotId?: string) => {
if (!typebotId) return
try {
return (
sessionStorage.getItem(`${sessionStorageKey}-${typebotId}`) ??
localStorage.getItem(`${sessionStorageKey}-${typebotId}`) ??
undefined
)
} catch {
/* empty */
}
}
export const setResultInStorage =
(storageType: 'local' | 'session' = 'session') =>
(typebotId: string, resultId: string) => {
try {
;(storageType === 'session' ? localStorage : sessionStorage).removeItem(
`${sessionStorageKey}-${typebotId}`
)
return (
storageType === 'session' ? sessionStorage : localStorage
).setItem(`${sessionStorageKey}-${typebotId}`, resultId)
} catch {
/* empty */
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@typebot.io/react",
"version": "0.0.51",
"version": "0.0.52",
"description": "React library to display typebots on your website",
"main": "dist/index.js",
"types": "dist/index.d.ts",

View File

@ -1,11 +1,19 @@
import { z } from 'zod'
export const rememberUserStorages = ['session', 'local'] as const
const generalSettings = z.object({
isBrandingEnabled: z.boolean(),
isTypingEmulationEnabled: z.boolean().optional(),
isInputPrefillEnabled: z.boolean().optional(),
isHideQueryParamsEnabled: z.boolean().optional(),
isNewResultOnRefreshEnabled: z.boolean().optional(),
rememberUser: z
.object({
isEnabled: z.boolean().optional(),
storage: z.enum(rememberUserStorages).optional(),
})
.optional(),
})
const typingEmulation = z.object({
@ -32,7 +40,9 @@ export const settingsSchema = z.object({
export const defaultSettings: Settings = {
general: {
isBrandingEnabled: true,
isNewResultOnRefreshEnabled: true,
rememberUser: {
isEnabled: false,
},
isInputPrefillEnabled: true,
isHideQueryParamsEnabled: true,
},