Skip to content

Commit

Permalink
fix #1617 - refactor User page handler (#1618)
Browse files Browse the repository at this point in the history
  • Loading branch information
Atmosfearful committed Oct 2, 2023
1 parent 456659c commit 5c0cf6b
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Spinner } from "components/shared/Spinner";
import { Text } from "components/Text";
import { isAddress } from "ethers-v6";
import { getUser, loginUser, postUser, putUser, verifyUser } from "lib/api";
import { VALID_HANDLE_REGEX } from "lib/constants";
import { User } from "lib/types/carbonmark.types";
import { isNil } from "lodash";
import { FC, useState } from "react";
Expand Down Expand Up @@ -150,7 +151,7 @@ export const EditProfile: FC<Props> = (props) => {
message: t`Handle is required`,
},
pattern: {
value: /^[a-zA-Z0-9]+$/, // no special characters!
value: VALID_HANDLE_REGEX, // no special characters!
message: t`Handle should not contain any special characters`,
},
validate: {
Expand Down
2 changes: 2 additions & 0 deletions carbonmark/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ const ENVIRONMENT: Environment =

export const MINIMUM_TONNE_PRICE = 0.1;
export const CARBONMARK_FEE = 0.0; // 0%
/** No special chars */
export const VALID_HANDLE_REGEX = /^[a-zA-Z0-9]+$/;

export const getConnectErrorStrings = () => ({
default: t({
Expand Down
165 changes: 101 additions & 64 deletions carbonmark/pages/users/[user].tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,105 @@
import { isAddress } from "ethers-v6";
import { GetStaticProps } from "next";
import { ParsedUrlQuery } from "querystring";

import { PageProps, Users } from "components/pages/Users";
import { isAddress } from "ethers-v6";
import { client } from "lib/api/client";
import { VALID_HANDLE_REGEX } from "lib/constants";
import { loadTranslation } from "lib/i18n";
import { getAddressByDomain } from "lib/shared/getAddressByDomain";
import { getIsDomainInURL } from "lib/shared/getIsDomainInURL";

import { client } from "lib/api/client";
import { User } from "lib/types/carbonmark.types";
import { GetStaticProps } from "next";
import { ParsedUrlQuery } from "querystring";

interface Params extends ParsedUrlQuery {
user: string;
}

type UserType = "address" | "domain" | "handle";

/**
* Validates the user param and returns the type, otherwise throws if invalid
* */
const getUserType = (user: string): UserType => {
if (isAddress(user)) return "address";
if (VALID_HANDLE_REGEX.test(user)) return "handle";
if (getIsDomainInURL(user)) return "domain";
throw new Error(
`Param '${user}' is invalid. Must be handle, address, or domain.`
);
};

/**
* Attempts to resolve a valid 0x address string to a user handle.
* Redirects if handle is found, otherwise render empty page props.
* */
const resolveAddress = async (params: { address: string; locale?: string }) => {
const response = await client["/users/{walletOrHandle}"].get({
params: { walletOrHandle: params.address },
});
// Might also be a 404 or invalid API response, but we let those silently fail (page can still render)
const carbonmarkUser = !!response.ok ? await response.json() : null;
// Handle urls are canonical & more user friendly, redirect if possible
if (carbonmarkUser?.handle) {
return {
redirect: {
destination: `/users/${carbonmarkUser.handle}`,
permanent: true,
},
};
}
const translation = await loadTranslation(params.locale);
if (!translation) {
throw new Error("No translation found");
}
return {
props: {
userAddress: params.address,
userDomain: null,
carbonmarkUser, // may be null -- all we need is a valid address
translation,
fixedThemeName: "theme-light",
},
revalidate: 10,
};
};

/**
* Attempts to resolve a valid KNS or ENS domain. Throws if domain can't be resolved.
* */
const resolveDomain = async (params: { domain: string; locale?: string }) => {
// sad path: this throws and is caught by parent -> 404
const address = await getAddressByDomain(params.domain);
return resolveAddress({ address, locale: params.locale });
};

/**
* Attempts to resolve a valid user handle. Throws if handle can't be resolved.
* */
const resolveHandle = async (params: { handle: string; locale?: string }) => {
const response = await client["/users/{walletOrHandle}"].get({
params: { walletOrHandle: params.handle },
});

const carbonmarkUser = !!response.ok ? await response.json() : null;
if (!carbonmarkUser?.wallet) {
throw new Error(`${params.handle} could not be resolved`);
}

const translation = await loadTranslation(params.locale);
if (!translation) {
throw new Error("No translation found");
}

return {
props: {
userAddress: carbonmarkUser.wallet,
userDomain: null,
carbonmarkUser,
translation,
fixedThemeName: "theme-light",
},
revalidate: 10,
};
};

export const getStaticProps: GetStaticProps<PageProps, Params> = async (
ctx
) => {
Expand All @@ -24,64 +110,15 @@ export const getStaticProps: GetStaticProps<PageProps, Params> = async (
}

try {
const translation = await loadTranslation(locale);

if (!translation) {
throw new Error("No translation found");
const userType = getUserType(params.user);
switch (userType) {
case "address":
return resolveAddress({ address: params.user, locale });
case "domain":
return resolveDomain({ domain: params.user, locale });
case "handle":
return resolveHandle({ handle: params.user, locale });
}

const userInUrl = params.user;
const isDomainInURL = getIsDomainInURL(userInUrl);
const isValidAddress = !isDomainInURL && isAddress(userInUrl);

let carbonmarkUser: User | null = null;

if (!isDomainInURL && !isValidAddress) {
const response = await client["/users/{walletOrHandle}"].get({
params: { walletOrHandle: params.user },
});
if (response.ok) {
carbonmarkUser = await response.json();
} else {
throw new Error("Not a valid user address");
}
}

let userAddress: string;
if (isDomainInURL) {
userAddress = await getAddressByDomain(userInUrl); // this fn should throw if it fails to resolve
} else {
userAddress = carbonmarkUser?.wallet || userInUrl;
}

// Haven't fetched carbonmark API yet?
if (!carbonmarkUser) {
const response = await client["/users/{walletOrHandle}"].get({
params: {
walletOrHandle: userAddress,
},
});
if (response.ok) {
const userData = await response.json();
return {
redirect: {
destination: `/users/${userData?.handle}`,
permanent: true,
},
};
}
}

return {
props: {
userAddress,
userDomain: isDomainInURL ? userInUrl : null,
carbonmarkUser,
translation,
fixedThemeName: "theme-light",
},
revalidate: 10,
};
} catch (e) {
console.error("Failed to generate Carbonmark Users Page", e);
return {
Expand Down

1 comment on commit 5c0cf6b

@vercel
Copy link

@vercel vercel bot commented on 5c0cf6b Oct 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

demo-integration – ./examples/nextjs-typescript-integration

klimadao-sable.vercel.app
demo-integration-git-staging-klimadao.vercel.app
demo-integration-klimadao.vercel.app
integration-demo.carbonmark.com

Please sign in to comment.