@@ -12,6 +12,8 @@ import FilterAggregations from "@/app/(sok)/_types/FilterAggregations";
12
12
import { SearchLocation } from "@/app/(sok)/page" ;
13
13
import { FilterSource } from "@/app/_common/monitoring/amplitudeHelpers" ;
14
14
import ScreenReaderText from "./ScreenReaderText" ;
15
+ import { containsEmail , containsValidFnrOrDnr } from "@/app/_common/utils/utils" ;
16
+ import { ComboboxOption } from "@navikt/ds-react/esm/form/combobox/types" ;
15
17
16
18
interface SearchComboboxProps {
17
19
aggregations : FilterAggregations ;
@@ -20,13 +22,39 @@ interface SearchComboboxProps {
20
22
function SearchCombobox ( { aggregations, locations } : SearchComboboxProps ) {
21
23
const [ showComboboxList , setShowComboboxList ] = useState < boolean | undefined > ( undefined ) ;
22
24
const [ windowWidth , setWindowWidth ] = useState < number > ( 0 ) ;
25
+ const [ errorMessage , setErrorMessage ] = useState < string | null > ( null ) ;
26
+ const [ optionList , setOptionList ] = useState < ComboboxOption [ ] > ( [ ] ) ;
27
+ const [ filteredOptions , setFilteredOptions ] = useState < ComboboxOption [ ] > ( [ ] ) ;
23
28
const query = useQuery ( ) ;
24
29
25
30
const options = useMemo ( ( ) => getSearchBoxOptions ( aggregations , locations ) , [ aggregations , locations ] ) ;
26
31
27
32
const selectedOptions = useMemo ( ( ) => buildSelectedOptions ( query . urlSearchParams ) , [ query . urlSearchParams ] ) ;
28
33
34
+ const isValidFreeText = ( val : string ) : boolean => {
35
+ if ( containsValidFnrOrDnr ( val ) || containsEmail ( val ) ) {
36
+ setErrorMessage (
37
+ "Teksten du har skrevet inn kan inneholde personopplysninger. Dette er ikke tillatt av personvernhensyn. Hvis du mener dette er feil, kontakt oss på [email protected] " ,
38
+ ) ;
39
+ return false ;
40
+ } else if ( val . length > 100 ) {
41
+ setErrorMessage ( "Søkeord kan ikke ha mer enn 100 tegn" ) ;
42
+ return false ;
43
+ }
44
+ return true ;
45
+ } ;
46
+
29
47
useEffect ( ( ) => {
48
+ setOptionList ( [
49
+ ...options . map ( ( o ) => {
50
+ const filterLabel = findLabelForFilter ( o . value . split ( "-" ) [ 0 ] ) ;
51
+ return filterLabel
52
+ ? { label : `${ o . label } ${ filterLabel } ` , value : o . value }
53
+ : { label : o . label , value : o . value } ;
54
+ } ) ,
55
+ ...selectedOptions ,
56
+ ] ) ;
57
+
30
58
function handleResize ( ) {
31
59
setWindowWidth ( window . innerWidth ) ;
32
60
}
@@ -46,20 +74,15 @@ function SearchCombobox({ aggregations, locations }: SearchComboboxProps) {
46
74
}
47
75
} , [ selectedOptions ] ) ;
48
76
49
- const optionList = options . map ( ( o ) => {
50
- const filterLabel = findLabelForFilter ( o . value . split ( "-" ) [ 0 ] ) ;
51
- return filterLabel
52
- ? { label : `${ o . label } ${ filterLabel } ` , value : o . value }
53
- : { label : o . label , value : o . value } ;
54
- } ) ;
55
-
56
77
const handleFreeTextSearchOption = ( value : string , isSelected : boolean ) => {
57
78
if ( isSelected ) {
58
79
query . append ( QueryNames . SEARCH_STRING , value ) ;
59
80
logAmplitudeEvent ( "Text searched" , { searchTerm : "Add" } ) ;
81
+ setOptionList ( [ ...optionList , { label : value , value : value } ] ) ;
60
82
} else {
61
83
query . remove ( QueryNames . SEARCH_STRING , value ) ;
62
84
logAmplitudeEvent ( "Text searched" , { searchTerm : "Remove" } ) ;
85
+ setOptionList ( optionList . filter ( ( option ) => option . value !== value ) ) ;
63
86
}
64
87
} ;
65
88
@@ -144,8 +167,13 @@ function SearchCombobox({ aggregations, locations }: SearchComboboxProps) {
144
167
} ;
145
168
146
169
const onToggleSelected = ( option : string , isSelected : boolean , isCustomOption : boolean ) => {
170
+ setErrorMessage ( null ) ;
147
171
if ( isCustomOption ) {
148
- handleFreeTextSearchOption ( option , isSelected ) ;
172
+ if ( isValidFreeText ( option ) ) {
173
+ handleFreeTextSearchOption ( option , isSelected ) ;
174
+ } else {
175
+ setShowComboboxList ( false ) ;
176
+ }
149
177
} else {
150
178
handleFilterOption ( option , isSelected ) ;
151
179
}
@@ -154,9 +182,13 @@ function SearchCombobox({ aggregations, locations }: SearchComboboxProps) {
154
182
return (
155
183
< >
156
184
< Combobox
185
+ filteredOptions = { filteredOptions }
157
186
onChange = { ( val ) => {
158
187
// Only show combobox list suggestion when user has started typing
159
- if ( val . length > 0 ) {
188
+ if ( val . length > 0 && val . length < 100 ) {
189
+ setFilteredOptions (
190
+ optionList . filter ( ( option ) => option . label . toLowerCase ( ) . includes ( val . toLowerCase ( ) ) ) ,
191
+ ) ;
160
192
setShowComboboxList ( undefined ) ;
161
193
} else if ( selectedOptions . length > 0 ) {
162
194
setShowComboboxList ( false ) ;
@@ -174,6 +206,7 @@ function SearchCombobox({ aggregations, locations }: SearchComboboxProps) {
174
206
// Hide selected options in combobox below sm breakpoint
175
207
shouldShowSelectedOptions = { ! ( windowWidth < 480 ) }
176
208
options = { optionList }
209
+ error = { errorMessage }
177
210
/>
178
211
< Show below = "sm" >
179
212
< ComboboxExternalItems
0 commit comments