Skip to content

Commit deced05

Browse files
Merge pull request #7 from reactedge/refactor/widget-state-flow
refactor(widget): simplify state and hooks to support scalable interpretation flow
2 parents 2d7e42d + 047245e commit deced05

57 files changed

Lines changed: 1174 additions & 625 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

vite_project/src/IntentDiscoveryWidgetWrapper.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const IntentDiscoveryWidgetWrapper = ({ host }: Props) => {
1717
if (error) return <ErrorState error={error} />
1818

1919
return <SystemStateProvider config={config.integrations} store={config.storeCode}>
20-
<IntentStateProvider>
20+
<IntentStateProvider config={config.data}>
2121
<TranslationStateProvider translations={config.translations}>
2222
<div className="intent-widget-container">
2323
{loading ? <SpinnerOverlay /> : <IntentLookup config={config} />}

vite_project/src/components/AttributeLayer/AttributeSelectorLayer.tsx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,43 +4,44 @@ import { useIntentAttributes } from "../../hooks/domain/useIntentAttributes.tsx"
44
import { AttributeTile } from "./AttributeTile.tsx";
55
import { useTranslationState } from "../../state/Translation/useTranslationState.ts";
66
import type { IntentDiscoveryDataConfig } from "../../domain/intent-discovery.types.ts";
7-
import type { MagentoAggregation } from "../../hooks/infra/useProductAttributeLayer.tsx";
87
import { useInteractionState } from "../../state/Interaction/useInteractionState.ts";
98
import {NoResult} from "../global/NoResult.tsx";
109
import {useIntentState} from "../../state/Intent/useIntentState.ts";
10+
import type {MagentoLayeredNavigation} from "../../hooks/domain/useLayeredNavigation.tsx";
11+
import type {MergedAttribute} from "../../hooks/infra/useMagentoLayeredData.tsx";
1112

1213
type Props = {
13-
isDisabled: boolean
14-
aggregations: MagentoAggregation[]
14+
attributeLayerData: MagentoLayeredNavigation
1515
config: IntentDiscoveryDataConfig
1616
}
1717

1818
export const AttributeSelectorLayer = ({
19-
isDisabled,
20-
aggregations,
19+
attributeLayerData,
2120
config
2221
}: Props) => {
2322
const { setActiveAttribute } = useInteractionState();
2423
const [showAll, setShowAll] = useState(false);
2524
const {intentState} = useIntentState()
2625
const { interactionState } = useInteractionState()
2726

28-
const allAttributes = useIntentAttributes(aggregations, config)
27+
const allAttributes = useIntentAttributes(attributeLayerData.attributes as MergedAttribute[], config)
2928
const visibleAttributes = showAll ? allAttributes : allAttributes.slice(0, 3);
3029
const { displayFor } =
31-
useSelectedPreferences(aggregations, intentState);
30+
useSelectedPreferences(attributeLayerData.attributes as MergedAttribute[], intentState);
3231
const { t } = useTranslationState()
3332

3433
const isAttributeSelected = (code: string) => {
3534
return interactionState?.navigation.activeAttribute === code;
3635
}
3736

38-
if (!aggregations?.length) return <NoResult />;
37+
const isDisabled = intentState.status === "suggestionProcessing"
38+
39+
if (!attributeLayerData?.totalCount) return <NoResult />;
3940

4041
return (
4142
<div className={`step-finder ${isDisabled ? 'step-finder--disabled' : ''}`}>
4243
{visibleAttributes.map((attr) => {
43-
const code = attr.attribute_code;
44+
const code = attr.code;
4445
const isSelected = isAttributeSelected(code);
4546

4647
return (

vite_project/src/components/AttributeLayer/AttributeTile.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import type {MagentoAggregation} from "../../hooks/infra/useProductAttributeLayer.tsx";
21
import {Icon} from "./Icon.tsx";
32
import {decodeHtmlEntities} from "../../lib/string.ts";
3+
import type {MergedAttribute} from "../../hooks/infra/useMagentoLayeredData.tsx";
44

55
type AttributeTileProps = {
6-
attr: MagentoAggregation;
6+
attr: MergedAttribute;
77
isSelected: boolean;
88
value?: string[];
99
onClick: () => void;
@@ -16,7 +16,7 @@ export const AttributeTile = ({ attr, isSelected, value, onClick }: AttributeTil
1616
return (
1717
<div
1818
className="choice-tile"
19-
data-intent-card={attr.attribute_code}
19+
data-intent-card={attr.code}
2020
data-intent-active={isSelected}
2121
data-intent-activated={value && value?.length > 0}
2222
onClick={onClick}
@@ -34,7 +34,7 @@ export const AttributeTile = ({ attr, isSelected, value, onClick }: AttributeTil
3434
)}
3535
</span>}
3636

37-
<Icon attribute_code={attr.attribute_code}/>
37+
<Icon attribute_code={attr.code}/>
3838
</div>
3939
)
4040
}
Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,50 @@
11
import {useTranslationState} from "../../state/Translation/useTranslationState.ts";
2+
import {IntentReadiness} from "../IntentDiscovery/IntentReadiness.tsx";
3+
import {useIntentState} from "../../state/Intent/useIntentState.ts";
4+
import type {IntentControllerState} from "../../domain/intent.types.ts";
5+
import type {MagentoLayeredNavigation} from "../../hooks/domain/useLayeredNavigation.tsx";
26

37
type IntentExplanationProps = {
8+
attributeLayerData: MagentoLayeredNavigation
9+
intent: IntentControllerState
410
remainingChars: number;
5-
searchPossible: boolean;
611
onAsk: () => void;
712
};
813

914
export const IntentExplanation = ({
10-
remainingChars,
11-
searchPossible,
12-
onAsk
15+
attributeLayerData,
16+
intent,
17+
remainingChars,
18+
onAsk
1319
}: IntentExplanationProps) => {
1420
const { t } = useTranslationState();
21+
const { intentState, getAiReadiness } = useIntentState()
22+
const coveragePct = getAiReadiness(attributeLayerData)
23+
const canInterpretOrSuggest =
24+
intentState.intentInterpretationReady || coveragePct === 100;
1525

16-
return (
17-
<div className="intent-explanations">
18-
<button
19-
onClick={onAsk}
20-
className="filter-apply-button"
21-
disabled={remainingChars > 0 && !searchPossible}
22-
>
23-
{t("Suggest Products")}
24-
</button>
26+
const intentStarted = !!intent?.text?.trim()
2527

26-
<div className={`intent-ai-threshold ${remainingChars === 0 ? "ready" : ""}`}>
27-
{remainingChars > 0
28-
? t("Add %s+ characters in the text intent or refine your preferences", remainingChars)
29-
: t("AI ready to interpret your request")}
28+
return (
29+
<>
30+
<div className="intent-explanations">
31+
<button
32+
onClick={onAsk}
33+
className="filter-apply-button"
34+
disabled={!canInterpretOrSuggest}
35+
>
36+
{t("Suggest Products")}
37+
</button>
38+
<label className="intent-subtitle">
39+
{t("Describe what you're looking for")}
40+
</label>
3041
</div>
31-
32-
<label className="intent-subtitle">
33-
{t("Describe what you're looking for")}
34-
</label>
35-
</div>
42+
<IntentReadiness
43+
attributeLayerData={attributeLayerData}
44+
intentStarted={intentStarted}
45+
remainingChars={remainingChars}
46+
canInterpretOrSuggest={canInterpretOrSuggest}
47+
/>
48+
</>
3649
);
3750
};

vite_project/src/components/FinderWidget/StepFinder.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,27 @@
11
import { unescapeHtml } from "../../lib/string.ts";
22
import { useFindAttributeOptionsByCode } from "../../hooks/domain/useFindAttributeOptionsByCode.tsx";
3-
import type {MagentoAggregationOption, MagentoProducts} from "../../hooks/infra/useProductAttributeLayer.tsx";
43
import {useInteractionState} from "../../state/Interaction/useInteractionState.ts";
54
import {activity} from "../../activity";
65
import {useIntentState} from "../../state/Intent/useIntentState.ts";
6+
import type {MagentoLayeredNavigation} from "../../hooks/domain/useLayeredNavigation.tsx";
7+
import type {MergedAttributeOption} from "../../hooks/infra/useMagentoLayeredData.tsx";
78

89
interface StepFinderProps {
910
optionCode: string
10-
attributeLayerData: MagentoProducts
11+
attributeLayerData: MagentoLayeredNavigation
1112
}
1213

1314
export const StepFinder: React.FC<StepFinderProps> = ({ optionCode, attributeLayerData }: StepFinderProps) => {
1415
const { setActiveAttribute, setFocusedOption } = useInteractionState()
1516
const { attributeData } = useFindAttributeOptionsByCode(optionCode, attributeLayerData)
1617
const { setPreference, intentState } = useIntentState()
18+
//const { dispatch } = useIntentState()
1719

18-
const handleOnClick = async (option: MagentoAggregationOption) => {
20+
const handleOnClick = async (option: MergedAttributeOption) => {
1921
setActiveAttribute(optionCode);
2022
setPreference(optionCode, option.value)
2123
setFocusedOption(option.value)
24+
//dispatch( { type : "FILTER_CHANGED", attributeLayerData})
2225

2326
activity('intent-discovery-option', 'Intent Option Selection', {intentState, optionCode, value: option.value});
2427
};
@@ -28,13 +31,13 @@ export const StepFinder: React.FC<StepFinderProps> = ({ optionCode, attributeLay
2831

2932
return (
3033
<div className="step-finder">
31-
{attributeData?.options.map((option: MagentoAggregationOption) => (
34+
{attributeData?.options.map((option: MergedAttributeOption) => (
3235
<label
3336
key={option.value}
3437
className="choice-tile"
3538
data-intent-option={option.label}
3639
data-intent-selected={isOptionSelected(option.value)}
37-
data-intent-count={option.count}
40+
data-intent-count={option.filteredCount}
3841
>
3942
<input
4043
type="radio"
@@ -46,7 +49,7 @@ export const StepFinder: React.FC<StepFinderProps> = ({ optionCode, attributeLay
4649
/>
4750

4851
<span className={`choice-tile__label ${isOptionSelected(option.value) ? 'choice-tile__label--active' : ''}`}>
49-
{unescapeHtml(option.label)} ({option.count})
52+
{unescapeHtml(option.label)} ({option.filteredCount})
5053
</span>
5154
</label>
5255
))}

vite_project/src/components/FinderWidget/StepPriceFinder.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import {useFindAttributeOptionsByCode} from "../../hooks/domain/useFindAttributeOptionsByCode.tsx";
22
import {formatRange} from "../../lib/price.ts";
3-
import type {MagentoAggregationOption, MagentoProducts} from "../../hooks/infra/useProductAttributeLayer.tsx";
3+
import type {MagentoLayeredNavigation} from "../../hooks/domain/useLayeredNavigation.tsx";
4+
import type {MergedAttributeOption} from "../../hooks/infra/useMagentoLayeredData.tsx";
45

56
interface StepFinderProps {
6-
attributeLayerData: MagentoProducts
7+
attributeLayerData: MagentoLayeredNavigation
78
}
89

910
export const StepPriceFinder = ({attributeLayerData}: StepFinderProps) => {
@@ -16,7 +17,7 @@ export const StepPriceFinder = ({attributeLayerData}: StepFinderProps) => {
1617

1718
return (
1819
<div className="step-finder">
19-
{attributeData?.options.map((option: MagentoAggregationOption) => (
20+
{attributeData?.options.map((option: MergedAttributeOption) => (
2021
<label
2122
key={option.value}
2223
className="choice-tile"
Lines changed: 50 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,89 @@
1-
import { useState} from "react";
1+
import {useEffect, useRef, useState} from "react";
22
import type {IntentDiscoveryDataConfig} from "../../domain/intent-discovery.types.ts";
3-
import type {MagentoAggregation} from "../../hooks/infra/useProductAttributeLayer.tsx";
43
import type {IntentControllerState} from "../../domain/intent.types.ts";
54
import {useAskAi} from "../../hooks/domain/useAiInterpretation.tsx";
65
import {AttributeSelectorLayer} from "../AttributeLayer/AttributeSelectorLayer.tsx";
76
import {IntentExplanation} from "../AttributeLayer/IntentExplanation.tsx";
87
import {SearchSpinnerOverlay} from "../global/SearchSpinnerOverlay.tsx";
98
import {useIntentState} from "../../state/Intent/useIntentState.ts";
9+
import {useAnalyseSearch} from "../../hooks/domain/useAnalyseSearch.tsx";
10+
import type {CategoryData} from "../../types/infra/magento/category.types.ts";
11+
import type {MagentoLayeredNavigation} from "../../hooks/domain/useLayeredNavigation.tsx";
1012

1113
type Props = {
1214
config: IntentDiscoveryDataConfig
1315
intent: IntentControllerState
14-
searchPossible: boolean
15-
aggregations: MagentoAggregation[],
16-
disabled: boolean
16+
attributeLayerData: MagentoLayeredNavigation,
17+
categoryData: CategoryData
1718
}
1819

1920
export const AttributeLayer = ({
20-
config,
21-
intent,
22-
searchPossible,
23-
aggregations,
24-
disabled
25-
}: Props) => {
21+
config,
22+
intent,
23+
attributeLayerData,
24+
categoryData
25+
}: Props) => {
2626
const [loading, setLoading] = useState(false);
27-
const { setIntentStatus } = useIntentState()
27+
const { dispatch, intentState } = useIntentState()
2828

2929
const askAi = useAskAi({
3030
intent,
31-
aggregations,
31+
attributeLayerData,
3232
config,
3333
setLoading
3434
})
3535

36-
const handleAsk = () => {
37-
if (intent.text?.trim()) {
38-
askAi()
39-
} else {
40-
setIntentStatus("readyToSearch")
36+
const askAiRecommendations = useAnalyseSearch({
37+
attributeLayerData,
38+
categoryData,
39+
intentState
40+
});
41+
42+
useEffect(() => {
43+
if (intentState.status === "readyToRecommend") {
44+
askAiRecommendations()
45+
}
46+
}, [intentState.status])
47+
48+
const prevRemaining = useRef<number | null>(null);
49+
50+
useEffect(() => {
51+
const prev = prevRemaining.current;
52+
const current = intent.remainingChars;
53+
54+
const crossedThreshold =
55+
prev !== null &&
56+
prev > 0 &&
57+
current <= 0;
58+
59+
if (crossedThreshold) {
60+
dispatch({ type: "INTERPRETATION_READY" });
4161
}
62+
63+
prevRemaining.current = current;
64+
}, [intent.remainingChars, intent.text]);
65+
66+
const handleAsk = () => {
67+
dispatch({ type: "INTERPRETATION_READY" })
68+
askAi()
4269
}
4370

4471
if (loading) return <SearchSpinnerOverlay />
4572

4673
return (
4774
<div className="finder">
4875
<IntentExplanation
76+
attributeLayerData={attributeLayerData}
77+
intent={intent}
4978
remainingChars={intent.remainingChars}
50-
searchPossible={searchPossible}
5179
onAsk={handleAsk}
5280
/>
5381
<AttributeSelectorLayer
54-
isDisabled={disabled}
55-
aggregations={aggregations}
82+
attributeLayerData={attributeLayerData}
5683
config={config}
5784
/>
5885
</div>
5986
);
6087
};
88+
89+

0 commit comments

Comments
 (0)