From 4f463b2780bc282573b0209d0499b5c6e2ad8467 Mon Sep 17 00:00:00 2001
From: Matt Solomon
Date: Thu, 12 Aug 2021 14:03:56 -0700
Subject: [PATCH] feat: support adding grants to cart by saving them to
localStorage
---
app/src/utils/utils.ts | 91 +++++++++++++++++-----
app/src/views/GrantRegistryGrantDetail.vue | 7 +-
app/src/views/GrantRegistryList.vue | 52 +++++++------
3 files changed, 104 insertions(+), 46 deletions(-)
diff --git a/app/src/utils/utils.ts b/app/src/utils/utils.ts
index aab32e0e..2e9ea4d8 100644
--- a/app/src/utils/utils.ts
+++ b/app/src/utils/utils.ts
@@ -3,21 +3,34 @@
*/
import router from 'src/router/index';
import { RouteLocationRaw } from 'vue-router';
-import { BigNumber, isAddress } from 'src/utils/ethers';
+import { BigNumber, isAddress, parseUnits } from 'src/utils/ethers';
import { BigNumberish, Contract, ContractTransaction } from 'ethers';
-import { GrantRound } from '@dgrants/types';
+import { Grant, GrantRound } from '@dgrants/types';
+// --- Formatters ---
// Returns an address with the following format: 0x1234...abcd
export function formatAddress(address: string) {
if (!address || address.length !== 42) return null;
return `${address.slice(0, 6)}...${address.slice(38)}`;
}
-// Navigates to the specified page and pushes a new entry into the history stack
-export async function pushRoute(to: RouteLocationRaw) {
- await router.push(to);
+// Expects a unix timestamp and will return a human readable message of how far in the past/future it is
+export function daysAgo(val = 0) {
+ // Use a formatter to establish "in 10 days" vs "10 days ago"
+ const formatter = new Intl.RelativeTimeFormat();
+ // Number of days since now
+ const deltaDays = (val * 1000 - Date.now()) / (1000 * 3600 * 24);
+
+ // Format "days ago" as string
+ return formatter.format(Math.round(deltaDays), 'days');
}
+// convert a unix ts to a toLocaleString
+export function unixToLocaleString(time: BigNumberish) {
+ return new Date(BigNumber.from(time).toNumber() * 1000).toLocaleString();
+}
+
+// --- Validation ---
// Returns true if the provided URL is a valid URL
export function isValidUrl(val: string | undefined) {
return val && val.includes('://'); // TODO more robust URL validation
@@ -28,22 +41,60 @@ export function isValidAddress(val: string | undefined) {
return val && isAddress(val);
}
-// Expects a unix timestamp and will return a human readable message of how far in the past/future it is
-export function daysAgo(val = 0) {
- // Use a formatter to establish "in 10 days" vs "10 days ago"
- const formatter = new Intl.RelativeTimeFormat();
- // Number of days since now
- const deltaDays = (val * 1000 - Date.now()) / (1000 * 3600 * 24);
+// --- Grants + Cart ---
+const CART_KEY = 'cart';
+const DEFAULT_CONTRIBUTION_TOKEN_ADDRESS = '0x6B175474E89094C44Da98b954EedeAC495271d0F'; // DAI
+const DEFAULT_CONTRIBUTION_AMOUNT = parseUnits('5', 18).toString(); // DAI has 18 decimals
+type CartItem = {
+ grantId: string;
+ contributionTokenAddress: string; // store address instead of TokenInfo to reduce localStorage size used
+ contributionAmount: string;
+};
- // Format "days ago" as string
- return formatter.format(Math.round(deltaDays), 'days');
+// Loads cart data
+export function loadCart(): CartItem[] {
+ // Return empty array if nothing found
+ const rawCart = localStorage.getItem(CART_KEY);
+ if (!rawCart) return [];
+
+ // Parse the data. If the data is an array, return it,
+ const cart = JSON.parse(rawCart);
+ if (Array.isArray(cart)) return cart;
+
+ // Otherwise clear the localStorage key and return empty array
+ localStorage.removeItem(CART_KEY);
+ return [];
}
-// convert a unix ts to a toLocaleString
-export function unixToLocaleString(time: BigNumberish) {
- return new Date(BigNumber.from(time).toNumber() * 1000).toLocaleString();
+// Adds a grant to the cart
+export function addToCart(grant: Grant) {
+ // If this grant is already in the cart, do nothing
+ const cart = loadCart();
+ if (cart.map((grant) => grant.grantId).includes(grant.id.toString())) return;
+
+ // Otherwise, add it to the cart and update localStorage
+ cart.push({
+ grantId: grant.id.toString(),
+ contributionTokenAddress: DEFAULT_CONTRIBUTION_TOKEN_ADDRESS,
+ contributionAmount: DEFAULT_CONTRIBUTION_AMOUNT,
+ });
+ localStorage.setItem(CART_KEY, JSON.stringify(cart));
+}
+
+// Removes a grant from the cart
+export function removeFromCart(grantId: BigNumberish) {
+ const cart = loadCart();
+ const newCart = cart.filter((grant) => grant.grantId !== BigNumber.from(grantId).toString());
+ localStorage.setItem(CART_KEY, JSON.stringify(newCart));
}
+// Check against the grantRounds status for a match
+export function hasStatus(status: string) {
+ // returns a fn (currying the given status)
+ return (round: GrantRound) => round.status === status;
+}
+
+// --- Tokens ---
// Check for approved allowance
export async function checkAllowance(token: Contract, ownerAddress: string | undefined, spenderAddress: string) {
// return the balance held for userAddress
@@ -58,8 +109,8 @@ export async function getApproval(token: Contract, address: string, amount: BigN
await tx.wait();
}
-// Check against the grantRounds status for a match
-export function hasStatus(status: string) {
- // returns a fn (currying the given status)
- return (round: GrantRound) => round.status === status;
+// --- Other ---
+// Navigates to the specified page and pushes a new entry into the history stack
+export async function pushRoute(to: RouteLocationRaw) {
+ await router.push(to);
}
diff --git a/app/src/views/GrantRegistryGrantDetail.vue b/app/src/views/GrantRegistryGrantDetail.vue
index d651fd83..cd93b4a8 100644
--- a/app/src/views/GrantRegistryGrantDetail.vue
+++ b/app/src/views/GrantRegistryGrantDetail.vue
@@ -10,7 +10,8 @@
Metadata URL: {{ grant.metaPtr }}