Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix #1617 - refactor User page handler #1618

Merged
merged 1 commit into from
Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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