Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: prototype of referral system in console #142

Merged
merged 30 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a5f28c5
wip: referrals
travis Oct 24, 2024
f90eda3
feat: shift to just regular route handlers for referral api
travis Oct 24, 2024
2bc5ed2
fix: remove console.log
travis Oct 25, 2024
016ca71
feat: add rewards section to settings page
travis Nov 1, 2024
741eb78
fix: move some styles to the normal tailwind customization system
travis Nov 5, 2024
66e1026
fix: get referral and settings pages looking right
travis Nov 6, 2024
1f6c1fa
fix: move referrals hook
travis Nov 6, 2024
ccdefc1
feat: add new pricing table with free trial
travis Nov 6, 2024
64c1f08
feat: add email back to authentication submitted page
travis Nov 6, 2024
177f244
Merge remote-tracking branch 'origin/main' into feat/referral-program
travis Nov 7, 2024
a33b008
fix: remove extra handler
travis Nov 7, 2024
c180795
feat: move referrals service out
travis Nov 8, 2024
8591b4d
revert package.json to main
travis Nov 8, 2024
972f8b6
revert pnpm-lock from main
travis Nov 8, 2024
7f9a2d1
feat: various improvements
travis Nov 19, 2024
f82f7e9
fix: loader size and color
travis Nov 26, 2024
e1bc9f6
fix: configure referrals URLs
travis Nov 26, 2024
8a10563
Merge branch 'main' into feat/referral-program
travis Nov 26, 2024
b0a59b3
fix: yaml syntax error
travis Nov 26, 2024
5839e8e
Merge remote-tracking branch 'refs/remotes/origin/feat/referral-progr…
travis Nov 26, 2024
02c61c2
chore: add console.log for easier debugging in the preview env
travis Nov 26, 2024
cab9fd0
chore: more preview env debugging
travis Nov 26, 2024
6c80086
fix: try to fix preview env
travis Nov 26, 2024
27fe789
fix: I did React wrong
travis Nov 26, 2024
fdb6f3f
fix: get referral page working as expected
travis Nov 26, 2024
de6e0aa
fix: configure trial pricing table ID in preview
travis Nov 27, 2024
c1444c0
fix: deploy scripts and a bugfix in the hooks
travis Nov 27, 2024
47ba373
chore: remove unused code
travis Dec 3, 2024
3be95ed
feat: feature flag settings page referral UI
travis Dec 3, 2024
aa53fe1
Update src/app/referrals/page.tsx
travis Dec 3, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ NEXT_PUBLIC_STRIPE_CUSTOMER_PORTAL_LINK=https://billing.stripe.com/p/login/test_

# set this to skip forcing users to pick a Stripe plan
NEXT_PUBLIC_DISABLE_PLAN_GATE=false

# point these at the marketing website and referrals service
NEXT_PUBLIC_REFERRAL_URL=http://localhost:3001/referred
NEXT_PUBLIC_REFERRALS_SERVICE_URL=http://localhost:4001
8 changes: 8 additions & 0 deletions .github/workflows/deploy-storacha.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,13 @@ jobs:
echo "NEXT_PUBLIC_W3UP_RECEIPTS_URL=https://staging.up.storacha.network/receipt/" >> .env
echo "NEXT_PUBLIC_W3UP_PROVIDER=did:web:staging.web3.storage" >> .env
echo "NEXT_PUBLIC_STRIPE_PRICING_TABLE_ID=prctbl_1NzhdvF6A5ufQX5vKNZuRhie" >> .env
echo "NEXT_PUBLIC_STRIPE_TRIAL_PRICING_TABLE_ID=prctbl_1QIDHGF6A5ufQX5vOK9Xl8Up" >> .env
echo "NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_51LO87hF6A5ufQX5viNsPTbuErzfavdrEFoBuaJJPfoIhzQXdOUdefwL70YewaXA32ZrSRbK4U4fqebC7SVtyeNcz00qmgNgueC" >> .env
echo "NEXT_PUBLIC_STRIPE_CUSTOMER_PORTAL_LINK=https://billing.stripe.com/p/login/test_6oE29Gff99KO6mk8ww" >> .env

# use example.com in preview because we can't predict the preview URL of the storacha.network site
echo "NEXT_PUBLIC_REFERRAL_URL=http://example.com/referred" >> .env
echo "NEXT_PUBLIC_REFERRALS_SERVICE_URL=https://staging-referrals.storacha.network" >> .env
# as long as this uses https://github.com/cloudflare/next-on-pages/blob/dc529d7efa8f8568ea8f71b5cdcf78df89be6c12/packages/next-on-pages/bin/index.js,
# env vars won't get passed through to wrangler, so if wrangler will need them, write them to .env like the previous step
- run: pnpm pages:build
Expand Down Expand Up @@ -131,8 +136,11 @@ jobs:
echo "NEXT_PUBLIC_W3UP_RECEIPTS_URL=https://up.storacha.network/receipt/" >> .env
echo "NEXT_PUBLIC_W3UP_PROVIDER=did:web:web3.storage" >> .env
echo "NEXT_PUBLIC_STRIPE_PRICING_TABLE_ID=prctbl_1OCJ1qF6A5ufQX5vM5DWg4rA" >> .env
echo "NEXT_PUBLIC_STRIPE_TRIAL_PRICING_TABLE_ID=prctbl_1QPYsuF6A5ufQX5vdIGAe54g" >> .env
echo "NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_51LO87hF6A5ufQX5vQTO5BHyz8y9ybJp4kg1GsBjYuqwluuwtQTkbeZzkoQweFQDlv7JaGjuIdUWAyuwXp3tmCfsM005lJK9aS8" >> .env
echo "NEXT_PUBLIC_STRIPE_CUSTOMER_PORTAL_LINK=https://billing.stripe.com/p/login/cN22aA62U6bO1sA9AA" >> .env
echo "NEXT_PUBLIC_REFERRAL_URL=http://storacha.network/referred" >> .env
echo "NEXT_PUBLIC_REFERRALS_SERVICE_URL=https://referrals.storacha.network" >> .env
- run: pnpm pages:build
# as long as this uses https://github.com/cloudflare/next-on-pages/blob/dc529d7efa8f8568ea8f71b5cdcf78df89be6c12/packages/next-on-pages/bin/index.js,
# env vars won't get passed through to wrangler, so if wrangler will need them, write them to .env like the previous step
Expand Down
42 changes: 0 additions & 42 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -29,48 +29,6 @@
background-position: bottom;
background-repeat: no-repeat;
}
.bg-hot-red,
.hover\:bg-hot-red:hover {
background-color: var(--hot-red);
}
.bg-hot-red-light,
.hover\:bg-hot-red-light:hover {
background-color: var(--hot-red-light);
}
.bg-hot-blue-light {
background-color: var(--hot-blue-light);
}
.bg-hot-yellow {
background-color: var(--hot-yellow);
}
.hover\:bg-hot-yellow:hover {
background-color: var(--hot-yellow);
}
.bg-hot-yellow-light,
.hover\:bg-hot-yellow-light:hover {
background-color: var(--hot-yellow-light);
}
.border-hot-red,
.hover\:border-hot-red:hover {
border-color: var(--hot-red);
}
.border-hot-yellow {
border-color: var(--hot-yellow);
}
.border-hot-yellow-light {
border-color: var(--hot-yellow-light);
}
.text-hot-red,
.hover\:text-hot-red:hover {
color: var(--hot-red);
}
.text-hot-yellow {
color: var(--hot-yellow);
}
.text-hot-blue {
color: var(--hot-blue);
}

.w3ui-button-colors {
@apply text-white bg-slate-800 hover:bg-blue-800 transition-colors ease-in;
}
Expand Down
148 changes: 148 additions & 0 deletions src/app/referrals/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
'use client'

import CopyButton from '@/components/CopyButton'
import DefaultLoader from '@/components/Loader'
import { H1, H3 } from '@/components/Text'
import { RefcodeResult, useReferrals } from '@/lib/referrals/hooks'
import { useEffect } from 'react'
import { KeyedMutator } from 'swr'

export const runtime = "edge"

export function RefcodeCreator ({
accountEmail,
urlQueryEmail,
createRefcode,
mutateRefcode,
setReferrerEmail
}: {
accountEmail: string
urlQueryEmail: string | null
createRefcode: (form: FormData) => Promise<Response>
mutateRefcode: KeyedMutator<RefcodeResult>
setReferrerEmail: (email: string) => void
}
) {
const prefilledEmail = urlQueryEmail || accountEmail
useEffect(function () {
if (prefilledEmail) {
(async () => {
const form = new FormData()
form.append('email', prefilledEmail)
await createRefcode(form)
await mutateRefcode()
})()
}
}, [prefilledEmail])
return (
<>
{
prefilledEmail ? (
<DefaultLoader className="w-6 h-6 color-hot-red" />
) : (
<form onSubmit={async (e) => {
e.preventDefault()
try {
const form = new FormData(e.currentTarget)
const email = form.get('email')
if (email){
setReferrerEmail(email.toString())
await createRefcode(form)
} else {
console.log("email was undefined, this is strange!")
}
} finally {
// mutate here to pick up any changes from either create or set
mutateRefcode()
}
}} className=''>
<label className='block mb-2 uppercase text-xs text-hot-red font-epilogue m-1' htmlFor='email'>Your Email</label>
<input
id='email'
name='email'
type='email'
className='text-black py-2 px-2 rounded-xl block mb-4 border border-hot-red w-80'
placeholder='Email'
defaultValue={urlQueryEmail || ''}
required={true}
/>
<button type='submit' className={`inline-block bg-hot-red border border-hot-red hover:bg-white hover:text-hot-red font-epilogue text-white uppercase text-sm px-6 py-2 rounded-full whitespace-nowrap`}>
Create
</button>
</form>
)
}
</>
)
}

export function RefcodeLink ({ referralLink }: { referralLink: string }) {
return (
<div className="border border-hot-red rounded-full px-4 py-2 flex flex-row justify-between items-center">
<div>{referralLink}</div>
<CopyButton text={referralLink} />
</div>
)
}

export function ReferralsList () {
const { referrals } = useReferrals()
return (
(referrals && referrals.length > 0) ? (
<>
<H3>Referrals</H3>
<div className="divide-solid divide-hot-red py-4">
{
/**
* TODO: once we can determine when a user signed up and what plan they signed up for, update
* this UI to differentiate between them with different names and give users a countdown timer
* in the lozenge.
*/
referrals.map((referral, i) =>
<div key={i} className="flex flex-row justify-between items-center py-4">
<div>Referred Racha</div>
<div className="rounded-full bg-hot-red-light text-hot-red px-4 py-2 font-mono text-sm">In Progress</div>
</div>
)
}
</div>
</>
) : (
<>
<H3>Earn Free Storage and Racha Points!</H3>
<p className='text-hot-red mb-4 max-w-lg'>
Turn your friends into Lite or Business Rachas and receive up to 16 months of Lite or
3 months of Business for free! You can also earn Racha Points.
</p>
</>
)
)
}

export default function ReferralsPage () {
const { refcodeIsLoading, referralLink, setReferrerEmail, accountEmail, urlQueryEmail, createRefcode, mutateRefcode, } = useReferrals()
return (
<div className='p-10 bg-racha-fire/50 w-full h-screen'>
<H1>Generate a Referral Code</H1>
<div className='border border-hot-red rounded-2xl bg-white p-5'>
{refcodeIsLoading ? (
<DefaultLoader className="text-hot-red h-6 w-6" />
) : (
<>
<ReferralsList />
{referralLink ? (
<RefcodeLink referralLink={referralLink} />
) : (
<RefcodeCreator
accountEmail={accountEmail}
urlQueryEmail={urlQueryEmail}
createRefcode={createRefcode}
mutateRefcode={mutateRefcode}
setReferrerEmail={setReferrerEmail} />
)}
</>
)}
</div>
</div >
)
}
48 changes: 47 additions & 1 deletion src/app/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import useSWR from 'swr'
import Link from 'next/link'
import { usePlan } from '@/hooks'
import { SettingsNav } from './layout'
import { H1, H2 } from '@/components/Text'
import { H1, H2, H3 } from '@/components/Text'
import { GB, TB, filesize } from '@/lib'
import DefaultLoader from '@/components/Loader'
import { RefcodeLink, ReferralsList, RefcodeCreator } from '../referrals/page'
import { useReferrals } from '@/lib/referrals/hooks'
import { useSearchParams } from 'next/navigation'

const Plans: Record<`did:${string}`, { name: string, limit: number }> = {
'did:web:starter.web3.storage': { name: 'Starter', limit: 5 * GB },
Expand All @@ -16,6 +19,9 @@ const Plans: Record<`did:${string}`, { name: string, limit: number }> = {
'did:web:free.web3.storage': { name: 'Free', limit: Infinity },
}

const MAX_REFERRALS = 11
const MAX_CREDITS = 460
Comment on lines +22 to +23
Copy link
Member

Choose a reason for hiding this comment

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

suggestion: read it from env.

Copy link
Member Author

Choose a reason for hiding this comment

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

ah yea - I'm going to come back to this once we have support for actually giving people credits and displaying them - will move to config once that's done!


export default function SettingsPage (): JSX.Element {
const [{ client, accounts }] = useW3()
// TODO: introduce account switcher
Expand Down Expand Up @@ -62,10 +68,50 @@ export default function SettingsPage (): JSX.Element {
const allocated = Object.values(usage ?? {}).reduce((total, n) => total + n, 0)
const limit = plan?.product ? Plans[plan.product]?.limit : 0

const { referrals, referralLink, setReferrerEmail, accountEmail, urlQueryEmail, createRefcode, mutateRefcode, } = useReferrals()

const referred = referrals?.length || 0

// TODO: need to calculate these from the referral information that gets added during the TBD cronjob
const credits = 0
const points = 0
const params = useSearchParams()
const referralsEnabled = (params.get('referrals') === 'enabled')
return (
<>
<SettingsNav />
<H1>Settings</H1>
{referralsEnabled && (
<>
<H2>Rewards</H2>
<div className='flex flex-row space-x-2 justify-between max-w-4xl mb-4'>
<div className='border border-hot-red rounded-2xl bg-white p-5 flex-grow'>
<H3>Referred</H3>
<span className='text-4xl'>{referred}</span> / {MAX_REFERRALS}
</div>
<div className='border border-hot-red rounded-2xl bg-white p-5 flex-grow'>
<H3>USD Credits</H3>
<span className='text-4xl'>{credits}</span> / {MAX_CREDITS}
</div>
<div className='border border-hot-red rounded-2xl bg-white p-5 flex-grow'>
<H3>Racha Points</H3>
<span className='text-4xl'>{points}</span>
</div>
</div>
<div className='border border-hot-red rounded-2xl bg-white p-5 max-w-4xl mb-4'>
<ReferralsList />
{referralLink ? (
<RefcodeLink referralLink={referralLink} />
) : (
<RefcodeCreator
accountEmail={accountEmail}
urlQueryEmail={urlQueryEmail}
createRefcode={createRefcode}
mutateRefcode={mutateRefcode}
setReferrerEmail={setReferrerEmail} />)}
</div>
</>
)}
<div className='border border-hot-red rounded-2xl bg-white p-5 max-w-4xl'>
<H2>Plan</H2>
<p className='font-epilogue mb-4'>
Expand Down
8 changes: 8 additions & 0 deletions src/components/Authenticator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
import { Logo } from '../brand'
import { TopLevelLoader } from './Loader'

import { useRecordRefcode } from '@/lib/referrals/hooks'

export function AuthenticationForm (): JSX.Element {
const [{ submitted }] = useAuthenticator()
return (
Expand Down Expand Up @@ -39,6 +41,12 @@ export function AuthenticationForm (): JSX.Element {
export function AuthenticationSubmitted (): JSX.Element {
const [{ email }] = useAuthenticator()

// ensure the referral of this user is tracked if necessary.
// we might use the result of this hook in the future to tell
// people that they get special pricing on the next page after
// they verify their email.
useRecordRefcode()

return (
<div className='authenticator'>
<div className='text-hot-red bg-white border border-hot-red rounded-2xl shadow-md px-10 pt-8 pb-8'>
Expand Down
Loading
Loading