Skip to content

Commit

Permalink
Merge branch 'develop' into feat/create-campaign
Browse files Browse the repository at this point in the history
  • Loading branch information
MilanVojnovic95 committed Nov 29, 2023
2 parents c1e25ba + b4e48aa commit 10d1089
Show file tree
Hide file tree
Showing 10 changed files with 246 additions and 215 deletions.
10 changes: 10 additions & 0 deletions packages/frontend/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# @carrot-kpi/host-frontend

## 0.33.2

### Patch Changes

- acc813f: Move KPITokenCreationForm to dedicated component to avoid
unncessary Layout component re-renders, triggering many http calls
- b573a8b: Centered menu items in top navbar.
- Updated dependencies [b573a8b]
- @carrot-kpi/ui@0.66.4

## 0.33.1

### Patch Changes
Expand Down
1 change: 0 additions & 1 deletion packages/frontend/e2e/tests/connect-wallet.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { test } from "../utils/fixtures";
/**
*@description Basic connect wallet on Polygon Mumbai network
*/

test.beforeEach(async ({ homePage }) => {
await homePage.goToHomePage();
Expand Down
4 changes: 2 additions & 2 deletions packages/frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@carrot-kpi/host-frontend",
"license": "GPL-3.0-or-later",
"version": "0.33.1",
"version": "0.33.2",
"description": "Frontend for Carrot KPI protocol v1.",
"main": "./dist/library-mode-entrypoint.js",
"module": "./dist/library-mode-entrypoint.js",
Expand Down Expand Up @@ -35,7 +35,7 @@
"config-react-env": "./scripts/config-react-env.js",
"start:staging": "STAGING=true craco start",
"start": "craco start",
"test:e2e": "yarn prepare-fathom && yarn playwright test packages/frontend/e2e/tests/connect-wallet.test.ts",
"test:e2e": "yarn prepare-fathom && yarn playwright test",
"test:create-campaign": "yarn playwright test packages/frontend/e2e/tests/create-campaign.test.ts"
},
"dependencies": {
Expand Down
187 changes: 187 additions & 0 deletions packages/frontend/src/components/campaign-creation-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import React, { useCallback, useEffect } from "react";
import {
KPITokenCreationForm,
usePreferDecentralization,
type SerializableObject,
type TemplateComponentStateUpdater,
useIPFSGatewayURL,
} from "@carrot-kpi/react";
import { useState } from "react";
import { Fetcher, type ResolvedTemplate } from "@carrot-kpi/sdk";
import { useLocation, useNavigate, useParams } from "react-router-dom";
import { ErrorFeedback, Loader } from "@carrot-kpi/ui";
import { useTranslation } from "react-i18next";
import { useInvalidateLatestKPITokens } from "../hooks/useInvalidateLatestKPITokens";
import { useAddTransaction } from "../hooks/useAddTransaction";
import { useAccount, useNetwork, usePublicClient } from "wagmi";

export function CampaignCreationForm<S extends SerializableObject<S>>() {
const { i18n, t } = useTranslation();
const navigate = useNavigate();
const { templateId } = useParams();
const publicClient = usePublicClient();
const { state } = useLocation();
const { chain } = useNetwork();
const { address } = useAccount();
const addTransaction = useAddTransaction();
const invalidateLatestKPITokens = useInvalidateLatestKPITokens();
const preferDecentralization = usePreferDecentralization();
const ipfsGatewayURL = useIPFSGatewayURL();

const [loading, setLoading] = useState(true);
const [template, setTemplate] = useState<ResolvedTemplate | null>(
state && "specification" in state.template ? state.template : null,
);
const [formKey, setFormKey] = useState(0);
const [draftState, setDraftState] = useState<{
templateId?: number;
state: S;
}>({
templateId: undefined,
state: {} as S,
});

// every time the chain or the connected address changes,
// reset the creation form state
useEffect(() => {
setFormKey((prevState) => prevState + 1);
}, [chain, address]);

useEffect(() => {
if (!!state?.template) {
setDraftState((previousState) => ({
templateId: state.template.id,
state: previousState.state,
}));
setTemplate(state.template);
return;
}
if (!templateId) {
console.warn("no template in state and no template id");
return;
}
const parsedTemplateId = parseInt(templateId);
if (isNaN(parsedTemplateId)) {
console.warn(`non numeric template id ${templateId}`);
return;
}
let cancelled = false;
const fetchData = async () => {
if (!cancelled) setLoading(true);
try {
const templates = await Fetcher.fetchKPITokenTemplates({
publicClient,
preferDecentralization,
ids: [parsedTemplateId],
});
if (templates.length !== 1) {
console.warn(
`inconsistent array length while fetching template with id ${templateId} on ${chain?.name}`,
);
if (!cancelled) setTemplate(null);
return;
}
const resolvedTemplates = await Fetcher.resolveTemplates({
ipfsGatewayURL,
templates: templates,
});
if (resolvedTemplates.length !== 1) {
console.warn(
`inconsistent array length while resolving template with id ${templateId} on ${chain?.name}`,
);
if (!cancelled) setTemplate(null);
return;
}
const resolvedTemplate = resolvedTemplates[0];
setDraftState((previousState) => ({
templateId: resolvedTemplate.id,
state: previousState.state,
}));
if (!cancelled) setTemplate(resolvedTemplate);
} catch (error) {
console.error(
`could not fetch template with id ${templateId}`,
error,
);
} finally {
if (!cancelled) setLoading(false);
}
};
void fetchData();
return () => {
cancelled = true;
};
}, [
chain?.name,
ipfsGatewayURL,
preferDecentralization,
publicClient,
state?.template,
templateId,
]);

const handleStateChange = useCallback(
(stateOrUpdater: S | TemplateComponentStateUpdater<S>) => {
setDraftState((prevState) => {
const newState =
typeof stateOrUpdater === "function"
? stateOrUpdater(prevState.state)
: (stateOrUpdater as S);

return {
templateId: prevState.templateId,
state: newState,
};
});
},
[],
);

const handleCreate = useCallback(() => {
invalidateLatestKPITokens();
}, [invalidateLatestKPITokens]);

return loading ? (
<div className="h-screen py-20 text-black flex justify-center">
<Loader />
</div>
) : template ? (
<KPITokenCreationForm
key={formKey}
template={template}
fallback={
<div className="h-screen py-20 text-black flex justify-center">
<Loader />
</div>
}
error={
<div className="h-screen py-20 flex justify-center">
<ErrorFeedback
messages={{
title: t("error.initializing.creation.title"),
description: t(
"error.initializing.creation.description",
),
}}
/>
</div>
}
i18n={i18n}
className={{ root: "w-full h-full" }}
state={draftState.state}
onStateChange={handleStateChange}
onCreate={handleCreate}
navigate={navigate}
onTx={addTransaction}
/>
) : (
<div className="py-20 flex justify-center">
<ErrorFeedback
messages={{
title: t("error.initializing.creation.title"),
description: t("error.initializing.creation.description"),
}}
/>
</div>
);
}
1 change: 1 addition & 0 deletions packages/frontend/src/components/connect-wallet/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export const ConnectWallet = ({
window.removeEventListener("mousedown", handleMouseDown);
};
}, []);

const handleNetworksPopoverOpen = useCallback(() => {
setNetworksPopoverOpen(true);
}, []);
Expand Down
59 changes: 28 additions & 31 deletions packages/frontend/src/components/ui/navbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,37 +100,34 @@ export const Navbar = ({
)}
</div>
</div>
<nav className="hidden md:flex">
<ul className="flex items-center space-x-8">
{links.map((link) => {
const additionalProps = link.external
? {
target: "_blank",
rel: "noopener noreferrer",
}
: {};
return (
<li key={link.to}>
<NavLink
className="flex items-start space-x-2 cursor-pointer"
to={link.to}
onClick={() => setOpen(false)}
{...additionalProps}
>
<span className="font-mono text-2xl xl:text-base">
</span>
<p
data-testid={`header-${link.title}-button`}
className="font-mono text-black text-2xl hover:underline xl:text-base uppercase underline-offset-[12px]"
>
{link.title}
</p>
</NavLink>
</li>
);
})}
</ul>
<nav className="absolute hidden md:flex left-1/2 transform -translate-x-1/2 justify-center space-x-8">
{links.map((link) => {
const additionalProps = link.external
? {
target: "_blank",
rel: "noopener noreferrer",
}
: {};
return (
<NavLink
className="flex items-start space-x-2 cursor-pointer"
to={link.to}
onClick={() => setOpen(false)}
key={link.to}
{...additionalProps}
>
<span className="font-mono text-2xl xl:text-base">
</span>
<p
data-testid={`header-${link.title}-button`}
className="font-mono text-black text-2xl hover:underline xl:text-base uppercase underline-offset-[12px]"
>
{link.title}
</p>
</NavLink>
);
})}
</nav>
<div className="flex md:flex-row items-center gap-4">
<div className="hidden md:block">
Expand Down
Loading

0 comments on commit 10d1089

Please sign in to comment.