diff --git a/uplink-client/src/app/spacebuilder/SpaceForm.tsx b/uplink-client/src/app/spacebuilder/SpaceForm.tsx
index 7f8b08c5..b11fc8e4 100644
--- a/uplink-client/src/app/spacebuilder/SpaceForm.tsx
+++ b/uplink-client/src/app/spacebuilder/SpaceForm.tsx
@@ -1,397 +1,250 @@
"use client";;
-import { useState } from "react";
-import { HiTrash } from "react-icons/hi2";
-import { useReducer, useEffect } from "react";
+import { SpaceSettingsInput, SpaceSettingsOutput, SpaceSettingsStateT, useSpaceSettings } from "@/hooks/useSpaceReducer";
import { useSession } from "@/providers/SessionProvider";
-import {
- reducer,
- SpaceBuilderProps,
- validateSpaceBuilderProps,
- createSpace,
- editSpace,
-} from "@/app/spacebuilder/spaceHandler";
-import { useRouter } from "next/navigation";
-import toast from "react-hot-toast";
-import WalletConnectButton from "../../ui/ConnectButton/WalletConnectButton";
-import useSWRMutation from "swr/mutation";
-import { useSWRConfig } from "swr";
-import { mutateSpaces } from "../mutate";
-import { AvatarUpload } from "@/ui/MediaUpload/AvatarUpload";
-import { Label } from "@/ui/DesignKit/Label";
-import { Input } from "@/ui/DesignKit/Input";
import { Button } from "@/ui/DesignKit/Button";
+import { FormInput } from "@/ui/DesignKit/Form";
import { Info } from "@/ui/DesignKit/Info";
-
-export default function SpaceForm({
- initialState,
- isNewSpace,
- referral,
- spaceId,
+import { Label } from "@/ui/DesignKit/Label";
+import { AvatarUpload } from "@/ui/MediaUpload/AvatarUpload";
+import { useEffect, useState } from "react";
+import { HiTrash } from "react-icons/hi2";
+import useSWRMutation from "swr/mutation";
+import { mutateSpaces } from "../mutate";
+import toast from "react-hot-toast";
+import WalletConnectButton from "@/ui/ConnectButton/WalletConnectButton";
+import { useRouter } from "next/navigation";
+import { insertSpace, InsertSpaceArgs } from "@/lib/fetch/insertSpace";
+import { updateSpace, UpdateSpaceArgs } from "@/lib/fetch/updateSpace";
+
+
+export const AdminRow = ({
+ admins,
+ setField,
+ index,
+ isNewSpace,
+ error,
+ onDeleteRow,
+ onEditRow
}: {
- initialState: SpaceBuilderProps;
- isNewSpace: boolean;
- referral?: string;
- spaceId?: string;
-}) {
- const [state, dispatch] = useReducer(reducer, initialState);
- const router = useRouter();
- const { data: session, status } = useSession();
- const { mutate } = useSWRConfig();
- const [isUploading, setIsUploading] = useState(false);
- const { trigger, error, isMutating, reset } = useSWRMutation(
- isNewSpace
- ? `/api/createContest/${spaceId}`
- : `/api/editContest/${spaceId}`,
- isNewSpace ? createSpace : editSpace,
- {
- onError: (err) => {
- console.log(err);
- toast.error(
- "Oops, something went wrong. Please check the fields and try again."
- );
- reset();
- },
- }
- );
-
- const validate = async () => {
- const result = await validateSpaceBuilderProps(state);
-
- if (!result.isValid) {
- dispatch({
- type: "setTotalState",
- payload: { spaceBuilderData: result.values, errors: result.errors },
- });
- }
-
- return result;
- };
-
- const onFormSubmit = async () => {
- const { isValid, values } = await validate();
- if (!isValid) return;
-
- try {
- await trigger({
- ...(!isNewSpace && { spaceId: spaceId }),
- spaceData: values,
- csrfToken: session.csrfToken,
- }).then(({ success, spaceName, errors }) => {
- if (success) {
- mutateSpaces(spaceName);
- toast.success(
- isNewSpace
- ? "Space created successfully!"
- : "Successfully saved your changes",
- {
- icon: "🚀",
+ admins: string[];
+ setField: any;
+ index: number;
+ isNewSpace: boolean;
+ error?: string;
+ onDeleteRow: () => void;
+ onEditRow: (val: string) => void;
+}) => {
+ const { data: session, status } = useSession();
+
+ // // the user can never remove themself as an admin (if they are one)
+ // // on session change, check if the user is an admin. if they are, lock the row and set the address
+ // // if they aren't, unlock the row for editing
+
+ const isLocked = isNewSpace
+ ? index === 0
+ : status === "authenticated"
+ ? session?.user?.address === admins[index]
+ : true;
+
+ useEffect(() => {
+ if (isNewSpace) {
+ // set the session / address if the user is signed in and is an admin
+ if (status === "authenticated" && admins[index] === "you") {
+ setField("admins", admins.map((a, i) => i === index ? session?.user?.address : a));
}
- );
- router.refresh();
- router.push(`/${spaceName}`);
- } else {
- // set the errors
- dispatch({
- type: "setErrors",
- payload: {
- ...(errors?.name && { name: errors.name }),
- ...(errors?.website && { website: errors.website }),
- ...(errors?.twitter && { twitter: errors.twitter }),
- ...(errors?.logoUrl && { logoUrl: errors.logoUrl }),
- },
- });
- toast.error(
- "Oops, something went wrong. Please check the fields and try again."
- );
}
- });
- } catch (e) {
- reset();
- }
- };
-
- return (
-
-
-
Space Builder
- {referral === 'home' && (
-
- Spaces are like profiles for your organization, project, community, or yourself! After creating a space, you can create contests and mintboards inside of it.
-
- )}
-
-
-
setIsUploading(status)}
- ipfsImageCallback={(url) => {
- if (url) {
- dispatch({
- type: "setLogoUrl",
- payload: url,
- })
- }
- }}
- error={state.errors.logoUrl}
- />
-
-
- {/* */}
-
-
-
-
-
- Save
- {isMutating || isUploading && (
-
+
+ onEditRow(e.target.value)}
/>
- )}
+ {!isLocked && (
+
+
+
+ )}
-
-
-
-
- );
-}
-
-const SpaceName = ({
- state,
- dispatch,
-}: {
- state: SpaceBuilderProps;
- dispatch: any;
-}) => {
- return (
-
-
Space Name
-
{
- dispatch({
- type: "setSpaceName",
- payload: e.target.value,
- });
- }}
- placeholder="Nouns"
- className="max-w-xs"
- />
- {state.errors?.name && (
-
- {state.errors.name}
-
- )}
-
- );
-};
-
-const SpaceWebsite = ({
- state,
- dispatch,
-}: {
- state: SpaceBuilderProps;
- dispatch: any;
-}) => {
- return (
-
-
- {`Website (optional)`}
-
-
{
- dispatch({
- type: "setWebsite",
- payload: e.target.value,
- });
- }}
- placeholder="nouns.wtf"
- className="max-w-xs"
- />
- {state.errors?.website && (
-
-
- {state.errors.website}
-
-
- )}
-
- );
+
+ );
};
-const SpaceTwitter = ({
- state,
- dispatch,
-}: {
- state: SpaceBuilderProps;
- dispatch: any;
-}) => {
- return (
-
-
- Twitter
-
-
{
- dispatch({
- type: "setTwitter",
- payload: e.target.value,
- });
- }}
- placeholder="@nounsdao"
- className="max-w-xs"
- />
- {state.errors?.twitter && (
-
-
- {state.errors.twitter}
-
-
- )}
-
- );
-};
-
-const SpaceAdmins = ({
- state,
- dispatch,
- isNewSpace,
-}: {
- state: SpaceBuilderProps;
- dispatch: any;
- isNewSpace: boolean;
-}) => {
- return (
-
-
- Admins
-
-
- {state.admins.map((admin: string, index: number) => {
- return (
-
- );
- })}
-
{
- dispatch({
- type: "addAdmin",
+export const SpaceForm = ({ initialState, spaceId }: { initialState: SpaceSettingsStateT, spaceId?: string }) => {
+ const { data: session, status } = useSession();
+ const [isUploading, setIsUploading] = useState(false);
+ const router = useRouter();
+
+ const isNewSpace = !spaceId;
+ const {
+ state,
+ setField,
+ validateSettings
+ } = useSpaceSettings(initialState);
+
+ const { trigger, error, isMutating, reset } = useSWRMutation(
+ isNewSpace
+ ? `/api/createSpace/${spaceId}`
+ : `/api/editSpace/${spaceId}`,
+ isNewSpace ? insertSpace : updateSpace,
+ {
+ onError: (err) => {
+ console.log(err);
+ toast.error(
+ "Oops, something went wrong. Please check the fields and try again."
+ );
+ reset();
+ },
+ }
+ );
+
+
+ const onFormSubmit = async () => {
+ const result = await validateSettings();
+ if (!result.success) return toast.error("Oops, something went wrong. Please check the fields and try again.")
+
+ try {
+ await trigger({
+ ...result.data,
+ spaceId,
+ csrfToken: session.csrfToken,
+ }).then(({ success }) => {
+ if (success) {
+ const spaceName = result.data.name.replace(' ', '').toLowerCase()
+ mutateSpaces(spaceName);
+ toast.success(
+ isNewSpace
+ ? "Space created successfully!"
+ : "Successfully saved your changes",
+ {
+ icon: "🚀",
+ }
+ );
+ router.refresh();
+ router.push(`/${spaceName}`);
+ } else {
+ toast.error("Form submission failed. Please check the fields and try again.");
+ reset();
+ }
});
- }}
- >
- + Add
-
-
-
- );
-};
-
-const AdminRow = ({
- error,
- dispatch,
- admin,
- index,
- isNewSpace,
-}: {
- error: string;
- dispatch: any;
- admin: string;
- index: number;
- isNewSpace: boolean;
-}) => {
- const { data: session, status } = useSession();
+ } catch (e) {
+ reset();
+ }
+ }
- // the user can never remove themself as an admin (if they are one)
- // on session change, check if the user is an admin. if they are, lock the row and set the address
- // if they aren't, unlock the row for editing
+ return (
+
+
+
Space Builder
+
+ {isNewSpace &&
+
+ Spaces are like profiles for your organization, project, community, or yourself! After creating a space, you can create contests and mintboards inside of it.
+
+ }
+
+
setIsUploading(status)}
+ ipfsImageCallback={(url) => {
+ if (url) {
+ setField("logoUrl", url)
+ }
+ }}
+ error={state.errors?.logoUrl?._errors}
+ />
- const isLocked = isNewSpace
- ? index === 0
- : status === "authenticated"
- ? session?.user?.address === admin
- : true;
+ setField("name", e.target.value)}
+ error={state.errors?.name?._errors[0]}
+ />
- // set the
- useEffect(() => {
- if (isNewSpace) {
- // set the session / address if the user is signed in and is an admin
- if (status === "authenticated" && admin === "you") {
- dispatch({
- type: "setAdmin",
- payload: {
- index: index,
- value: session?.user?.address,
- },
- });
- }
- }
- }, [status, session?.user?.address]);
+ setField("website", e.target.value)}
+ error={state.errors?.website?._errors[0]}
+ />
- return (
-
-
-
- dispatch({
- type: "setAdmin",
- payload: { index: index, value: e.target.value },
- })
- }
- />
- {!isLocked && (
-
{
- dispatch({ type: "removeAdmin", payload: index });
- }}
+
+
+ Admins
+
+
+ Admins have the ability to create contests, mintboards, and manage the space settings.
+
+
+ {state.admins.map((admin: string, index: number) => {
+ return (
+
{
+ setField("admins", state.admins.filter((_, i) => i !== index));
+ }}
+ onEditRow={(val) => {
+ setField("admins", state.admins.map((a, i) => i === index ? val : a));
+ }}
+ />
+ );
+ })}
+ {state.errors?.admins?._errors && (
+
+ {state.errors?.admins?._errors.join(",")}
+
+ )}
+ {
+ setField("admins", [...state.admins, ""]);
+ }}
+ >
+ + Add
+
+
+
+
+
+
+
+
+
+
+ Save
+ {isMutating || isUploading && (
+
+ )}
+
+
+
- >
-
-
- )}
-
- {error && (
-
- invalid address
-
- )}
-
- );
-};
+
+
+ );
+}
\ No newline at end of file
diff --git a/uplink-client/src/app/spacebuilder/create/page.tsx b/uplink-client/src/app/spacebuilder/create/page.tsx
index eca80c85..cfda7261 100644
--- a/uplink-client/src/app/spacebuilder/create/page.tsx
+++ b/uplink-client/src/app/spacebuilder/create/page.tsx
@@ -1,18 +1,15 @@
-import SpaceForm from "@/app/spacebuilder/SpaceForm";
+import { SpaceForm } from "@/app/spacebuilder/SpaceForm";
+import { SpaceSettingsStateT } from "@/hooks/useSpaceReducer";
export default function Page({ searchParams }: { searchParams: { [key: string]: string | undefined } }) {
- const initialState = {
+ const initialState: SpaceSettingsStateT = {
name: "",
- logoBlob: "",
logoUrl: "",
website: "",
- twitter: "",
admins: ["you", ""],
- errors: {
- admins: [],
- },
+ errors: {},
};
- return
;
+ return
;
}
diff --git a/uplink-client/src/app/spacebuilder/edit/[name]/page.tsx b/uplink-client/src/app/spacebuilder/edit/[name]/page.tsx
index 51cbb3a5..4b0acdaf 100644
--- a/uplink-client/src/app/spacebuilder/edit/[name]/page.tsx
+++ b/uplink-client/src/app/spacebuilder/edit/[name]/page.tsx
@@ -1,24 +1,22 @@
-import SpaceForm from "@/app/spacebuilder/SpaceForm";
+import { SpaceForm } from "@/app/spacebuilder/SpaceForm";
+import { SpaceSettingsStateT } from "@/hooks/useSpaceReducer";
import fetchSingleSpace from "@/lib/fetch/fetchSingleSpace";
import { Admin } from "@/types/space";
export default async function Page({ params }: { params: { name: string } }) {
const spaceData = await fetchSingleSpace(params.name);
- const initialState = {
+ const initialState: SpaceSettingsStateT = {
...spaceData,
name: spaceData.displayName,
- logoBlob: spaceData.logoUrl,
+ logoUrl: spaceData.logoUrl,
admins: spaceData.admins.map((admin: Admin) => admin.address),
- errors: {
- admins: Array(spaceData.admins.length).fill(null),
- },
+ errors: {},
};
return (
);
diff --git a/uplink-client/src/app/spacebuilder/spaceHandler.ts b/uplink-client/src/app/spacebuilder/spaceHandler.ts
deleted file mode 100644
index 0c13cfb9..00000000
--- a/uplink-client/src/app/spacebuilder/spaceHandler.ts
+++ /dev/null
@@ -1,360 +0,0 @@
-import { validateEthAddress } from "@/lib/ethAddress";
-import { handleMutationError } from "@/lib/handleMutationError";
-
-export type FormField = {
- value: string;
- error: string | null;
-};
-
-export type SpaceBuilderErrors = {
- name?: string;
- logoUrl?: string;
- website?: string;
- twitter?: string;
- admins: (string | null)[];
-};
-
-export type SpaceBuilderProps = {
- name: string;
- logoUrl: string;
- logoBlob: string;
- website?: string;
- twitter?: string;
- admins: string[];
- errors: SpaceBuilderErrors;
-};
-
-export const reducer = (state: SpaceBuilderProps, action: any) => {
- switch (action.type) {
-
- case "setSpaceName":
- return {
- ...state,
- name: action.payload,
- errors: { ...state.errors, name: null },
- };
- case "setLogoBlob":
- return {
- ...state,
- logoBlob: action.payload,
- };
- case "setLogoUrl":
- return {
- ...state,
- logoUrl: action.payload,
- errors: { ...state.errors, logoUrl: null },
- };
-
- case "setWebsite": {
- const { website: websiteError, ...otherErrors } = state.errors;
-
- return {
- ...state,
- website: action.payload !== "" ? action.payload : undefined,
- errors: otherErrors,
- };
- }
-
- case "setTwitter": {
- const { twitter: twitterError, ...otherErrors } = state.errors;
-
- return {
- ...state,
- twitter: action.payload !== "" ? action.payload : undefined,
- errors: otherErrors,
- };
- }
-
- case "setPfp":
- return {
- ...state,
- pfp: action.payload,
- errors: { ...state.errors, pfp: null },
- };
- case "addAdmin":
- return {
- ...state,
- admins: [...state.admins, ""],
- errors: { ...state.errors, admins: [...state.errors.admins, null] },
- };
- case "removeAdmin":
- return {
- ...state,
- admins: state.admins.filter(
- (admin: string, index: number) => index !== action.payload
- ),
- errors: {
- ...state.errors,
- admins: state.errors.admins.filter(
- (admin: string | null, index: number) => index !== action.payload
- ),
- },
- };
- case "setAdmin":
- return {
- ...state,
- admins: state.admins.map((admin: string, index: number) =>
- index === action.payload.index ? action.payload.value : admin
- ),
- errors: {
- ...state.errors,
- admins: state.errors.admins.map((admin: string | null, index: number) =>
- index === action.payload.index ? null : admin
- ),
- },
- };
- case "setErrors": {
- return {
- ...state,
- errors: {
- ...state.errors,
- ...action.payload
- },
- };
- }
- case "setTotalState":
- return {
- ...state,
- ...action.payload.spaceBuilderData,
- errors: {
- ...state.errors,
- ...action.payload.errors
- }
- };
- default:
- return state;
- }
-};
-
-
-export const validateSpaceName = (name: SpaceBuilderProps['name']): { error: string | null, value: SpaceBuilderProps['name'] } => {
-
- const cleanedName = name.trim();
-
- if (!cleanedName) return { value: cleanedName, error: "Name is required" };
- if (cleanedName.length < 3) return { value: cleanedName, error: "Name must be at least 3 characters long" }
- if (cleanedName.length > 30) return { value: cleanedName, error: "Name must be less than 30 characters long" }
- if (!cleanedName.match(/^[a-zA-Z0-9_ ]+$/)) return { value: cleanedName, error: "Name must only contain alphanumeric characters and underscores" }
-
- return {
- error: null,
- value: cleanedName
- }
-}
-
-export const validateSpaceLogo = (logoUrl: SpaceBuilderProps['logoUrl']): { error: string | null, value: SpaceBuilderProps['logoUrl'] } => {
-
- if (!logoUrl) return { value: logoUrl, error: "Logo is required" };
- const pattern = /^https:\/\/uplink\.mypinata\.cloud\/ipfs\/[a-zA-Z0-9]+/;
- if (!pattern.test(logoUrl)) return { value: logoUrl, error: "Logo is not valid" };
-
- return {
- error: null,
- value: logoUrl
- };
-}
-
-export const validateSpaceWebsite = (website: SpaceBuilderProps['website']): { error: string | null, value?: SpaceBuilderProps['website'] } => {
-
- if (!website) return { error: null };
-
- const cleanedWebsite = website.trim().toLowerCase();
-
- const pattern = /^(https?:\/\/)?(www\.)?([a-zA-Z0-9]+)\.([a-z]{2,})(\.[a-z]{2,})?$/;
- if (!pattern.test(website)) return { value: cleanedWebsite, error: "Website is not valid" };
-
- return {
- error: null,
- value: cleanedWebsite
- };
-}
-
-export const validateSpaceTwitter = (twitter: SpaceBuilderProps['twitter']): { error: string | null, value?: SpaceBuilderProps['twitter'] } => {
-
- if (!twitter) return { error: null };
-
- const cleanedTwitter = twitter.trim().toLowerCase();
-
- const pattern = /^@(\w){1,15}$/;
- if (!pattern.test(twitter)) return { value: cleanedTwitter, error: "Twitter handle is not valid" };
-
- return {
- error: null,
- value: cleanedTwitter
- };
-}
-
-
-
-/**
- *
- * need to return the cleaned addresses array and the errors array
- * errors should be in the same order as the addresses
- *
- */
-
-export const validateSpaceAdmins = async (admins: SpaceBuilderProps['admins']): Promise<{ error: SpaceBuilderErrors['admins'], value: SpaceBuilderProps['admins'] }> => {
- type adminField = {
- value: string,
- error: string | null
- }
-
- const promises = admins.map(async (admin) => {
- const field: adminField = {
- value: admin,
- error: null
- }
-
- if (!field.value || field.value.length === 0) {
- return null;
- }
-
- const cleanAddress = await validateEthAddress(field.value);
-
- if (!cleanAddress) {
- field.error = "invalid address";
- return field;
- }
-
- field.value = cleanAddress;
- return field;
- })
-
- const adminFields = await Promise.all(promises);
-
-
- // Filter out undefined and null fields, and remove duplicates
- const uniqueAdminFields = adminFields.reduce((acc: adminField[], field) => {
- if (field && !acc.some(item => item.value === field.value)) {
- acc.push(field);
- }
- return acc;
- }, []);
-
- // Store errors and values in separate arrays
- const errors = uniqueAdminFields.map(field => field.error);
- const values = uniqueAdminFields.map(field => field.value);
-
- // Return the result
- return { error: errors, value: values };
-
-}
-
-
-
-export const validateSpaceBuilderProps = async (props: SpaceBuilderProps) => {
- const { error: nameError, value: nameValue } = validateSpaceName(props.name);
- const { error: logoErrors, value: logoValue } = validateSpaceLogo(props.logoUrl);
- const { error: websiteErrors, value: websiteValue } = validateSpaceWebsite(props.website);
- const { error: twitterErrors, value: twitterValue } = validateSpaceTwitter(props.twitter);
- const { error: adminsErrors, value: adminsValue } = await validateSpaceAdmins(props.admins);
-
- const errors = {
- ...(nameError ? { name: nameError } : {}),
- ...(logoErrors ? { logoUrl: logoErrors } : {}),
- ...(websiteErrors ? { website: websiteErrors } : {}),
- ...(twitterErrors ? { twitter: twitterErrors } : {}),
- admins: adminsErrors
- }
-
- const values = {
- name: nameValue,
- logoUrl: logoValue,
- ...(websiteValue ? { website: websiteValue } : {}),
- ...(twitterValue ? { twitter: twitterValue } : {}),
- admins: adminsValue,
- }
-
-
- const { admins, ...otherErrors } = errors;
-
- const hasAdminErrors = admins.some((admin) => typeof admin === 'string');
- return {
- isValid: Object.keys(otherErrors).length === 0 && !hasAdminErrors,
- errors,
- values
- }
-
-}
-
-
-export const createSpace = async (
- url,
- {
- arg,
- }: {
- arg: any;
- }
-) => {
- return fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/graphql`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- "X-CSRF-TOKEN": arg.csrfToken,
- },
- credentials: "include",
- body: JSON.stringify({
- query: `
- mutation CreateSpace($spaceData: SpaceBuilderInput!){
- createSpace(spaceData: $spaceData){
- spaceName
- success
- errors{
- name
- logoUrl
- twitter
- website
- admins
- }
- }
- }`,
- variables: {
- spaceData: arg.spaceData,
- },
- }),
- })
- .then((res) => res.json())
- .then(handleMutationError)
- .then((res) => res.data.createSpace);
-};
-
-export const editSpace = async (
- url,
- {
- arg,
- }: {
- arg: any;
- }
-) => {
- return fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/graphql`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- "X-CSRF-TOKEN": arg.csrfToken,
- },
- credentials: "include",
- body: JSON.stringify({
- query: `
- mutation EditSpace($spaceId: ID!, $spaceData: SpaceBuilderInput!){
- editSpace(spaceId: $spaceId, spaceData: $spaceData){
- spaceName
- success
- errors{
- name
- logoUrl
- twitter
- website
- admins
- }
- }
- }`,
- variables: {
- spaceId: arg.spaceId,
- spaceData: arg.spaceData,
- },
- }),
- })
- .then((res) => res.json())
- .then(handleMutationError)
- .then((res) => res.data.editSpace);
-};
\ No newline at end of file
diff --git a/uplink-client/src/app/user/[identifier]/client.tsx b/uplink-client/src/app/user/[identifier]/client.tsx
deleted file mode 100644
index 09dfc51c..00000000
--- a/uplink-client/src/app/user/[identifier]/client.tsx
+++ /dev/null
@@ -1,319 +0,0 @@
-// "use client"
-// import useMe from "@/hooks/useMe"
-// import { ZoraAbi, getContractFromChainId } from "@/lib/abi/zoraEdition";
-// import { getChainName, supportedChains } from "@/lib/chains/supportedChains";
-// import { TokenContractApi } from "@/lib/contract";
-// import { useSession } from "@/providers/SessionProvider";
-// import { ChainLabel } from "@/ui/ContestLabels/ContestLabels";
-// import { UserSubmissionDisplay } from "@/ui/Submission/SubmissionDisplay";
-// import { useEffect, useState } from "react";
-// import { Decimal } from 'decimal.js'
-// import toast from "react-hot-toast";
-// import { TbLoader2 } from "react-icons/tb";
-// import Link from "next/link";
-// import { AddFundsButton, SwitchNetworkButton } from "@/ui/Zora/common";
-// import { Submission } from "@/types/submission";
-// import WalletConnectButton from "@/ui/ConnectButton/WalletConnectButton";
-// import { User } from "@/types/user";
-// import { HiPencil } from "react-icons/hi2";
-// import { FaTwitter } from "react-icons/fa";
-// import { MdOutlineCancelPresentation } from "react-icons/md";
-// import UplinkImage from "@/lib/UplinkImage"
-
-// export const ManageAccountButton = ({ }) => {
-
-// }
-
-// const hasProfile = (user: User) => {
-// return user.userName && user.displayName
-// }
-
-// export const RewardsSkeleton = () => {
-// return (
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-// )
-// }
-
-// export const ClientUserProfile = ({ accountAddress }: { accountAddress: string }) => {
-// const { me: user, isMeLoading, isMeError } = useMe(accountAddress);
-// return (
-//
-//
-//
-// {user.profileAvatar ? (
) : (
-//
-// )}
-//
-
-//
-//
-// {hasProfile(user) ? (
-// <>
-//
-//
{user.displayName}
-//
{user.userName}
-//
-//
-//
-
-//
-// >
-// ) : (
-//
-// Set up profile
-//
-// )}
-
-//
-// {hasProfile(user) ? (
-// <>
-//
-//
{user.displayName}
-//
{user.userName}
-//
-//
-//
-//
-// >
-// ) : (
-//
-// Set up profile
-//
-// )}
-
-// {user.twitterHandle && user.visibleTwitter && (
-//
-//
-//
-// {user.twitterHandle}
-//
-//
-// )}
-// {!hasProfile &&
Claim Account }
-//
-//
-//
-//
-//
-// )
-// }
-
-// export const UserSubmissions = ({ accountAddress, isMintableOnly }: { accountAddress: string, isMintableOnly: boolean }) => {
-// const { me, isMeLoading, isMeError } = useMe(accountAddress);
-// const filteredSubs = isMintableOnly ? me.submissions.filter((el: Submission) => el.edition) : me.submissions
-
-// const user: User = {
-// ...me,
-// submissions: filteredSubs
-// }
-
-// return
-// }
-
-// const useClaimableBalance = (chainId: number, contractAddress: string) => {
-// const [balance, setBalance] = useState
(null);
-// const [triggerRefresh, setTriggerRefresh] = useState(0);
-// const { data: session, status } = useSession();
-// const tokenApi = new TokenContractApi(chainId);
-
-// const getBalance = (userAddress: string) => {
-// tokenApi.zoraGetRewardBalance({ contractAddress, userAddress }).then(balance => {
-// setBalance(balance.toString());
-// })
-// }
-
-// useEffect(() => {
-// if (session?.user?.address) {
-// getBalance(session?.user?.address)
-// }
-
-// }, [session?.user?.address, triggerRefresh])
-
-// return {
-// balance,
-// isLoading: balance === null,
-// triggerRefresh: () => {
-// setTriggerRefresh(triggerRefresh + 1)
-// }
-// }
-// }
-
-
-// const ProtocolRewards = ({ accountAddress }: { accountAddress: string }) => {
-// const { me, isUserAuthorized, isMeLoading, isMeError } = useMe(accountAddress);
-// if (isMeLoading) return
-// if (!isMeLoading && !isUserAuthorized) return null;
-// return (
-//
-//
Protocol Rewards
-// {supportedChains.map(chain => {
-// return
-// })}
-//
-// )
-// }
-
-
-// export const ClaimableUserRewards = ({ accountAddress, chainId }: { accountAddress: string, chainId: number }) => {
-// const { data: session, status } = useSession();
-// const { rewards_contract } = getContractFromChainId(chainId);
-// const { balance, isLoading: isBalanceLoading, triggerRefresh } = useClaimableBalance(chainId, rewards_contract)
-// const [isModalOpen, setIsModalOpen] = useState(false);
-
-// return (
-
-//
-//
-//
{getChainName(chainId)}
-//
-//
-
-//
-// {isBalanceLoading ?
-// (
)
-// :
-// (
{`${new Decimal(balance).div(Decimal.pow(10, 18)).toString()} ETH`}
)
-// }
-//
-//
-// {!isBalanceLoading && new Decimal(balance).greaterThan(0) ? (
-// setIsModalOpen(true)}>Claim
-// ) : (
-//
-// )}
-//
-//
setIsModalOpen(false)} chainId={chainId} claimableBalance={balance} rewardsContract={rewards_contract} triggerRefresh={triggerRefresh} />
-//
-
-// )
-// }
-
-
-
-
-// const ClaimRewardsModal = ({ isModalOpen, onClose, chainId, claimableBalance, rewardsContract, triggerRefresh }) => {
-// const { data: session, status } = useSession();
-// const { config, error: prepareError, isError: isPrepareError } = usePrepareContractWrite({
-// //chainId: chainId,
-// address: rewardsContract,
-// abi: ZoraAbi,
-// functionName: 'withdraw',
-// args: [session?.user?.address, BigInt(claimableBalance || '0')],
-// enabled: true,
-// });
-
-// const isInsufficientFundsError = isPrepareError ? prepareError.message.includes("insufficient funds for gas * price + value") : false;
-
-// const {
-// data,
-// write,
-// isLoading: isWriteLoading,
-// error: writeError,
-// isError: isWriteError
-// } = useContractWrite({
-// ...config,
-// onError(err) {
-// if (err.message.includes("User rejected the request")) {
-// toast.error("Signature request rejected")
-// }
-// }
-// });
-
-// const { isLoading: isTxPending, isSuccess: isTxSuccessful } = useWaitForTransaction({
-// hash: data?.hash,
-// onSettled: (data, err) => {
-// if (err) {
-// console.log(err)
-// return toast.error('Error claiming rewards')
-// }
-// if (data) {
-// toast.success('Successfully claimed your rewards')
-// }
-// },
-
-// });
-
-// useEffect(() => {
-// if (isTxSuccessful) {
-// triggerRefresh();
-// toast.success('Successfully claimed your rewards')
-// onClose();
-// }
-// }, [isTxSuccessful])
-
-
-// if (isModalOpen) {
-// return (
-//
-//
-//
-//
-//
Claim Rewards
-//
-//
-//
-//
Nice work! You have {`${new Decimal(claimableBalance).div(Decimal.pow(10, 18)).toString()} ETH`} in protocol rewards on {getChainName(chainId)}.
-// {isInsufficientFundsError
-// ?
-// (
)
-// :
-//
-// write?.()}
-// >
-// {isWriteLoading ?
-//
-//
Awaiting signature
-//
-//
-// :
-// isTxPending ? (
-//
-// ) : "Claim"
-// }
-//
-//
-// }
-//
-//
-//
-// );
-// }
-// return null;
-// }
\ No newline at end of file
diff --git a/uplink-client/src/app/user/[identifier]/page.tsx b/uplink-client/src/app/user/[identifier]/page.tsx
deleted file mode 100644
index bc6fcf3a..00000000
--- a/uplink-client/src/app/user/[identifier]/page.tsx
+++ /dev/null
@@ -1,139 +0,0 @@
-import fetchUser from "@/lib/fetch/fetchUser";
-import { Submission } from "@/types/submission";
-import { User, UserIdentifier, isUserAddress } from "@/types/user";
-import { SubmissionDisplaySkeleton, UserSubmissionDisplay } from "@/ui/Submission/SubmissionDisplay";
-import Image from "next/image";
-import Link from "next/link";
-import { FaTwitter } from "react-icons/fa6";
-// import { ClaimableUserRewards, ClientUserProfile, RewardsSkeleton, UserSubmissions } from "./client";
-import SwrProvider from "@/providers/SwrProvider";
-import { Suspense } from "react";
-import { useSession } from "@/providers/SessionProvider";
-import { HiPencil } from "react-icons/hi2";
-
-
-const SuspendableUserCard = async ({ userPromise, accountAddress }: { userPromise: Promise, accountAddress: string }) => {
-
- const user = await userPromise;
- const fallback = {
- [`me/${user.address}`]: user,
- };
- return (
-
- loading
- {/* */}
-
- )
-}
-const SuspendableUserSubmissions = async ({ userPromise, isMintableOnly }: { userPromise: Promise, isMintableOnly: boolean }) => {
-
- const user = await userPromise;
- const fallback = {
- [`me/${user.address}`]: user,
- };
- return (
-
-
- {/*
Collection */}
- {user.submissions.length > 0 &&
-
-
- All
-
- {!isMintableOnly &&
}
-
-
-
-
- Drops
-
- {isMintableOnly &&
}
-
-
- }
- {/*
*/}
-
-
- )
-}
-
-const UserSubmissionSkeleton = () => {
- return (
-
- {/*
Collection */}
-
-
-
- )
-}
-
-const UserCardSkeleton = () => {
- return (
-
- )
-}
-
-
-
-export default async function Page({ params, searchParams }: { params: { identifier: UserIdentifier }, searchParams: { [key: string]: string | string[] | undefined } }) {
- // const userPromise = fetchUser(params.identifier)
- // const isMintableOnly = searchParams?.drops === 'true'
- // return (
- //
- //
- // }>
- //
- //
- // }>
- //
- //
- //
- //
- // )
-
- return (
-
-
Under Construction
-
- Profiles will be back soon!
-
- )
-
-
-}
diff --git a/uplink-client/src/app/user/[identifier]/settings/page.tsx b/uplink-client/src/app/user/[identifier]/settings/page.tsx
deleted file mode 100644
index 10b7eac5..00000000
--- a/uplink-client/src/app/user/[identifier]/settings/page.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import fetchUser from "@/lib/fetch/fetchUser"
-import { UserIdentifier } from "@/types/user"
-import WalletConnectButton from "@/ui/ConnectButton/WalletConnectButton"
-import Settings from "./settings"
-import SwrProvider from "@/providers/SwrProvider"
-
-export default async function Page({ params }: { params: { identifier: UserIdentifier } }) {
- const user = await fetchUser(params.identifier)
- const fallback = {
- [`me/${user.address}`]: user,
- };
- return (
-
-
-
-
Profile
-
-
-
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/uplink-client/src/app/user/[identifier]/settings/settings.tsx b/uplink-client/src/app/user/[identifier]/settings/settings.tsx
deleted file mode 100644
index 3ca9b1e3..00000000
--- a/uplink-client/src/app/user/[identifier]/settings/settings.tsx
+++ /dev/null
@@ -1,285 +0,0 @@
-"use client";
-import useMe from "@/hooks/useMe";
-import { handleMutationError } from "@/lib/handleMutationError";
-import { useSession } from "@/providers/SessionProvider";
-import { useRouter } from "next/navigation";
-import { useReducer, useState } from "react";
-import toast from "react-hot-toast";
-import { TbLoader2 } from "react-icons/tb";
-import useSWRMutation from "swr/mutation";
-import { z } from "zod";
-import { AvatarUpload } from "@/ui/MediaUpload/AvatarUpload";
-import { Label } from "@/ui/DesignKit/Label";
-import { Input } from "@/ui/DesignKit/Input";
-import { Button } from "@/ui/DesignKit/Button";
-import Toggle from "@/ui/DesignKit/Toggle";
-
-const configurableUserSettings = z.object({
- profileAvatarUrl: z.string().min(1, { message: "profile picture is required" }),
- profileAvatarBlob: z.string(),
- displayName: z.string().min(3, { message: "display name must contain at least 3 characters" }).max(20, { message: "display name must not exceed 20 characters" }),
- visibleTwitter: z.boolean(),
-})
-
-type ZodSafeParseErrorFormat = {
- [key: string]: { _errors: string[] };
-};
-
-type ConfigurableUserSettings = z.infer;
-
-const BasicInput = ({ value, label, placeholder, onChange, error, inputType }) => {
- return (
-
-
- {label}
-
-
e.currentTarget.blur()}
- spellCheck="false"
- value={value}
- onChange={onChange}
- placeholder={placeholder}
- className="w-full max-w-xs"
- />
- {error && (
-
- {error.join(",")}
-
- )}
-
- )
-}
-
-const reducer = (
- state: ConfigurableUserSettings & { errors?: ZodSafeParseErrorFormat },
- action: any
-) => {
- switch (action.type) {
- case "SET_FIELD":
- return {
- ...state,
- [action.payload.field]: action.payload.value,
- errors: { ...state.errors, [action.payload.field]: undefined }, // Clear error when field is set
- };
- case "SET_ERRORS":
- return { ...state, errors: action.payload };
- default:
- return state;
- }
-}
-
-const TwitterDisplayToggle = ({
- state,
- dispatch,
-}: {
- state: ConfigurableUserSettings;
- dispatch: any;
-}) => {
-
- return (
-
-
-
Show Twitter Handle
-
- Different parts of uplink may collect your twitter handle. Should we display it publicly in your profile?
-
-
-
-
- dispatch({ type: "setDisplayTwitter", payload: !isSelected })
- }
- />
-
-
- );
-
-}
-
-
-const postUser = async (url,
- {
- arg,
- }: {
- url: string;
- arg: {
- displayName: string;
- profileAvatar: string;
- visibleTwitter: boolean;
- csrfToken: string;
- }
- }
-) => {
- return fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/graphql`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- "X-CSRF-TOKEN": arg.csrfToken,
- },
- credentials: "include",
- body: JSON.stringify({
- query: `
- mutation UpdateUser($displayName: String!, $profileAvatar: String!, $visibleTwitter: Boolean!){
- updateUser(displayName: $displayName, profileAvatar: $profileAvatar, visibleTwitter: $visibleTwitter){
- success
- }
- }`,
- variables: {
- csrfToken: arg.csrfToken,
- displayName: arg.displayName,
- profileAvatar: arg.profileAvatar,
- visibleTwitter: arg.visibleTwitter,
- },
- }),
- })
- .then((res) => res.json())
- .then(handleMutationError)
- .then((res) => res.data.updateUser);
-}
-
-const validateForm = (state: ConfigurableUserSettings, dispatch: any) => {
- const result = configurableUserSettings.safeParse(state);
-
- if (!result.success) {
- // Formatting errors and dispatching
- const formattedErrors = (result as z.SafeParseError).error.format();
- dispatch({
- type: "SET_ERRORS",
- payload: formattedErrors, // Pass the formatted errors directly
- });
- }
-
- return result;
-}
-
-const LoadingDialog = () => {
- return (
-
- );
-};
-
-const Settings = ({ accountAddress }: { accountAddress: string }) => {
- const { data: session, status } = useSession();
- const [isUploading, setIsUploading] = useState(false);
- const { me: user, isMeLoading, mutateMe } = useMe(accountAddress)
- const router = useRouter();
- const [state, dispatch] = useReducer(reducer, {
- profileAvatarUrl: user.profileAvatar || "",
- profileAvatarBlob: user.profileAvatar || "",
- displayName: user.displayName || "",
- visibleTwitter: user.visibleTwitter || true,
- errors: {}
- });
-
- const { trigger, data, error, isMutating, reset } = useSWRMutation(
- `/api/updateUser/${user.id}}`,
- postUser,
- {
- onError: (err) => {
- console.log(err);
- reset();
- },
- }
- );
- const onSubmit = async () => {
- const result = validateForm(state, dispatch);
- if (result.success) {
-
- try {
- trigger({
- profileAvatar: result.data.profileAvatarUrl,
- displayName: result.data.displayName,
- visibleTwitter: result.data.visibleTwitter,
- csrfToken: session.csrfToken,
- }).then((response) => {
-
- if (!response.success) {
- reset();
- }
-
- toast.success('Profile updated!')
- mutateMe();
- router.refresh();
- router.push(`/user/${user.address}`);
- return;
- });
- } catch (e) {
- console.log(e)
- reset();
- }
-
- }
- }
-
- if (status === 'loading' || isMeLoading) return
- if (status === 'authenticated') {
- if (session?.user?.address !== user.address) {
- return (
-
-
You are not the owner of this account
-
- )
- } else {
- return (
-
-
-
setIsUploading(status)}
- ipfsImageCallback={(url) => {
- if (url) {
- dispatch({
- type: "SET_FIELD",
- payload: { field: "profileAvatarUrl", value: url },
- });
- }
- }}
- error={state.errors?.profileAvatarUrl?._errors.join(",")}
- />
- dispatch({ type: "SET_FIELD", payload: { field: "displayName", value: e.target.value } })}
- error={state.errors?.displayName?._errors}
- inputType="text"
- />
-
-
-
-
- {isMutating ?
-
- : isUploading ? (
-
- ) : "Save"
- }
-
-
- )
- }
- }
-
-}
-
-export default Settings;
\ No newline at end of file
diff --git a/uplink-client/src/hooks/useContestInteractionAPI.ts b/uplink-client/src/hooks/useContestInteractionAPI.ts
deleted file mode 100644
index 31c279b6..00000000
--- a/uplink-client/src/hooks/useContestInteractionAPI.ts
+++ /dev/null
@@ -1,244 +0,0 @@
-import { useSession } from "@/providers/SessionProvider";
-import { useContestState } from "@/providers/ContestStateProvider";
-import useSWR from "swr";
-import { IToken } from "@/types/token";
-
-export type UserSubmissionParams = {
- maxSubPower: string;
- remainingSubPower: string;
- userSubmissions: { id: string }[];
- restrictionResults: {
- result: boolean;
- restriction: {
- restrictionType: string;
- tokenRestriction: {
- token: IToken;
- threshold: string;
- };
- };
- }[];
-};
-
-export type UserVote = {
- id: string;
- votes: string;
- submissionId: string;
- submissionUrl: string;
-
-};
-
-export type UserVotingParams =
- | {
- totalVotingPower: string;
- votesRemaining: string;
- votesSpent: string;
- userVotes: Array;
- }
- | undefined;
-
-export interface ContestInteractionProps {
- userSubmitParams: UserSubmissionParams;
- areUserSubmitParamsLoading: boolean;
- isUserSubmitParamsError: any;
- userVoteParams: UserVotingParams;
- areUserVotingParamsLoading: boolean;
- isUserVotingParamsError: any;
- mutateUserVotingParams: any; //(newParams: UserVotingParams, options?: any) => void;
- downloadGnosisResults: () => void;
-}
-
-// fetcher functions
-
-const getUserSubmissionParams = async (
- contestId: string,
- csrfToken: string | null
-) => {
- return fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/graphql`, {
- method: "POST",
- credentials: "include",
- headers: {
- "Content-Type": "application/json",
- "X-CSRF-TOKEN": csrfToken,
- },
- body: JSON.stringify({
- query: `
- query GetUserSubmissionParams($contestId: ID!) {
- getUserSubmissionParams(contestId: $contestId) {
- maxSubPower
- remainingSubPower
- userSubmissions {
- id
- }
- restrictionResults {
- result
- restriction {
- restrictionType
- tokenRestriction {
- token {
- address
- decimals
- symbol
- tokenId
- type
- }
- threshold
- }
- }
- }
- }
- }`,
- variables: {
- contestId,
- },
- }),
- })
- .then((res) => res.json())
- .then((res) => res.data.getUserSubmissionParams)
- .catch((err) => {
- return {
- userSubmissions: [],
- maxSubPower: 0,
- remainingSubPower: 0,
- };
- });
-};
-
-const getUserVotingParams = async (
- contestId: string,
- csrfToken: string | null
-) => {
- const response = await fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/graphql`, {
- method: "POST",
- credentials: "include",
- headers: {
- "Content-Type": "application/json",
- "X-CSRF-TOKEN": csrfToken,
- },
- body: JSON.stringify({
- query: `
- query getUserVotingParams($contestId: ID!) {
- getUserVotingParams(contestId: $contestId) {
- totalVotingPower
- votesRemaining
- votesSpent
- userVotes {
- votes
- submissionId
- submissionUrl
- }
- }
- }`,
- variables: {
- contestId,
- },
- }),
- })
- .then((res) => res.json())
- .then((res) => res.data.getUserVotingParams)
- .catch((err) => {
- return {
- totalVotingPower: "0",
- votesRemaining: "0",
- votesSpent: "0",
- userVotes: [],
- };
- });
- return response;
-};
-
-const getGnosisResults = async (contestId: string) => {
- const data = await fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/graphql`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- query: `
- query Query($contestId: ID!){
- contest(contestId: $contestId){
- gnosisResults
- }
- }`,
- variables: {
- contestId,
- },
- }),
- })
- .then((res) => res.json())
- .then((res) => res.data.contest.gnosisResults);
-
- return data;
-};
-
-
-export const useContestInteractionApi = (contestId: string) => {
-
- const { data: session, status } = useSession();
- const { contestState } = useContestState();
- const isAuthed = status === "authenticated";
- const isSubmitPeriod = contestState === "submitting";
- const isVotingPeriod = contestState === "voting";
-
- const submitParamsSwrKey =
- isAuthed && isSubmitPeriod && session?.user?.address
- ? [`/api/userSubmitParams/${contestId}`, session.user.address]
- : null;
- const voteParamsSwrKey =
- isAuthed && isVotingPeriod && session?.user?.address
- ? [`/api/userVotingParams/${contestId}`, session.user.address]
- : null;
-
- // user submission params
- // The key will be undefined until the user is authenticated and the contest is in the submitting stage
-
- const {
- data: userSubmitParams,
- isLoading: areUserSubmitParamsLoading,
- error: isUserSubmitParamsError,
- }: { data: any; isLoading: boolean; error: any } = useSWR(
- submitParamsSwrKey,
- () => getUserSubmissionParams(contestId, session.csrfToken)
- );
-
- // user voting params
- // The key will be undefined until the user is authenticated and the contest is in the voting stage
- const {
- data: userVoteParams,
- isLoading: areUserVotingParamsLoading,
- error: isUserVotingParamsError,
- mutate: mutateUserVotingParams,
- }: {
- data: UserVotingParams;
- isLoading: boolean;
- error: any;
- mutate: any;
- } = useSWR(voteParamsSwrKey, () =>
- getUserVotingParams(contestId, session.csrfToken)
- );
-
- const postProcessCsvResults = (results: string, type: string) => {
- const endcodedUri = encodeURI(results);
- const link = document.createElement("a");
- link.setAttribute("href", endcodedUri);
- link.setAttribute("download", `${contestId}-${type}-results.csv`);
- document.body.appendChild(link);
- link.click();
- };
-
- const downloadGnosisResults = () => {
- getGnosisResults(contestId).then((res: string) =>
- postProcessCsvResults(res, "gnosis")
- );
- };
-
- return {
- userSubmitParams,
- areUserSubmitParamsLoading,
- isUserSubmitParamsError,
- userVoteParams,
- areUserVotingParamsLoading,
- isUserVotingParamsError,
- mutateUserVotingParams,
- downloadGnosisResults,
- }
-}
\ No newline at end of file
diff --git a/uplink-client/src/hooks/useCreateMintBoardTemplate.ts b/uplink-client/src/hooks/useCreateMintBoardTemplate.ts
deleted file mode 100644
index 55bd1196..00000000
--- a/uplink-client/src/hooks/useCreateMintBoardTemplate.ts
+++ /dev/null
@@ -1,164 +0,0 @@
-import { uint64MaxSafe } from "@/utils/uint64";
-import { useReducer, useState } from "react";
-import { z } from "zod";
-import { Decimal } from 'decimal.js';
-import { handleMutationError } from "@/lib/handleMutationError";
-import { Session } from "@/providers/SessionProvider";
-import { validateEthAddress } from "@/lib/ethAddress";
-import { supportedChains } from "@/lib/chains/supportedChains";
-
-export const MintBoardTemplateSchema = z.object({
- chainId: z.number().refine((n) => supportedChains.map(chain => chain.id).includes(n), { message: "Must be base network" }),
- enabled: z.boolean(),
- threshold: z.number(),
- boardTitle: z.string().min(1, { message: "Board title is required" }),
- boardDescription: z.string().min(1, { message: "Board description is required" }),
- name: z.string().min(1, { message: "Name is required" }),
- symbol: z.string().min(1, { message: "Symbol is required" }),
- editionSize: z.string(),
- description: z.string().min(1, { message: "Description is required" }),
- publicSalePrice: z.string(),
- publicSaleStart: z.string(),
- publicSaleEnd: z.string(),
- referrer: z.string().min(1, { message: "Referral reward recipient is required" })
-}).superRefine(async (data, ctx) => {
-
- const isEns = data.referrer.endsWith(".eth");
- if (isEns) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- path: ["referrer"],
- message: "ENS not supported",
- })
- }
-
- const cleanAddress = await validateEthAddress(data.referrer);
- if (!Boolean(cleanAddress)) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- path: ["referrer"],
- message: "Invalid Address",
- })
- }
-})
-
-
-export type MintBoardTemplate = z.infer;
-
-
-type ZodSafeParseErrorFormat = {
- [key: string]: { _errors: string[] };
-};
-export const EditionWizardReducer = (state: MintBoardTemplate & { errors?: ZodSafeParseErrorFormat }, action: any) => {
- switch (action.type) {
- case "SET_FIELD":
- return {
- ...state,
- [action.payload.field]: action.payload.value,
- errors: { ...state.errors, [action.payload.field]: undefined }, // Clear error when field is set
- };
- case "SET_ERRORS":
- return {
- ...state,
- errors: action.payload,
- };
- default:
- return state;
- }
-}
-
-
-
-export const configureMintBoard = async (url,
- {
- arg,
- }: {
- url: string;
- arg: {
- csrfToken: string;
- spaceName: string;
- mintBoardData: MintBoardTemplate;
- }
- }
-) => {
- return fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/graphql`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- "X-CSRF-TOKEN": arg.csrfToken,
- },
- credentials: "include",
- body: JSON.stringify({
- query: `
- mutation ConfigureMintBoard($spaceName: String!, $mintBoardData: MintBoardInput!){
- configureMintBoard(spaceName: $spaceName, mintBoardData: $mintBoardData){
- success
- }
- }`,
- variables: {
- csrfToken: arg.csrfToken,
- spaceName: arg.spaceName,
- mintBoardData: arg.mintBoardData,
- },
- }),
- })
- .then((res) => res.json())
- .then(handleMutationError)
- .then((res) => res.data.configureMintBoard);
-}
-
-
-
-export default function useCreateMintBoardTemplate(templateConfig?: MintBoardTemplate) {
-
- const baseConfig = {
- chainId: supportedChains[0].id,
- enabled: false,
- threshold: 0,
- boardTitle: "",
- boardDescription: "",
- name: "",
- symbol: "",
- editionSize: "open",
- description: "",
- publicSalePrice: "free",
- publicSaleStart: "now",
- publicSaleEnd: "3 days",
- referrer: "",
- errors: {},
- }
-
- const initState = templateConfig ? { ...baseConfig, ...templateConfig } : baseConfig;
-
- const [state, dispatch] = useReducer(EditionWizardReducer, initState)
-
-
- const setField = (field: string, value: string | boolean | number) => {
- dispatch({
- type: 'SET_FIELD',
- payload: { field, value },
- });
- }
-
-
- const validate = async () => {
- const { errors, ...rest } = state;
- const result = await MintBoardTemplateSchema.safeParseAsync(rest);
- if (!result.success) {
- const formattedErrors = (result as z.SafeParseError).error.format();
- dispatch({
- type: "SET_ERRORS",
- payload: formattedErrors,
- });
- }
- return result;
- }
-
-
-
- return {
- state,
- setField,
- validate,
- }
-}
diff --git a/uplink-client/src/hooks/useCreateTokenReducer.ts b/uplink-client/src/hooks/useCreateTokenReducer.ts
index 8c174984..9282d182 100644
--- a/uplink-client/src/hooks/useCreateTokenReducer.ts
+++ b/uplink-client/src/hooks/useCreateTokenReducer.ts
@@ -1,13 +1,12 @@
-"use client";
+"use client";;
import { parseIpfsUrl, pinJSONToIpfs, replaceGatewayLinksInString } from '@/lib/ipfs';
import { z } from 'zod';
import { CreateTokenConfig } from "@tx-kit/sdk";
import { validateCreateTokenInputs } from "@tx-kit/sdk/utils";
-import { Address, http, maxUint256 } from 'viem';
-import { useEffect, useReducer, useState } from 'react';
+import { maxUint256 } from 'viem';
+import { useReducer, useState } from 'react';
import { useCreateToken, useCreateTokenIntent } from "@tx-kit/hooks"
import { UploadToIpfsTokenMetadata } from '@/types/channel';
-import { useConnect, useConnectors, useWalletClient } from 'wagmi';
const constructTokenMetadata = (input: CreateTokenInputs): UploadToIpfsTokenMetadata => {
diff --git a/uplink-client/src/hooks/useCreateZoraEdition.ts b/uplink-client/src/hooks/useCreateZoraEdition.ts
deleted file mode 100644
index 350f6c0e..00000000
--- a/uplink-client/src/hooks/useCreateZoraEdition.ts
+++ /dev/null
@@ -1,421 +0,0 @@
-import { uint64MaxSafe } from "@/utils/uint64";
-import { useReducer, useState } from "react";
-import { z } from "zod";
-import { Decimal } from 'decimal.js';
-import { handleMutationError } from "@/lib/handleMutationError";
-import { Session } from "@/providers/SessionProvider";
-import { parseIpfsUrl } from "@/lib/ipfs";
-import toast from "react-hot-toast";
-import { supportedChains } from "@/lib/chains/supportedChains";
-
-
-
-
-export const EditionConfig = z.object({
- name: z.string(),
- symbol: z.string(),
- editionSize: z.string(),
- royaltyBPS: z.number(),
- fundsRecipient: z.string(),
- defaultAdmin: z.string(),
- saleConfig: z.object({
- publicSalePrice: z.string(),
- maxSalePurchasePerAddress: z.number(),
- publicSaleStart: z.string(),
- publicSaleEnd: z.string(),
- presaleStart: z.string(),
- presaleEnd: z.string(),
- presaleMerkleRoot: z.string(),
- }),
- description: z.string(),
- animationURI: z.string(),
- imageURI: z.string(),
- referrer: z.string(),
-})
-
-export const ZoraEdition = z.object({
- chainId: z.number().refine((n) => supportedChains.map(val => val.id).includes(n), { message: "Unsupported network" }),
- address: z.string(),
- config: EditionConfig,
-});
-
-export const EditionNameSchema = z.string().min(1, { message: "Name is required" });
-export const EditionSymbolSchema = z.string().min(1, { message: "Symbol is required" });
-export const EditionSizeSchema = z.union([z.literal("open"), z.string().min(1, { message: "Edition size is required" })]).transform((val, ctx) => {
- if (val === "open") { return uint64MaxSafe.toString(); }
- if (val === "one") { return "1"; }
- const result = BigInt(val);
- if (!result) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: "Edition size must be greater than 0",
- path: ['editionSize'],
- })
- return z.NEVER;
- }
-
- return result.toString();
-})
-
-export const EditionRoyaltyBPSSchema = z.union([z.literal("zero"), z.literal("five"), z.string().min(1, { message: "Royalty % is required" })]).transform((val) => {
- if (val === "zero") { return 0 }
- if (val === "five") { return 500 }
- const bps = parseInt(new Decimal(val).times(100).toString());
- return bps
-})
-
-export const EditionPublicSalePriceSchema = z.union([z.literal("free"), z.string().min(1, { message: "Edition price is required" })]).transform((val) => {
- if (val === "free") return "0";
- return new Decimal(val).times(10 ** 18).toString();
-})
-
-const calcSaleStart = (saleStart: string) => {
- const unixInS = (str: string | number | Date) => Math.floor(new Date(str).getTime() / 1000);
- const now = unixInS(new Date(Date.now()));
- if (saleStart === "now") return now;
- return unixInS(saleStart);
-}
-
-const calcSaleEnd = (saleEnd: string) => {
- const unixInS = (str: string | number | Date) => Math.floor(new Date(str).getTime() / 1000);
- const now = unixInS(new Date(Date.now()));
- const three_days = now + 259200;
- const week = now + 604800;
- if (saleEnd === "forever") return uint64MaxSafe;
- if (saleEnd === "3 days") return three_days;
- if (saleEnd === "week") return week;
- return unixInS(saleEnd);
-}
-
-export const EditionSaleConfigSchema = z.object({
- publicSalePrice: EditionPublicSalePriceSchema,
- publicSaleStart: z.union([z.string().datetime(), z.literal("now")]),
- publicSaleEnd: z.union([z.string().datetime(), z.literal("forever"), z.literal("week"), z.literal("3 days")]),
-}).transform((val, ctx) => {
- const { publicSalePrice, publicSaleStart, publicSaleEnd } = val;
- const unixInS = (str: string | number | Date) => Math.floor(new Date(str).getTime() / 1000);
- const now = unixInS(new Date(Date.now()));
- const week = now + 604800;
- const unixSaleStart = calcSaleStart(publicSaleStart);
- const unixSaleEnd = calcSaleEnd(publicSaleEnd);
- if (unixSaleStart < now) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: "Public sale start must be in the future",
- path: ['publicSaleStart'],
- })
- return z.NEVER;
- }
-
- if (unixSaleStart > unixSaleEnd) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: "Public sale end must be after public sale start",
- path: ['publicSaleEnd'],
- })
- return z.NEVER;
- }
-
- return {
- publicSalePrice,
- publicSaleStart: unixSaleStart.toString(),
- publicSaleEnd: unixSaleEnd.toString(),
- }
-})
-
-
-export const ConfigurableZoraEditionSchema = z.object({
- creator: z.string().min(1, { message: "You must be signed in" }),
- name: z.string().min(1, { message: "Name is required" }),
- symbol: z.string().min(1, { message: "Symbol is required" }),
- editionSize: EditionSizeSchema,
- royaltyBPS: EditionRoyaltyBPSSchema,
- description: z.string().min(1, { message: "Description is required" }),
- animationURI: z.string(),
- imageURI: z.string(),
- saleConfig: EditionSaleConfigSchema,
-}).transform((val, ctx) => {
- if (val.animationURI && !val.imageURI) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: "Video thumbnail must be set",
- path: ['animationURI'],
- })
- return z.NEVER;
- }
-
- if (!val.imageURI) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: "Image must be set",
- path: ['imageURI'],
- })
- return z.NEVER;
- }
-
- const output: ZoraEditionConfig = {
- name: val.name,
- symbol: val.symbol,
- editionSize: val.editionSize,
- royaltyBPS: val.royaltyBPS,
- fundsRecipient: val.creator,
- defaultAdmin: val.creator,
- saleConfig: {
- publicSalePrice: val.saleConfig.publicSalePrice,
- maxSalePurchasePerAddress: 2147483647, // max int32
- publicSaleStart: val.saleConfig.publicSaleStart,
- publicSaleEnd: val.saleConfig.publicSaleEnd,
- presaleStart: "0",
- presaleEnd: "0",
- presaleMerkleRoot: "0x0000000000000000000000000000000000000000000000000000000000000000"
- },
- description: val.description,
- animationURI: parseIpfsUrl(val.animationURI).raw,
- imageURI: parseIpfsUrl(val.imageURI).raw,
- referrer: "0xa943e039B1Ce670873ccCd4024AB959082FC6Dd8",
- }
-
- return output;
-});
-
-export type ZoraEditionConfig = z.infer;
-export type ConfigurableZoraEdition = z.infer;
-export type ConfigurableZoraEditionInput = z.input;
-export type ConfigurableZoraEditionOutput = z.output;
-
-
-type ZodSafeParseErrorFormat = {
- [key: string]: { _errors: string[] };
-};
-export const EditionWizardReducer = (state: ConfigurableZoraEditionInput & { errors?: ZodSafeParseErrorFormat }, action: any) => {
- switch (action.type) {
- case "SET_FIELD":
- return {
- ...state,
- [action.payload.field]: action.payload.value,
- errors: { ...state.errors, [action.payload.field]: undefined }, // Clear error when field is set
- };
- case "SET_ERRORS":
- return {
- ...state,
- errors: action.payload,
- };
- default:
- return state;
- }
-}
-
-export const reserveMintBoardSlot = async (url,
- {
- arg,
- }: {
- url: string;
- arg: {
- csrfToken: string;
- spaceName: string;
- }
- }
-) => {
- return fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/graphql`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- "X-CSRF-TOKEN": arg.csrfToken,
- },
- credentials: "include",
- body: JSON.stringify({
- query: `
- mutation ReserveMintBoardSlot($spaceName: String!){
- reserveMintBoardSlot(spaceName: $spaceName){
- success
- slot
- }
- }`,
- variables: {
- csrfToken: arg.csrfToken,
- spaceName: arg.spaceName,
- },
- }),
- })
- .then((res) => res.json())
- .then(handleMutationError)
- .then((res) => res.data.reserveMintBoardSlot);
-}
-
-
-export const postToMintBoard = async (url,
- {
- arg,
- }: {
- url: string;
- arg: {
- csrfToken: string;
- spaceName: string;
- contractAddress: string;
- chainId: number;
- dropConfig: ConfigurableZoraEditionOutput;
- }
- }
-) => {
- return fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/graphql`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- "X-CSRF-TOKEN": arg.csrfToken,
- },
- credentials: "include",
- body: JSON.stringify({
- query: `
- mutation CreateMintBoardPost($spaceName: String!, $contractAddress: String!, $chainId: Int!, $dropConfig: DropConfig!){
- createMintBoardPost(spaceName: $spaceName, contractAddress: $contractAddress, chainId: $chainId, dropConfig: $dropConfig){
- success
- }
- }`,
- variables: {
- csrfToken: arg.csrfToken,
- spaceName: arg.spaceName,
- contractAddress: arg.contractAddress,
- chainId: arg.chainId,
- dropConfig: arg.dropConfig,
- },
- }),
- })
- .then((res) => res.json())
- .then(handleMutationError)
- .then((res) => res.data.createMintBoardPost);
-}
-
-
-export const postDrop = async (url,
- {
- arg,
- }: {
- url: string;
- arg: {
- csrfToken: string;
- submissionId: string;
- contestId: string;
- contractAddress: string;
- chainId: number;
- dropConfig: ConfigurableZoraEditionOutput;
- }
- }
-) => {
- return fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/graphql`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- "X-CSRF-TOKEN": arg.csrfToken,
- },
- credentials: "include",
- body: JSON.stringify({
- query: `
- mutation CreateUserDrop($submissionId: ID!, $contestId: ID!, $contractAddress: String!, $chainId: Int!, $dropConfig: DropConfig!){
- createUserDrop(submissionId: $submissionId, contestId: $contestId, contractAddress: $contractAddress, chainId: $chainId, dropConfig: $dropConfig){
- success
- }
- }`,
- variables: {
- csrfToken: arg.csrfToken,
- submissionId: arg.submissionId,
- contestId: arg.contestId,
- contractAddress: arg.contractAddress,
- chainId: arg.chainId,
- dropConfig: arg.dropConfig,
- },
- }),
- })
- .then((res) => res.json())
- .then(handleMutationError)
- .then((res) => res.data.createUserDrop);
-}
-
-export const flattenContractArgs = (args: ConfigurableZoraEditionOutput) => {
- if (!args) return null;
- return Object.entries(args).map(([key, val], idx) => {
- if (key === "saleConfig") return Object.values(val)
- return val;
- })
-}
-
-export default function useCreateZoraEdition(referrer?: string, templateConfig?: ConfigurableZoraEditionInput) {
- const [contractArguments, setContractArguments] = useState(null);
-
- const isReferralValid = referrer ? referrer.startsWith('0x') && referrer.length === 42 : false;
-
- const baseConfig = {
- name: "",
- symbol: "",
- editionSize: "open",
- royaltyBPS: "zero",
- description: "",
- animationURI: "",
- imageURI: "",
- saleConfig: {
- publicSalePrice: "free",
- publicSaleStart: "now",
- publicSaleEnd: "forever",
- },
- errors: {},
- }
-
- const initState = templateConfig ? { ...baseConfig, ...templateConfig } : baseConfig;
-
- const [state, dispatch] = useReducer(EditionWizardReducer, initState);
-
-
- const setField = (field: string, value: string) => {
- const keys = field.split('.');
- const lastKey = keys.pop();
- const lastObj = keys.reduce((stateObj, key) => stateObj[key] = stateObj[key] || {}, state);
-
- if (lastKey) {
- lastObj[lastKey] = value;
- }
-
- dispatch({
- type: 'SET_FIELD',
- payload: { field, value },
- });
- };
-
-
- const validate = async (userAddress: Session['user']['address']) => {
-
- //const videoThumbnailUrl = state.animationURI ? await handleThumbnailChoice() : '';
-
- const { errors, ...rest } = state;
-
- const result = ConfigurableZoraEditionSchema.safeParse({
- ...rest,
- creator: userAddress,
- imageURI: state.imageURI,
- animationURI: state.animationURI
- });
-
- if (!result.success) {
- // Formatting errors and dispatching
- const formattedErrors = (result as z.SafeParseError).error.format();
- dispatch({
- type: "SET_ERRORS",
- payload: formattedErrors, // Pass the formatted errors directly
- });
- }
- else if (result.success) {
- setContractArguments({
- ...result.data,
- referrer: isReferralValid ? referrer : "0xa943e039B1Ce670873ccCd4024AB959082FC6Dd8"
- });
- }
- return result;
- }
-
-
- return {
- contractArguments,
- setContractArguments,
- state,
- setField,
- validate,
- }
-}
diff --git a/uplink-client/src/hooks/useDeleteContestSubmission.ts b/uplink-client/src/hooks/useDeleteContestSubmission.ts
deleted file mode 100644
index 1c9ced67..00000000
--- a/uplink-client/src/hooks/useDeleteContestSubmission.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-import useSWRMutation from "swr/mutation";
-import useLiveSubmissions from './useLiveSubmissions';
-import toast from "react-hot-toast";
-import { handleMutationError } from "@/lib/handleMutationError";
-import { useSession } from "@/providers/SessionProvider";
-import { useRouter } from 'next/navigation';
-
-
-const deleteContestSubmission = async (url, { arg }: {
- url: string;
- arg: {
- csrfToken: string;
- submissionId: string
- contestId: string;
- }
-}
-) => {
- return fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/graphql`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- "X-CSRF-TOKEN": arg.csrfToken,
- },
- credentials: "include",
- body: JSON.stringify({
- query: `
- mutation DeleteContestSubmission($submissionId: ID!, $contestId: ID!) {
- deleteContestSubmission(submissionId: $submissionId, contestId: $contestId) {
- success
- }
- }`,
- variables: {
- csrfToken: arg.csrfToken,
- submissionId: arg.submissionId,
- contestId: arg.contestId,
- },
- }),
- })
- .then((res) => res.json())
- .then(handleMutationError)
- .then((res) => res.data.deleteContestSubmission);
-}
-
-
-
-export const useDeleteContestSubmission = (contestId: string) => {
- const { data: session, status } = useSession();
- const { mutateLiveSubmissions } = useLiveSubmissions(contestId);
-
- const { trigger: triggerDeleteContestSubmission, error, isMutating: isDeleteContestSubmissionMutating, reset: resetDeleteContestSubmission } = useSWRMutation(
- `/api/deleteContestSubmission`,
- deleteContestSubmission,
- {
- onError: (err) => {
- console.log(err);
- toast.error(
- "Oops, something went wrong. Please check the fields and try again."
- );
- resetDeleteContestSubmission();
- },
- }
- );
-
-
-
- const handleDeleteContestSubmission = async (submissionId: string, onSuccess: () => void) => {
- try {
- await triggerDeleteContestSubmission({
- submissionId,
- contestId,
- csrfToken: session.csrfToken
- }).then(({ success }) => {
- if (success) {
- mutateLiveSubmissions();
- toast.success("Post successfully deleted", { icon: "🚀" });
- onSuccess();
- } else {
- // set the errors
- toast.error(
- "Oops, something went wrong. Please try again later."
- );
- }
- });
- } catch (e) {
- resetDeleteContestSubmission();
- }
- }
-
- return {
- handleDeleteContestSubmission
- }
-}
\ No newline at end of file
diff --git a/uplink-client/src/hooks/useDeleteMintboardPost.ts b/uplink-client/src/hooks/useDeleteMintboardPost.ts
deleted file mode 100644
index 12e3fa2e..00000000
--- a/uplink-client/src/hooks/useDeleteMintboardPost.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-import useSWRMutation from "swr/mutation";
-import toast from "react-hot-toast";
-import { handleMutationError } from "@/lib/handleMutationError";
-import { useSession } from "@/providers/SessionProvider";
-
-
-const deleteMintboardPost = async (url, { arg }: {
- url: string;
- arg: {
- csrfToken: string;
- postId: string
- spaceId: string;
- }
-}
-) => {
- return fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/graphql`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- "X-CSRF-TOKEN": arg.csrfToken,
- },
- credentials: "include",
- body: JSON.stringify({
- query: `
- mutation DeleteMintboardPost($postId: ID!, $spaceId: ID!) {
- deleteMintboardPost(postId: $postId, spaceId: $spaceId) {
- success
- }
- }`,
- variables: {
- csrfToken: arg.csrfToken,
- postId: arg.postId,
- spaceId: arg.spaceId,
- },
- }),
- })
- .then((res) => res.json())
- .then(handleMutationError)
- .then((res) => res.data.deleteMintboardPost);
-}
-
-
-
-export const useDeleteMintboardPost = (onSuccess: () => void) => {
- const { data: session, status } = useSession();
-
- const { trigger: triggerDeleteMintboardPost, error, isMutating: isDeleteMintboardPostMutating, reset: resetDeleteMintboardPost } = useSWRMutation(
- `/api/deleteMintboardPost`,
- deleteMintboardPost,
- {
- onError: (err) => {
- console.log(err);
- toast.error(
- "Oops, something went wrong. Please check the fields and try again."
- );
- resetDeleteMintboardPost();
- },
- }
- );
-
-
-
- const handleDeleteMintboardPost = async (postId: string, spaceId: string) => {
- try {
- await triggerDeleteMintboardPost({
- postId,
- spaceId,
- csrfToken: session.csrfToken
- }).then(({ success }) => {
- if (success) {
- toast.success("Post successfully deleted", { icon: "🚀" });
- onSuccess();
- } else {
- // set the errors
- toast.error(
- "Oops, something went wrong. Please try again later."
- );
- }
- });
- } catch (e) {
- resetDeleteMintboardPost();
- }
- }
-
- return {
- handleDeleteMintboardPost
- }
-}
\ No newline at end of file
diff --git a/uplink-client/src/hooks/useEnsName.ts b/uplink-client/src/hooks/useEnsName.ts
deleted file mode 100644
index ed05d9a0..00000000
--- a/uplink-client/src/hooks/useEnsName.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { useState, useEffect } from 'react';
-import { createWeb3Client } from '@/lib/viem';
-
-const publicClient = createWeb3Client(1);
-
-const useEnsName = (address: string) => {
- const [ensName, setEnsName] = useState(null);
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState(null);
-
- useEffect(() => {
- async function fetchENSName() {
- try {
- const name = await publicClient.getEnsName({ address: address as `0x${string}` })
- setEnsName(name);
- setLoading(false);
- } catch (err) {
- setError(err);
- setLoading(false);
- }
- }
-
- fetchENSName();
- }, [address]);
-
- return { ensName, loading, error };
-}
-
-export default useEnsName;
diff --git a/uplink-client/src/hooks/useLiveSubmissions.ts b/uplink-client/src/hooks/useLiveSubmissions.ts
deleted file mode 100644
index 585b2ecc..00000000
--- a/uplink-client/src/hooks/useLiveSubmissions.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-import useSWR from "swr";
-import { mutateSubmissions } from "@/app/mutate";
-import { useEffect } from "react";
-import { Submission } from "@/types/submission";
-
-// local client side fetch, don't use the server-only fetch
-const fetchSubmissions = async (contestId: string) => {
- const data = await fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/graphql`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- query: `
- query Query($contestId: ID!){
- contest(contestId: $contestId){
- submissions {
- id
- contestId
- totalVotes
- rank
- created
- type
- url
- version
- edition {
- id
- chainId
- contractAddress
- name
- symbol
- editionSize
- royaltyBPS
- fundsRecipient
- defaultAdmin
- saleConfig {
- publicSalePrice
- maxSalePurchasePerAddress
- publicSaleStart
- publicSaleEnd
- presaleStart
- presaleEnd
- presaleMerkleRoot
- }
- description
- animationURI
- imageURI
- referrer
- }
- author {
- id
- address
- userName
- displayName
- profileAvatar
- }
- }
- }
- }`,
- variables: {
- contestId,
- },
- }),
- })
- .then((res) => res.json())
- .then((res) => res.data.contest)
- .then(res => res.submissions)
- .then(async (submissions) => {
- return await Promise.all(
- submissions.map(async (submission, idx) => {
- const data = await fetch(submission.url).then((res) => res.json());
- return { ...submission, data: data };
- })
- );
- })
- return data;
-};
-
-
-
-const useLiveSubmissions = (contestId: string) => {
- const {
- data: liveSubmissions,
- isLoading: areSubmissionsLoading,
- error: isSubmissionError,
- mutate: mutateSWRSubmissions,
- }: { data: Array; isLoading: boolean; error: any; mutate: any } = useSWR(
- `submissions/${contestId}`,
- () => fetchSubmissions(contestId),
- { refreshInterval: 10000 }
- );
-
- const mutateLiveSubmissions = () => {
- mutateSWRSubmissions(); // mutate the SWR cache
- mutateSubmissions(contestId); // mutate the server cache
- };
-
- return {
- liveSubmissions,
- areSubmissionsLoading,
- isSubmissionError,
- mutateLiveSubmissions,
- }
-}
-
-
-export default useLiveSubmissions;
\ No newline at end of file
diff --git a/uplink-client/src/hooks/useMe.ts b/uplink-client/src/hooks/useMe.ts
deleted file mode 100644
index 3189e097..00000000
--- a/uplink-client/src/hooks/useMe.ts
+++ /dev/null
@@ -1,124 +0,0 @@
-import handleNotFound from "@/lib/handleNotFound";
-import { useSession } from "@/providers/SessionProvider";
-import { BaseSubmission, Submission } from "@/types/submission";
-import { User } from "@/types/user";
-import useSWR, { useSWRConfig } from "swr";
-
-const fetchMe = async (csrfToken: string) => {
- const data = await fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/graphql`, {
- method: "POST",
- credentials: "include",
- headers: {
- "Content-Type": "application/json",
- "X-CSRF-TOKEN": csrfToken,
- },
- body: JSON.stringify({
- query: `
- query Me {
- me {
- id
- address
- displayName
- profileAvatar
- submissions {
- id
- contestId
- type
- version
- url
- edition {
- id
- chainId
- contractAddress
- name
- symbol
- editionSize
- royaltyBPS
- fundsRecipient
- defaultAdmin
- saleConfig {
- publicSalePrice
- maxSalePurchasePerAddress
- publicSaleStart
- publicSaleEnd
- presaleStart
- presaleEnd
- presaleMerkleRoot
- }
- description
- animationURI
- imageURI
- referrer
- }
- author {
- address
- id
- displayName
- userName
- profileAvatar
- }
- }
- twitterAvatar
- twitterHandle
- userName
- visibleTwitter
- }
- }`,
- }),
- })
- .then((res) => res.json())
- .then((res) => res.data.me)
- .then(handleNotFound)
- .then(async (res) => {
- const subData = await Promise.all(
- res.submissions.map(async (submission: BaseSubmission) => {
- const data: Submission = await fetch(submission.url).then((res) => res.json());
- return { ...submission, data: data };
- })
- );
- return {
- ...res,
- submissions: subData
- }
- });
- return data;
-
-};
-
-
-
-
-const useMe = (accountAddress: string) => {
- const { data: session, status } = useSession();
- const { fallback } = useSWRConfig()
-
- const isAuthed = status === "authenticated" && accountAddress === session?.user?.address
- const meParamsSwrKey = isAuthed ? `me/${accountAddress}` : null
-
- const {
- data,
- isLoading: isMeLoading,
- error: isMeError,
- mutate: mutateMe
- }: { data: any; isLoading: boolean; error: any; mutate: any } = useSWR(
- meParamsSwrKey,
- () => fetchMe(session.csrfToken),
- );
-
-
- const getCachedFallback = () => {
- return fallback[`me/${accountAddress}`];
- }
-
- return {
- me: meParamsSwrKey ? data : getCachedFallback(),
- getFallbackData: () => getCachedFallback(),
- isUserAuthorized: isAuthed,
- mutateMe,
- isMeLoading,
- isMeError,
- }
-}
-
-
-export default useMe;
\ No newline at end of file
diff --git a/uplink-client/src/hooks/useMintBoardUserStats.ts b/uplink-client/src/hooks/useMintBoardUserStats.ts
deleted file mode 100644
index cf7bfd34..00000000
--- a/uplink-client/src/hooks/useMintBoardUserStats.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { handleMutationError } from "@/lib/handleMutationError";
-import { useSession } from "@/providers/SessionProvider";
-import useSWR from "swr";
-
-const fetchMintBoardUserStats = async (
- csrfToken: string,
- boardId: string
-) => {
- return fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/graphql`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- "X-CSRF-TOKEN": csrfToken,
- },
- credentials: "include",
- body: JSON.stringify({
- query: `
- query MintBoardUserStats($boardId: ID!) {
- mintBoardUserStats(boardId: $boardId) {
- totalMints
- }
- }`,
- variables: {
- csrfToken: csrfToken,
- boardId: boardId,
- },
- }),
- })
- .then((res) => res.json())
- .then(handleMutationError)
- .then((res) => res.data.mintBoardUserStats);
-}
-
-
-
-export const useMintBoardUserStats = (spaceName: string, boardId: string | null) => {
- const { data: session, status } = useSession();
- const key = (boardId && status === 'authenticated') ? `/api/mintBoardUserStats/${spaceName}/${session.user.address}` : null;
-
- const {
- data,
- isLoading,
- error,
- mutate
- }: { data: any; isLoading: boolean; error: any; mutate: any } = useSWR(
- key,
- () => fetchMintBoardUserStats(
- session.csrfToken,
- boardId
- ), { refreshInterval: 10_000 }
- );
-
- return {
- data,
- isLoading,
- error,
- mutate
- }
-
-}
\ No newline at end of file
diff --git a/uplink-client/src/hooks/useSpaceReducer.ts b/uplink-client/src/hooks/useSpaceReducer.ts
new file mode 100644
index 00000000..0dfa599f
--- /dev/null
+++ b/uplink-client/src/hooks/useSpaceReducer.ts
@@ -0,0 +1,126 @@
+"use client";;
+import { validateEthAddress } from '@/lib/ethAddress';
+import { createWeb3Client } from '@/lib/viem';
+import { normalize } from 'path';
+import { useReducer } from 'react';
+import { Address, getAddress } from 'viem';
+import { z } from 'zod';
+
+const mainnetClient = createWeb3Client(1);
+
+export const SpaceSettingsSchema = z.object({
+ name: z.string().min(3, { message: "Name must contain at least 3 characters" }).max(30).regex(/^[a-zA-Z0-9_ ]+$/, { message: "Name must only contain alphanumeric characters and underscores" }),
+ logoUrl: z.string().min(1, { message: "Logo is required" }),
+ website: z.string().optional(),
+ admins: z.array(z.string()),
+}).transform(async (data, ctx) => {
+
+ const name = data.name.trim();
+ const logoUrl = data.logoUrl.trim();
+ const website = data.website?.trim();
+ const admins = await Promise.all(data.admins.map(validateEthAddress));
+
+ //const anyNulls = admins.some((admin) => !admin);
+ const adminErrs = admins.map(admin => !admin ? true : false)
+ const hasErrs = adminErrs.some((err) => err);
+
+ if (hasErrs) {
+ const firstErrIdx = adminErrs.findIndex((err) => err);
+
+ ctx.addIssue({
+ path: ["admins", firstErrIdx],
+ code: z.ZodIssueCode.custom,
+ "message": "Invalid Ethereum address",
+ })
+ }
+
+ return {
+ name,
+ logoUrl,
+ website,
+ admins
+ }
+
+});
+
+export type SpaceSettingsInput = z.infer;
+export type SpaceSettingsOutput = z.output;
+
+type ZodSafeParseErrorFormat = {
+ [key: string]: { _errors: string[] };
+};
+
+export type SpaceSettingsStateT = SpaceSettingsInput & { errors: ZodSafeParseErrorFormat }
+
+export const baseConfig: SpaceSettingsStateT = {
+ name: "",
+ logoUrl: "",
+ website: "",
+ admins: [],
+ errors: {}
+}
+
+export const StateReducer = (state: SpaceSettingsStateT, action: { type: string, payload: any }) => {
+ switch (action.type) {
+ case "SET_FIELD":
+ return {
+ ...state,
+ [action.payload.field]: action.payload.value,
+ errors: { ...state.errors, [action.payload.field]: undefined }, // Clear error when field is set
+ };
+ case "SET_ERRORS":
+ return {
+ ...state,
+ errors: action.payload,
+ };
+ default:
+ return state;
+ }
+}
+
+export const useSpaceSettings = (priorState: SpaceSettingsStateT) => {
+
+ const initialState = { ...baseConfig, ...priorState };
+ const [state, dispatch] = useReducer(StateReducer, initialState);
+
+ const setField = (field: string, value: any) => {
+ dispatch({
+ type: 'SET_FIELD',
+ payload: { field, value },
+ });
+ }
+
+
+ const validateSettings = async () => {
+ const { errors, ...rest } = state;
+ const result = await SpaceSettingsSchema.safeParseAsync({
+ ...rest,
+ admins: state.admins.filter((admin) => admin !== "")
+ });
+ if (!result.success) {
+ const formattedErrors = (result as z.SafeParseError).error.format();
+ dispatch({
+ type: "SET_ERRORS",
+ payload: formattedErrors,
+ });
+ }
+
+ return result;
+ }
+
+ return {
+ state,
+ setField,
+ validateSettings
+ }
+
+}
+
+// export const NewSpaceSettingsSchema = SpaceSettingsSchema.transform(async (data, ctx) => {
+
+// })
+
+// export const EditSpaceSettingsSchema = SpaceSettingsSchema.transform(async (data, ctx) => {
+
+// })
+
diff --git a/uplink-client/src/hooks/useStandardSubmissionCreator.ts b/uplink-client/src/hooks/useStandardSubmissionCreator.ts
deleted file mode 100644
index a6f31e60..00000000
--- a/uplink-client/src/hooks/useStandardSubmissionCreator.ts
+++ /dev/null
@@ -1,173 +0,0 @@
-import type { OutputData } from "@editorjs/editorjs";
-import { useReducer } from "react";
-import { toast } from "react-hot-toast";
-
-export type SubmissionBuilderProps = {
- title: string;
- videoAsset: string | null;
- previewAsset: string | null;
- submissionBody: OutputData | null;
- errors: {
- type?: string;
- title?: string;
- previewAsset?: string;
- videoAsset?: string;
- submissionBody?: string;
- };
-};
-
-
-export const reducer = (state: SubmissionBuilderProps, action: any) => {
- switch (action.type) {
- case "SET_FIELD":
- return {
- ...state,
- [action.payload.field]: action.payload.value,
- errors: {
- ...state.errors,
- [action.payload.field]: undefined,
- },
- };
-
- case "SET_ERRORS":
- return {
- ...state,
- errors: action.payload,
- };
- default:
- return state;
- }
-};
-
-export const useStandardSubmissionCreator = () => {
- const [state, dispatch] = useReducer(reducer, {
- title: "",
- previewAsset: null,
- videoAsset: null,
- submissionBody: null,
- errors: {},
- });
-
-
- const setSubmissionTitle = (value: string) => {
- dispatch({
- type: "SET_FIELD",
- payload: { field: "title", value },
- });
- }
-
- const setSubmissionBody = (value: OutputData) => {
- dispatch({
- type: "SET_FIELD",
- payload: { field: "submissionBody", value },
- });
- }
-
- const setPreviewAsset = (value: string) => {
- dispatch({
- type: "SET_FIELD",
- payload: { field: "previewAsset", value },
- });
- }
-
- const setVideoAsset = (value: string) => {
- dispatch({
- type: "SET_FIELD",
- payload: { field: "videoAsset", value },
- });
- }
-
- const setErrors = (value: any) => {
- dispatch({
- type: "SET_ERRORS",
- payload: value,
- });
- }
-
- return {
- submission: state,
- setSubmissionTitle,
- setSubmissionBody,
- setPreviewAsset,
- setVideoAsset,
- setErrors
- }
-
-}
-
-export const validateSubmission = async (state: SubmissionBuilderProps, onError: (data: any) => void) => {
- const {
- title,
- previewAsset,
- videoAsset,
- submissionBody,
- } = state;
-
- const isVideo = Boolean(videoAsset)
-
- if (!title) {
- toast.error("Please provide a title");
- onError({
- title: "Please provide a title",
- });
- return {
- isError: true,
- };
- }
-
- if (title.length < 3) {
- toast.error("Title must be at least 3 characters")
- onError({
- title: "Title must be at least 3 characters",
- })
- return {
- isError: true,
- };
- }
-
- if (title.length > 100) {
- toast.error("Title must be less than 100 characters")
- onError({
- title: "Title must be less than 100 characters",
- })
- return {
- isError: true,
- };
- }
-
- let type = null;
- if (isVideo) type = "video";
- else if (previewAsset) type = "image";
- else if (submissionBody) type = "text";
-
- if (!type) {
- toast.error("Please upload media or provide body content")
- onError({
- type: "Please upload media or provide body content",
- })
- return {
- isError: true,
- };
- }
-
- if (type === "text" && submissionBody?.blocks.length === 0) {
- toast.error("Please provide a submission body")
- onError({
- submissionBody: "Please provide a submission body",
- })
- return {
- isError: true,
- };
- }
-
- return {
- isError: false,
- payload: {
- title,
- body: submissionBody,
- previewAsset: previewAsset ? previewAsset : null,
- videoAsset : videoAsset ? videoAsset : null
- }
- };
-
-}
\ No newline at end of file
diff --git a/uplink-client/src/hooks/useThreadCreator.ts b/uplink-client/src/hooks/useThreadCreator.ts
deleted file mode 100644
index 388c4750..00000000
--- a/uplink-client/src/hooks/useThreadCreator.ts
+++ /dev/null
@@ -1,393 +0,0 @@
-import { useEffect, useReducer, useState } from "react";
-import { nanoid } from 'nanoid';
-import handleMediaUpload, { IpfsUpload, MediaUploadError } from "@/lib/twitterMediaUpload";
-import { toast } from "react-hot-toast";
-
-
-// client context thread item
-export type ThreadItem = {
- id: string;
- text: string;
- primaryAssetUrl: string | null;
- primaryAssetBlob: string | null;
- videoThumbnailUrl: string | null;
- videoThumbnailBlobIndex: number | null;
- videoThumbnailOptions: string[] | null;
- assetSize: number | null;
- assetType: string | null;
- isVideo: boolean;
- isUploading: boolean;
- error?: string;
-};
-
-// api context thread item
-export type ApiThreadItem = {
- text: string;
- previewAsset?: string;
- videoAsset?: string;
- assetSize?: number;
- assetType?: string;
-};
-
-
-const threadReducer = (
- state: ThreadItem[],
- action: any
-): ThreadItem[] => {
- switch (action.type) {
- case "SET_FIELD":
- return state.map((item) => {
- if (item.id === action.payload.id) {
- return {
- ...item,
- [action.payload.field]: action.payload.value,
- error: undefined,
- };
- } else {
- return item;
- }
- });
-
- case "ADD_TWEET":
- return [...state, action.payload];
- case "REMOVE_TWEET":
- return state.filter((item) => item.id !== action.payload);
- case "SET_ERROR":
- return state.map((item) => {
- if (item.id === action.payload.id) {
- return {
- ...item,
- error: action.payload,
- }
- }
- else {
- return item;
- }
- });
- case "RESET":
- return action.payload;
- default:
- return state;
- }
-}
-
-
-
-export const useThreadCreator = (initialThread?: ThreadItem[]) => {
-
- const [state, dispatch] = useReducer(threadReducer,
- initialThread ? initialThread.map((tweet) => {
- return { // if initialThread is passed, do nothing
- ...tweet,
- }
- }) : [{ // if initialThread is null, then use this default value
- id: nanoid(),
- text: "",
- primaryAssetUrl: null,
- primaryAssetBlob: null,
- videoThumbnailUrl: null,
- videoThumbnailBlobIndex: null,
- videoThumbnailOptions: null,
- assetSize: null,
- assetType: null,
- isVideo: false,
- isUploading: false,
- }]);
-
- useEffect(() => {
- return () => {
- reset();
- }
- }, [])
-
- const addTweet = () => {
- dispatch({
- type: "ADD_TWEET",
- payload: {
- id: nanoid(),
- text: "",
- primaryAssetUrl: null,
- primaryAssetBlob: null,
- videoThumbnailUrl: null,
- videoThumbnailBlobIndex: null,
- videoThumbnailOptions: null,
- assetSize: null,
- assetType: null,
- isVideo: false,
- isUploading: false,
- }
- });
- }
-
- const removeTweet = (id: string) => {
- dispatch({
- type: "REMOVE_TWEET",
- payload: id,
- });
- }
-
- const setTweetText = (id: string, text: string) => {
- dispatch({
- type: "SET_FIELD",
- payload: { id, field: 'text', value: text },
- });
-
- }
-
- const setTweetPrimaryAssetUrl = (id: string, url: string) => {
- dispatch({
- type: "SET_FIELD",
- payload: { id, field: 'primaryAssetUrl', value: url },
- });
- }
-
- const setTweetPrimaryAssetBlob = (id: string, blob: string) => {
- dispatch({
- type: "SET_FIELD",
- payload: { id, field: 'primaryAssetBlob', value: blob },
- });
- }
-
- const setTweetVideoThumbnailUrl = (id: string, url: string) => {
- dispatch({
- type: "SET_FIELD",
- payload: { id, field: 'videoThumbnailUrl', value: url },
- });
- }
-
- const setTweetVideoThumbnailBlobIndex = (id: string, index: number | null) => {
- dispatch({
- type: "SET_FIELD",
- payload: { id, field: 'videoThumbnailBlobIndex', value: index },
- });
- }
-
- const setTweetVideoThumbnailOptions = (id: string, options: string[]) => {
- dispatch({
- type: "SET_FIELD",
- payload: { id, field: 'videoThumbnailOptions', value: options },
- });
- }
-
- const setTweetAssetSize = (id: string, size: number) => {
- dispatch({
- type: "SET_FIELD",
- payload: { id, field: 'assetSize', value: size }
- })
- }
-
-
- const setTweetAssetType = (id: string, type: string) => {
- dispatch({
- type: "SET_FIELD",
- payload: { id, field: 'assetType', value: type }
- })
- }
-
- const removeTweetMedia = (id: string) => {
- setIsUploading(id, false);
- setIsVideo(id, false);
- setTweetPrimaryAssetUrl(id, null);
- setTweetPrimaryAssetBlob(id, null);
- setTweetVideoThumbnailUrl(id, null);
- setTweetVideoThumbnailBlobIndex(id, null);
- setTweetVideoThumbnailOptions(id, null);
- setTweetAssetSize(id, null);
- setTweetAssetType(id, null);
- }
-
-
- const setIsUploading = (id: string, isUploading: boolean) => {
- dispatch({
- type: "SET_FIELD",
- payload: { id, field: 'isUploading', value: isUploading },
- });
- }
-
- const setIsVideo = (id: string, isVideo: boolean) => {
- dispatch({
- type: "SET_FIELD",
- payload: { id, field: 'isVideo', value: isVideo },
- });
- }
-
-
- const reset = () => {
- dispatch({
- type: "RESET",
- payload: [{
- id: nanoid(),
- text: "",
- primaryAssetUrl: null,
- primaryAssetBlob: null,
- videoThumbnailUrl: null,
- videoThumbnailBlobIndex: null,
- assetSize: null,
- assetType: null,
- isVideo: false,
- isUploading: false,
- }]
- });
- }
-
- const handleFileChange = ({
- id,
- event,
- isVideo,
- mode
- }: {
- id: string;
- event: any;
- isVideo: boolean;
- mode: "primary" | "thumbnail";
- }) => {
- if (mode === "primary") {
- setIsUploading(id, true)
-
- handleMediaUpload(
- event,
- ["image", "video"],
- (mimeType) => {
- setIsVideo(id, mimeType.includes("video"));
- setTweetAssetType(id, mimeType)
- },
- (base64, mimeType) => {
- setTweetPrimaryAssetBlob(id, base64);
- },
- (ipfsUrl) => {
- setTweetPrimaryAssetUrl(id, ipfsUrl);
- setIsUploading(id, false);
- },
- (thumbnails) => {
- setTweetVideoThumbnailOptions(id, thumbnails);
- setTweetVideoThumbnailBlobIndex(id, 1);
- },
- (size) => {
- setTweetAssetSize(id, size)
- }
- ).catch((err) => {
-
- if (err instanceof MediaUploadError) {
- // toast the message
- toast.error(err.message)
- }
- else {
- // log the message and toast a generic error
- console.log(err)
- toast.error('Something went wrong. Please try again later.')
- }
-
- // clear out all the fields for the users next attempt
- removeTweetMedia(id);
-
- });
- }
- else if (mode === "thumbnail") {
- handleMediaUpload(
- event,
- ["image"],
- (mimeType) => { },
- (base64) => {
- const existingThumbnailOptions = state.filter((item) => item.id === id)[0].videoThumbnailOptions;
- setTweetVideoThumbnailOptions(id, [...existingThumbnailOptions, base64])
- setTweetVideoThumbnailBlobIndex(id, existingThumbnailOptions.length);
- },
- (ipfsUrl) => {
- setTweetVideoThumbnailUrl(id, ipfsUrl);
- },
-
- ).catch(() => {
-
- });
- }
- };
-
-
-
- // check for errors and produce a clean thread object
- const validateThread = async () => {
-
- if (state.length === 0 || !state) {
- return {
- isError: true,
- }
- }
-
-
- if (state.some((item) => item.isUploading)) {
- toast.error('One of your tweets is still uploading. Please wait for it to finish before submitting.')
- return {
- isError: true,
- }
- }
-
-
- for await (const item of state) {
-
- const hasText = item.text.trim().length > 0;
- const hasMedia = item.primaryAssetUrl !== null || item.primaryAssetBlob !== null;
-
- if (!hasText && !hasMedia) {
- toast.error('One of your tweets is empty. Please add text or media to continue.')
- return {
- isError: true,
- }
- }
-
- if (hasText) {
- if (item.text.length > 280) {
- toast.error('One of your tweets is too long. Please shorten it to continue.')
- return {
- isError: true,
- }
- }
- }
-
- if (hasMedia) {
- if (item.isVideo) {
- if (!item.videoThumbnailOptions[item.videoThumbnailBlobIndex]) {
- toast.error('One of your tweets is missing a video thumbnail. Please add a thumbnail to continue.')
- return {
- isError: true,
- }
- }
- else {
- // if there is a thumbnail, upload the blob and add it to the thread
- // convert the base64 to blob first
- const blob = await fetch(item.videoThumbnailOptions[item.videoThumbnailBlobIndex]).then(r => r.blob())
- await IpfsUpload(blob).then(url => {
- item.videoThumbnailUrl = url;
- }).catch(err => {
- console.log(err)
- toast.error('Something went wrong. Please try again.')
- return {
- isError: true,
- }
- })
- }
- }
- }
- }
-
- return {
- isError: false,
- }
-
- }
-
- return {
- thread: state,
- addTweet,
- removeTweet,
- removeTweetMedia,
- setTweetText,
- setTweetVideoThumbnailBlobIndex,
- handleFileChange,
- validateThread,
- resetThread: reset,
- setTweetPrimaryAssetUrl,
- setTweetVideoThumbnailUrl,
- setTweetPrimaryAssetBlob,
- }
-
-};
\ No newline at end of file
diff --git a/uplink-client/src/hooks/useTokenBalance.ts b/uplink-client/src/hooks/useTokenBalance.ts
index 5493de5c..10ac5ea0 100644
--- a/uplink-client/src/hooks/useTokenBalance.ts
+++ b/uplink-client/src/hooks/useTokenBalance.ts
@@ -1,7 +1,7 @@
import { NATIVE_TOKEN } from "@tx-kit/sdk";
import { useEffect, useState } from "react";
import { Address, erc20Abi, zeroAddress } from "viem";
-import { useAccount, useChainId, usePublicClient, useWalletClient } from "wagmi";
+import { usePublicClient, useWalletClient } from "wagmi";
export const useErc20Balance = (tokenContract: Address, chainId: number) => {
const [balance, setBalance] = useState(BigInt(0));
diff --git a/uplink-client/src/hooks/useTokenInfo.ts b/uplink-client/src/hooks/useTokenInfo.ts
index b8fcf787..b3c95bd2 100644
--- a/uplink-client/src/hooks/useTokenInfo.ts
+++ b/uplink-client/src/hooks/useTokenInfo.ts
@@ -2,8 +2,8 @@
import { getTokenInfo } from "@/lib/tokenInfo";
import { ChainId } from "@/types/chains";
import { NATIVE_TOKEN } from "@tx-kit/sdk";
-import { useEffect, useState } from "react";
-import { Address, checksumAddress, getAddress, isAddress, zeroAddress } from "viem";
+import { useState } from "react";
+import { Address, checksumAddress, isAddress, zeroAddress } from "viem";
import { useMemo } from "react";
export const useTokenInfo = (tokenContract: string, chainId: ChainId) => {
diff --git a/uplink-client/src/hooks/useTokens.ts b/uplink-client/src/hooks/useTokens.ts
index 356d5707..ef8c298c 100644
--- a/uplink-client/src/hooks/useTokens.ts
+++ b/uplink-client/src/hooks/useTokens.ts
@@ -1,8 +1,7 @@
-"use client";
+"use client";;
import useSWRInfinite from "swr/infinite";
import { FetchFiniteChannelTokensV2Response, FetchPopularTokensResponse, FetchTokenIntentsResponse, FetchTokensV1Response, FetchTokensV2Response } from "@/lib/fetch/fetchTokensV2";
import { ContractID } from "@/types/channel";
-import { useCallback } from "react";
export const fetchTokensV1_client = async (contractId: ContractID, pageSize: number, skip: number): Promise => {
return fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/v2/channel_tokensV1?contractId=${contractId}&pageSize=${pageSize}&skip=${skip}`, {
diff --git a/uplink-client/src/hooks/useTweetQueueStatus.ts b/uplink-client/src/hooks/useTweetQueueStatus.ts
deleted file mode 100644
index c0c577a4..00000000
--- a/uplink-client/src/hooks/useTweetQueueStatus.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import useSWR from "swr";
-
-
-// return whether a tweet has been queued for a given contest
-
-const getTweetQueueStatus = async (contestId: string) => {
- return fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/graphql`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- query: `
- query Query($contestId: ID!) {
- isContestTweetQueued(contestId: $contestId)
- }`,
- variables: {
- contestId,
- },
- }),
- })
-
- .then((res) => res.json())
- .then(res => res.data.isContestTweetQueued)
-};
-
-
-
-
-const useTweetQueueStatus = (contestId: string) => {
- const swrKey = `/api/tweetQueueStatus/${contestId}`
- const { data: isTweetQueued, error, isLoading, mutate: mutateIsTweetQueued } = useSWR(swrKey, () => getTweetQueueStatus(contestId), { refreshInterval: 1000 * 60 });
- return { isTweetQueued, isLoading, mutateIsTweetQueued };
-}
-
-
-export default useTweetQueueStatus;
\ No newline at end of file
diff --git a/uplink-client/src/hooks/useVote.ts b/uplink-client/src/hooks/useVote.ts
deleted file mode 100644
index 349bbe98..00000000
--- a/uplink-client/src/hooks/useVote.ts
+++ /dev/null
@@ -1,504 +0,0 @@
-"use client";;
-import { useEffect } from "react";
-
-import { toast } from "react-hot-toast";
-import { handleMutationError } from "@/lib/handleMutationError";
-import { Decimal } from "decimal.js";
-import { useSession } from "@/providers/SessionProvider";
-import { Submission } from "@/types/submission";
-import { useContestInteractionApi, UserVote, UserVotingParams } from "./useContestInteractionAPI";
-import { useLocalStorage } from "./useLocalStorage";
-
-// inherit voting params from contest interaction provider
-// provide an API for managing user votes
-
-
-
-export type VotableSubmission = Submission & {
- votes: string;
-}
-
-
-const apiCastVotes = async (
- contestId: string,
- castVotePayload: any,
- csrfToken: string | null
-): Promise => {
- return fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/graphql`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- "X-CSRF-Token": csrfToken,
- },
- credentials: "include",
- body: JSON.stringify({
- query: `
- mutation Mutation($contestId: ID!, $castVotePayload: [CastVotePayload!]!){
- castVotes(contestId: $contestId, castVotePayload: $castVotePayload){
- success
- userVotingParams {
- totalVotingPower
- votesRemaining
- votesSpent
- userVotes {
- id
- submissionId
- submissionUrl
- votes
- }
- }
- }
- }`,
- variables: {
- contestId,
- castVotePayload,
- },
- }),
- })
- .then((res) => res.json())
- .then(handleMutationError)
- .then((res) => res.data.castVotes);
-};
-
-const apiRemoveSingleVote = async (
- contestId: string,
- submissionId: string,
- csrfToken: string | null
-): Promise => {
- return fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/graphql`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- "X-CSRF-Token": csrfToken,
- },
- credentials: "include",
- body: JSON.stringify({
- query: `
- mutation Mutation($contestId: ID!, $submissionId: ID!){
- removeSingleVote(contestId: $contestId, submissionId: $submissionId){
- success
- userVotingParams {
- totalVotingPower
- votesRemaining
- votesSpent
- userVotes {
- id
- votes
- submissionId
- submissionUrl
- votes
- }
- }
- }
- }`,
- variables: {
- contestId,
- submissionId,
- },
- }),
- })
- .then((res) => res.json())
- .then(handleMutationError)
- .then((res) => res.data.removeSingleVote);
-};
-
-const apiRemoveAllVotes = async (
- contestId: string,
- csrfToken: string | null
-): Promise => {
- return fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/graphql`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- "X-CSRF-Token": csrfToken,
- },
- credentials: "include",
- body: JSON.stringify({
- query: `
- mutation Mutation($contestId: ID!){
- removeAllVotes(contestId: $contestId){
- success
- userVotingParams {
- totalVotingPower
- votesRemaining
- votesSpent
- userVotes {
- id
- votes
- submissionId
- submissionUrl
- votes
- }
- }
- }
- }`,
- variables: {
- contestId,
- },
- }),
- })
- .then((res) => res.json())
- .then(handleMutationError)
- .then((res) => res.data.removeAllVotes);
-};
-
-export interface VoteActionProps {
- removeAllVotes: () => void;
- removeSingleVote: (
- submissionId: string,
- mode: "current" | "proposed"
- ) => void;
- addProposedVote: (el: Submission) => void;
- updateVoteAmount: (id: string, newAmount: string, mode: "current" | "proposed") => void;
- submitVotes: () => void;
- areCurrentVotesDirty: boolean;
- areUserVotingParamsLoading: boolean;
- proposedVotes: Array;
- totalVotingPower: string;
- votesSpent: string;
- votesRemaining: string;
- currentVotes: Array;
-}
-
-
-export const useVote = (contestId: string) => {
- // userVoteParams will be undefined if not in the voting window TODO: verify this
- const { userVoteParams, areUserVotingParamsLoading, mutateUserVotingParams } = useContestInteractionApi(contestId);
- const [proposedVotes, setProposedVotes] = useLocalStorage>(`proposedVotes-${contestId}`, []);
- const [areCurrentVotesDirty, setAreCurrentVotesDirty] = useLocalStorage(`areVotesDirty-${contestId}`, false);
- const { data: session, status } = useSession();
-
- // handle cases where the user was signed out, added proposed votes, then signed in
- // if proposed votes already exist in current votes, remove them from proposed votes
-
- useEffect(() => {
- if (status === "authenticated" && userVoteParams?.userVotes.length > 0) {
- const newProposedVotes = proposedVotes.filter(
- (el) =>
- !userVoteParams?.userVotes.find(
- (vote: UserVote) => vote?.submissionId === el.id
- )
- );
- setProposedVotes(newProposedVotes);
- }
- }, [status, userVoteParams?.userVotes]);
-
- // add submission to proposed votes
- const addProposedVote = (el: Submission) => {
- if (proposedVotes.find(vote => vote.id === el.id)) return toast.error("This selection is already in your cart.");
- if (userVoteParams) {
- if (userVoteParams?.userVotes?.find((vote: UserVote) => vote.submissionId === el.id)) return toast.error("This selection is already in your cart.");
- }
- setProposedVotes([...proposedVotes, { ...el, votes: "" }]);
- };
-
- // remove a single current or proposed vote.
- // proposed votes are local, current votes require an api request
- const removeSingleVote = async (
- submissionId: string,
- mode: "current" | "proposed"
- ) => {
- if (mode === "proposed") {
- //TODO: need to update the vote differentials here
- setProposedVotes(
- proposedVotes.filter((el) => el.id !== submissionId)
- );
- }
-
- if (mode === "current") {
- const toRemoveIdx = userVoteParams?.userVotes.findIndex(
- (el: UserVote) => el.submissionId === submissionId
- );
-
- // send the mutation request and optimistically update the cache
- // once the request completes, the cache will be updated with the response
- // if the request fails, the cache will be reverted to original value of userVoteParams
-
- const options = {
- optimisticData: {
- ...userVoteParams,
- votesRemaining: new Decimal(userVoteParams?.votesRemaining || "0")
- .plus(userVoteParams?.userVotes[toRemoveIdx].votes)
- .toString(),
- votesSpent: new Decimal(userVoteParams?.votesSpent || "0")
- .minus(userVoteParams?.userVotes[toRemoveIdx].votes)
- .toString(),
- userVotes: [
- ...userVoteParams?.userVotes.slice(0, toRemoveIdx),
- ...userVoteParams?.userVotes.slice(toRemoveIdx + 1),
- ],
- },
- populateCache: (newData: {
- userVotingParams: UserVotingParams;
- success: boolean;
- }) => {
- return newData.userVotingParams;
- },
- rollbackOnError: true,
- revalidate: false,
- };
- try {
- await mutateUserVotingParams(
- apiRemoveSingleVote(contestId, submissionId, session.csrfToken),
- options
- );
- toast.success("Your vote has been removed");
- } catch (err) {
- console.log(err);
- mutateUserVotingParams(userVoteParams, { revalidate: false });
- }
- }
- };
-
- // reset the total user vote state.
- const removeAllVotes = async () => {
- // if the user is signed out, just reset the local proposed vote state
-
- if (status !== "authenticated") return setProposedVotes([]);
-
- const previousProposedVotes = [...proposedVotes];
-
- // send the mutation request and optimistically update the cache
- // once the request completes, the cache will be updated with the response
- // if the request fails, the cache will be reverted to original value of userVoteParams
-
- const options = {
- optimisticData: () => {
- setProposedVotes([]);
- return {
- ...userVoteParams,
- votesRemaining: userVoteParams?.totalVotingPower || "0",
- votesSpent: "0",
- userVotes: [],
- };
- },
- populateCache: (newData: {
- userVotingParams: UserVotingParams;
- success: boolean;
- }) => {
- return newData.userVotingParams;
- },
- rollbackOnError: true,
- revalidate: false,
- };
- try {
- await mutateUserVotingParams(
- apiRemoveAllVotes(contestId, session.csrfToken),
- options
- );
- toast.success("Your votes have been removed");
- } catch (err) {
- console.log(err);
- setProposedVotes(previousProposedVotes);
- mutateUserVotingParams(userVoteParams, { revalidate: false });
- }
- };
-
- // update the amount of votes allocated to a submission
- const updateVoteAmount = (
- id: string,
- newAmount: string,
- mode: "current" | "proposed"
- ) => {
- // in proposed mode, we update the local state of the proposed votes along with the swr state of our user voting params
- // in current mode, we only update the swr state of our user voting params
-
- const constructVoteDifferential = () => {
- // return object should adjust the votesSpent and votesRemaining values
- const voteToUpdate =
- mode === "current"
- ? (userVoteParams?.userVotes || []).find(
- (vote) => vote.submissionId === id
- )
- : proposedVotes.find((vote) => vote.id === id);
-
- if (!voteToUpdate) return { votesSpent, votesRemaining };
-
- const oldAmountDecimal = new Decimal(voteToUpdate.votes || "0");
- const newAmountDecimal = new Decimal(newAmount || "0");
- const difference = newAmountDecimal.minus(oldAmountDecimal);
- voteToUpdate.votes = newAmount;
-
- const newVotesSpent = new Decimal(userVoteParams?.votesSpent ?? "0")
- .plus(difference)
- .toString();
-
- const newVotesRemaining = new Decimal(
- userVoteParams?.votesRemaining ?? "0"
- )
- .minus(difference)
- .toString();
-
- return {
- votesSpent: newVotesSpent,
- votesRemaining: newVotesRemaining,
- };
- };
-
- const { votesSpent, votesRemaining } = constructVoteDifferential();
-
- if (mode === "proposed") {
- // update the proposed votes to match new value
- setProposedVotes((prevVotes) => {
- const voteIndex = prevVotes.findIndex(
- (vote) => vote.id === id
- );
- if (voteIndex < 0) return prevVotes;
- return [
- ...prevVotes.slice(0, voteIndex),
- { ...prevVotes[voteIndex], votes: newAmount },
- ...prevVotes.slice(voteIndex + 1),
- ];
- });
- // update the user voting params swr state to reflect the new differential
- mutateUserVotingParams(
- {
- ...userVoteParams,
- votesSpent,
- votesRemaining,
- },
- {
- revalidate: false,
- }
- );
- } else if (mode === "current") {
- // update the user voting params swr state to reflect the new differential
- const voteIndex = userVoteParams.userVotes.findIndex(
- (vote: UserVote) => vote.submissionId === id
- );
- const newUserVotes =
- voteIndex < 0
- ? [...userVoteParams.userVotes]
- : [
- ...userVoteParams.userVotes.slice(0, voteIndex),
- { ...userVoteParams.userVotes[voteIndex], votes: newAmount },
- ...userVoteParams.userVotes.slice(voteIndex + 1),
- ];
- setAreCurrentVotesDirty(true);
- mutateUserVotingParams(
- {
- ...userVoteParams,
- userVotes: newUserVotes,
- votesSpent,
- votesRemaining,
- },
- {
- revalidate: false,
- }
- );
- }
- };
- // get the votes payload ready for the API request
- const prepareVotes = () => {
- let runningSum = new Decimal(0);
- let castVotePayload = [];
- let optimisticVotes = [];
-
- for (const el of userVoteParams.userVotes) {
- const decimalAmount = new Decimal(el.votes || "0");
- if (decimalAmount.greaterThan(0)) {
- runningSum = runningSum.plus(decimalAmount);
- castVotePayload.push({
- submissionId: el.submissionId,
- votes: el.votes,
- });
- optimisticVotes.push(el);
- }
- }
-
- for (const el of proposedVotes) {
- const decimalAmount = new Decimal(el.votes || "0");
- if (decimalAmount.greaterThan(0)) {
- runningSum = runningSum.plus(decimalAmount);
- castVotePayload.push({
- submissionId: el.id,
- votes: el.votes,
- });
- optimisticVotes.push({
- submissionId: el.id,
- votes: el.votes,
- submissionUrl: el.url,
- });
- }
- }
-
-
-
-
- return {
- runningSum,
- castVotePayload,
- optimisticVotes,
- };
- };
- // send votes to the API
- const submitVotes = async () => {
- const { runningSum, castVotePayload, optimisticVotes } = prepareVotes();
- if (castVotePayload.length === 0)
- return toast.error("Please add votes to your selections");
- if (runningSum.greaterThan(userVoteParams.totalVotingPower))
- return toast.error("Insufficient voting power");
-
- const prevProposedVotes = [...proposedVotes];
- const prevVotesDirty = JSON.parse(JSON.stringify(areCurrentVotesDirty));
-
- // send the mutation request and optimistically update the cache
- // once the request completes, the cache will be updated with the response
- // if the request fails, the cache will be reverted to original value of userVoteParams
-
- const options = {
- optimisticData: () => {
- setProposedVotes([]);
- setAreCurrentVotesDirty(false);
- return {
- ...userVoteParams,
- userVotes: optimisticVotes,
- votesRemaining: new Decimal(userVoteParams?.votesRemaining || "0")
- .minus(runningSum)
- .toString(),
- votesSpent: new Decimal(userVoteParams?.votesSpent || "0")
- .plus(runningSum)
- .toString(),
- };
- },
- populateCache: (newData: {
- userVotingParams: UserVotingParams;
- success: boolean;
- }) => {
- return newData.userVotingParams;
- },
- rollbackOnError: true,
- revalidate: false,
- };
-
- try {
- await mutateUserVotingParams(
- apiCastVotes(contestId, castVotePayload, session.csrfToken),
- options
- );
- toast.success("Your votes have been submitted");
- } catch (err) {
- console.log(err);
- setProposedVotes(prevProposedVotes);
- setAreCurrentVotesDirty(prevVotesDirty);
- mutateUserVotingParams(userVoteParams, { revalidate: false });
- }
- };
-
- return {
- removeAllVotes,
- removeSingleVote,
- addProposedVote,
- updateVoteAmount,
- submitVotes,
- // fwd the user voting params from interaction provider
- areUserVotingParamsLoading,
- totalVotingPower: userVoteParams?.totalVotingPower || "0",
- votesSpent: userVoteParams?.votesSpent || "0",
- votesRemaining: userVoteParams?.votesRemaining || "0",
- currentVotes: userVoteParams?.userVotes || [],
- // along with endpoints from this API
- areCurrentVotesDirty,
- proposedVotes,
- }
-}
diff --git a/uplink-client/src/hooks/useWalletDisplay.ts b/uplink-client/src/hooks/useWalletDisplay.ts
index d2bbe732..c1bf38e3 100644
--- a/uplink-client/src/hooks/useWalletDisplay.ts
+++ b/uplink-client/src/hooks/useWalletDisplay.ts
@@ -1,8 +1,7 @@
-import { useEffect, useState, useCallback } from 'react';
+import { useState, useCallback } from 'react';
import { createWeb3Client } from '@/lib/viem'; // adjust the import path as necessary
import debounce from 'lodash/debounce';
import { Address } from 'viem';
-import { normalize } from 'viem/ens';
// Create a Web3 client instance
const client = createWeb3Client(1);
diff --git a/uplink-client/src/hooks/useWindowSize.ts b/uplink-client/src/hooks/useWindowSize.ts
deleted file mode 100644
index e2de8b63..00000000
--- a/uplink-client/src/hooks/useWindowSize.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { useState, useEffect } from 'react';
-
-interface Size {
- width: number | undefined;
- height: number | undefined;
-}
-
-const useWindowSize = (): Size => {
- const [windowSize, setWindowSize] = useState({
- width: undefined,
- height: undefined,
- });
-
- useEffect(() => {
- function handleResize() {
- setWindowSize({
- width: window.innerWidth,
- height: window.innerHeight,
- });
- }
-
- window.addEventListener('resize', handleResize);
-
- handleResize();
-
- return () => window.removeEventListener('resize', handleResize);
- }, []);
-
- return windowSize;
-}
-
-export default useWindowSize;
diff --git a/uplink-client/src/lib/OptimizedImage.tsx b/uplink-client/src/lib/OptimizedImage.tsx
new file mode 100644
index 00000000..df0a2b2b
--- /dev/null
+++ b/uplink-client/src/lib/OptimizedImage.tsx
@@ -0,0 +1,58 @@
+"use client";
+import Image from "next/image";
+import { useEffect, useState } from "react";
+
+const OptimizedImage = ({
+ src,
+ alt,
+ sizes,
+ width,
+ height,
+ fill = false,
+ quality = 85,
+ draggable,
+ className
+}: {
+ src: string,
+ alt: string,
+ sizes?: string,
+ width?: number,
+ height?: number,
+ fill?: boolean,
+ quality?: number,
+ draggable?: boolean,
+ className?: string
+}) => {
+
+ const [error, setError] = useState(false)
+
+ const urlWidth = width ? `img-width=${width}` : ''
+ const urlHeight = height ? `&img-height=${height}` : ''
+ const urlQuality = `&img-quality=${quality}`
+ const url = `${src}?${urlWidth}${urlHeight}${urlQuality}`
+
+ useEffect(() => {
+ setError(false)
+ }, [url])
+
+ const props = {
+ ...(width && { width }),
+ ...(height && { height }),
+ ...(sizes && { sizes }),
+ fill,
+ draggable,
+ className,
+ }
+
+ return (
+ setError(true)}
+ {...props}
+ />
+ )
+
+}
+
+export default OptimizedImage;
\ No newline at end of file
diff --git a/uplink-client/src/lib/UplinkImage.tsx b/uplink-client/src/lib/UplinkImage.tsx
deleted file mode 100644
index b77b7c3a..00000000
--- a/uplink-client/src/lib/UplinkImage.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-"use client";
-import Image, { StaticImageData } from "next/image";
-import { useState } from "react";
-const normalizeSrc = (src) => {
- return src.startsWith('/') ? process.env.NEXT_PUBLIC_CLIENT_URL + src : src;
-};
-
-
-export const imageLoader = ({ src, width, quality }) => {
- const isObjectURL = !src.startsWith('http')
- const qualitySetting = quality || 'auto:best'; // default to auto:good if not specified
- const modifiers = `w_${width},q_${qualitySetting},c_limit,f_auto`; // c_fill for Cloudinary fill mode
- return isObjectURL ? src : `https://res.cloudinary.com/drrkx8iye/image/fetch/${modifiers}/${normalizeSrc(src)}`;
-};
-
-
-export const blurLoader = ({ src, width, quality }) => {
- const isObjectURL = !src.startsWith('http')
- const adjustedWidth = width > 200 ? 200 : width
- const modifiers = `w_${adjustedWidth},q_auto:low,e_blur:2000,c_limit,f_auto`; // c_fill for Cloudinary fill mode
- return isObjectURL ? src : `https://res.cloudinary.com/drrkx8iye/image/fetch/${modifiers}/${normalizeSrc(src)}`;
-};
-
-export default function UplinkImage(props: { src: string | StaticImageData, alt: string, width?: number, height?: number, fill?: boolean, sizes?: string, className?: string, blur?: boolean, quality?: number, draggable?: boolean, priority?: boolean }) {
- const { src, sizes, alt, blur = true, className, ...rest } = props;
- const [isPlaceholderError, setIsPlaceholderError] = useState(false);
- const [hasLoaded, setHasLoaded] = useState(false);
-
-
-
- // if (blur) {
- // const blurredSrc = typeof src === 'string' ? blurLoader({ src, width: 200, quality: '1' }) : '';
-
- // return (
- // <>
- // {/* {!isPlaceholderError && !hasLoaded && setIsPlaceholderError(true)}
- // alt={alt}
- // sizes={sizes}
- // className={`${className}`}
- // {...rest}
- // unoptimized={true}
-
- // />
- // }
- // */}
-
- // {!isPlaceholderError && !hasLoaded && setIsPlaceholderError(true)}
- // alt={alt}
- // sizes={sizes}
- // className={className}
- // {...rest}
- // unoptimized={true}
-
- // />
- // }
- // setHasLoaded(true)}
- // src={src}
- // alt={alt}
- // sizes={sizes}
- // className={`${className} ${hasLoaded ? 'opacity-100' : 'opacity-0'}`}
- // {...rest}
- // />
- // >
- // );
- // }
-
-
- return (
-
- )
-
-}
diff --git a/uplink-client/src/lib/blockParser.tsx b/uplink-client/src/lib/blockParser.tsx
index 3e4075be..dd0e42c1 100644
--- a/uplink-client/src/lib/blockParser.tsx
+++ b/uplink-client/src/lib/blockParser.tsx
@@ -1,9 +1,10 @@
"use client";
-import { ImageWrapper } from "@/ui/Submission/MediaWrapper";
+import { ImageWrapper } from "@/app/(legacy)/contest/components/MediaWrapper";
import type { OutputData } from "@editorjs/editorjs";
import React, { useEffect } from "react";
import Output, { LinkToolOutput, ListOutput, ParagraphOutput } from 'editorjs-react-renderer';
-import UplinkImage from "@/lib/UplinkImage"
+import OptimizedImage from "@/lib/OptimizedImage"
+
const createLinks = (text: string): string => {
const urlRegex = /(https?:\/\/[^\s]+)/g;
const twitterRegex = /([^\S]|^)@(\w+)/gi;
@@ -22,7 +23,7 @@ const ImageRenderer = ({ data }: { data: any }) => {
return (
- {
- return (
-
-
-
- )
-}
-
-
diff --git a/uplink-client/src/lib/farcaster/utils.ts b/uplink-client/src/lib/farcaster/utils.ts
index cfdc67ca..39664b25 100644
--- a/uplink-client/src/lib/farcaster/utils.ts
+++ b/uplink-client/src/lib/farcaster/utils.ts
@@ -1,16 +1,24 @@
import { FrameRequest, MockFrameRequest, FrameValidationResponse, FrameMetadataHtmlResponse } from "./types";
import { neynarFrameValidation } from "./neynar";
+import probe from "probe-image-size";
-export const calculateImageAspectRatio = async (url: string) => {
+export const calculateImageAspectRatio = async (url: string): Promise => {
try {
- const fileInfo = await fetch(`https://res.cloudinary.com/drrkx8iye/image/fetch/fl_getinfo/${url}`).then(res => res.json())
- const { output } = fileInfo;
- if (output.width / output.height > 1.45) return "1.91:1";
- return "1:1"
+ const fileInfo = await probe(url);
+ const ratio = fileInfo.width / fileInfo.height;
+
+ if (ratio > 1.45) {
+ // Landscape: Width is greater than height
+ return "1.91:1";
+ }
+ // Square or portrait: Height is equal to or greater than width
+ return "1:1";
} catch (e) {
- return "1:1"
+ console.error("Error calculating aspect ratio:", e);
+ return "1:1";
}
-}
+};
+
type FrameMessageOptions =
| {
diff --git a/uplink-client/src/lib/fetch/fetchContest.ts b/uplink-client/src/lib/fetch/fetchContest.ts
deleted file mode 100644
index 53352523..00000000
--- a/uplink-client/src/lib/fetch/fetchContest.ts
+++ /dev/null
@@ -1,164 +0,0 @@
-import { ReadableContest } from "@/types/contest";
-import handleNotFound from "../handleNotFound";
-
-
-export type FetchSingleContestResponse = ReadableContest & {
- space: {
- id: string;
- name: string;
- displayName: string;
- logoUrl: string;
- admins: Array<{
- address: string;
- }>;
- };
-}
-
-
-const fetchContest = async (contestId: string): Promise => {
- const data = await fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/graphql`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- "X-API-TOKEN": process.env.API_SECRET!,
- },
- body: JSON.stringify({
- query: `
- query Contest($contestId: ID!) {
- contest(contestId: $contestId) {
- id
- spaceId
- chainId
- created
- promptUrl
- tweetId
- space {
- id
- name
- displayName
- logoUrl
- admins {
- address
- }
- }
- metadata {
- category
- type
- }
- deadlines {
- startTime
- voteTime
- endTime
- snapshot
- }
- votingPolicy {
- ... on ArcadeVotingStrategyOption {
- strategyType
- arcadeVotingStrategy {
- token {
- tokenHash
- type
- symbol
- decimals
- address
- tokenId
- }
- votingPower
- }
- }
- ... on WeightedVotingStrategyOption {
- strategyType
- weightedVotingStrategy {
- token {
- tokenHash
- type
- symbol
- decimals
- address
- tokenId
- }
- }
- }
- }
- submitterRewards {
- rank
- reward {
- ... on SubmitterTokenReward {
- tokenReward {
- ... on FungibleReward {
- token {
- tokenHash
- type
- symbol
- decimals
- address
- tokenId
- }
- amount
- }
- ... on NonFungibleReward {
- token {
- tokenHash
- type
- symbol
- decimals
- address
- tokenId
- }
- tokenId
- }
- }
- }
- }
- }
- voterRewards {
- rank
- reward {
- ... on VoterTokenReward {
- tokenReward {
- ... on FungibleReward {
- amount
- token {
- tokenHash
- type
- symbol
- decimals
- address
- tokenId
- }
- }
- }
- }
- }
- }
- submitterRestrictions {
- ... on TokenRestrictionOption {
- restrictionType
- tokenRestriction {
- threshold
- token {
- tokenHash
- type
- symbol
- decimals
- address
- tokenId
- }
- }
- }
- }
- }
- }`,
- variables: {
- contestId,
- },
- }),
- next: { tags: [`contest/${contestId}`], revalidate: 60 },
- })
- .then((res) => res.json())
- .then((res) => res.data.contest)
- .then(handleNotFound)
- return data;
-};
-
-export default fetchContest;
\ No newline at end of file
diff --git a/uplink-client/src/lib/fetch/fetchLegacyContest.ts b/uplink-client/src/lib/fetch/fetchLegacyContest.ts
new file mode 100644
index 00000000..83bc7a56
--- /dev/null
+++ b/uplink-client/src/lib/fetch/fetchLegacyContest.ts
@@ -0,0 +1,30 @@
+import { LegacyContest } from "@/types/contest";
+import handleNotFound from "../handleNotFound";
+import { BaseSubmission, Submission } from "@/types/submission";
+
+const fetchLegacyContest = async (contestId: string): Promise => {
+ const data = await fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/v2/legacy_singleContest?id=${contestId}`, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ "X-API-TOKEN": process.env.API_SECRET!,
+ },
+ next: { tags: [`contest/${contestId}`] },
+ })
+ .then((res) => res.json())
+ .then(handleNotFound)
+ .then(async data => {
+ return {
+ ...data,
+ submissions: await Promise.all(
+ data.submissions.map(async (submission: BaseSubmission) => {
+ const data: Submission = await fetch(submission.url).then((res) => res.json());
+ return { ...submission, data: data };
+ })
+ )
+ }
+ })
+ return data;
+}
+
+export default fetchLegacyContest;
\ No newline at end of file
diff --git a/uplink-client/src/lib/fetch/fetchMintBoard.ts b/uplink-client/src/lib/fetch/fetchMintBoard.ts
deleted file mode 100644
index c3d98065..00000000
--- a/uplink-client/src/lib/fetch/fetchMintBoard.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import { MintBoard } from "@/types/mintBoard";
-
-const fetchMintBoard = async (spaceName: string): Promise => {
- const data = await fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/graphql`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- "X-API-TOKEN": process.env.API_SECRET!,
- },
- body: JSON.stringify({
- query: `
- query mintBoard($spaceName: String!){
- mintBoard(spaceName: $spaceName) {
- id
- space {
- id
- logoUrl
- name
- displayName
- admins {
- address
- }
- }
- enabled
- threshold
- editionSize
- description
- chainId
- created
- boardTitle
- boardDescription
- name
- publicSaleEnd
- publicSalePrice
- publicSaleStart
- referrer
- spaceId
- symbol
- }
- }`,
- variables: {
- spaceName,
- },
- }),
- next: { tags: [`mintBoard/${spaceName}`], revalidate: 60 },
- })
- .then((res) => res.json())
- .then((res) => res.data.mintBoard)
- return data;
-};
-
-export default fetchMintBoard;
\ No newline at end of file
diff --git a/uplink-client/src/lib/fetch/fetchMintBoardPosts.ts b/uplink-client/src/lib/fetch/fetchMintBoardPosts.ts
deleted file mode 100644
index 651677e5..00000000
--- a/uplink-client/src/lib/fetch/fetchMintBoardPosts.ts
+++ /dev/null
@@ -1,136 +0,0 @@
-import { MintBoardPost, PaginatedMintBoardPosts } from "@/types/mintBoard";
-import handleNotFound from "../handleNotFound";
-
-export const fetchPaginatedMintBoardPosts = async (spaceName: string, lastCursor: string | null, limit: number): Promise => {
- const data = await fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/graphql`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- "X-API-TOKEN": process.env.API_SECRET!,
- },
- body: JSON.stringify({
- query: `
- query PaginatedMintBoardPosts($spaceName: String! $lastCursor: String $limit: Int!) {
- paginatedMintBoardPosts(spaceName: $spaceName lastCursor: $lastCursor limit: $limit) {
- posts {
- id
- created
- totalMints
- author {
- id
- address
- userName
- displayName
- profileAvatar
- }
- edition {
- id
- chainId
- contractAddress
- name
- symbol
- editionSize
- royaltyBPS
- fundsRecipient
- defaultAdmin
- saleConfig {
- publicSalePrice
- maxSalePurchasePerAddress
- publicSaleStart
- publicSaleEnd
- presaleStart
- presaleEnd
- presaleMerkleRoot
- }
- description
- animationURI
- imageURI
- referrer
- }
- }
- pageInfo {
- endCursor
- hasNextPage
- }
- }
- }`,
- variables: {
- spaceName,
- lastCursor,
- limit
- },
- }),
- next: { tags: [`mintBoard/${spaceName}/posts?lastCursor=${lastCursor}&limit=${limit}`], revalidate: 60 },
- })
- .then((res) => res.json())
- .then((res) => res.data.paginatedMintBoardPosts)
- .then(handleNotFound);
- return data;
-}
-
-
-export const fetchPopularMintBoardPosts = async (spaceName: string): Promise> => {
- const data = await fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/graphql`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- "X-API-TOKEN": process.env.API_SECRET!,
- },
- body: JSON.stringify({
- query: `
- query PopularMintBoardPosts($spaceName: String!){
- popularMintBoardPosts(spaceName: $spaceName) {
- id
- created
- totalMints
- author {
- id
- address
- userName
- displayName
- profileAvatar
- }
- edition {
- id
- chainId
- contractAddress
- name
- symbol
- editionSize
- royaltyBPS
- fundsRecipient
- defaultAdmin
- saleConfig {
- publicSalePrice
- maxSalePurchasePerAddress
- publicSaleStart
- publicSaleEnd
- presaleStart
- presaleEnd
- presaleMerkleRoot
- }
- description
- animationURI
- imageURI
- referrer
- }
- }
- }`,
- variables: {
- spaceName,
- },
- }),
- next: { tags: [`mintBoard/${spaceName}/popular`], revalidate: 60 },
- })
- .then((res) => res.json())
- .then((res) => res.data.popularMintBoardPosts)
- .then(handleNotFound)
- return data;
-}
-
-
-export const fetchSingleMintboardPost = async (spaceName: string, postId: string): Promise => {
- const offestPostId = parseInt(postId) + 1;
- const post = await fetchPaginatedMintBoardPosts(spaceName, offestPostId.toString(), 1).then(data => data.posts[0]);
- return post;
-}
\ No newline at end of file
diff --git a/uplink-client/src/lib/fetch/fetchPopularSubmissions.ts b/uplink-client/src/lib/fetch/fetchPopularSubmissions.ts
deleted file mode 100644
index 0d2b6168..00000000
--- a/uplink-client/src/lib/fetch/fetchPopularSubmissions.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-import { BaseSubmission, Submission } from "@/types/submission";
-
-const fetchPopularSubmissions = async (): Promise> => {
- const data = await fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/graphql`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- "X-API-TOKEN": process.env.API_SECRET!,
- },
- body: JSON.stringify({
- query: `
- query PopularSubmissions {
- popularSubmissions {
- contestId
- created
- id
- type
- url
- version
- edition {
- id
- chainId
- contractAddress
- name
- symbol
- editionSize
- royaltyBPS
- fundsRecipient
- defaultAdmin
- saleConfig {
- publicSalePrice
- maxSalePurchasePerAddress
- publicSaleStart
- publicSaleEnd
- presaleStart
- presaleEnd
- presaleMerkleRoot
- }
- description
- animationURI
- imageURI
- referrer
- }
- author {
- id
- address
- userName
- displayName
- profileAvatar
- }
- }
- }`,
- }),
- next: { revalidate: 60 }
- })
- .then((res) => res.json())
- .then((res) => res.data.popularSubmissions)
- .then(async (submissions) => {
- return Promise.all(
- submissions.map(async (submission: BaseSubmission, idx: number) => {
- const data = await fetch(submission.url).then((res) => res.json());
- return { ...submission, data: data };
- })
- );
- });
- return data;
-};
-
-export default fetchPopularSubmissions;
\ No newline at end of file
diff --git a/uplink-client/src/lib/fetch/fetchSingleSpace.ts b/uplink-client/src/lib/fetch/fetchSingleSpace.ts
index e0771098..b01f22af 100644
--- a/uplink-client/src/lib/fetch/fetchSingleSpace.ts
+++ b/uplink-client/src/lib/fetch/fetchSingleSpace.ts
@@ -1,52 +1,18 @@
import { Space } from "@/types/space";
-import handleNotFound from "../handleNotFound";
-const fetchSingleSpace = async (name: string): Promise => {
- const data = await fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/graphql`, {
- method: "POST",
+const fetchSingleSpace = async (spaceName: string): Promise => {
+
+ const data = await fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/v2/space?spaceName=${spaceName}`, {
+ method: "GET",
headers: {
"Content-Type": "application/json",
"X-API-TOKEN": process.env.API_SECRET!,
},
- body: JSON.stringify({
- query: `
- query space($name: String!){
- space(name: $name) {
- id
- name
- displayName
- logoUrl
- twitter
- website
- admins{
- address
- }
- spaceTokens {
- token {
- type
- address
- decimals
- symbol
- tokenId
- chainId
- }
- }
- }
- }`,
- variables: {
- name,
- },
- }),
- next: { tags: [`space/${name}`], revalidate: 60 },
+ next: { revalidate: 60, tags: [`space/${spaceName}`] },
})
- // .then(data =>{
- // console.log(data);
- // return data;
- // })
- .then((res) => res.json())
- .then((res) => res.data.space)
- .then(handleNotFound);
- return data;
-};
+ .then(res => res.json())
+
+ return data;
+}
export default fetchSingleSpace;
\ No newline at end of file
diff --git a/uplink-client/src/lib/fetch/fetchSingleSubmission.ts b/uplink-client/src/lib/fetch/fetchSingleSubmission.ts
index e4611dd3..2ad7e21d 100644
--- a/uplink-client/src/lib/fetch/fetchSingleSubmission.ts
+++ b/uplink-client/src/lib/fetch/fetchSingleSubmission.ts
@@ -1,73 +1,22 @@
import { BaseSubmission, Submission } from "@/types/submission";
-import handleNotFound from "../handleNotFound";
+
const fetchSingleSubmission = async (submissionId: string): Promise => {
- const data = await fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/graphql`, {
- method: "POST",
+ return fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/v2/singleLegacyContestSubmission?id=${submissionId}`, {
+ method: "GET",
headers: {
"Content-Type": "application/json",
"X-API-TOKEN": process.env.API_SECRET!,
},
- body: JSON.stringify({
- query: `
- query submission($submissionId: ID!){
- submission(submissionId: $submissionId) {
- id
- contestId
- created
- rank
- totalVotes
- type
- url
- version
- author {
- id
- address
- profileAvatar
- userName
- displayName
- }
- edition {
- id
- chainId
- contractAddress
- name
- symbol
- editionSize
- royaltyBPS
- fundsRecipient
- defaultAdmin
- saleConfig {
- publicSalePrice
- maxSalePurchasePerAddress
- publicSaleStart
- publicSaleEnd
- presaleStart
- presaleEnd
- presaleMerkleRoot
- }
- description
- animationURI
- imageURI
- referrer
- }
- }
- }`,
- variables: {
- submissionId,
- },
- }),
- next: { tags: [`submission/${submissionId}`], revalidate: 60 },
+
+ next: { tags: [`submission/${submissionId}`] },
+
})
- .then((res) => res.json())
- .then((res) => res.data.submission)
- .then(handleNotFound)
+ .then(res => res.json())
.then(async (res: BaseSubmission) => {
const data = await fetch(res.url).then((res) => res.json());
return { ...res, data: data };
})
-
- return data;
-};
+}
export default fetchSingleSubmission;
\ No newline at end of file
diff --git a/uplink-client/src/lib/fetch/fetchSpaceChannels.ts b/uplink-client/src/lib/fetch/fetchSpaceChannels.ts
index a65685d8..4104bbed 100644
--- a/uplink-client/src/lib/fetch/fetchSpaceChannels.ts
+++ b/uplink-client/src/lib/fetch/fetchSpaceChannels.ts
@@ -1,7 +1,16 @@
+import { ChainId } from "@/types/chains";
import { Channel } from "@/types/channel";
+import { LegacyContest } from "@/types/contest";
-const fetchSpaceChannels = async (spaceName: string): Promise> => {
- const data = await fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/v2/space_channels?spaceName=${spaceName}`, {
+
+export type SpaceChannels = {
+ finiteChannels: Array;
+ infiniteChannels: Array;
+ legacyContests: Array;
+}
+
+const fetchSpaceChannels = async (spaceName: string, chainId: ChainId): Promise => {
+ const data = await fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/v2/space_channels?spaceName=${spaceName}&chainId=${chainId}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
diff --git a/uplink-client/src/lib/fetch/fetchSpaceContests.ts b/uplink-client/src/lib/fetch/fetchSpaceContests.ts
deleted file mode 100644
index 462e25fa..00000000
--- a/uplink-client/src/lib/fetch/fetchSpaceContests.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-import handleNotFound from "../handleNotFound";
-import { Deadlines, Metadata, ContestPromptData } from '@/types/contest';
-
-
-
-export type SpaceContest = {
- id: string;
- tweetId: string | null;
- deadlines: Deadlines
- metadata: Metadata;
- promptUrl: string;
- promptData: ContestPromptData;
-}
-
-export type FetchSpaceContestResponse = {
- id: string;
- logoUrl: string;
- contests: Array
-}
-
-
-const fetchSpaceContests = async (spaceName: string): Promise => {
-
- const data = await fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/graphql`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- "X-API-TOKEN": process.env.API_SECRET!,
- },
- body: JSON.stringify({
- query: `
- query space($name: String!){
- space(name: $name) {
- id
- logoUrl
- contests {
- id
- tweetId
- promptUrl
- deadlines {
- endTime
- snapshot
- startTime
- voteTime
- }
- metadata {
- category
- type
- }
- }
- }
- }`,
- variables: {
- name: spaceName,
- },
- }),
- next: { tags: [`space/${spaceName}/contests`], revalidate: 60 },
- })
- .then((res) => res.json())
- .then(res => res.data.space)
- .then(handleNotFound)
- .then(async spaceWithContests => {
- const resolvedContests = await Promise.all(
- spaceWithContests.contests.map(async (contest) => {
- // fetch prompt url
- const promptData = await fetch(contest.promptUrl).then((res) =>
- res.json()
- );
- return {
- ...contest,
- promptData,
- };
- })
- );
- return {
- ...spaceWithContests,
- contests: resolvedContests,
- }
- })
- return data;
-}
-
-export default fetchSpaceContests;
\ No newline at end of file
diff --git a/uplink-client/src/lib/fetch/fetchSpaceStats.ts b/uplink-client/src/lib/fetch/fetchSpaceStats.ts
index 8a53e1cd..b0f19ceb 100644
--- a/uplink-client/src/lib/fetch/fetchSpaceStats.ts
+++ b/uplink-client/src/lib/fetch/fetchSpaceStats.ts
@@ -1,8 +1,6 @@
import { SpaceStats } from "@/types/spaceStats";
import { ChainId } from "@/types/chains";
-
-
const fetchSpaceStats = async (spaceName: string, chainId: ChainId): Promise => {
return fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/v2/space_stats?spaceName=${spaceName}&chainId=${chainId}`, {
method: "GET",
diff --git a/uplink-client/src/lib/fetch/fetchSpaces.ts b/uplink-client/src/lib/fetch/fetchSpaces.ts
index 6c748674..713854ea 100644
--- a/uplink-client/src/lib/fetch/fetchSpaces.ts
+++ b/uplink-client/src/lib/fetch/fetchSpaces.ts
@@ -1,27 +1,18 @@
import { Space } from "@/types/space";
const fetchSpaces = async (): Promise> => {
- return fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/graphql`, {
- method: "POST",
+
+ const data = await fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/v2/spaces`, {
+ method: "GET",
headers: {
"Content-Type": "application/json",
"X-API-TOKEN": process.env.API_SECRET!,
},
- body: JSON.stringify({
- query: `
- query Spaces{
- spaces{
- name
- displayName
- members
- logoUrl
- }
- }`,
- }),
- next: { tags: ["spaces"], revalidate: 60 },
+ next: { revalidate: 60, tags: [`spaces`] },
})
- .then((res) => res.json())
- .then((res) => res.data.spaces);
-};
-export default fetchSpaces;
\ No newline at end of file
+ .then(res => res.json())
+
+ return data;
+}
+export default fetchSpaces;
diff --git a/uplink-client/src/lib/fetch/fetchSubmissions.ts b/uplink-client/src/lib/fetch/fetchSubmissions.ts
deleted file mode 100644
index e2aac516..00000000
--- a/uplink-client/src/lib/fetch/fetchSubmissions.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-"use server";
-import { BaseSubmission, Submission } from "@/types/submission";
-import handleNotFound from "../handleNotFound";
-
-const fetchSubmissions = async (contestId: string): Promise> => {
- const data = await fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/graphql`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- "X-API-TOKEN": process.env.API_SECRET,
- },
- body: JSON.stringify({
- query: `
- query Query($contestId: ID!){
- contest(contestId: $contestId){
- submissions {
- id
- contestId
- author {
- id
- address
- profileAvatar
- userName
- displayName
- }
- totalVotes
- rank
- created
- type
- url
- version
- edition {
- id
- chainId
- contractAddress
- name
- symbol
- editionSize
- royaltyBPS
- fundsRecipient
- defaultAdmin
- saleConfig {
- publicSalePrice
- maxSalePurchasePerAddress
- publicSaleStart
- publicSaleEnd
- presaleStart
- presaleEnd
- presaleMerkleRoot
- }
- description
- animationURI
- imageURI
- referrer
- }
- }
- }
- }`,
- variables: {
- contestId,
- },
- }),
- next: { tags: [`submissions/${contestId}`], revalidate: 60 }, // cache submissions for 60 seconds
- })
- .then((res) => res.json())
- .then((res) => res.data.contest)
- .then(handleNotFound)
- .then(res => res.submissions)
- .then(async (submissions) => {
- return await Promise.all(
- submissions.map(async (submission: BaseSubmission) => {
- const data: Submission = await fetch(submission.url).then((res) => res.json());
- return { ...submission, data: data };
- })
- );
- });
- return data;
-};
-
-export default fetchSubmissions;
\ No newline at end of file
diff --git a/uplink-client/src/lib/fetch/fetchTrendingSpaces.ts b/uplink-client/src/lib/fetch/fetchTrendingSpaces.ts
deleted file mode 100644
index b33522db..00000000
--- a/uplink-client/src/lib/fetch/fetchTrendingSpaces.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import {Space} from "@/types/space";
-import handleNotFound from "../handleNotFound";
-
-const fetchTrendingSpaces = async (): Promise> => {
- return fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/graphql`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- "X-API-TOKEN": process.env.API_SECRET!,
- },
- body: JSON.stringify({
- query: `
- query trendingSpaces($limit: Int!){
- trendingSpaces(limit: $limit) {
- id
- name
- displayName
- logoUrl
- }
- }`,
- variables: { limit: 10 },
- }),
- next: { tags: [`trendingSpaces}`], revalidate: 60 },
- })
- .then((res) => res.json())
- .then((res) => res.data.trendingSpaces)
- .then(handleNotFound)
-};
-
-export default fetchTrendingSpaces;
\ No newline at end of file
diff --git a/uplink-client/src/lib/fetch/fetchUser.ts b/uplink-client/src/lib/fetch/fetchUser.ts
deleted file mode 100644
index 1c841c5d..00000000
--- a/uplink-client/src/lib/fetch/fetchUser.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-import { User } from "@/types/user";
-import handleNotFound from "../handleNotFound";
-import { BaseSubmission, Submission } from "@/types/submission";
-
-
-const fetchUser = async (userIdentifier: string): Promise => {
- const data = await fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/graphql`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- "X-API-TOKEN": process.env.API_SECRET!,
- },
- body: JSON.stringify({
- query: `
- query User($userIdentifier: String!) {
- user(userIdentifier: $userIdentifier) {
- id
- displayName
- userName
- address
- profileAvatar
- twitterHandle
- visibleTwitter
- submissions {
- id
- contestId
- type
- version
- url
- author {
- id
- address
- displayName
- userName
- profileAvatar
- }
- edition {
- id
- chainId
- contractAddress
- name
- symbol
- editionSize
- royaltyBPS
- fundsRecipient
- defaultAdmin
- saleConfig {
- publicSalePrice
- maxSalePurchasePerAddress
- publicSaleStart
- publicSaleEnd
- presaleStart
- presaleEnd
- presaleMerkleRoot
- }
- description
- animationURI
- imageURI
- referrer
- }
- }
- }
- }`,
- variables: {
- userIdentifier,
- },
- }),
- next: { tags: [`user/${userIdentifier}`], revalidate: 60 },
- })
- .then((res) => res.json())
- .then((res) => res.data.user)
- .then(handleNotFound)
- .then(async (res) => {
- const subData = await Promise.all(
- res.submissions.map(async (submission: BaseSubmission) => {
- const data: Submission = await fetch(submission.url).then((res) => res.json());
- return { ...submission, data: data };
- })
- );
- return {
- ...res,
- submissions: subData
- }
- });
- return data;
-};
-
-export default fetchUser;
\ No newline at end of file
diff --git a/uplink-client/src/lib/fetch/insertSpace.ts b/uplink-client/src/lib/fetch/insertSpace.ts
new file mode 100644
index 00000000..0e3ab337
--- /dev/null
+++ b/uplink-client/src/lib/fetch/insertSpace.ts
@@ -0,0 +1,36 @@
+"use client";;
+import { handleV2MutationError } from "./handleV2MutationError";
+import { SpaceSettingsOutput } from "@/hooks/useSpaceReducer";
+
+export type InsertSpaceArgs = SpaceSettingsOutput & {
+ csrfToken: string
+ spaceId?: string
+}
+
+
+export const insertSpace = async (url,
+ {
+ arg,
+ }: {
+ url: string
+ arg: InsertSpaceArgs
+ }
+) => {
+ return fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/v2/insert_space`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "X-CSRF-TOKEN": arg.csrfToken,
+ },
+ credentials: "include",
+ body: JSON.stringify({
+ spaceId: undefined,
+ name: arg.name,
+ logoUrl: arg.logoUrl,
+ website: arg.website,
+ admins: arg.admins,
+
+ })
+ })
+ .then(handleV2MutationError)
+}
diff --git a/uplink-client/src/lib/fetch/updateSpace.ts b/uplink-client/src/lib/fetch/updateSpace.ts
new file mode 100644
index 00000000..247f4e7c
--- /dev/null
+++ b/uplink-client/src/lib/fetch/updateSpace.ts
@@ -0,0 +1,36 @@
+"use client";;
+import { handleV2MutationError } from "./handleV2MutationError";
+import { SpaceSettingsOutput } from "@/hooks/useSpaceReducer";
+
+export type UpdateSpaceArgs = SpaceSettingsOutput & {
+ csrfToken: string
+ spaceId?: string
+}
+
+
+export const updateSpace = async (url,
+ {
+ arg,
+ }: {
+ url: string
+ arg: UpdateSpaceArgs
+ }
+) => {
+ return fetch(`${process.env.NEXT_PUBLIC_HUB_URL}/v2/update_space`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "X-CSRF-TOKEN": arg.csrfToken,
+ },
+ credentials: "include",
+ body: JSON.stringify({
+ spaceId: arg.spaceId,
+ name: arg.name,
+ logoUrl: arg.logoUrl,
+ website: arg.website,
+ admins: arg.admins,
+
+ })
+ })
+ .then(handleV2MutationError)
+}
diff --git a/uplink-client/src/lib/gramajo.json b/uplink-client/src/lib/gramajo.json
deleted file mode 100644
index d41e1d49..00000000
--- a/uplink-client/src/lib/gramajo.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "name": "The Covenant, Season 0",
- "description": "***Nominators:***\n\nHolders of at least one Behind The Screen With Gramajo podcast NFT on Base (via \\[pods.media/behind-the-screen]\\(https://pods.media/behind-the-screen))\n\n***Voters:***\n\nPast guests on Behind The Screen With Gramajo Onchain listeners of the podcast\n\n***Nomination:***\n\nOne guest nomination per proposal (you can submit multiple proposals for multiple guests, just create a new proposal each time)\n\nIn your proposal, please include the following:\n\n* Guest name\n* Guest's affiliation (company or project name)\n* Relevant links to their website or socials\n* Topics you'd like to hear them talk about\n* Why listeners would enjoy hearing from this guest\n\nKeep in mind the following ideas when nominating:\n\n* Our mission at 0773H is to distill the biggest news in web3, recommendations and go learn from up and coming artist, collectors, builders in web3 while being entertaining. High signal to noise ration. No spam--just the best content.\n* My audience is someone who wants to learn, grow and have fun in the space. Art is already high brow; tech can be difficult to understand. We don’t need to scare patrons with our attitudes but instead welcome them.\n* Be cool, do cool shit.\n\n\n\n***Process:***\n\nNominations open for a week through October 7. Voting starts immediately after through October 14th.",
- "image": "ipfs://QmVDqE2qcpouKG1jiEDMXaHyYViM7akwWS9Uy831BP7P2v",
- "content": {
- "uri": "ipfs://QmVDqE2qcpouKG1jiEDMXaHyYViM7akwWS9Uy831BP7P2v"
- }
-}
\ No newline at end of file
diff --git a/uplink-client/src/lib/threadParser.tsx b/uplink-client/src/lib/threadParser.tsx
index 36d26daf..f536fb55 100644
--- a/uplink-client/src/lib/threadParser.tsx
+++ b/uplink-client/src/lib/threadParser.tsx
@@ -1,9 +1,9 @@
import type { TwitterSubmission } from "@/types/submission";
-import { ImageWrapper } from "@/ui/Submission/MediaWrapper";
+import { ImageWrapper } from "@/app/(legacy)/contest/components/MediaWrapper";
import { RenderStandardVideoWithLoader } from "@/ui/VideoPlayer";
import Image from "next/image";
import sanitizeHtml from "sanitize-html";
-import UplinkImage from "@/lib/UplinkImage"
+import OptimizedImage from "@/lib/OptimizedImage"
const createLinks = (text: string): string => {
const urlRegex = /(https?:\/\/[^\s]+)/g;
const twitterRegex = /([^\S]|^)@(\w+)/gi;
@@ -43,7 +43,7 @@ export const ParseThread = ({
) : (
- {
- return fetch(dataUrl)
- .then(res => res.blob())
-}
-
-export const IpfsUpload = async (file: File | Blob) => {
-
- const formData = new FormData();
- formData.append('file', file);
-
- try {
- const response = await fetch("https://api.pinata.cloud/pinning/pinFileToIPFS", {
- method: 'POST',
- headers: {
- 'Authorization': `Bearer ${process.env.NEXT_PUBLIC_PINATA_JWT}`,
- },
- // @ts-expect-error
- body: formData
- });
-
- if (!response.ok) {
- console.error(`HTTP error! Status: ${response.status}`);
- return null
- }
-
- const responseData = await response.json();
- return `https://uplink.mypinata.cloud/ipfs/${responseData.IpfsHash}`;
- } catch (err) {
- console.error("Fetch error:", err);
- return null;
- }
-};
-
-const loadVideo = (file: File) =>
- new Promise((resolve, reject) => {
- try {
- let video = document.createElement('video');
- video.preload = 'metadata';
-
- // Create an object URL for the file
- const objectUrl = URL.createObjectURL(file);
- video.src = objectUrl;
-
- video.onloadedmetadata = function () {
- // Release the object URL after metadata is loaded
- URL.revokeObjectURL(objectUrl);
- resolve(this);
- }
-
- video.onerror = function () {
- reject("Invalid video. Please select a video file.");
- }
- } catch (e) {
- reject(e);
- }
- });
-
-const handleMediaUpload = async (
- event: any,
- acceptedFormats: string[],
- mimeTypeCallback: (mimeType: string) => void,
- readerCallback: (data: any, mimeType: string) => void,
- ipfsCallback: (uri: string, mimeType: string) => void,
- videoThumbnailCallback?: (thumbnails: string[]) => void,
- fileSizeCallback?: (size: number) => void,
-) => {
-
- const acceptedMimeTypes = acceptedFormats.reduce((acc: string[], format: string) => {
- if (format === 'image') {
- return [...acc, 'image/png', 'image/jpeg', 'image/jpg', 'image/gif'];
- }
- if (format === 'video') {
- return [...acc, 'video/mp4'];
- }
- if (format === 'svg') {
- return [...acc, 'image/svg+xml'];
- }
- return acc;
- }, []);
-
- const file = event.target.files[0];
- if (!file) {
- throw new MediaUploadError({ code: 1, message: 'No file selected' })
- }
-
- const mimeType = file.type;
- mimeTypeCallback(mimeType);
- const fileSize = file.size;
- if (fileSizeCallback) fileSizeCallback(fileSize)
-
- if (mimeType.includes("video")) {
- const video: any = await loadVideo(file);
-
- if (video.duration > 140) throw new MediaUploadError({ code: 2, message: 'Videos must be less than 140 seconds' });
-
- if (videoThumbnailCallback) { // if its a video and caller wants thumbnails, generate thumbnails
- const thumbnails = await generateVideoThumbnails(file, 3, 'jpeg');
- videoThumbnailCallback(thumbnails);
- }
- } else {
- if (fileSize > 5000000) throw new MediaUploadError({ code: 2, message: 'Images must be less than 5MB' });
- }
-
- if (!acceptedMimeTypes.includes(mimeType)) throw new MediaUploadError({ code: 3, message: 'Invalid file type.' });
-
- const reader = new FileReader();
- reader.readAsDataURL(file);
- reader.onload = () => {
- readerCallback(reader.result, mimeType);
- };
- reader.onerror = (error) => {
- throw new MediaUploadError({ code: 4, message: 'Error reading file' })
- };
-
- const response = await IpfsUpload(file);
- if (!response) throw new MediaUploadError({ code: 5, message: 'Error uploading to IPFS' })
-
- ipfsCallback(response, mimeType);
-};
-
-export default handleMediaUpload;
diff --git a/uplink-client/src/providers/ContestStateProvider.tsx b/uplink-client/src/providers/ContestStateProvider.tsx
deleted file mode 100644
index 540019c8..00000000
--- a/uplink-client/src/providers/ContestStateProvider.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-"use client";
-import { useTicks } from "@/hooks/useTicks";
-import { FetchSingleContestResponse } from "@/lib/fetch/fetchContest";
-import { ContestState } from "@/types/contest";
-import { calculateContestStatus } from "@/utils/staticContestState";
-import React, { createContext, useState } from "react"
-
-
-export type ContestStateContextValue = {
- contestId: string,
- chainId: number,
- contestState: ContestState | null,
- stateRemainingTime: string | null,
- isLoading: boolean,
- tweetId: string,
- type: string,
- category: string,
- contestAdmins: string[],
-}
-
-export const ContestStateContext = createContext?.(
- undefined
-);
-
-export const ContestStateProvider = ({ contest, children }: { contest: FetchSingleContestResponse, children: React.ReactNode }) => {
- const [contestState, setContestState] = useState(null);
- const [stateRemainingTime, setStateRemainingTime] = useState(null);
-
- const update = (data: FetchSingleContestResponse) => {
- if (!data) return
- const { contestState: v1, stateRemainingTime: v2 } = calculateContestStatus(data.deadlines, data.metadata.type, data.tweetId);
- setContestState(v1);
- setStateRemainingTime(v2)
- }
-
- useTicks(() => update(contest))
-
- const value = {
- contestId: contest.id,
- chainId: contest.chainId,
- isLoading: contestState === null,
- contestState,
- stateRemainingTime,
- tweetId: contest.tweetId,
- type: contest.metadata.type,
- category: contest.metadata.category,
- contestAdmins: contest.space.admins.map((admin) => admin.address),
- }
- return (
-
- {children}
-
- )
-}
-
-export const useContestState = () => {
- const context = React.useContext(ContestStateContext);
- if (context === undefined) {
- throw new Error(
- "useContestState must be used within a ContestStateProvider"
- );
- }
- return context;
-}
\ No newline at end of file
diff --git a/uplink-client/src/test/ContestHandler.test.ts b/uplink-client/src/test/ContestHandler.test.ts
deleted file mode 100644
index 350c82f1..00000000
--- a/uplink-client/src/test/ContestHandler.test.ts
+++ /dev/null
@@ -1,610 +0,0 @@
-import { describe, expect, test } from "@jest/globals";
-import { sampleERC1155Token, sampleERC20Token, sampleERC721Token, sampleETHToken } from "./sampleTokens";
-import { Prompt, SubmitterRewards, VoterRewards, VotingPolicyType, validateDeadlines, validateMetadata, validatePrompt, validateSubmitterRewards, validateVoterRewards, validateVotingPolicy } from "@/ui/ContestForm/contestHandler";
-
-
-
-describe("Contest Handler", () => {
- describe("Validate Contest Metadata", () => {
- test("fail with null values", () => {
- const { isError, errors, data } = validateMetadata({ type: null, category: null })
- expect(isError).toBe(true)
- expect(errors).toEqual({
- type: "Please select a contest type",
- category: "Please select a contest category",
- })
- })
-
- test("pass with valid inputs", () => {
- const { isError, errors, data } = validateMetadata({
- type: "standard",
- category: "art",
- })
- expect(isError).toBe(false)
- expect(errors).toEqual({
- type: "",
- category: "",
- })
- expect(data).toEqual({
- type: "standard",
- category: "art",
- })
- })
- });
-
- describe("Validate Contest Deadlines", () => {
- test("fail with empty string values", () => {
- const deadlines = {
- startTime: "",
- voteTime: "",
- endTime: "",
- snapshot: "",
- }
- const { isError, errors, data } = validateDeadlines(deadlines, false)
-
- expect(isError).toBe(true)
- expect(errors).toEqual({
- snapshot: "snapshot date is required",
- startTime: "start date is required",
- voteTime: "vote date is required",
- endTime: "end date is required",
- })
- expect(data).toEqual(deadlines)
- })
-
- test("fail with incorrect order #1", () => {
- const deadlines = {
- startTime: new Date(Date.now() + 5 * 864e5).toISOString(),
- voteTime: new Date(Date.now() + 4 * 864e5).toISOString(),
- endTime: new Date(Date.now() + 3 * 864e5).toISOString(),
- snapshot: new Date(Date.now() + 6 * 864e5).toISOString(),
- }
- const { isError, errors, data } = validateDeadlines(deadlines, false)
-
- expect(isError).toBe(true)
- expect(errors).toEqual({
- snapshot: "snapshot date must be less than or equal to start date",
- voteTime: "vote date must be after start date",
- endTime: "end date must be after start date",
- startTime: "",
- })
- expect(data).toEqual(deadlines)
- })
-
- test("fail with incorrect order #2", () => {
- const deadlines = {
- snapshot: new Date(Date.now() + 1 * 864e5).toISOString(),
- startTime: new Date(Date.now() + 2 * 864e5).toISOString(),
- voteTime: new Date(Date.now() + 4 * 864e5).toISOString(),
- endTime: new Date(Date.now() + 3 * 864e5).toISOString(),
- }
- const { isError, errors, data } = validateDeadlines(deadlines, false)
-
- expect(isError).toBe(true)
- expect(errors).toEqual({
- snapshot: "",
- voteTime: "",
- endTime: "end date must be after vote date",
- startTime: "",
- })
- expect(data).toEqual(deadlines)
- })
-
- test("pass with correct order", () => {
- const deadlines = {
- snapshot: new Date(Date.now() + 1 * 864e5).toISOString(),
- startTime: new Date(Date.now() + 2 * 864e5).toISOString(),
- voteTime: new Date(Date.now() + 3 * 864e5).toISOString(),
- endTime: new Date(Date.now() + 4 * 864e5).toISOString(),
- }
- const { isError, errors, data } = validateDeadlines(deadlines, false)
- expect(isError).toBe(false)
- expect(errors).toEqual({
- snapshot: "",
- startTime: "",
- voteTime: "",
- endTime: "",
-
- })
- expect(data).toEqual(deadlines)
- });
-
- test("pass with now for snapshot", () => {
- const deadlines = {
- snapshot: "now",
- startTime: new Date(Date.now() + 2 * 864e5).toISOString(),
- voteTime: new Date(Date.now() + 3 * 864e5).toISOString(),
- endTime: new Date(Date.now() + 4 * 864e5).toISOString(),
- }
- const { isError, errors, data } = validateDeadlines(deadlines, false)
- expect(isError).toBe(false)
- expect(errors).toEqual({
- snapshot: "",
- startTime: "",
- voteTime: "",
- endTime: "",
-
- })
- expect(data).toEqual(deadlines)
- })
- test("pass with now for startTime", () => {
- const deadlines = {
- snapshot: new Date(Date.now()).toISOString(),
- startTime: "now",
- voteTime: new Date(Date.now() + 3 * 864e5).toISOString(),
- endTime: new Date(Date.now() + 4 * 864e5).toISOString(),
- }
- const { isError, errors, data } = validateDeadlines(deadlines, false)
- expect(isError).toBe(false)
- expect(errors).toEqual({
- snapshot: "",
- startTime: "",
- voteTime: "",
- endTime: "",
-
- })
- expect(data).toEqual(deadlines)
- })
-
- test("pass with now for startTime + snapshot", () => {
- const deadlines = {
- snapshot: "now",
- startTime: "now",
- voteTime: new Date(Date.now() + 3 * 864e5).toISOString(),
- endTime: new Date(Date.now() + 4 * 864e5).toISOString(),
- }
- const { isError, errors, data } = validateDeadlines(deadlines, false)
- expect(isError).toBe(false)
- expect(errors).toEqual({
- snapshot: "",
- startTime: "",
- voteTime: "",
- endTime: "",
-
- })
- expect(data).toEqual(deadlines)
- })
-
- test("return cleaned data", () => {
- const deadlines = {
- snapshot: "now",
- startTime: "now",
- voteTime: new Date(Date.now() + 3 * 864e5).toISOString(),
- endTime: new Date(Date.now() + 4 * 864e5).toISOString(),
- }
- const { isError, errors, data } = validateDeadlines(deadlines, true)
- expect(isError).toBe(false)
- expect(errors).toEqual({
- snapshot: "",
- startTime: "",
- voteTime: "",
- endTime: "",
-
- })
- expect(data.voteTime).toEqual(deadlines.voteTime)
- expect(data.endTime).toEqual(deadlines.endTime)
- expect(data.snapshot.length).toBeGreaterThan(3) // check that it is not "now" anymore
- expect(data.startTime.length).toBeGreaterThan(3) // check that it is not "now" anymore
- })
- })
-
-
- describe("Validate Contest Prompt", () => {
-
- test("fail with invalid values", () => {
- const prompt: Prompt = {
- title: "",
- body: null,
- coverUrl: "https://google.com/image.png",
- coverBlob: "asdf"
- }
- const { isError, errors, data } = validatePrompt(prompt)
-
- expect(isError).toBe(true)
- expect(errors).toEqual({
- title: "Please provide a title",
- body: "Please provide a prompt body",
- coverUrl: "Invalid cover image",
- })
- expect(data).toEqual(prompt)
- })
-
-
- test("fail with ", () => {
- const prompt: Prompt = {
- title: "",
- body: null,
- coverUrl: "",
- coverBlob: ""
- }
- const { isError, errors, data } = validatePrompt(prompt)
-
- expect(isError).toBe(true)
- expect(errors).toEqual({
- title: "Please provide a title",
- body: "Please provide a prompt body",
- coverUrl: "",
- })
- expect(data).toEqual(prompt)
- })
-
- test("fail with empty blocks", () => {
- const prompt: Prompt = {
- title: " aaaaaa ",
- body: {
- blocks: []
- },
- coverUrl: "",
- coverBlob: ""
- }
- const { isError, errors, data } = validatePrompt(prompt)
-
- expect(isError).toBe(true)
- expect(errors).toEqual({
- title: "",
- body: "Please provide a prompt body",
- coverUrl: "",
- })
- expect(data).toEqual(prompt)
- })
-
- test("pass with valid inputs", () => {
- const prompt = {
- title: " aaaaaa ",
- body: {
- blocks: [{
- type: "paragraph",
- data: {
- text: "test"
- }
- }]
- },
- coverUrl: "https://uplink.mypinata.cloud/ipfs/QmZ1Z2Z3Z4Z5Z6Z7Z8Z9Z0",
- coverBlob: "asdf"
- }
- const { isError, errors, data } = validatePrompt(prompt)
-
- expect(isError).toBe(false)
- expect(errors).toEqual({
- title: "",
- body: "",
- coverUrl: "",
- })
- expect(data).toEqual(prompt)
- })
- });
-
-
- // remember that this function is also cleaning the data (for now)
- describe("Validate Submitter Rewards", () => {
-
- test("fail with duplicate ranks", () => {
- const rewards: SubmitterRewards = {
- ETH: sampleETHToken,
- payouts: [
- {
- rank: 1,
- ETH: { amount: '1' },
- },
- {
- rank: 1,
- ETH: { amount: '1' },
- },
- {
- rank: 2,
- ETH: { amount: '1' },
- },
- ],
- };
-
- const { isError, errors, data } = validateSubmitterRewards(rewards);
- expect(isError).toBe(true);
- expect(errors).toEqual({
- duplicateRanks: [1]
- });
- expect(data).toEqual(rewards);
-
- })
- test("pass with empty rewards", () => {
- const rewards: SubmitterRewards = {};
-
- const { isError, errors, data } = validateSubmitterRewards(rewards);
- expect(isError).toBe(false);
- expect(errors).toEqual({
- duplicateRanks: []
- });
- expect(data).toEqual(rewards);
-
- })
- test("pass with valid rewards", () => {
- const rewards: SubmitterRewards = {
- ETH: sampleETHToken,
- ERC20: sampleERC20Token,
- ERC721: sampleERC721Token,
- ERC1155: sampleERC1155Token,
- payouts: [
- {
- rank: 1,
- ETH: { amount: '1' },
- ERC20: { amount: '1' },
- ERC721: { tokenId: 1 },
- ERC1155: { amount: '2' },
- },
- {
- rank: 2,
- ETH: { amount: '1' },
- ERC20: { amount: '1' },
- ERC721: { tokenId: 1 },
- ERC1155: { amount: '2' },
- },
- ],
- };
-
- const { isError, errors, data } = validateSubmitterRewards(rewards);
- expect(isError).toBe(false);
- expect(errors).toEqual({
- duplicateRanks: []
- });
- expect(data).toEqual(rewards);
-
- })
- test("clean empty rewards #1", () => {
- const rewards: SubmitterRewards = {
- ETH: sampleETHToken,
- ERC20: sampleERC20Token,
- ERC721: sampleERC721Token,
- ERC1155: sampleERC1155Token,
- payouts: [
- {
- rank: 1,
- ETH: { amount: '1' },
- ERC20: { amount: '1' },
- ERC721: { tokenId: 1 },
- },
- {
- rank: 2,
- ETH: { amount: '1' },
- ERC20: { amount: '1' },
- ERC721: { tokenId: 1 },
- },
- ],
- };
-
- const { isError, errors, data } = validateSubmitterRewards(rewards);
- expect(isError).toBe(false);
- expect(errors).toEqual({
- duplicateRanks: []
- });
- expect(data).toEqual({ ...rewards, ERC1155: undefined });
- })
-
- test("clean empty rewards #2", () => {
- const rewards: SubmitterRewards = {
- ETH: sampleETHToken,
- ERC20: sampleERC20Token,
- ERC721: sampleERC721Token,
- payouts: [
- {
- rank: 1,
- ETH: { amount: '1' },
- ERC20: { amount: '1' },
- ERC721: { tokenId: null },
- },
- {
- rank: 2,
- ETH: { amount: '1' },
- ERC20: { amount: '' },
- ERC721: { tokenId: null },
- },
- ],
- };
-
- const { isError, errors, data } = validateSubmitterRewards(rewards);
- expect(isError).toBe(false);
- expect(errors).toEqual({
- duplicateRanks: []
- });
- expect(data).toEqual({
- ETH: sampleETHToken,
- ERC20: sampleERC20Token,
- payouts: [
- {
- rank: 1,
- ETH: { amount: '1' },
- ERC20: { amount: '1' },
- },
- {
- rank: 2,
- ETH: { amount: '1' },
- },
- ]
- });
- })
-
- test("clean empty rewards #3", () => {
- const rewards: SubmitterRewards = {
- ETH: sampleETHToken,
- ERC20: sampleERC20Token,
- ERC721: sampleERC721Token,
- payouts: [
- {
- rank: 1,
- ETH: { amount: '' },
- ERC20: { amount: '' },
- ERC721: { tokenId: null },
- },
- {
- rank: 2,
- ETH: { amount: '' },
- ERC20: { amount: '' },
- ERC721: { tokenId: null },
- },
- ],
- };
-
- const { isError, errors, data } = validateSubmitterRewards(rewards);
- expect(isError).toBe(false);
- expect(errors).toEqual({
- duplicateRanks: []
- });
- expect(data).toEqual({});
- })
- })
-
-
-
- describe("Validate Voter Rewards", () => {
- test("fail with duplicate ranks", () => {
- const rewards: VoterRewards = {
- ETH: sampleETHToken,
- payouts: [
- {
- rank: 1,
- ETH: { amount: '1' },
- },
- {
- rank: 1,
- ETH: { amount: '1' },
- },
- ]
- }
- const { errors, isError, data } = validateVoterRewards(rewards);
- expect(isError).toBe(true);
- expect(errors).toEqual({
- duplicateRanks: [1]
- });
- expect(data).toEqual(rewards);
- });
-
- test("pass with empty rewards", () => {
- const rewards: VoterRewards = {};
- const { errors, isError, data } = validateVoterRewards(rewards);
- expect(isError).toBe(false);
- expect(errors).toEqual({
- duplicateRanks: []
- });
- expect(data).toEqual(rewards);
- });
-
- test("pass with valid rewards", () => {
- const rewards: VoterRewards = {
- ETH: sampleETHToken,
- ERC20: sampleERC20Token,
- payouts: [
- {
- rank: 1,
- ETH: { amount: '1' },
- },
- {
- rank: 2,
- ERC20: { amount: '30' },
- }
- ]
- }
-
- const { errors, isError, data } = validateVoterRewards(rewards);
- expect(isError).toBe(false);
- expect(errors).toEqual({
- duplicateRanks: []
- });
- expect(data).toEqual(rewards);
- });
-
- test("clean empty rewards #1", () => {
- const rewards: VoterRewards = {
- ETH: sampleETHToken,
- ERC20: sampleERC20Token,
- payouts: [
- {
- rank: 1,
- ETH: { amount: '1' },
- },
- {
- rank: 2,
- ERC20: { amount: '' },
- }
-
- ]
- }
- const { errors, isError, data } = validateVoterRewards(rewards);
- expect(isError).toBe(false);
- expect(errors).toEqual({
- duplicateRanks: []
- });
- expect(data).toEqual({
- ETH: sampleETHToken,
- payouts: [
- {
- rank: 1,
- ETH: { amount: '1' },
- },
- ]
- });
- });
-
- test("clean empty rewards #2", () => {
- const rewards: VoterRewards = {
- ETH: sampleETHToken,
- ERC20: sampleERC20Token,
- payouts: [
- {
- rank: 1,
- ETH: { amount: '' },
- },
- {
- rank: 2,
- ERC20: { amount: '' },
- }
-
- ]
- }
- const { errors, isError, data } = validateVoterRewards(rewards);
- expect(isError).toBe(false);
- expect(errors).toEqual({
- duplicateRanks: []
- });
- expect(data).toEqual({});
- });
-
-
-
-
- describe("Validate Voting Policy", () => {
- test("fail with empty policy", () => {
- const votingPolicy: VotingPolicyType[] = [];
- const { errors, isError, data } = validateVotingPolicy(votingPolicy);
- expect(isError).toBe(true);
- expect(errors).toEqual(
- "Please add at least one voting policy",
- );
- expect(data).toEqual(votingPolicy);
- });
-
- test("pass with valid policy", () => {
- const votingPolicy: VotingPolicyType[] = [
- {
- token: sampleETHToken,
- strategy: {
- type: "weighted",
- }
-
- },
- {
- token: sampleERC20Token,
- strategy: {
- type: "arcade",
- votingPower: '1',
- }
- }
- ]
-
- const { errors, isError, data } = validateVotingPolicy(votingPolicy);
- expect(isError).toBe(false);
- expect(errors).toEqual("");
- expect(data).toEqual(votingPolicy);
- });
- })
-
- });
-
-})
\ No newline at end of file
diff --git a/uplink-client/src/test/SpaceHandler.test.ts b/uplink-client/src/test/SpaceHandler.test.ts
deleted file mode 100644
index 7126f1b0..00000000
--- a/uplink-client/src/test/SpaceHandler.test.ts
+++ /dev/null
@@ -1,197 +0,0 @@
-import { SpaceBuilderProps, validateSpaceBuilderProps, reducer, validateSpaceAdmins, validateSpaceName, validateSpaceLogo, validateSpaceWebsite, validateSpaceTwitter } from '@/app/spacebuilder/spaceHandler';
-import { describe, expect, test } from "@jest/globals";
-
-const calabaraAddress = "0xa943e039B1Ce670873ccCd4024AB959082FC6Dd8"
-const vitalikAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
-
-describe('Space Handler', () => {
- describe('Validate Space Name', () => {
- test('should fail with empty string', () => {
- const { error, value } = validateSpaceName('');
- expect(error).toEqual('Name is required');
- expect(value).toEqual('');
- })
- test('should fail with string too short', () => {
- const { error, value } = validateSpaceName('a'.repeat(2));
- expect(error).toEqual('Name must be at least 3 characters long');
- expect(value).toEqual('a'.repeat(2));
- })
- test('should fail with string too long', () => {
- const { error, value } = validateSpaceName('a'.repeat(31));
- expect(error).toEqual('Name must be less than 30 characters long');
- expect(value).toEqual('a'.repeat(31));
- })
- test('should fail with non-alphanumeric chars', () => {
- const name = 'test!';
- const { error, value } = validateSpaceName(name);
- expect(error).toEqual('Name must only contain alphanumeric characters and underscores');
- expect(value).toEqual(name);
- })
- })
-
- describe('Validate Space Logo', () => {
- test('should succeed with platform ipfs link', () => {
- const logo = 'https://uplink.mypinata.cloud/ipfs/QmUxRzCcizzuNdyPRxMdn4LFQQQ5ce9cRqubnUCaR4G7Bz';
- const { error, value } = validateSpaceLogo(logo);
- expect(error).toBeNull();
- expect(value).toEqual(logo);
- })
-
- test('should fail with invalid ipfs link', () => {
- const logo = 'https://google.com';
- const { error, value } = validateSpaceLogo(logo);
- expect(error).toEqual('Logo is not valid');
- expect(value).toEqual(logo);
- })
-
-
- test('should fail with empty string', () => {
- const logo = '';
- const { error, value } = validateSpaceLogo(logo);
- expect(error).toEqual('Logo is required');
- expect(value).toEqual(logo);
- })
-
- });
-
- describe('validate space website', () => {
- test('should succeed with valid website #1', () => {
- const website = 'https://google.com';
- const { error, value } = validateSpaceWebsite(website);
- expect(error).toBeNull();
- expect(value).toEqual(website);
- })
-
- test('should succeed with valid website #2', () => {
- const website = 'https://gnars.wtf';
- const { error, value } = validateSpaceWebsite(website);
- expect(error).toBeNull();
- expect(value).toEqual(website);
- })
-
- test('should succeed with valid website #3', () => {
- const website = 'nouns.wtf';
- const { error, value } = validateSpaceWebsite(website);
- expect(error).toBeNull();
- expect(value).toEqual(website);
- })
-
- test('should fail with invalid website', () => {
- const website = 'test';
- const { error, value } = validateSpaceWebsite(website);
- expect(error).toEqual('Website is not valid');
- expect(value).toEqual(website);
- });
- })
-
- describe('validate space twitter', () => {
- test('should succeed with valid twitter handle', () => {
- const twitter = '@calabara';
- const { error, value } = validateSpaceTwitter(twitter);
- expect(error).toBeNull();
- expect(value).toEqual(twitter);
- })
-
- test('should fail with invalid twitter handle', () => {
- const twitter = 'test';
- const { error, value } = validateSpaceTwitter(twitter);
- expect(error).toEqual('Twitter handle is not valid');
- expect(value).toEqual(twitter);
- })
- })
-
-
- describe('validate space admins', () => {
-
- test('should succeed with valid addresses', async () => {
- const admins = ['calabara.eth', 'vitalik.eth'];
- const { error, value } = await validateSpaceAdmins(admins);
- expect(error).toEqual([null, null])
- expect(value).toEqual([calabaraAddress, vitalikAddress])
- })
-
- test('should succeed and strip empty addresses', async () => {
- const admins = ['calabara.eth', ''];
- const { error, value } = await validateSpaceAdmins(admins);
- expect(error).toEqual([null])
- expect(value).toEqual([calabaraAddress])
- })
-
- test('should strip empty addresses and return errors', async () => {
- const admins = ['calabara.eth', 'test', 'vitalik.eth', ''];
- const { error, value } = await validateSpaceAdmins(admins);
- expect(error).toEqual([null, 'invalid address', null])
- expect(value).toEqual([calabaraAddress, 'test', vitalikAddress])
- })
-
- test('should strip empty addresses, remove duplicates, and return error addresses', async () => {
- const admins = ['calabara.eth', 'test', 'vitalik.eth', '', 'vitalik.eth'];
- const { error, value } = await validateSpaceAdmins(admins);
- expect(error).toEqual([null, 'invalid address', null])
- expect(value).toEqual([calabaraAddress, 'test', vitalikAddress])
- })
- })
-
-
- describe('Validate Space Builder Props', () => {
-
- test('should return errors', async () => {
- const props: SpaceBuilderProps = {
- name: "",
- logoBlob: "",
- logoUrl: "",
- website: "",
- twitter: "",
- admins: [],
- errors: {
- admins: [],
- },
- }
-
- const { isValid, errors, values } = await validateSpaceBuilderProps(props);
- expect(isValid).toBe(false);
- expect(errors).toEqual({
- name: "Name is required",
- logoUrl: "Logo is required",
- admins: []
- })
- expect(values).toEqual({
- name: "",
- logoUrl: "",
- admins: [],
- })
- })
-
- test('should return valid props', async () => {
- const props: SpaceBuilderProps = {
- name: "sample space",
- logoBlob: "asdf",
- logoUrl: "https://uplink.mypinata.cloud/ipfs/asdfasdf",
- website: "twitter.com",
- twitter: "@uplinkwtf",
- admins: [calabaraAddress, vitalikAddress],
- errors: {
- admins: [],
- },
- }
-
- const { isValid, errors, values } = await validateSpaceBuilderProps(props);
- expect(isValid).toBe(true);
- expect(errors).toEqual({
- admins: [null, null]
- })
- expect(values).toEqual({
- name: "sample space",
- logoUrl: "https://uplink.mypinata.cloud/ipfs/asdfasdf",
- website: "twitter.com",
- twitter: "@uplinkwtf",
- admins: [calabaraAddress, vitalikAddress],
- })
- })
-
- });
-
-
-
-
-})
diff --git a/uplink-client/src/test/sampleTokens.ts b/uplink-client/src/test/sampleTokens.ts
deleted file mode 100644
index 55d3b690..00000000
--- a/uplink-client/src/test/sampleTokens.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { IERCToken, INativeToken, IToken } from "@/types/token";
-
-export const sampleERC1155Token: IERCToken = {
- type: "ERC1155",
- address: "0x7c2748C7Ec984b559EADc39C7a4944398E34911a",
- symbol: "TNS",
- decimals: 0,
- tokenId: 2,
- chainId: 1
-}
-
-export const sampleERC20Token: IERCToken = {
- type: "ERC20",
- address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
- symbol: "USDC",
- decimals: 6,
- tokenId: null,
- chainId: 1,
-}
-
-export const sampleERC721Token: IERCToken = {
- type: "ERC721",
- address: "0x9C8fF314C9Bc7F6e59A9d9225Fb22946427eDC03",
- symbol: "NOUN",
- decimals: 0,
- tokenId: null,
- chainId: 1,
-}
-
-export const sampleETHToken: INativeToken = {
- type: "ETH",
- symbol: "ETH",
- decimals: 18,
- chainId: 1,
-}
\ No newline at end of file
diff --git a/uplink-client/src/test/zora.test.ts b/uplink-client/src/test/zora.test.ts
deleted file mode 100644
index 118254a4..00000000
--- a/uplink-client/src/test/zora.test.ts
+++ /dev/null
@@ -1,368 +0,0 @@
-import { describe, expect, test } from "@jest/globals";
-import {
- ConfigurableZoraEditionSchema,
- ConfigurableZoraEditionInput,
- EditionNameSchema,
- EditionSymbolSchema,
- EditionSizeSchema,
- EditionRoyaltyBPSSchema,
- EditionPublicSalePriceSchema,
- EditionSalesConfigSchema,
-} from "@/hooks/useCreateZoraEdition";
-import { uint64MaxSafe } from "@/utils/uint64";
-
-const unixRegex = /^\d{10}$/;
-
-describe("validate zora config", () => {
-
- describe("validate edition name", () => { })
- describe("validate edition symbol", () => { })
- describe("validate edition description", () => { })
-
- describe("validate edition size", () => {
- test("open edition", () => {
- const size = "open";
- const result = EditionSizeSchema.safeParse(size);
- expect(result.success).toBe(true);
- if (result.success) {
- expect(result.data).toBe("18446744073709551615");
- }
- })
- test("1/1", () => {
- const size = "one";
- const result = EditionSizeSchema.safeParse(size);
- expect(result.success).toBe(true);
- if (result.success) {
- expect(result.data).toBe("1");
- }
- })
- test("fixed", () => {
- const size = "100";
- const result = EditionSizeSchema.safeParse(size);
- expect(result.success).toBe(true);
- if (result.success) {
- expect(result.data).toBe("100");
- }
- })
- test("fail with empty string", () => {
- const size = "";
- const result: any = EditionSizeSchema.safeParse(size);
- const errors = result.error.format();
- expect(result.success).toBe(false);
- expect(errors._errors[0]).toBe("Edition size is required");
- })
- })
-
- describe("validate edition royaltyBPS", () => {
- test("0%", () => {
- const bps = "zero";
- const result = EditionRoyaltyBPSSchema.safeParse(bps);
- expect(result.success).toBe(true);
- if (result.success) {
- expect(result.data).toBe(0);
- }
- })
- test("5%", () => {
- const bps = "five";
- const result = EditionRoyaltyBPSSchema.safeParse(bps);
- expect(result.success).toBe(true);
- if (result.success) {
- expect(result.data).toBe(500);
- }
- })
- test("5.5%", () => {
- const bps = "5.5"
- const result = EditionRoyaltyBPSSchema.safeParse(bps);
- expect(result.success).toBe(true);
- if (result.success) {
- expect(result.data).toBe(550);
- }
- })
- test("0.05%", () => {
- const bps = "0.05"
- const result = EditionRoyaltyBPSSchema.safeParse(bps);
- expect(result.success).toBe(true);
- if (result.success) {
- expect(result.data).toBe(5);
- }
- })
- test("integer precision", () => {
- const bps = "5.00005"
- const result = EditionRoyaltyBPSSchema.safeParse(bps);
- expect(result.success).toBe(true);
- if (result.success) {
- expect(result.data).toBe(500);
- }
- })
- test("fail with empty string", () => {
- const bps = "";
- const result: any = EditionRoyaltyBPSSchema.safeParse(bps);
- const errors = result.error.format();
- expect(result.success).toBe(false);
- expect(errors._errors[0]).toBe("Royalty % is required");
- })
- })
-
- describe("validate edition public sale price", () => {
- test("free", () => { })
- test("0.001 eth", () => {
- const price = "0.001";
- const result = EditionPublicSalePriceSchema.safeParse(price);
- expect(result.success).toBe(true);
- if (result.success) {
- expect(result.data).toBe("1000000000000000");
- }
- })
-
- test("0.00420", () => {
- const price = "0.00420";
- const result = EditionPublicSalePriceSchema.safeParse(price);
- expect(result.success).toBe(true);
- if (result.success) {
- expect(result.data).toBe("4200000000000000");
- }
- })
-
- test("1.5 eth", () => {
- const price = "1.5";
- const result = EditionPublicSalePriceSchema.safeParse(price);
- expect(result.success).toBe(true);
- if (result.success) {
- expect(result.data).toBe("1500000000000000000");
- }
- })
- test("fail with empty string", () => {
- const price = "";
- const result: any = EditionPublicSalePriceSchema.safeParse(price);
- const errors = result.error.format();
- expect(result.success).toBe(false);
- expect(errors._errors[0]).toBe("Edition price is required");
- })
- })
-
-
- describe("validate public sale datetimes", () => {
- test("startTime = now, endTime = forever", () => {
- const nowUnix = Math.floor(new Date().getTime() / 1000);
- const salesConfig = {
- publicSalePrice: "1",
- publicSaleStart: "now",
- publicSaleEnd: "forever",
- }
-
- const result = EditionSalesConfigSchema.safeParse(salesConfig);
- expect(result.success).toBe(true);
- if (result.success) {
- expect(result.data.publicSaleStart).toMatch(unixRegex);
- expect(result.data.publicSaleEnd).toBe(uint64MaxSafe.toString());
- expect(nowUnix - parseInt(result.data.publicSaleStart)).toBeLessThan(5); // 5 second tolerance
- }
- })
-
- test("startTime = now, endTime = week", () => {
- const salesConfig = {
- publicSalePrice: "1",
- publicSaleStart: "now",
- publicSaleEnd: "week",
- }
-
- const result = EditionSalesConfigSchema.safeParse(salesConfig);
- expect(result.success).toBe(true);
- if (result.success) {
- expect(result.data.publicSaleStart).toMatch(unixRegex);
- expect(result.data.publicSaleEnd).toMatch(unixRegex);
- expect(parseInt(result.data.publicSaleEnd) - parseInt(result.data.publicSaleStart)).toBe(604800);
- }
- })
-
- test("startTime = custom, endTime = custom", () => {
- const plus1day = new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString();
- const plus2day = new Date(Date.now() + 1000 * 60 * 60 * 48).toISOString();
- const unixPlus1Day = Math.floor(new Date(plus1day).getTime() / 1000);
- const unixPlus2Day = Math.floor(new Date(plus2day).getTime() / 1000);
- const salesConfig = {
- publicSalePrice: "1",
- publicSaleStart: plus1day,
- publicSaleEnd: plus2day,
- }
- const result = EditionSalesConfigSchema.safeParse(salesConfig);
- expect(result.success).toBe(true);
-
- if (result.success) {
- expect(result.data.publicSaleStart).toMatch(unixRegex);
- expect(result.data.publicSaleEnd).toMatch(unixRegex);
- expect(parseInt(result.data.publicSaleEnd) - parseInt(result.data.publicSaleStart)).toBe(86400);
- expect(parseInt(result.data.publicSaleStart)).toBe(unixPlus1Day);
- expect(parseInt(result.data.publicSaleEnd)).toBe(unixPlus2Day);
- }
- })
-
- test("startTime < now", () => {
- const minus1day = new Date(Date.now() - 1000 * 60 * 60 * 24).toISOString();
- const salesConfig = {
- publicSalePrice: "1",
- publicSaleStart: minus1day,
- publicSaleEnd: "forever",
- }
- const result = EditionSalesConfigSchema.safeParse(salesConfig);
- expect(result.success).toBe(false);
- const errrors = result.error.format();
- expect(errrors.publicSaleStart._errors[0]).toBe("Public sale start must be in the future");
- })
-
- test("endTime < startTime", () => {
- const plus1day = new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString();
- const plus2day = new Date(Date.now() + 1000 * 60 * 60 * 48).toISOString();
-
- const salesConfig = {
- publicSalePrice: "1",
- publicSaleStart: plus2day,
- publicSaleEnd: plus1day,
- }
- const result = EditionSalesConfigSchema.safeParse(salesConfig);
- expect(result.success).toBe(false);
- const errrors = result.error.format();
- expect(errrors.publicSaleEnd._errors[0]).toBe("Public sale end must be after public sale start");
- })
- })
-
- // test("should succeed with valid config", () => {
- // const input: ConfigurableZoraEditionInput = {
- // name: "test",
- // symbol: "TST",
- // editionSize: "1",
- // royaltyBPS: "1000",
- // description: "test",
- // animationURI: "",
- // imageURI: "https://test.com",
- // salesConfig: {
- // publicSalePrice: "1",
- // publicSaleStart: "now",
- // publicSaleEnd: new Date(Date.now() + 1000 * 60 * 60).toISOString(),
- // }
- // }
-
- // const result: any = ConfigurableZoraEditionSchema.safeParse(input);
- // expect(result.success).toBe(true);
- // const { salesConfig, ...output } = result.data;
- // const { salesConfig: sc_in, ...rest } = input;
- // expect(output).toStrictEqual(rest);
- // expect(salesConfig.publicSaleStart).toMatch(unixRegex);
- // expect(salesConfig.publicSaleEnd).toMatch(unixRegex);
- // });
-
- // test("should fail with invalid text inputs", () => {
- // const input: ConfigurableZoraEditionInput = {
- // name: "",
- // symbol: "",
- // editionSize: "1",
- // royaltyBPS: "1000",
- // description: "",
- // animationURI: "",
- // imageURI: "https://test.com",
- // salesConfig: {
- // publicSalePrice: "1",
- // publicSaleStart: "now",
- // publicSaleEnd: new Date(Date.now() + 1000 * 60 * 60).toISOString(),
- // }
- // }
-
- // const result: any = ConfigurableZoraEditionSchema.safeParse(input);
- // const errors = result.error.format();
- // expect(result.success).toBe(false);
- // expect(errors.name._errors[0]).toBe("Name is required");
- // expect(errors.symbol._errors[0]).toBe("Symbol is required");
- // expect(errors.description._errors[0]).toBe("Description is required");
- // })
-
-
- // test("should fail without imageURI", () => {
- // const input: ConfigurableZoraEditionInput = {
- // name: "test",
- // symbol: "test",
- // editionSize: "1",
- // royaltyBPS: "1000",
- // description: "test",
- // animationURI: "",
- // imageURI: "",
- // salesConfig: {
- // publicSalePrice: "1",
- // publicSaleStart: "now",
- // publicSaleEnd: new Date(Date.now() + 1000 * 60 * 60).toISOString(),
- // }
- // }
-
- // const result: any = ConfigurableZoraEditionSchema.safeParse(input);
- // const errors = result.error.format();
- // expect(result.success).toBe(false);
- // expect(errors.imageURI._errors[0]).toBe("Image must be set");
- // })
-
- // test("should fail without video thumbnail", () => {
- // const input: ConfigurableZoraEditionInput = {
- // name: "test",
- // symbol: "test",
- // editionSize: "1",
- // royaltyBPS: "1000",
- // description: "test",
- // animationURI: "https://test",
- // imageURI: "",
- // salesConfig: {
- // publicSalePrice: "1",
- // publicSaleStart: "now",
- // publicSaleEnd: new Date(Date.now() + 1000 * 60 * 60).toISOString(),
- // }
- // }
-
- // const result: any = ConfigurableZoraEditionSchema.safeParse(input);
- // const errors = result.error.format();
- // expect(result.success).toBe(false);
- // expect(errors.animationURI._errors[0]).toBe("Video thumbnail must be set");
- // })
-
- // test("should fail with sale start > sale end", () => {
- // const input: ConfigurableZoraEditionInput = {
- // name: "test",
- // symbol: "test",
- // editionSize: "1",
- // royaltyBPS: "1000",
- // description: "test",
- // animationURI: "",
- // imageURI: "https://test",
- // salesConfig: {
- // publicSalePrice: "1",
- // publicSaleStart: new Date(Date.now() + 1000 * 60 * 60).toISOString(),
- // publicSaleEnd: new Date(Date.now() + 1000 * 60 * 30).toISOString(),
- // }
- // }
-
- // const result: any = ConfigurableZoraEditionSchema.safeParse(input);
- // const errors = result.error.format();
- // expect(result.success).toBe(false);
- // expect(errors.salesConfig.publicSaleStart._errors[0]).toBe("Public sale start must be before public sale end");
- // })
-
- // test("should fail with sale start in past", () => {
- // const input: ConfigurableZoraEditionInput = {
- // name: "test",
- // symbol: "test",
- // editionSize: "1",
- // royaltyBPS: "1000",
- // description: "test",
- // animationURI: "",
- // imageURI: "https://test",
- // salesConfig: {
- // publicSalePrice: "1",
- // publicSaleStart: new Date(Date.now() - 1000 * 60 * 60).toISOString(),
- // publicSaleEnd: new Date(Date.now() + 1000 * 60 * 30).toISOString(),
- // }
- // }
-
- // const result: any = ConfigurableZoraEditionSchema.safeParse(input);
- // const errors = result.error.format();
- // expect(result.success).toBe(false);
- // expect(errors.salesConfig.publicSaleStart._errors[0]).toBe("Public sale start must be in the future");
- // })
-
-
-})
\ No newline at end of file
diff --git a/uplink-client/src/types/contest.ts b/uplink-client/src/types/contest.ts
index 6cf00241..8672a29c 100644
--- a/uplink-client/src/types/contest.ts
+++ b/uplink-client/src/types/contest.ts
@@ -1,3 +1,5 @@
+import { Space } from "./space";
+import { Submission } from "./submission";
import { IToken } from "./token";
import type { OutputData } from "@editorjs/editorjs";
@@ -152,8 +154,6 @@ export const isWeightedVotingStrategy = (votingStrategy: VotingStrategy): voting
return votingStrategy.strategyType === 'weighted';
}
-//
-
export type ContestState = "pending" | "submitting" | "voting" | "closed";
export type ContestPromptData = {
@@ -162,7 +162,7 @@ export type ContestPromptData = {
body: OutputData;
}
-export type ReadableContest = {
+export type LegacyContest = {
id: string;
chainId: number;
spaceId: string;
@@ -176,4 +176,8 @@ export type ReadableContest = {
submitterRewards: Array;
voterRewards: Array;
votingPolicy: Array;
+ submissions: Array;
+ space: Space;
};
+
+export type LegacyContestWithPrompt = LegacyContest & { promptData: ContestPromptData };
\ No newline at end of file
diff --git a/uplink-client/src/types/edition.ts b/uplink-client/src/types/edition.ts
deleted file mode 100644
index 37ed643d..00000000
--- a/uplink-client/src/types/edition.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-export type Edition = {
- id: string;
- chainId: number;
- contractAddress: string;
- name: string
- symbol: string
- editionSize: string
- royaltyBPS: number
- fundsRecipient: string
- defaultAdmin: string
- saleConfig: EditionSaleConfig;
- description: string;
- animationURI: string;
- imageURI: string;
- referrer: string;
-
-}
-
-export type EditionSaleConfig = {
- publicSalePrice: string;
- maxSalePurchasePerAddress: number;
- publicSaleStart: string;
- publicSaleEnd: string;
- presaleStart: string;
- presaleEnd: string;
- presaleMerkleRoot: string;
-}
\ No newline at end of file
diff --git a/uplink-client/src/types/mintBoard.ts b/uplink-client/src/types/mintBoard.ts
deleted file mode 100644
index 346ebad3..00000000
--- a/uplink-client/src/types/mintBoard.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { Edition } from "./edition";
-import { Space } from "./space";
-import { User } from "./user";
-import { z } from "zod";
-import { zeroAddress } from "viem";
-
-
-export type MintBoard = {
- id: string;
- space: Space
- spaceId: string;
- created: string;
- chainId: number;
- enabled: boolean;
- threshold: number;
- boardTitle: string;
- boardDescription: string;
- name: string;
- symbol: string;
- editionSize: string;
- publicSalePrice: string;
- publicSaleStart: string;
- publicSaleEnd: string;
- description: string;
- referrer: string;
-}
-
-export type PaginatedMintBoardPosts = {
- posts: Array;
- pageInfo: {
- endCursor: string;
- hasNextPage: boolean
- }
-}
-
-export type MintBoardPost = {
- id: string;
- created: string;
- edition: Edition;
- author: User;
- totalMints: number;
-}
diff --git a/uplink-client/src/types/space.ts b/uplink-client/src/types/space.ts
index 35759445..0e801663 100644
--- a/uplink-client/src/types/space.ts
+++ b/uplink-client/src/types/space.ts
@@ -1,4 +1,3 @@
-import { MintBoard } from "./mintBoard";
import { IToken } from "./token";
export type Admin = {
diff --git a/uplink-client/src/types/submission.ts b/uplink-client/src/types/submission.ts
index cbd1aa37..62232e6b 100644
--- a/uplink-client/src/types/submission.ts
+++ b/uplink-client/src/types/submission.ts
@@ -2,7 +2,6 @@
export type SubmissionType = "standard" | "twitter"
export type SubmissionFormat = "image" | "video" | "text"
import type { OutputData } from "@editorjs/editorjs";
-import { Edition } from "./edition";
import { User } from "./user";
export type BaseSubmission = {
@@ -15,7 +14,6 @@ export type BaseSubmission = {
author: User | null;
rank: string | null;
totalVotes: string | null;
- edition: Edition | null;
};
export type TwitterSubmission = BaseSubmission & {
@@ -53,7 +51,3 @@ export const isStandardSubmission = (submission: Submission): submission is Stan
export const isTwitterSubmission = (submission: Submission): submission is TwitterSubmission => {
return submission.type === 'twitter';
}
-
-export const isNftSubmission = (submission: Submission): boolean => {
- return Boolean(submission.edition);
-}
\ No newline at end of file
diff --git a/uplink-client/src/ui/AddressDisplay/AddressDisplay.tsx b/uplink-client/src/ui/AddressDisplay/AddressDisplay.tsx
index 0060eb4f..a139225c 100644
--- a/uplink-client/src/ui/AddressDisplay/AddressDisplay.tsx
+++ b/uplink-client/src/ui/AddressDisplay/AddressDisplay.tsx
@@ -1,12 +1,9 @@
-"use client";
-
+"use client";;
import { User } from "@/types/user";
import Noggles from "../Noggles/Noggles";
-import useEnsName from "@/hooks/useEnsName";
import { Session } from "@/providers/SessionProvider";
-import UplinkImage from "@/lib/UplinkImage";
-import { ImageWrapper } from "@/ui/Submission/MediaWrapper"
-import { Address } from "viem";
+import OptimizedImage from "@/lib/OptimizedImage";
+import { ImageWrapper } from "@/app/(legacy)/contest/components/MediaWrapper"
import { useWalletDisplayText } from "@/hooks/useWalletDisplay";
import { useEffect } from "react";
@@ -120,7 +117,7 @@ export const UserAvatar = ({
if (user?.profileAvatar) return (
-
+
)
diff --git a/uplink-client/src/ui/Card/Card.tsx b/uplink-client/src/ui/Card/Card.tsx
deleted file mode 100644
index 7dbbde27..00000000
--- a/uplink-client/src/ui/Card/Card.tsx
+++ /dev/null
@@ -1,79 +0,0 @@
-import * as React from "react"
-
-import { cn } from "@/lib/shadcn"
-
-const Card = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-))
-Card.displayName = "Card"
-
-const CardHeader = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-))
-CardHeader.displayName = "CardHeader"
-
-const CardTitle = React.forwardRef<
- HTMLParagraphElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-))
-CardTitle.displayName = "CardTitle"
-
-const CardDescription = React.forwardRef<
- HTMLParagraphElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-))
-CardDescription.displayName = "CardDescription"
-
-const CardContent = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-))
-CardContent.displayName = "CardContent"
-
-const CardFooter = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-))
-CardFooter.displayName = "CardFooter"
-
-export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
diff --git a/uplink-client/src/ui/ChainLabel/ChainLabel.tsx b/uplink-client/src/ui/ChainLabel/ChainLabel.tsx
new file mode 100644
index 00000000..7e251553
--- /dev/null
+++ b/uplink-client/src/ui/ChainLabel/ChainLabel.tsx
@@ -0,0 +1,20 @@
+
+
+const BaseChainLogo = ({ px }: { px: number }) => {
+ return (
+
+
+
+ )
+}
+
+export const ChainLabel = ({ chainId, px }: { chainId: number, px: number }) => {
+ if (chainId === 8453 || chainId === 84532) {
+ return (
+
+ )
+ }
+
+ return null;
+}
+
diff --git a/uplink-client/src/ui/ChannelSettings/ChainSelect.tsx b/uplink-client/src/ui/ChannelSettings/ChainSelect.tsx
index 288bc747..b28de604 100644
--- a/uplink-client/src/ui/ChannelSettings/ChainSelect.tsx
+++ b/uplink-client/src/ui/ChannelSettings/ChainSelect.tsx
@@ -1,6 +1,6 @@
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/ui/DesignKit/Select";
import { getChainName, supportedChains } from "@/lib/chains/supportedChains";
-import { ChainLabel } from "../ContestLabels/ContestLabels";
+import { ChainLabel } from "../ChainLabel/ChainLabel";
export const ChainSelect = ({ chainId, setChainId }: { chainId: 8453 | 84532, setChainId: (val: 8453 | 84532) => void }) => {
diff --git a/uplink-client/src/ui/ChannelSidebar/ContestDetailsV2.tsx b/uplink-client/src/ui/ChannelSidebar/ContestDetailsV2.tsx
index 94364cb3..ba1cd586 100644
--- a/uplink-client/src/ui/ChannelSidebar/ContestDetailsV2.tsx
+++ b/uplink-client/src/ui/ChannelSidebar/ContestDetailsV2.tsx
@@ -19,7 +19,7 @@ import { TbLoader2 } from "react-icons/tb";
import toast from "react-hot-toast";
import { useTransmissionsErrorHandler } from "@/hooks/useTransmissionsErrorHandler";
import { ChainId } from "@/types/chains";
-import { ExpandSection } from "../ContestDetails/client";
+import { ExpandSection } from "../../app/(legacy)/contest/[id]/client";
const compact_formatter = new Intl.NumberFormat('en', { notation: 'compact' })
@@ -331,11 +331,13 @@ const RewardsSection = ({
// rewards (need types)
const ContestDetailsV2 = ({
+ spaceName,
contractId,
transportConfig,
creatorLogic,
minterLogic
}: {
+ spaceName: string;
contractId: ContractID;
transportConfig: IFiniteTransportConfig;
creatorLogic: ILogicConfig | null;
@@ -343,7 +345,6 @@ const ContestDetailsV2 = ({
}) => {
const { chainId, contractAddress } = splitContractID(contractId);
- const { channelState, stateRemainingTime } = useFiniteTransportLayerState(contractId);
const { settle, status, txHash, error } = useSettleFiniteChannel();
const isSettling = status === "pendingApproval" || status === "txInProgress";
const { mutateSwrChannel } = useChannel(contractId);
@@ -388,29 +389,6 @@ const ContestDetailsV2 = ({
- {/* */}
-
- {/*
-
- Submit
-
-
-
-
-
- {isSettling ? (
-
- ) : "Settle"}
-
- */}
{({ isLoading, channelState, stateRemainingTime }) => {
@@ -425,7 +403,7 @@ const ContestDetailsV2 = ({
{channelState ? stateRemainingTime : }
-
+
Submit