Skip to content

Commit 731744f

Browse files
committed
Searchable dropdowns for custom events
1 parent 2da2c2e commit 731744f

16 files changed

Lines changed: 167 additions & 52 deletions

File tree

web/app/components/FeedbackModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ const FeedbackModal = ({ isOpened, onClose }: FeedbackModalProps) => {
234234
maxLength={MAX_MESSAGE_LENGTH}
235235
placeholder={t('feedback.placeholder')}
236236
aria-label={t('feedback.inputLabel')}
237-
error={error || false}
237+
error={error}
238238
disabled={isSubmitting}
239239
classes={{ textarea: 'min-h-32 resize-none' }}
240240
onChange={(event) => {

web/app/pages/Auth/CreateNewPassword/CreateNewPassword.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ const CreateNewPassword = () => {
4343
<Input
4444
name='password'
4545
type='password'
46+
autoComplete='new-password'
4647
label={t('auth.recovery.newPassword')}
4748
hint={t('auth.common.hint', { amount: MIN_PASSWORD_CHARS })}
4849
error={actionData?.fieldErrors?.password}
@@ -51,6 +52,7 @@ const CreateNewPassword = () => {
5152
<Input
5253
name='repeat'
5354
type='password'
55+
autoComplete='new-password'
5456
label={t('auth.common.repeat')}
5557
error={actionData?.fieldErrors?.repeat}
5658
disabled={isSubmitting}

web/app/pages/Auth/ForgotPassword/ForgotPassword.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ const ForgotPassword = () => {
5858
<Input
5959
name='email'
6060
type='email'
61+
autoComplete='email'
6162
label={t('auth.common.email')}
6263
error={actionData?.fieldErrors?.email}
6364
placeholder='name@company.com'

web/app/pages/Auth/Signin/Signin.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,7 @@ const Signin = () => {
370370
<Input
371371
label={t('auth.linkAccount.enterPassword')}
372372
type='password'
373+
autoComplete='current-password'
373374
value={linkingPassword}
374375
onChange={(e) => setLinkingPassword(e.target.value)}
375376
disabled={isLinkingLoading}
@@ -384,6 +385,8 @@ const Signin = () => {
384385
<Input
385386
label={t('auth.linkAccount.enter2FA')}
386387
value={linking2FACode}
388+
autoComplete='one-time-code'
389+
inputMode='numeric'
387390
placeholder={t('auth.signin.6digitCode')}
388391
onChange={(e) => setLinking2FACode(e.target.value)}
389392
disabled={isLinkingLoading}
@@ -442,6 +445,8 @@ const Signin = () => {
442445
<Input
443446
label={t('profileSettings.enter2faToDisable')}
444447
value={twoFACode}
448+
autoComplete='one-time-code'
449+
inputMode='numeric'
445450
placeholder={t('auth.signin.6digitCode')}
446451
onChange={handle2FAInput}
447452
disabled={is2FALoading}
@@ -548,6 +553,7 @@ const Signin = () => {
548553
<Input
549554
name='email'
550555
type='email'
556+
autoComplete='email'
551557
label={t('auth.common.email')}
552558
error={getFieldError('email')}
553559
placeholder='name@company.com'
@@ -557,6 +563,7 @@ const Signin = () => {
557563
<Input
558564
name='password'
559565
type='password'
566+
autoComplete='current-password'
560567
label={t('auth.common.password')}
561568
labelCorner={
562569
<Link

web/app/pages/Auth/Signup/InvitationSignup.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ const InvitationSignup = () => {
137137
<Input
138138
name='password'
139139
type='password'
140+
autoComplete='new-password'
140141
label={t('auth.common.password')}
141142
hint={t('auth.common.hint', { amount: MIN_PASSWORD_CHARS })}
142143
error={getFieldError('password')}

web/app/pages/Auth/Signup/Signup.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@ const Signup = () => {
304304
<Input
305305
name='email'
306306
type='email'
307+
autoComplete='email'
307308
label={t('auth.common.email')}
308309
error={getFieldError('email')}
309310
placeholder='name@company.com'
@@ -314,6 +315,7 @@ const Signup = () => {
314315
<Input
315316
name='password'
316317
type='password'
318+
autoComplete='new-password'
317319
label={t('auth.common.password')}
318320
hint={t('auth.common.hint', { amount: MIN_PASSWORD_CHARS })}
319321
error={getFieldError('password')}

web/app/pages/Project/Settings/Alerts/ProjectAlertsSettings.tsx

Lines changed: 113 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import {
22
CheckCircleIcon,
33
ClockIcon,
4+
CursorClickIcon,
5+
EyeIcon,
6+
PulseIcon,
7+
UsersIcon,
8+
WarningIcon,
49
WarningOctagonIcon,
510
XCircleIcon,
611
} from '@phosphor-icons/react'
@@ -12,22 +17,25 @@ import _size from 'lodash/size'
1217
import _split from 'lodash/split'
1318
import _toNumber from 'lodash/toNumber'
1419
import _values from 'lodash/values'
15-
import React, { useEffect, useMemo, useState } from 'react'
20+
import React, { useEffect, useMemo, useRef, useState } from 'react'
1621
import { useTranslation, Trans } from 'react-i18next'
1722
import { Link } from '~/ui/Link'
1823
import { useFetcher } from 'react-router'
1924
import { toast } from 'sonner'
2025

26+
import { getDimensionValues } from '~/api/v2/endpoints'
2127
import { useDeduplicateFetcherResponse } from '~/hooks/useDeduplicateFetcherResponse'
2228
import { QUERY_CONDITION, QUERY_METRIC, QUERY_TIME } from '~/lib/constants'
2329
import { Alerts } from '~/lib/models/Alerts'
2430
import type { NotificationChannel } from '~/lib/models/NotificationChannel'
2531
import { useAuth } from '~/providers/AuthProvider'
32+
import { useTheme } from '~/providers/ThemeProvider'
2633
import type { NotificationChannelActionData } from '~/routes/notification-channel'
2734
import type { ProjectViewActionData } from '~/routes/projects.$id'
2835
import Button from '~/ui/Button'
2936
import Checkbox from '~/ui/Checkbox'
3037
import FeedbackButton from '~/ui/FeedbackButton'
38+
import FilterValueInput from '~/ui/FilterValueInput'
3139
import Input from '~/ui/Input'
3240
import Loader from '~/ui/Loader'
3341
import Alert from '~/ui/Alert'
@@ -45,6 +53,14 @@ import {
4553
import AlertTemplateEditor from './AlertTemplateEditor'
4654
import { BackButton } from '../../View/components/BackButton'
4755

56+
const QUERY_METRIC_ICONS: Record<string, React.ReactNode> = {
57+
[QUERY_METRIC.PAGE_VIEWS]: <EyeIcon className='size-4' />,
58+
[QUERY_METRIC.UNIQUE_PAGE_VIEWS]: <UsersIcon className='size-4' />,
59+
[QUERY_METRIC.ONLINE_USERS]: <PulseIcon className='size-4' />,
60+
[QUERY_METRIC.CUSTOM_EVENTS]: <CursorClickIcon className='size-4' />,
61+
[QUERY_METRIC.ERRORS]: <WarningIcon className='size-4' />,
62+
}
63+
4864
interface ProjectAlertsSettingsProps {
4965
alertId?: string | null
5066
projectId: string
@@ -65,8 +81,12 @@ const ProjectAlertsSettings = ({
6581
backLink,
6682
}: ProjectAlertsSettingsProps) => {
6783
const { isLoading: authLoading } = useAuth()
84+
const { theme } = useTheme()
6885

69-
const { t } = useTranslation('common')
86+
const {
87+
t,
88+
i18n: { language },
89+
} = useTranslation('common')
7090
const fetcher = useFetcher<ProjectViewActionData>()
7191
const channelsFetcher = useFetcher<NotificationChannelActionData>()
7292
const shouldHandleFetcherData =
@@ -97,6 +117,35 @@ const ProjectAlertsSettings = ({
97117
const [availableChannels, setAvailableChannels] = useState<
98118
NotificationChannel[]
99119
>([])
120+
const [customEvents, setCustomEvents] = useState<string[]>([])
121+
const [customEventsLoading, setCustomEventsLoading] = useState(false)
122+
const customEventsFetchedRef = useRef(false)
123+
124+
useEffect(() => {
125+
if (
126+
form.queryMetric !== QUERY_METRIC.CUSTOM_EVENTS ||
127+
customEventsFetchedRef.current
128+
) {
129+
return
130+
}
131+
132+
customEventsFetchedRef.current = true
133+
setCustomEventsLoading(true)
134+
getDimensionValues(projectId, 'event', { type: 'traffic' })
135+
.then(({ data }) => {
136+
setCustomEvents(
137+
(data || []).filter(
138+
(item): item is string => typeof item === 'string',
139+
),
140+
)
141+
})
142+
.catch((reason) => {
143+
console.error('Failed to fetch custom events:', reason)
144+
})
145+
.finally(() => {
146+
setCustomEventsLoading(false)
147+
})
148+
}, [form.queryMetric, projectId])
100149

101150
useEffect(() => {
102151
const fd = new FormData()
@@ -546,40 +595,79 @@ const ProjectAlertsSettings = ({
546595
<Select
547596
id='queryMetric'
548597
label={t('alert.metric')}
549-
items={_values(queryMetricTMapping)}
598+
items={_values(QUERY_METRIC)}
599+
labelExtractor={(item) => queryMetricTMapping[item]}
600+
iconExtractor={(item) => (
601+
<span className='text-gray-500 dark:text-gray-400'>
602+
{QUERY_METRIC_ICONS[item]}
603+
</span>
604+
)}
550605
title={
551-
form.queryMetric ? queryMetricTMapping[form.queryMetric] : ''
606+
form.queryMetric ? (
607+
<span className='flex items-center gap-2'>
608+
<span className='text-gray-500 dark:text-gray-400'>
609+
{QUERY_METRIC_ICONS[form.queryMetric]}
610+
</span>
611+
{queryMetricTMapping[form.queryMetric]}
612+
</span>
613+
) : (
614+
''
615+
)
552616
}
553617
onSelect={(item) => {
554-
const key = _findKey(
555-
queryMetricTMapping,
556-
(predicate) => predicate === item,
557-
)
558-
559-
// @ts-expect-error
560618
setForm((prevForm) => ({
561619
...prevForm,
562-
queryMetric: key,
620+
queryMetric: item,
563621
}))
564622
}}
565623
capitalise
566-
selectedItem={
567-
form.queryMetric
568-
? queryMetricTMapping[form.queryMetric]
569-
: undefined
570-
}
624+
selectedItem={form.queryMetric}
571625
/>
572626
</div>
573627
{form.queryMetric === QUERY_METRIC.CUSTOM_EVENTS ? (
574-
<Input
575-
name='queryCustomEvent'
576-
label={t('alert.customEvent')}
577-
value={form.queryCustomEvent || ''}
578-
placeholder={t('alert.customEvent')}
579-
className='mt-4'
580-
onChange={handleInput}
581-
error={beenSubmitted ? errors.queryCustomEvent : null}
582-
/>
628+
<div className='mt-4 flex flex-col gap-1'>
629+
<Text
630+
as='span'
631+
className='flex leading-tight'
632+
size='sm'
633+
weight='medium'
634+
colour='primary'
635+
>
636+
{t('alert.customEvent')}
637+
</Text>
638+
<input
639+
type='hidden'
640+
name='queryCustomEvent'
641+
value={form.queryCustomEvent || ''}
642+
/>
643+
<FilterValueInput
644+
items={customEvents}
645+
value={form.queryCustomEvent || ''}
646+
onChange={(value) =>
647+
setForm((prevForm) => ({
648+
...prevForm,
649+
queryCustomEvent: value,
650+
}))
651+
}
652+
placeholder={t('project.filterSearchOrType')}
653+
column='event'
654+
language={language}
655+
isLoading={customEventsLoading}
656+
theme={theme}
657+
commitOnType
658+
/>
659+
{beenSubmitted && errors.queryCustomEvent ? (
660+
<Text
661+
as='span'
662+
className='block'
663+
size='sm'
664+
colour='error'
665+
role='alert'
666+
>
667+
{errors.queryCustomEvent}
668+
</Text>
669+
) : null}
670+
</div>
583671
) : null}
584672
{form.queryMetric === QUERY_METRIC.CUSTOM_EVENTS ? (
585673
<Checkbox

web/app/pages/Project/Settings/ProjectSettings.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1432,6 +1432,7 @@ const ProjectSettings = () => {
14321432
}
14331433
name='captchaSecretKey'
14341434
type='password'
1435+
autoComplete='off'
14351436
className='mt-4 lg:w-1/2'
14361437
value={captchaSecretKey}
14371438
readOnly
@@ -1608,6 +1609,7 @@ const ProjectSettings = () => {
16081609
<Input
16091610
name='password'
16101611
type='password'
1612+
autoComplete='new-password'
16111613
label={t('project.settings.password')}
16121614
value={form?.password || ''}
16131615
className='mt-4 px-4 sm:px-0'

web/app/pages/Project/Settings/tabs/Revenue.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ const Revenue = ({ projectId }: Props) => {
284284
<Input
285285
name={`${provider}ApiKey`}
286286
type='password'
287+
autoComplete='off'
287288
label={t('project.settings.revenue.providerApiKey', {
288289
provider: config.label,
289290
})}

web/app/pages/Project/View/components/PasswordRequiredModal.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ const PasswordRequiredModal = ({
100100
<Input
101101
name='password'
102102
type='password'
103+
autoComplete='current-password'
103104
label={t('auth.common.password')}
104105
value={password}
105106
placeholder={t('auth.common.password')}

0 commit comments

Comments
 (0)