From 1186f39547798b69fb5f846e557829a9c2b4f656 Mon Sep 17 00:00:00 2001 From: Julien Genestoux Date: Sun, 24 Mar 2024 08:05:14 -0400 Subject: [PATCH] feat(unlock-app): Event Referrals (#13512) * feat(unlock-app): adding a UI to setup referrer fees and copy referral URLs for events * Apply suggestions from code review --- .../content/event/CopyUrlButton.tsx | 6 +- .../components/content/event/EventDetails.tsx | 7 +- .../event/Registration/EmbeddedCheckout.tsx | 15 +- .../content/event/Settings/EventSettings.tsx | 7 + .../content/event/Settings/Referrals.tsx | 244 ++++++++++++++++++ unlock-app/src/hooks/useReferrerFee.ts | 6 +- unlock-app/src/pages/locks/settings.tsx | 1 + 7 files changed, 274 insertions(+), 12 deletions(-) create mode 100644 unlock-app/src/components/content/event/Settings/Referrals.tsx diff --git a/unlock-app/src/components/content/event/CopyUrlButton.tsx b/unlock-app/src/components/content/event/CopyUrlButton.tsx index 623d67375e2..7a64a610123 100644 --- a/unlock-app/src/components/content/event/CopyUrlButton.tsx +++ b/unlock-app/src/components/content/event/CopyUrlButton.tsx @@ -5,11 +5,11 @@ import { ToastHelper } from '~/components/helpers/toast.helper' import useClipboard from 'react-use-clipboard' interface CopyUrlButtonProps { - eventUrl: string + url: string } -export const CopyUrlButton = ({ eventUrl }: CopyUrlButtonProps) => { - const [_, setCopied] = useClipboard(eventUrl, { +export const CopyUrlButton = ({ url }: CopyUrlButtonProps) => { + const [_, setCopied] = useClipboard(url, { successDuration: 1000, }) diff --git a/unlock-app/src/components/content/event/EventDetails.tsx b/unlock-app/src/components/content/event/EventDetails.tsx index a7a01e9df1d..e117c13687c 100644 --- a/unlock-app/src/components/content/event/EventDetails.tsx +++ b/unlock-app/src/components/content/event/EventDetails.tsx @@ -276,13 +276,10 @@ Powered by Unlock Protocol`}
  • - +
  • - +
  • diff --git a/unlock-app/src/components/content/event/Registration/EmbeddedCheckout.tsx b/unlock-app/src/components/content/event/Registration/EmbeddedCheckout.tsx index fbf59b70b55..d17fe75ee35 100644 --- a/unlock-app/src/components/content/event/Registration/EmbeddedCheckout.tsx +++ b/unlock-app/src/components/content/event/Registration/EmbeddedCheckout.tsx @@ -1,20 +1,31 @@ import { Button, Modal } from '@unlock-protocol/ui' import { Checkout } from '~/components/interface/checkout/main' -import { useState } from 'react' +import { useMemo, useState } from 'react' import { selectProvider } from '~/hooks/useAuthenticate' import { useConfig } from '~/utils/withConfig' +import { useRouter } from 'next/router' export const EmbeddedCheckout = ({ checkoutConfig, refresh }: any) => { const [isCheckoutOpen, setCheckoutOpen] = useState(false) const config = useConfig() + const { query } = useRouter() const injectedProvider = selectProvider(config) + const paywallConfig = useMemo(() => { + if (query.referrer) { + return { + ...checkoutConfig.config, + referrer: query.referrer, + } + } + return checkoutConfig.config + }, [checkoutConfig, query]) return ( <> { refresh() setCheckoutOpen(false) diff --git a/unlock-app/src/components/content/event/Settings/EventSettings.tsx b/unlock-app/src/components/content/event/Settings/EventSettings.tsx index 7c841d63e6a..e2dcb2980f3 100644 --- a/unlock-app/src/components/content/event/Settings/EventSettings.tsx +++ b/unlock-app/src/components/content/event/Settings/EventSettings.tsx @@ -8,6 +8,7 @@ import { ReactNode, useState } from 'react' import { SettingTab } from '~/pages/locks/settings' import { PaywallConfigType, Event } from '@unlock-protocol/core' import { General } from './General' +import { Referrals } from './Referrals' import Link from 'next/link' interface EventSettingsProps { @@ -36,6 +37,12 @@ export const EventSettings = ({ description: `Update your event's public information such as its location, date and more!`, children: , }, + { + id: 'referrals', + label: 'Referrals', + description: `Create referral links to share with your community and reward them.`, + children: , + }, // { // id: 'checkout', // label: 'Checkout', diff --git a/unlock-app/src/components/content/event/Settings/Referrals.tsx b/unlock-app/src/components/content/event/Settings/Referrals.tsx new file mode 100644 index 00000000000..2011e3c86ae --- /dev/null +++ b/unlock-app/src/components/content/event/Settings/Referrals.tsx @@ -0,0 +1,244 @@ +import { FaTrash as TrashIcon } from 'react-icons/fa' +import { + Button, + Input, + Detail, + AddressInput, + Placeholder, +} from '@unlock-protocol/ui' +import { PaywallConfigType, Event } from '@unlock-protocol/core' +import { SettingCard } from '~/components/interface/locks/Settings/elements/SettingCard' +import { useLockData } from '~/hooks/useLockData' +import { onResolveName } from '~/utils/resolvers' +import { Controller, useForm } from 'react-hook-form' +import { useReferrerFee } from '~/hooks/useReferrerFee' +import { ToastHelper } from '~/components/helpers/toast.helper' +import useEns from '~/hooks/useEns' +import { addressMinify } from '~/utils/strings' +import CopyUrlButton from '../CopyUrlButton' +import { getEventUrl } from '../utils' + +interface ReferralsProps { + event: Event + checkoutConfig: { + id?: string + config: PaywallConfigType + } +} + +interface FormProps { + referralFeePercentage: number + referralAddress: string +} + +export const Referral = ({ + eventUrl, + onDelete, + referral: { referrer, fee }, +}: { + eventUrl: string + onDelete: (referrer: string) => void + referral: { referrer: string; fee: number } +}) => { + const addressToEns = useEns(referrer) + + const resolvedAddress = + addressToEns === referrer ? addressMinify(referrer) : addressToEns + + const eventUrlWithReferrer = new URL(eventUrl) + eventUrlWithReferrer.searchParams.append('referrer', referrer) + + return ( +
    + + {fee / 100}% + + + {resolvedAddress} + +
    + +
    + +
    +
    +
    + ) +} + +export const ReferralsForLock = ({ + eventUrl, + lockAddress, + network, +}: { + eventUrl: string + lockAddress: string + network: number +}) => { + const { lock, isLockLoading } = useLockData({ lockAddress, network }) + + const { + register, + handleSubmit, + reset, + control, + formState: { errors, isValid }, + } = useForm({}) + + const { + data: referralFees, + isLoading, + setReferrerFee, + refetch, + } = useReferrerFee({ + lockAddress, + network, + }) + + const onSubmit = async (data: FormProps) => { + const setReferrerFeePromise = setReferrerFee.mutateAsync({ + ...data, + }) + + await ToastHelper.promise(setReferrerFeePromise, { + loading: 'Setting a new referral fee!', + error: 'Failed to set a new referrer, please try again.', + success: 'Referral fee set!', + }) + + reset() + refetch() + } + + const deleteReferrer = async (referralAddress: string) => { + const setReferrerFeePromise = setReferrerFee.mutateAsync({ + referralAddress, + referralFeePercentage: 0, + }) + + await ToastHelper.promise(setReferrerFeePromise, { + loading: 'Deleting referral', + error: 'Failed to delete the referrer, please try again.', + success: 'Referral fee deleted!', + }) + + reset() + refetch() + } + + if (isLockLoading || !lock) { + return ( + + + + + + + ) + } + return ( + +
    +
    + + { + return ( + + ) + }} + /> + + + + + + + +
    + + {isLoading && ( + + + + + )} + {referralFees?.length > 0 && ( +

    Referrals:

    + )} +
    + {referralFees?.map((referral) => { + return ( + + ) + })} +
    +
    +
    + ) +} + +export const Referrals = ({ event, checkoutConfig }: ReferralsProps) => { + const eventUrl = getEventUrl({ + event, + }) + + const locks = Object.keys(checkoutConfig.config.locks) + return ( + <> + {locks.map((lock) => { + const network = + checkoutConfig.config.locks[lock].network || + checkoutConfig.config.network + return ( + + ) + })} + + ) +} diff --git a/unlock-app/src/hooks/useReferrerFee.ts b/unlock-app/src/hooks/useReferrerFee.ts index 1c4a174aecb..9c8be371ca5 100644 --- a/unlock-app/src/hooks/useReferrerFee.ts +++ b/unlock-app/src/hooks/useReferrerFee.ts @@ -33,7 +33,7 @@ export const useReferrerFee = ({ lockAddress, network }: ReferrerFeeProps) => { return service.lock( { where: { - id: lockAddress, + address: lockAddress, }, }, { @@ -52,7 +52,9 @@ export const useReferrerFee = ({ lockAddress, network }: ReferrerFeeProps) => { async () => getLock() ) - const data = referralFeesData?.referrerFees || [] + const data = (referralFeesData?.referrerFees || []).filter( + (referralFeesData) => referralFeesData.fee > 0 + ) return { isLoading, diff --git a/unlock-app/src/pages/locks/settings.tsx b/unlock-app/src/pages/locks/settings.tsx index 769e0635886..002a6bb615e 100644 --- a/unlock-app/src/pages/locks/settings.tsx +++ b/unlock-app/src/pages/locks/settings.tsx @@ -16,6 +16,7 @@ export type SettingTab = | 'emails' | 'verifiers' | 'checkout' + | 'referrals' const Settings: NextPage = () => { const { query } = useRouter()