Skip to content

Commit

Permalink
Merge pull request #72 from PotLock/feat/cart
Browse files Browse the repository at this point in the history
Feat/cart
  • Loading branch information
lachlanglen authored Nov 3, 2023
2 parents ac75b2c + 4ae05ab commit ca246d5
Show file tree
Hide file tree
Showing 36 changed files with 2,670 additions and 174 deletions.
2 changes: 1 addition & 1 deletion apps/potlock/widget/Buttons/ActionButton.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const Button = styled.button`
flex-direction: row;
justify-content: center;
align-items: center;
padding: 8px 24px;
padding: 16px 24px;
background: ${getButtonBackground()};
overflow: hidden;
box-shadow: 0px -2.700000047683716px 0px #4a4a4a inset;
Expand Down
15 changes: 12 additions & 3 deletions apps/potlock/widget/Buttons/NavigationButton.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ const getButtonColor = () => {

const Button = styled.a`
// width: 100%;
height: 100%;
// height: 100%;
flex-direction: row;
justify-content: center;
align-items: center;
padding: 8px 24px;
padding: 16px 24px;
background: ${getButtonBackground()};
overflow: hidden;
box-shadow: 0px -2.700000047683716px 0px #4a4a4a inset;
Expand All @@ -48,8 +48,17 @@ const Button = styled.a`

return (
<Button
target={props.target}
href={props.href}
onClick={props.onClick}
onClick={(e) => {
if (props.disabled) {
e.preventDefault();
return;
}
if (props.onClick) {
props.onClick(e);
}
}}
disabled={props.disabled}
style={{ ...props.style }}
>
Expand Down
226 changes: 226 additions & 0 deletions apps/potlock/widget/Cart/BreakdownSummary.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
const ownerId = "potlock.near";
const donationContractId = "donation.tests.potlock.near"; // TODO: update to donate.potlock.near after testing

const IPFS_BASE_URL = "https://nftstorage.link/ipfs/";
Big.PE = 100;

const Container = styled.div`
display: flex;
flex-direction: column;
gap: 24px;
width: 380px;
// background: white;
`;

const Title = styled.div`
color: #2e2e2e;
font-size: 24px;
font-weight: 600;
line-height: 32px;
word-wrap: break-word;
`;

const CurrencyHeader = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 8px;
border-radius: 5px;
background: #f0f0f0;
`;

const CurrencyHeaderText = styled.div`
color: #7b7b7b;
font-size: 12px;
font-weight: 400;
line-height: 14px;
word-wrap: break-word;
`;

const BreakdownItemContainer = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 8px;
`;

const BreakdownItemLeft = styled.div`
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
width: 50%;
gap: 8px;
`;

const BreakdownItemRight = styled.div`
display: flex;
flex: 1;
flex-direction: row;
align-items: center;
justify-content: flex-end;
`;

const BreakdownItemText = styled.div`
color: #2e2e2e;
font-size: 14px;
font-weight: 400;
line-height: 16px;
word-wrap: break-word;
`;

const CurrencyIcon = styled.img`
width: 20px;
height: 20px;
`;

const TotalContainer = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 8px;
border-top: 1px #7b7b7b solid;
`;

const TotalText = styled.div`
color: #2e2e2e;
font-size: 14px;
font-weight: 600;
line-height: 20px;
word-wrap: break-word;
`;

const ErrorText = styled.div`
color: #dd3345;
font-size: 14px;
font-weight: 400;
line-height: 20px;
word-wrap: break-word;
width: 100%;
text-align: center;
`;

// TODO: move this to state to handle selected FT once we support multiple FTs
// TODO: note this is duplicated in Cart.CheckoutItem
const SUPPORTED_FTS = {
NEAR: {
iconUrl: IPFS_BASE_URL + "bafkreicwkm5y7ojxnnfnmuqcs6ykftq2jvzg6nfeqznzbhctgl4w3n6eja",
toIndivisible: (amount) => new Big(amount).mul(new Big(10).pow(24)),
fromIndivisible: (amount) => amount / 10e24,
},
};

const MIN_REQUIRED_DONATION_AMOUNT_PER_PROJECT = 0.1;

const [amountsByFt, totalAmount, donationTooSmall] = useMemo(() => {
const amountsByFt = {};
let donationTooSmall = false;
Object.entries(props.cart).forEach(([projectId, { ft, amount }]) => {
if (!amountsByFt[ft]) amountsByFt[ft] = 0;
amountsByFt[ft] += parseFloat(amount || 0);
if (amountsByFt[ft] < MIN_REQUIRED_DONATION_AMOUNT_PER_PROJECT) donationTooSmall = true;
});
const totalAmount = Object.values(amountsByFt).reduce((acc, amount) => acc + amount, 0);
return [amountsByFt, totalAmount, donationTooSmall];
}, [props]);

// TODO: handle successful transaction
// https://everything.dev/potlock.near/widget/Index?tab=cart&transactionHashes=8pgCqfpsFe2PZsTeugM3BnqhtpUmmrPXy6v4XgJcQ6TW

const handleDonate = () => {
// const transactions = [
// // set data on social.near
// {
// contractName: "social.near",
// methodName: "set",
// deposit: Big(JSON.stringify(socialArgs).length * 0.00003).mul(Big(10).pow(24)),
// args: socialArgs,
// },
// ];
// if (!props.edit) {
// transactions.push(
// // register project on potlock
// {
// contractName: registryId,
// methodName: "register",
// deposit: Big(0.05).mul(Big(10).pow(24)),
// args: potlockRegistryArgs,
// }
// );
// if (!existingHorizonProject) {
// transactions.push(
// // register on NEAR Horizon
// {
// contractName: horizonId,
// methodName: "add_project",
// args: horizonArgs,
// }
// );
// }
// }
const transactions = [];
Object.entries(props.cart).forEach(([projectId, { ft, amount, referrerId }]) => {
const amountFloat = parseFloat(amount || 0);
const amountIndivisible = SUPPORTED_FTS[ft].toIndivisible(amountFloat);
const args = { recipient_id: projectId };
if (referrerId) args.referrer_id = referrerId;
transactions.push({
contractName: donationContractId,
methodName: "donate",
args,
deposit: amountIndivisible.toString(),
});
});
Near.call(transactions);
};

return (
<Container>
<Title>Breakdown summary</Title>
<CurrencyHeader>
<CurrencyHeaderText>Currency</CurrencyHeaderText>
<CurrencyHeaderText>USD</CurrencyHeaderText>
</CurrencyHeader>
{Object.entries(amountsByFt).map(([ft, amount]) => {
const amountFloat = parseFloat(amount || 0);
return (
<BreakdownItemContainer>
<BreakdownItemLeft>
<CurrencyIcon src={SUPPORTED_FTS[ft].iconUrl} />
<BreakdownItemText>{amountFloat.toFixed(2)}</BreakdownItemText>
</BreakdownItemLeft>
<BreakdownItemRight>
<BreakdownItemText>
${!props.nearToUsd ? "-" : (amountFloat * props.nearToUsd).toFixed(2)}
</BreakdownItemText>
</BreakdownItemRight>
</BreakdownItemContainer>
);
})}
<TotalContainer>
<TotalText>Total</TotalText>
<TotalText>${!props.nearToUsd ? "-" : (totalAmount * props.nearToUsd).toFixed(2)}</TotalText>
</TotalContainer>
<Widget
src={`${ownerId}/widget/Buttons.ActionButton`}
props={{
type: "primary",
text: `Donate $${(totalAmount * props.nearToUsd || 0).toFixed(2)}`,
disabled: !Object.keys(props.cart).length || donationTooSmall || !context.accountId,
onClick: handleDonate,
style: {
width: "100%",
},
}}
/>
{donationTooSmall && (
<ErrorText>
Minimum required donation per project is {MIN_REQUIRED_DONATION_AMOUNT_PER_PROJECT} N
</ErrorText>
)}
{!context.accountId && <ErrorText>Please sign in to donate</ErrorText>}
</Container>
);
91 changes: 91 additions & 0 deletions apps/potlock/widget/Cart/CartModalItem.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
const profile = Social.getr(`${props.projectId}/profile`);

const IPFS_BASE_URL = "https://nftstorage.link/ipfs/";
const TRASH_ICON_URL =
IPFS_BASE_URL + "bafkreicwtubzlywmtvoxc4tqjfturyi5oqxtbpezceosiw3juv2d4uf7om";

const TrashContainer = styled.div`
position: absolute;
top: 0;
right: 0;
width: 32px;
height: 32px;
opacity: 0;
transition: opacity 0.3s ease;
display: flex;
align-items: flex-start;
justify-content: flex-end;
&:hover {
cursor: pointer;
}
`;

const TrashIcon = styled.img`
// width: 100%;
// height: 100%;
width: 20px;
height: 20px;
`;

const ItemContainer = styled.div`
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: center;
padding: 24px;
border-bottom: 1px #dbdbdb solid;
&:hover ${TrashContainer} {
opacity: 1;
}
`;

const ProjectDetails = styled.div`
display: flex;
flex-direction: column;
position: relative;
`;

const Text = styled.div`
color: #2e2e2e;
font-size: 16px;
line-height: 24px;
word-wrap: break-word;
max-width: 270px;
`;

const MAX_DESCRIPTION_LENGTH = 120;

if (!profile) return "";

return (
<ItemContainer>
<Widget
src="mob.near/widget/ProfileImage"
props={{
accountId: props.projectId,
style: {
width: "40px",
height: "40px",
border: "none",
marginRight: "24px",
},
className: "mb-2",
imageClassName: "rounded-circle w-100 h-100 d-block",
thumbnail: false,
}}
/>
<ProjectDetails>
<Text style={{ fontWeight: 600 }}>{profile.name}</Text>
<Text>
{profile.description.length > MAX_DESCRIPTION_LENGTH
? profile.description.slice(0, MAX_DESCRIPTION_LENGTH) + "..."
: profile.description}
</Text>
<TrashContainer onClick={() => props.removeProjectsFromCart([props.projectId])}>
<TrashIcon src={TRASH_ICON_URL} />
</TrashContainer>
</ProjectDetails>
</ItemContainer>
);
Loading

0 comments on commit ca246d5

Please sign in to comment.