From 0c8ce2ced728a27967216899da86633dd9e881df Mon Sep 17 00:00:00 2001 From: BFlat Date: Thu, 4 Jul 2024 03:12:14 +0700 Subject: [PATCH] feature/search objects (#575) - add functions in API manager to use the search API - adds search field to object tab, it searches through featured/mine objects depending on which tab you are on - this feature is hid behind a feature flag until we have a proper design and solution to handling assets --------- Co-authored-by: wil --- .env | 2 +- README.md | 21 ++- app/Classes/ApiManager/MediaFilesApi.ts | 71 ++++++++ app/components/reusable/Input/Input.tsx | 4 +- .../reusable/SearchFilter/SearchFilter.tsx | 88 ++++++++++ .../reusable/SearchFilter/index.tsx | 1 + app/components/reusable/index.ts | 1 + app/entry.client.tsx | 7 +- app/enums/FeatureFlags.ts | 3 + app/enums/index.ts | 1 + app/hooks/index.tsx | 1 - app/hooks/usePosthogFeatureFlag/index.ts | 1 + .../usePosthogFeatureFlag.tsx | 11 ++ app/pages/PageEnigma/PageEnigma.tsx | 4 +- .../SidePanelTabs/itemTabs/ExpressionTab.tsx | 24 +-- .../SidePanelTabs/itemTabs/ItemElements.tsx | 14 +- .../SidePanelTabs/itemTabs/ObjectsTab.tsx | 154 +++++++++++++++++- app/root.tsx | 12 -- app/styles/tailwind.css | 2 +- package-lock.json | 12 +- package.json | 4 +- 21 files changed, 371 insertions(+), 67 deletions(-) create mode 100644 app/components/reusable/SearchFilter/SearchFilter.tsx create mode 100644 app/components/reusable/SearchFilter/index.tsx create mode 100644 app/enums/FeatureFlags.ts delete mode 100644 app/hooks/index.tsx create mode 100644 app/hooks/usePosthogFeatureFlag/index.ts create mode 100644 app/hooks/usePosthogFeatureFlag/usePosthogFeatureFlag.tsx diff --git a/.env b/.env index a6b28343ce..b17f26e29b 100644 --- a/.env +++ b/.env @@ -6,4 +6,4 @@ GRAVATAR_API="https://www.gravatar.com" DEPLOY_PRIME_URL="https://studio-staging.studio.storyteller.ai" REACT_APP_PUBLIC_POSTHOG_KEY="phc_jBFgac0mVALAFk3negnSfYCcHgvkT00yBLQDmCDYNBb" REACT_APP_PUBLIC_POSTHOG_UI="https://us.i.posthog.com" -CONTEXT="PRODUCTION" +CONTEXT="DEVELOPMENT" diff --git a/README.md b/README.md index b38648c807..22992719d9 100644 --- a/README.md +++ b/README.md @@ -90,13 +90,20 @@ Add the feature flag for all users. To check the feature flag in the app use code similar to this ```ts -const [myFlag, setMyFlag] = useState(false); -useEffect(() => { - const flag = posthog.isFeatureEnabled(""); - if (flag) { - setMyFlag(true); - } -}, []); +import { usePosthogFeatureFlag } from "~/hooks/usePosthogFeatureflag"; +import { FeatureFlags } from "~/enums"; + +const Component = () => { + const flag = usePosthogFeatureFlag(FeaturesFlags.FLAG_NAME); + // if flag is true, the feature shoud be executed & showed; + // if flag is false, the feature should be disabled & hid; + // for example + return( + <> + {flag && } + + ); +}; ``` ## Other Documentation diff --git a/app/Classes/ApiManager/MediaFilesApi.ts b/app/Classes/ApiManager/MediaFilesApi.ts index f0aa1683b1..afd5e20ba1 100644 --- a/app/Classes/ApiManager/MediaFilesApi.ts +++ b/app/Classes/ApiManager/MediaFilesApi.ts @@ -34,6 +34,13 @@ interface ListUserMediaQuery { filter_engine_categories?: FilterEngineCategories[]; } +interface SearchFeaturedMediaQuery { + search_term: string; + filter_media_classes?: FilterMediaClasses[]; + filter_media_type?: FilterMediaType[]; + filter_engine_categories?: FilterEngineCategories[]; +} + export class MediaFilesApi extends ApiManager { public async DeleteMediaFileByToken({ mediaFileToken, @@ -215,6 +222,70 @@ export class MediaFilesApi extends ApiManager { }); } + public async SearchFeaturedMediaFiles( + query: SearchFeaturedMediaQuery, + ): Promise> { + const endpoint = `${this.ApiTargets.BaseApi}/v1/media_files/search_featured`; + const queryWithStrings = { + search_term: query.search_term, + filter_media_classes: query.filter_media_classes + ? query.filter_media_classes.join(",") + : undefined, + filter_media_type: query.filter_media_type + ? query.filter_media_type.join(",") + : undefined, + filter_engine_categories: query.filter_engine_categories + ? query.filter_engine_categories.join(",") + : undefined, + }; + return await this.get<{ + success: boolean; + results: MediaInfo[]; + pagination: Pagination; + }>({ endpoint, query: queryWithStrings }) + .then((response) => ({ + success: true, + data: response.results, + pagination: response.pagination, + })) + .catch((err) => ({ + success: false, + errorMessage: err.message, + })); + } + + public async SearchUserMediaFiles( + query: SearchFeaturedMediaQuery, + ): Promise> { + const endpoint = `${this.ApiTargets.BaseApi}/v1/media_files/search_session`; + const queryWithStrings = { + search_term: query.search_term, + filter_media_classes: query.filter_media_classes + ? query.filter_media_classes.join(",") + : undefined, + filter_media_type: query.filter_media_type + ? query.filter_media_type.join(",") + : undefined, + filter_engine_categories: query.filter_engine_categories + ? query.filter_engine_categories.join(",") + : undefined, + }; + return await this.get<{ + success: boolean; + results: MediaInfo[]; + pagination: Pagination; + }>({ endpoint, query: queryWithStrings }) + .then((response) => ({ + success: true, + data: response.results, + pagination: response.pagination, + })) + .catch((err) => ({ + success: false, + errorMessage: err.message, + })); + } + public async RenameMediaFileByToken({ mediaToken, name, diff --git a/app/components/reusable/Input/Input.tsx b/app/components/reusable/Input/Input.tsx index 852811b221..ffdec38278 100644 --- a/app/components/reusable/Input/Input.tsx +++ b/app/components/reusable/Input/Input.tsx @@ -12,6 +12,7 @@ import { interface InputProps extends React.InputHTMLAttributes { inputClassName?: string; + iconClassName?: string; label?: string; icon?: IconDefinition; isError?: boolean; @@ -24,6 +25,7 @@ export const Input = React.forwardRef( label, icon, inputClassName, + iconClassName, className, id, isError, @@ -42,7 +44,7 @@ export const Input = React.forwardRef( {icon && ( )} void; + placeholder?: string; + searchTerm?: string; + showFilters?: boolean; +} + +export const SearchFilter = ({ + onSearchChange, + placeholder = "Search...", + searchTerm = "", + showFilters = false, +}: SearchFilterProps) => { + const inputRef = useRef(null); + const [inputValue, setInputValue] = useState(searchTerm); + const [hasText, setHasText] = useState(searchTerm.length > 0); + + useEffect(() => { + setInputValue(searchTerm); + setHasText(searchTerm.length > 0); + }, [searchTerm]); + + // eslint-disable-next-line react-hooks/exhaustive-deps + const debouncedChangeHandler = useCallback( + debounce((value: string) => { + onSearchChange(value); + }, 500), + [], + ); + + const handleInputChange = (event: React.ChangeEvent) => { + const value = event.target.value; + setInputValue(value); + setHasText(value.length > 0); + debouncedChangeHandler(value); + }; + + const clearSearch = () => { + setInputValue(""); + setHasText(false); + onSearchChange(""); + }; + + return ( +
+
+ + {hasText && ( + + )} +
+ + {showFilters && ( + + + + )} +
+ ); +}; diff --git a/app/components/reusable/SearchFilter/index.tsx b/app/components/reusable/SearchFilter/index.tsx new file mode 100644 index 0000000000..73642fa5e6 --- /dev/null +++ b/app/components/reusable/SearchFilter/index.tsx @@ -0,0 +1 @@ +export * from "./SearchFilter"; diff --git a/app/components/reusable/index.ts b/app/components/reusable/index.ts index ca7752ff27..6d4c1b1f0b 100644 --- a/app/components/reusable/index.ts +++ b/app/components/reusable/index.ts @@ -17,6 +17,7 @@ export * from "./LoadingBar"; export * from "./NumberInput"; export * from "./Pagination"; export * from "./Pill"; +export * from "./SearchFilter"; export * from "./Select"; //Select's onChange type should be generic export * from "./Slider"; export * from "./Tabs"; diff --git a/app/entry.client.tsx b/app/entry.client.tsx index 5bc4b04ed0..a829e143b2 100644 --- a/app/entry.client.tsx +++ b/app/entry.client.tsx @@ -1,7 +1,8 @@ import { RemixBrowser } from "@remix-run/react"; import { startTransition, StrictMode, useEffect } from "react"; import { hydrateRoot } from "react-dom/client"; -import posthog from "posthog-js"; +import { posthog } from "posthog-js"; +import { PostHogProvider } from "posthog-js/react"; import EnvironmentVariables from "~/Classes/EnvironmentVariables"; function PosthogInit() { @@ -22,7 +23,9 @@ startTransition(() => { hydrateRoot( document, - + + + , ); diff --git a/app/enums/FeatureFlags.ts b/app/enums/FeatureFlags.ts new file mode 100644 index 0000000000..3102f6aa22 --- /dev/null +++ b/app/enums/FeatureFlags.ts @@ -0,0 +1,3 @@ +export enum FeatureFlags { + SHOW_SEARCH_OBJECTS = "show-search-objects", +} diff --git a/app/enums/index.ts b/app/enums/index.ts index 0c1b3a5a74..1dceb0a238 100644 --- a/app/enums/index.ts +++ b/app/enums/index.ts @@ -5,6 +5,7 @@ export * from "./authentication"; export * from "./Billing"; export * from "./ClipGroup"; export * from "./ClipType"; +export * from "./FeatureFlags"; export * from "./FileTypes"; export * from "./JobStatus"; export * from "./MediaFileAnimationTypes"; diff --git a/app/hooks/index.tsx b/app/hooks/index.tsx deleted file mode 100644 index b5cc5590a2..0000000000 --- a/app/hooks/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export * from "./useActiveJobs"; diff --git a/app/hooks/usePosthogFeatureFlag/index.ts b/app/hooks/usePosthogFeatureFlag/index.ts new file mode 100644 index 0000000000..4956801f03 --- /dev/null +++ b/app/hooks/usePosthogFeatureFlag/index.ts @@ -0,0 +1 @@ +export * from "./usePosthogFeatureFlag"; diff --git a/app/hooks/usePosthogFeatureFlag/usePosthogFeatureFlag.tsx b/app/hooks/usePosthogFeatureFlag/usePosthogFeatureFlag.tsx new file mode 100644 index 0000000000..198181e9ab --- /dev/null +++ b/app/hooks/usePosthogFeatureFlag/usePosthogFeatureFlag.tsx @@ -0,0 +1,11 @@ +import { useFeatureFlagEnabled } from "posthog-js/react"; +import { FeatureFlags } from "~/enums/FeatureFlags"; +import environmentVariables from "~/Classes/EnvironmentVariables"; + +export const usePosthogFeatureFlag = (flag: FeatureFlags): boolean => { + const enabled = useFeatureFlagEnabled(flag) ?? false; + if (environmentVariables.values.CONTEXT !== "PRODUCTION") { + return true; + } + return enabled; +}; diff --git a/app/pages/PageEnigma/PageEnigma.tsx b/app/pages/PageEnigma/PageEnigma.tsx index 7472caab52..f13b725a25 100644 --- a/app/pages/PageEnigma/PageEnigma.tsx +++ b/app/pages/PageEnigma/PageEnigma.tsx @@ -2,11 +2,11 @@ import { PageEditor } from "~/pages/PageEnigma/PageEditor"; import { TrackProvider } from "~/pages/PageEnigma/contexts/TrackContext/TrackProvider"; import { DragComponent } from "~/pages/PageEnigma/comps/DragComponent/DragComponent"; import { EngineProvider } from "./contexts/EngineContext"; -import { useActiveJobs } from "~/hooks"; +import { useActiveJobs } from "~/hooks/useActiveJobs"; +import { useBackgroundLoadingMedia } from "~/hooks/useBackgroundLoadingMedia"; import { useQueueHandler } from "./hooks/useQueueHandler"; import { ErrorDialog, LoadingDots } from "~/components"; import { GenerateModals } from "~/pages/PageEnigma/comps/GenerateModals/GenerateModals"; -import { useBackgroundLoadingMedia } from "~/hooks/useBackgroundLoadingMedia"; import { EditorLoadingBar } from "./comps/EditorLoadingBar"; import { useSignals } from "@preact/signals-react/runtime"; diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ExpressionTab.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ExpressionTab.tsx index df27c7b1b1..09fbe861b0 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ExpressionTab.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ExpressionTab.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useState } from "react"; import { useSignals } from "@preact/signals-react/runtime"; -import { faCirclePlus, faMobileNotch } from "@fortawesome/pro-solid-svg-icons"; +import { faCirclePlus } from "@fortawesome/pro-solid-svg-icons"; import { MediaInfo, MediaItem } from "~/pages/PageEnigma/models"; import { @@ -14,7 +14,6 @@ import { import { ItemElements } from "~/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ItemElements"; import { Button, - ButtonDialogue, FilterButtons, Pagination, UploadModalMovement, @@ -131,27 +130,6 @@ export const ExpressionTab = () => { > Upload Expression - Video Tutorial: Creating your own expressions} - > - -
); } - if (items.length === 0) { + if (items.length === 0 && !busy) { return ( -
-

You do not have anything here.

-

Plase upload some assets.

+
+ +

You do not have anything here.

+

Please upload some assets.

); } diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ObjectsTab.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ObjectsTab.tsx index 0307a64399..33132c70d9 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ObjectsTab.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ObjectsTab.tsx @@ -1,4 +1,5 @@ import { useCallback, useEffect, useState } from "react"; +import { usePosthogFeatureFlag } from "~/hooks/usePosthogFeatureFlag"; import { useSignals } from "@preact/signals-react/runtime"; import { faCirclePlus } from "@fortawesome/pro-solid-svg-icons"; @@ -6,6 +7,7 @@ import { faCirclePlus } from "@fortawesome/pro-solid-svg-icons"; import { AssetFilterOption, AssetType, + FeatureFlags, FilterEngineCategories, ToastTypes, } from "~/enums"; @@ -16,7 +18,13 @@ import { shapeItems } from "~/pages/PageEnigma/signals"; import { BucketConfig } from "~/api/BucketConfig"; import { ItemElements } from "~/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ItemElements"; -import { Button, FileWrapper, FilterButtons, Pagination } from "~/components"; +import { + Button, + FileWrapper, + FilterButtons, + SearchFilter, + Pagination, +} from "~/components"; import { TabTitle } from "~/pages/PageEnigma/comps/SidePanelTabs/comps/TabTitle"; import { MediaFilesApi } from "~/Classes/ApiManager"; import { addToast } from "~/signals"; @@ -24,6 +32,17 @@ import { addToast } from "~/signals"; export const ObjectsTab = () => { useSignals(); + const showSearchObjectComponent = usePosthogFeatureFlag( + FeatureFlags.SHOW_SEARCH_OBJECTS, + ); + + const [searchTermFeatured, setSearchTermFeatured] = useState(""); + const [searchTermMine, setSearchTermMine] = useState(""); + const [filteredSearchObjectsFeatured, setFilteredSearchObjectsFeatured] = + useState([]); + const [filteredSearchObjectsMine, setFilteredSearchObjectsMine] = useState< + MediaItem[] + >([]); const [userObjects, setUserObjects] = useState( undefined, ); @@ -46,12 +65,15 @@ export const ObjectsTab = () => { const [fetchStatuses, setFetchStatuses] = useState({ userObjectsFetch: FetchStatus.READY, featuredObjectsFetch: FetchStatus.READY, + searchFetch: FetchStatus.READY, }); const isFetching = fetchStatuses.userObjectsFetch === FetchStatus.READY || fetchStatuses.userObjectsFetch === FetchStatus.IN_PROGRESS || fetchStatuses.featuredObjectsFetch === FetchStatus.READY || - fetchStatuses.featuredObjectsFetch === FetchStatus.IN_PROGRESS; + fetchStatuses.featuredObjectsFetch === FetchStatus.IN_PROGRESS || + fetchStatuses.searchFetch === FetchStatus.READY || + fetchStatuses.searchFetch === FetchStatus.IN_PROGRESS; const responseMapping = (data: MediaInfo[]) => { return data.map((item) => { @@ -78,6 +100,86 @@ export const ObjectsTab = () => { }); }; + const fetchFeaturedSearchResults = useCallback(async () => { + setFetchStatuses((curr) => ({ + ...curr, + searchFetch: FetchStatus.IN_PROGRESS, + })); + const searchTerm = searchTermFeatured; + if (!searchTerm.trim()) { + setFilteredSearchObjectsFeatured([]); + setFetchStatuses((curr) => ({ + ...curr, + searchFetch: FetchStatus.SUCCESS, + })); + return; + } + + const mediaFilesApi = new MediaFilesApi(); + const response = await mediaFilesApi.SearchFeaturedMediaFiles({ + search_term: searchTerm, + filter_engine_categories: [FilterEngineCategories.OBJECT], + }); + + if (response.success && response.data) { + const newSearchObjects = responseMapping(response.data); + setFilteredSearchObjectsFeatured(newSearchObjects); + setFetchStatuses((curr) => ({ + ...curr, + searchFetch: FetchStatus.SUCCESS, + })); + } else { + addToast( + ToastTypes.ERROR, + response.errorMessage || "Failed to fetch search results", + ); + setFetchStatuses((curr) => ({ + ...curr, + searchFetch: FetchStatus.ERROR, + })); + } + }, [searchTermFeatured]); + + const fetchUserSearchResults = useCallback(async () => { + setFetchStatuses((curr) => ({ + ...curr, + searchFetch: FetchStatus.IN_PROGRESS, + })); + const searchTerm = searchTermMine; + if (!searchTerm.trim()) { + setFilteredSearchObjectsMine([]); + setFetchStatuses((curr) => ({ + ...curr, + searchFetch: FetchStatus.SUCCESS, + })); + return; + } + + const mediaFilesApi = new MediaFilesApi(); + const response = await mediaFilesApi.SearchUserMediaFiles({ + search_term: searchTerm, + filter_engine_categories: [FilterEngineCategories.OBJECT], + }); + + if (response.success && response.data) { + const newSearchObjects = responseMapping(response.data); + setFilteredSearchObjectsMine(newSearchObjects); + setFetchStatuses((curr) => ({ + ...curr, + searchFetch: FetchStatus.SUCCESS, + })); + } else { + addToast( + ToastTypes.ERROR, + response.errorMessage || "Failed to fetch search results", + ); + setFetchStatuses((curr) => ({ + ...curr, + searchFetch: FetchStatus.ERROR, + })); + } + }, [searchTermMine]); + const fetchUserObjects = useCallback(async () => { setFetchStatuses((curr) => ({ ...curr, @@ -86,7 +188,10 @@ export const ObjectsTab = () => { const mediaFilesApi = new MediaFilesApi(); const response = await mediaFilesApi.ListUserMediaFiles({ page_size: 100, - filter_engine_categories: [FilterEngineCategories.OBJECT, FilterEngineCategories.IMAGE_PLANE], + filter_engine_categories: [ + FilterEngineCategories.OBJECT, + FilterEngineCategories.IMAGE_PLANE, + ], }); if (response.success && response.data) { @@ -145,6 +250,20 @@ export const ObjectsTab = () => { } }, [userObjects, fetchUserObjects, featuredObjects, fetchFeaturedObjects]); + useEffect(() => { + if (selectedFilter === AssetFilterOption.FEATURED) { + fetchFeaturedSearchResults(); + } else if (selectedFilter === AssetFilterOption.MINE) { + fetchUserSearchResults(); + } + }, [ + searchTermFeatured, + searchTermMine, + fetchFeaturedSearchResults, + fetchUserSearchResults, + selectedFilter, + ]); + return ( { setSelectedFilter(Number(button)); }} /> -
+
+ {showSearchObjectComponent && ( + + )}
{ debug="objects tab" currentPage={currentPage} pageSize={pageSize} - items={filteredObjects} + items={ + selectedFilter === AssetFilterOption.FEATURED + ? searchTermFeatured + ? filteredSearchObjectsFeatured + : filteredObjects + : searchTermMine + ? filteredSearchObjectsMine + : filteredObjects + } />
{totalPages > 1 && ( @@ -188,7 +330,7 @@ export const ObjectsTab = () => { /> )} - )} // End FileWrapper Render + )} /> ); }; diff --git a/app/root.tsx b/app/root.tsx index 9499b7c4bc..119fbea362 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -55,18 +55,6 @@ export const links: LinksFunction = () => [ ]; export async function loader() { - // if (process.env.NODE_ENV === "development") { - // const env = { - // BASE_API: "", - // GOOGLE_API: "", - // // @ts-expect-error ProvessEnv is correct - // FUNNEL_API: process.env.FUNNEL_API || "%BUILD_FUNNEL_API%", - // // @ts-expect-error ProvessEnv is correct - // CDN_API: process.env.CDN_API || "%BUILD_CDN_API%", - // GRAVATAR_API: "", - // } as Record; - // return { ENV: env }; - // } const env = { // @ts-expect-error ProvessEnv is correct BASE_API: process.env.BASE_API || "%BUILD_BASE_API%", diff --git a/app/styles/tailwind.css b/app/styles/tailwind.css index 4473d25b6c..27ca1eddae 100644 --- a/app/styles/tailwind.css +++ b/app/styles/tailwind.css @@ -4,7 +4,7 @@ @layer components { .filter-tab { - @apply text-nowrap rounded-lg border-2 border-assets-background bg-assets-background px-2.5 py-1.5 text-sm font-medium text-white/80 transition-all duration-150 hover:border-assets-background/0 hover:bg-assets-background/80; + @apply text-nowrap rounded-lg border-2 border-assets-background bg-assets-background px-2.5 py-1 text-[13px] font-medium text-white/80 transition-all duration-150 hover:border-assets-background/0 hover:bg-assets-background/80; } .filter-tab.active { diff --git a/package-lock.json b/package-lock.json index cdb51e40ab..ecb9b1cff4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "@remix-run/router": "^1.15.3", "@remix-run/serve": "^2.8.1", "@tweenjs/tween.js": "^18.6.4", + "@types/lodash": "^4.17.6", "ccapture.js": "github:aceslowman/ccapture.js#npm-fix", "common": "file:common", "csstype": "^3.1.3", @@ -33,6 +34,7 @@ "detect-gpu": "^5.0.38", "http-proxy-middleware": "^3.0.0", "isbot": "^4.1.0", + "lodash.debounce": "^4.0.8", "posthog-js": "^1.141.0", "react": "^18.2.0", "react-copy-to-clipboard": "^5.1.0", @@ -7450,10 +7452,9 @@ "dev": true }, "node_modules/@types/lodash": { - "version": "4.17.5", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.5.tgz", - "integrity": "sha512-MBIOHVZqVqgfro1euRDWX7OO0fBVUUMrN6Pwm8LQsz8cWhEpihlvR70ENj3f40j58TNxZaWv2ndSkInykNBBJw==", - "dev": true + "version": "4.17.6", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.6.tgz", + "integrity": "sha512-OpXEVoCKSS3lQqjx9GGGOapBeuW5eUboYHRlHP9urXPX25IKZ6AnP5ZRxtVf63iieUbsHxLn8NQ5Nlftc6yzAA==" }, "node_modules/@types/mdast": { "version": "3.0.15", @@ -15874,8 +15875,7 @@ "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, "node_modules/lodash.memoize": { "version": "4.1.2", diff --git a/package.json b/package.json index 121ec2100a..3d7365cece 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "storybook": "storybook dev -p 6006", "build-storybook": "storybook build", "chromatic": "npx chromatic --project-token=chpt_0945f90d5d3626c", - "prepare": "husky install", + "prepare": "husky", "test": "jest" }, "dependencies": { @@ -37,6 +37,7 @@ "@remix-run/router": "^1.15.3", "@remix-run/serve": "^2.8.1", "@tweenjs/tween.js": "^18.6.4", + "@types/lodash": "^4.17.6", "ccapture.js": "github:aceslowman/ccapture.js#npm-fix", "common": "file:common", "csstype": "^3.1.3", @@ -45,6 +46,7 @@ "detect-gpu": "^5.0.38", "http-proxy-middleware": "^3.0.0", "isbot": "^4.1.0", + "lodash.debounce": "^4.0.8", "posthog-js": "^1.141.0", "react": "^18.2.0", "react-copy-to-clipboard": "^5.1.0",