diff --git a/apps/web/next.config.js b/apps/web/next.config.js index aeb5a253..c58f5075 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -10,11 +10,16 @@ const nextConfig = { ] }, async redirects() { + if (process.env.NODE_ENV === "development") { + return []; + } + return [ { source: "/", destination: "/previewnet", - permanent: true + permanent: true, + }, // Redirect to the main release that doesn't support Cadence v1.0 { diff --git a/apps/web/src/common/NetworkDropdown.tsx b/apps/web/src/common/NetworkDropdown.tsx index 1bca0b63..b5749b4f 100644 --- a/apps/web/src/common/NetworkDropdown.tsx +++ b/apps/web/src/common/NetworkDropdown.tsx @@ -36,12 +36,6 @@ export function NetworkDropdown(props: NetworkDropdownProps) { {network} ))} - window.open("https://previewnet.flowser.dev/previewnet")} - className="capitalize" - > - Previewnet - ) } diff --git a/apps/web/src/common/interaction-page-params.ts b/apps/web/src/common/interaction-page-params.ts index a1f66608..2dbc4087 100644 --- a/apps/web/src/common/interaction-page-params.ts +++ b/apps/web/src/common/interaction-page-params.ts @@ -1,8 +1,12 @@ import { FlowNetworkId } from "@onflowser/core/src/flow-utils"; +import { + InteractionTemplateFilters +} from "@onflowser/ui/src/interactions/components/InteractionTemplates/interaction-templates-controller.provider"; // Must be placed in separate (type-only) file, // so that opengraph-image doesn't reach code size limit. export type InteractionsPageParams = { networkId: FlowNetworkId; interaction?: string; + templateFilters: InteractionTemplateFilters; } diff --git a/apps/web/src/common/root.tsx b/apps/web/src/common/root.tsx index 4c0809d5..cb4a22de 100644 --- a/apps/web/src/common/root.tsx +++ b/apps/web/src/common/root.tsx @@ -50,6 +50,9 @@ import { BaseDialog } from "@onflowser/ui/src/common/overlays/dialogs/base/BaseD import { NetworkDropdown } from "./NetworkDropdown"; import { ProfileDropdown } from "@/common/ProfileDropdown"; import { ConfirmDialogProvider } from "@onflowser/ui/src/contexts/confirm-dialog.context"; +import { + InteractionTemplateFiltersProvider +} from "@onflowser/ui/src/interactions/components/InteractionTemplates/interaction-templates-controller.provider"; const indexSyncIntervalInMs = 500; @@ -448,7 +451,7 @@ function ApiSetupPrompt(props: { } function Content() { - const { networkId, interaction, setNetworkId } = useInteractionsPageParams(); + const { networkId, interaction, setNetworkId, templateFilters } = useInteractionsPageParams(); const interactionRegistry = useInteractionRegistry(); const templatesRegistry = useTemplatesRegistry(); @@ -488,19 +491,21 @@ function Content() { return ( - - - - - } - tabOrder={["templates", "history"]} - enabledInteractionSourceTypes={[ - 'session', - 'flix', - ]} - /> + + + + + + } + tabOrder={["templates", "history"]} + enabledInteractionSourceTypes={[ + 'session', + 'flix', + ]} + /> + ); } diff --git a/apps/web/src/common/use-interaction-page-params.ts b/apps/web/src/common/use-interaction-page-params.ts index 40eb76b6..50de156a 100644 --- a/apps/web/src/common/use-interaction-page-params.ts +++ b/apps/web/src/common/use-interaction-page-params.ts @@ -1,6 +1,10 @@ -import { useParams, useRouter } from "next/navigation"; +import { useParams, useRouter, useSearchParams } from "next/navigation"; import { FlowNetworkId, FlowUtils } from "@onflowser/core/src/flow-utils"; import { InteractionsPageParams } from "@/common/interaction-page-params"; +import { + InteractionTemplateFilters +} from "@onflowser/ui/src/interactions/components/InteractionTemplates/interaction-templates-controller.provider"; +import { ensurePrefixedAddress } from "@onflowser/core"; type UseInteractionPageParams = InteractionsPageParams & { setNetworkId: (network: FlowNetworkId) => void; @@ -8,24 +12,44 @@ type UseInteractionPageParams = InteractionsPageParams & { export function useInteractionsPageParams(): UseInteractionPageParams { const params = useParams(); + const search = useSearchParams(); const router = useRouter(); const interaction = params.interaction as string | undefined; const networkId = params.networkId; + const templateFilters: InteractionTemplateFilters = {}; + + const rawFlixDependency = search.get("flix-dependency"); + if (rawFlixDependency) { + // Accepts format A.address.ContractName + const [prefix, address, name] = rawFlixDependency.split("."); + + templateFilters.dependencies = { + contractAddress: ensurePrefixedAddress(address), + contractName: name + } + } if (!FlowUtils.isValidFlowNetwork(networkId)) { throw new Error(`Unknown Flow network: ${networkId}`) } function setNetworkId(network: FlowNetworkId) { + let prefix = ""; + + if (network === "previewnet") { + prefix = "https://previewnet.flowser.dev" + } + if (interaction) { - router.replace(`/${network}/${interaction}`) + router.replace(`${prefix}/${network}/${interaction}`) } else { - router.replace(`/${network}`) + router.replace(`${prefix}/${network}`) } } return { + templateFilters, networkId, interaction, setNetworkId diff --git a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx index 16a2f970..17ba54c6 100644 --- a/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx +++ b/packages/ui/src/interactions/components/InteractionTemplates/InteractionTemplates.tsx @@ -19,6 +19,9 @@ import { Shimmer } from "../../../common/loaders/Shimmer/Shimmer"; import { MenuItem } from "@szhsin/react-menu"; import { EditTemplateNameDialog } from "../EditTemplateNameDialog/EditTemplateNameDialog"; import { Menu } from "../../../common/overlays/Menu/Menu"; +import { useOptionalInteractionTemplateFilters } from "./interaction-templates-controller.provider"; +import { FlixUtils } from "@onflowser/core"; +import { useFlowNetworkId } from "../../../contexts/flow-network.context"; type InteractionTemplatesProps = { enabledSourceTypes: InteractionSourceType[]; @@ -35,14 +38,38 @@ export function InteractionTemplates(props: InteractionTemplatesProps): ReactEle function StoredTemplates(props: InteractionTemplatesProps) { const [searchTerm, setSearchTerm] = useState(""); + const networkId = useFlowNetworkId(); const templatesRegistry = useTemplatesRegistry(); + const filters = useOptionalInteractionTemplateFilters(); const [filterToSources, setFilterToSources] = useLocalStorage("interaction-filters", []); const filteredTemplates = useMemo(() => { - const searchQueryResults = searchTerm === "" ? templatesRegistry.templates : templatesRegistry.templates.filter((template) => template.name.toLowerCase().includes(searchTerm.toLowerCase())); + let results = Array.from(templatesRegistry.templates); - const sourceFilterResults = filterToSources.length === 0 ? searchQueryResults : searchQueryResults.filter(e => filterToSources.includes(e.source)); + if (filters?.dependencies) { + results = results.filter(template => template.flix && FlixUtils.getDependencies(template.flix, networkId).some(dependency => { + let shouldInclude = true; - return sourceFilterResults; + if (filters.dependencies?.contractAddress) { + shouldInclude = shouldInclude && dependency.address == filters.dependencies.contractAddress; + } + + if (filters.dependencies?.contractName) { + shouldInclude = shouldInclude && dependency.name === filters.dependencies.contractName; + } + + return shouldInclude; + })); + } + + if (searchTerm) { + results = results.filter((template) => template.name.toLowerCase().includes(searchTerm.toLowerCase())) + } + + if (filterToSources.length > 0) { + results = results.filter(template => filterToSources.includes(template.source)) + } + + return results; }, [searchTerm, filterToSources, templatesRegistry.templates]); const filteredAndSortedTemplates = useMemo( () => diff --git a/packages/ui/src/interactions/components/InteractionTemplates/interaction-templates-controller.provider.tsx b/packages/ui/src/interactions/components/InteractionTemplates/interaction-templates-controller.provider.tsx new file mode 100644 index 00000000..dda22fa8 --- /dev/null +++ b/packages/ui/src/interactions/components/InteractionTemplates/interaction-templates-controller.provider.tsx @@ -0,0 +1,27 @@ +import { createContext, ReactNode, useContext } from "react"; + +export type InteractionTemplateFilters = { + dependencies?: { + contractAddress?: string; + contractName?: string; + } +} + +const Context = createContext(undefined as never); + +type Props = { + filters: InteractionTemplateFilters; + children: ReactNode; +} + +export function InteractionTemplateFiltersProvider(props: Props) { + return ( + + {props.children} + + ) +} + +export function useOptionalInteractionTemplateFilters(): InteractionTemplateFilters | undefined { + return useContext(Context); +}