diff --git a/api/resolvers/growth.js b/api/resolvers/growth.js index f048d109..c4f5e7ac 100644 --- a/api/resolvers/growth.js +++ b/api/resolvers/growth.js @@ -1,6 +1,6 @@ const PLACEHOLDERS_NUM = 616 -function interval (when) { +export function interval (when) { switch (when) { case 'week': return '1 week' @@ -15,7 +15,7 @@ function interval (when) { } } -function timeUnit (when) { +export function timeUnit (when) { switch (when) { case 'week': case 'month': @@ -28,7 +28,7 @@ function timeUnit (when) { } } -function withClause (when) { +export function withClause (when) { const ival = interval(when) const unit = timeUnit(when) @@ -44,7 +44,7 @@ function withClause (when) { } // HACKY AF this is a performance enhancement that allows us to use the created_at indices on tables -function intervalClause (when, table, and) { +export function intervalClause (when, table, and) { if (when === 'forever') { return and ? '' : 'TRUE' } @@ -58,7 +58,7 @@ export default { return await models.$queryRaw( `${withClause(when)} SELECT time, json_build_array( - json_build_object('name', 'invited', 'value', count("inviteId")), + json_build_object('name', 'referrals', 'value', count("referrerId")), json_build_object('name', 'organic', 'value', count(users.id) FILTER(WHERE id > ${PLACEHOLDERS_NUM}) - count("inviteId")) ) AS data FROM times @@ -131,7 +131,8 @@ export default { json_build_object('name', 'any', 'value', count(distinct user_id)), json_build_object('name', 'posts', 'value', count(distinct user_id) FILTER (WHERE type = 'POST')), json_build_object('name', 'comments', 'value', count(distinct user_id) FILTER (WHERE type = 'COMMENT')), - json_build_object('name', 'rewards', 'value', count(distinct user_id) FILTER (WHERE type = 'EARN')) + json_build_object('name', 'rewards', 'value', count(distinct user_id) FILTER (WHERE type = 'EARN')), + json_build_object('name', 'referrals', 'value', count(distinct user_id) FILTER (WHERE type = 'REFERRAL')) ) AS data FROM times LEFT JOIN @@ -142,7 +143,11 @@ export default { UNION ALL (SELECT created_at, "userId" as user_id, 'EARN' as type FROM "Earn" - WHERE ${intervalClause(when, 'Earn', false)})) u ON time = date_trunc('${timeUnit(when)}', u.created_at) + WHERE ${intervalClause(when, 'Earn', false)}) + UNION ALL + (SELECT created_at, "referrerId" as user_id, 'REFERRAL' as type + FROM "ReferralAct" + WHERE ${intervalClause(when, 'ReferralAct', false)})) u ON time = date_trunc('${timeUnit(when)}', u.created_at) GROUP BY time ORDER BY time ASC`) }, @@ -152,18 +157,24 @@ export default { SELECT time, json_build_array( json_build_object('name', 'rewards', 'value', coalesce(floor(sum(airdrop)/1000),0)), json_build_object('name', 'posts', 'value', coalesce(floor(sum(post)/1000),0)), - json_build_object('name', 'comments', 'value', coalesce(floor(sum(comment)/1000),0)) + json_build_object('name', 'comments', 'value', coalesce(floor(sum(comment)/1000),0)), + json_build_object('name', 'referrals', 'value', coalesce(floor(sum(referral)/1000),0)) ) AS data FROM times LEFT JOIN ((SELECT "ItemAct".created_at, 0 as airdrop, CASE WHEN "Item"."parentId" IS NULL THEN 0 ELSE "ItemAct".msats END as comment, - CASE WHEN "Item"."parentId" IS NULL THEN "ItemAct".msats ELSE 0 END as post + CASE WHEN "Item"."parentId" IS NULL THEN "ItemAct".msats ELSE 0 END as post, + 0 as referral FROM "ItemAct" JOIN "Item" on "ItemAct"."itemId" = "Item".id WHERE ${intervalClause(when, 'ItemAct', true)} "ItemAct".act = 'TIP') UNION ALL - (SELECT created_at, msats as airdrop, 0 as post, 0 as comment + (SELECT created_at, 0 as airdrop, 0 as post, 0 as comment, msats as referral + FROM "ReferralAct" + WHERE ${intervalClause(when, 'ReferralAct', false)}) + UNION ALL + (SELECT created_at, msats as airdrop, 0 as post, 0 as comment, 0 as referral FROM "Earn" WHERE ${intervalClause(when, 'Earn', false)})) u ON time = date_trunc('${timeUnit(when)}', u.created_at) GROUP BY time diff --git a/api/resolvers/index.js b/api/resolvers/index.js index 7798a789..be4d925c 100644 --- a/api/resolvers/index.js +++ b/api/resolvers/index.js @@ -10,7 +10,8 @@ import upload from './upload' import growth from './growth' import search from './search' import rewards from './rewards' +import referrals from './referrals' import { GraphQLJSONObject } from 'graphql-type-json' export default [user, item, message, wallet, lnurl, notifications, invite, sub, - upload, growth, search, rewards, { JSONObject: GraphQLJSONObject }] + upload, growth, search, rewards, referrals, { JSONObject: GraphQLJSONObject }] diff --git a/api/resolvers/notifications.js b/api/resolvers/notifications.js index afe97dbe..d9405b99 100644 --- a/api/resolvers/notifications.js +++ b/api/resolvers/notifications.js @@ -160,6 +160,15 @@ export default { ORDER BY "sortTime" DESC LIMIT ${LIMIT}+$3)` ) + queries.push( + `(SELECT users.id::text, users.created_at AS "sortTime", NULL as "earnedSats", + 'Referral' AS type + FROM users + WHERE "users"."referrerId" = $1 + AND "inviteId" IS NULL + AND users.created_at <= $2 + LIMIT ${LIMIT}+$3)` + ) } if (meFull.noteEarning) { diff --git a/api/resolvers/referrals.js b/api/resolvers/referrals.js new file mode 100644 index 00000000..55ec2ad1 --- /dev/null +++ b/api/resolvers/referrals.js @@ -0,0 +1,55 @@ +import { AuthenticationError } from 'apollo-server-micro' +import { withClause, intervalClause, timeUnit } from './growth' + +export default { + Query: { + referrals: async (parent, { when }, { models, me }) => { + if (!me) { + throw new AuthenticationError('you must be logged in') + } + + const [{ totalSats }] = await models.$queryRaw(` + SELECT COALESCE(FLOOR(sum(msats) / 1000), 0) as "totalSats" + FROM "ReferralAct" + WHERE ${intervalClause(when, 'ReferralAct', true)} + "ReferralAct"."referrerId" = $1 + `, Number(me.id)) + + const [{ totalReferrals }] = await models.$queryRaw(` + SELECT count(*) as "totalReferrals" + FROM users + WHERE ${intervalClause(when, 'users', true)} + "referrerId" = $1 + `, Number(me.id)) + + const stats = await models.$queryRaw( + `${withClause(when)} + SELECT time, json_build_array( + json_build_object('name', 'referrals', 'value', count(*) FILTER (WHERE act = 'REFERREE')), + json_build_object('name', 'sats', 'value', FLOOR(COALESCE(sum(msats) FILTER (WHERE act IN ('BOOST', 'STREAM', 'FEE')), 0))) + ) AS data + FROM times + LEFT JOIN + ((SELECT "ReferralAct".created_at, "ReferralAct".msats / 1000.0 as msats, "ItemAct".act::text as act + FROM "ReferralAct" + JOIN "ItemAct" ON "ItemAct".id = "ReferralAct"."itemActId" + WHERE ${intervalClause(when, 'ReferralAct', true)} + "ReferralAct"."referrerId" = $1) + UNION ALL + (SELECT created_at, 0.0 as sats, 'REFERREE' as act + FROM users + WHERE ${intervalClause(when, 'users', true)} + "referrerId" = $1)) u ON time = date_trunc('${timeUnit(when)}', u.created_at) + GROUP BY time + ORDER BY time ASC`, Number(me.id)) + + console.log(totalSats) + + return { + totalSats, + totalReferrals, + stats + } + } + } +} diff --git a/api/resolvers/rewards.js b/api/resolvers/rewards.js index caddb485..8832416b 100644 --- a/api/resolvers/rewards.js +++ b/api/resolvers/rewards.js @@ -12,18 +12,19 @@ export default { }) const [result] = await models.$queryRaw` - SELECT coalesce(sum(sats), 0) as total, json_build_array( - json_build_object('name', 'donations', 'value', coalesce(sum(sats) FILTER(WHERE type = 'DONATION'), 0)), - json_build_object('name', 'fees', 'value', coalesce(sum(sats) FILTER(WHERE type NOT IN ('BOOST', 'STREAM', 'DONATION')), 0)), - json_build_object('name', 'boost', 'value', coalesce(sum(sats) FILTER(WHERE type = 'BOOST'), 0)), - json_build_object('name', 'jobs', 'value', coalesce(sum(sats) FILTER(WHERE type = 'STREAM'), 0)) + SELECT coalesce(FLOOR(sum(sats)), 0) as total, json_build_array( + json_build_object('name', 'donations', 'value', coalesce(FLOOR(sum(sats) FILTER(WHERE type = 'DONATION')), 0)), + json_build_object('name', 'fees', 'value', coalesce(FLOOR(sum(sats) FILTER(WHERE type NOT IN ('BOOST', 'STREAM', 'DONATION'))), 0)), + json_build_object('name', 'boost', 'value', coalesce(FLOOR(sum(sats) FILTER(WHERE type = 'BOOST')), 0)), + json_build_object('name', 'jobs', 'value', coalesce(FLOOR(sum(sats) FILTER(WHERE type = 'STREAM')), 0)) ) AS sources FROM ( - (SELECT msats / 1000 as sats, act::text as type + (SELECT ("ItemAct".msats - COALESCE("ReferralAct".msats, 0)) / 1000.0 as sats, act::text as type FROM "ItemAct" - WHERE created_at > ${lastReward.createdAt} AND "ItemAct".act <> 'TIP') + LEFT JOIN "ReferralAct" ON "ItemAct".id = "ReferralAct"."itemActId" + WHERE "ItemAct".created_at > ${lastReward.createdAt} AND "ItemAct".act <> 'TIP') UNION ALL - (SELECT sats, 'DONATION' as type + (SELECT sats::FLOAT, 'DONATION' as type FROM "Donation" WHERE created_at > ${lastReward.createdAt}) ) subquery` diff --git a/api/resolvers/user.js b/api/resolvers/user.js index 3485189d..8bb44ac7 100644 --- a/api/resolvers/user.js +++ b/api/resolvers/user.js @@ -271,6 +271,18 @@ export default { if (newInvitees.length > 0) { return true } + + const referral = await models.user.findFirst({ + where: { + referrerId: me.id, + createdAt: { + gt: lastChecked + } + } + }) + if (referral) { + return true + } } return false diff --git a/api/resolvers/wallet.js b/api/resolvers/wallet.js index 3255b230..82421db7 100644 --- a/api/resolvers/wallet.js +++ b/api/resolvers/wallet.js @@ -5,7 +5,7 @@ import { decodeCursor, LIMIT, nextCursorEncoded } from '../../lib/cursor' import lnpr from 'bolt11' import { SELECT } from './item' import { lnurlPayDescriptionHash } from '../../lib/lnurl' -import { msatsToSats } from '../../lib/format' +import { msatsToSats, msatsToSatsDecimal } from '../../lib/format' export async function getInvoice (parent, { id }, { me, models }) { if (!me) { @@ -110,6 +110,12 @@ export default { FROM "Earn" WHERE "Earn"."userId" = $1 AND "Earn".created_at <= $2 GROUP BY "userId", created_at)`) + queries.push( + `(SELECT ('referral' || "ReferralAct".id) as id, "ReferralAct".id as "factId", NULL as bolt11, + created_at as "createdAt", msats, + 0 as "msatsFee", NULL as status, 'referral' as type + FROM "ReferralAct" + WHERE "ReferralAct"."referrerId" = $1 AND "ReferralAct".created_at <= $2)`) } if (include.has('spent')) { @@ -287,8 +293,8 @@ export default { return item }, - sats: fact => msatsToSats(fact.msats), - satsFee: fact => msatsToSats(fact.msatsFee) + sats: fact => msatsToSatsDecimal(fact.msats), + satsFee: fact => msatsToSatsDecimal(fact.msatsFee) } } diff --git a/api/typeDefs/index.js b/api/typeDefs/index.js index 183069da..8eb96b29 100644 --- a/api/typeDefs/index.js +++ b/api/typeDefs/index.js @@ -11,6 +11,7 @@ import sub from './sub' import upload from './upload' import growth from './growth' import rewards from './rewards' +import referrals from './referrals' const link = gql` type Query { @@ -27,4 +28,4 @@ const link = gql` ` export default [link, user, item, message, wallet, lnurl, notifications, invite, - sub, upload, growth, rewards] + sub, upload, growth, rewards, referrals] diff --git a/api/typeDefs/notifications.js b/api/typeDefs/notifications.js index d880ba98..9d4d5c12 100644 --- a/api/typeDefs/notifications.js +++ b/api/typeDefs/notifications.js @@ -50,8 +50,12 @@ export default gql` sortTime: String! } + type Referral { + sortTime: String! + } + union Notification = Reply | Votification | Mention - | Invitification | Earn | JobChanged | InvoicePaid + | Invitification | Earn | JobChanged | InvoicePaid | Referral type Notifications { lastChecked: String diff --git a/api/typeDefs/referrals.js b/api/typeDefs/referrals.js new file mode 100644 index 00000000..5309f37d --- /dev/null +++ b/api/typeDefs/referrals.js @@ -0,0 +1,13 @@ +import { gql } from 'apollo-server-micro' + +export default gql` + extend type Query { + referrals(when: String): Referrals! + } + + type Referrals { + totalSats: Int! + totalReferrals: Int! + stats: [TimeData!]! + } +` diff --git a/api/typeDefs/wallet.js b/api/typeDefs/wallet.js index 5b4d3303..ff06a926 100644 --- a/api/typeDefs/wallet.js +++ b/api/typeDefs/wallet.js @@ -41,8 +41,8 @@ export default gql` factId: ID! bolt11: String createdAt: String! - sats: Int! - satsFee: Int + sats: Float! + satsFee: Float status: String type: String! description: String diff --git a/components/comment.js b/components/comment.js index f1be8849..bf121a34 100644 --- a/components/comment.js +++ b/components/comment.js @@ -18,6 +18,7 @@ import DontLikeThis from './dont-link-this' import Flag from '../svgs/flag-fill.svg' import { Badge } from 'react-bootstrap' import { abbrNum } from '../lib/format' +import Share from './share' function Parent ({ item, rootText }) { const ParentFrag = () => ( @@ -169,6 +170,7 @@ export default function Comment ({ localStorage.setItem(`commentCollapse:${item.id}`, 'yep') }} />)} + {topLevel && } {edit ? ( diff --git a/components/header.js b/components/header.js index 6e34a2b2..34c81fc1 100644 --- a/components/header.js +++ b/components/header.js @@ -92,13 +92,8 @@ export default function Header ({ sub }) { satistics - - invites - {me && !me.hasInvites && -
- {' '} -
} -
+ + referrals
diff --git a/components/item-act.js b/components/item-act.js index 073fe7ab..79ccb16f 100644 --- a/components/item-act.js +++ b/components/item-act.js @@ -84,7 +84,7 @@ export function ItemActModal () { {[1, 10, 100, 1000, 10000].map(num =>
{showFwdUser && item.fwdUser && } - {toc && } + {toc && + <> + + + } {children && (
diff --git a/components/notifications.js b/components/notifications.js index 4b4fd57c..75c650b9 100644 --- a/components/notifications.js +++ b/components/notifications.js @@ -20,7 +20,7 @@ function Notification ({ n }) {
{ - if (n.__typename === 'Earn') { + if (n.__typename === 'Earn' || n.__typename === 'Referral') { return } @@ -88,41 +88,50 @@ function Notification ({ n }) {
) - : n.__typename === 'InvoicePaid' + : n.__typename === 'Referral' ? ( -
- {n.earnedSats} sats were deposited in your account - {timeSince(new Date(n.sortTime))} -
) - : ( <> - {n.__typename === 'Votification' && - - your {n.item.title ? 'post' : 'reply'} {n.item.fwdUser ? 'forwarded' : 'stacked'} {n.earnedSats} sats{n.item.fwdUser && ` to @${n.item.fwdUser.name}`} - } - {n.__typename === 'Mention' && - - you were mentioned in - } - {n.__typename === 'JobChanged' && - - {n.item.status === 'ACTIVE' - ? 'your job is active again' - : (n.item.status === 'NOSATS' - ? 'your job promotion ran out of sats' - : 'your job has been stopped')} - } -
- {n.item.isJob - ? - : n.item.title - ? - : ( -
- -
)} -
- )} + + someone joined via one of your referral links + {timeSince(new Date(n.sortTime))} + + + ) + : n.__typename === 'InvoicePaid' + ? ( +
+ {n.earnedSats} sats were deposited in your account + {timeSince(new Date(n.sortTime))} +
) + : ( + <> + {n.__typename === 'Votification' && + + your {n.item.title ? 'post' : 'reply'} {n.item.fwdUser ? 'forwarded' : 'stacked'} {n.earnedSats} sats{n.item.fwdUser && ` to @${n.item.fwdUser.name}`} + } + {n.__typename === 'Mention' && + + you were mentioned in + } + {n.__typename === 'JobChanged' && + + {n.item.status === 'ACTIVE' + ? 'your job is active again' + : (n.item.status === 'NOSATS' + ? 'your job promotion ran out of sats' + : 'your job has been stopped')} + } +
+ {n.item.isJob + ? + : n.item.title + ? + : ( +
+ +
)} +
+ )} ) } diff --git a/components/share.js b/components/share.js new file mode 100644 index 00000000..74c82172 --- /dev/null +++ b/components/share.js @@ -0,0 +1,45 @@ +import { Dropdown } from 'react-bootstrap' +import ShareIcon from '../svgs/share-fill.svg' +import copy from 'clipboard-copy' +import { useMe } from './me' + +export default function Share ({ item }) { + const me = useMe() + const url = `https://stacker.news/items/${item.id}${me ? `/r/${me.name}` : ''}` + + return typeof window !== 'undefined' && navigator?.share + ? ( +
+ { + if (navigator.share) { + navigator.share({ + title: item.title || '', + text: '', + url + }).then(() => console.log('Successful share')) + .catch((error) => console.log('Error sharing', error)) + } else { + console.log('no navigator.share') + } + }} + /> +
) + : ( + + + + + + + { + copy(url) + }} + > + copy link + + + ) +} diff --git a/components/when-charts.js b/components/when-charts.js new file mode 100644 index 00000000..ba042264 --- /dev/null +++ b/components/when-charts.js @@ -0,0 +1,161 @@ +import { LineChart, Line, XAxis, YAxis, Tooltip, Legend, ResponsiveContainer, AreaChart, Area, ComposedChart, Bar } from 'recharts' +import { abbrNum } from '../lib/format' +import { useRouter } from 'next/router' + +const dateFormatter = when => { + return timeStr => { + const date = new Date(timeStr) + switch (when) { + case 'week': + case 'month': + return `${('0' + (date.getUTCMonth() % 12 + 1)).slice(-2)}/${date.getUTCDate()}` + case 'year': + case 'forever': + return `${('0' + (date.getUTCMonth() % 12 + 1)).slice(-2)}/${String(date.getUTCFullYear()).slice(-2)}` + default: + return `${date.getHours() % 12 || 12}${date.getHours() >= 12 ? 'pm' : 'am'}` + } + } +} + +function xAxisName (when) { + switch (when) { + case 'week': + case 'month': + return 'days' + case 'year': + case 'forever': + return 'months' + default: + return 'hours' + } +} + +const transformData = data => { + return data.map(entry => { + const obj = { time: entry.time } + entry.data.forEach(entry1 => { + obj[entry1.name] = entry1.value + }) + return obj + }) +} + +const COLORS = [ + 'var(--secondary)', + 'var(--info)', + 'var(--success)', + 'var(--boost)', + 'var(--theme-grey)', + 'var(--danger)' +] + +export function WhenAreaChart ({ data }) { + const router = useRouter() + if (!data || data.length === 0) { + return null + } + // transform data into expected shape + data = transformData(data) + // need to grab when + const when = router.query.when + + return ( + + + + + + + {Object.keys(data[0]).filter(v => v !== 'time' && v !== '__typename').map((v, i) => + )} + + + ) +} + +export function WhenLineChart ({ data }) { + const router = useRouter() + if (!data || data.length === 0) { + return null + } + // transform data into expected shape + data = transformData(data) + // need to grab when + const when = router.query.when + + return ( + + + + + + + {Object.keys(data[0]).filter(v => v !== 'time' && v !== '__typename').map((v, i) => + )} + + + ) +} + +export function WhenComposedChart ({ data, lineNames, areaNames, barNames }) { + const router = useRouter() + if (!data || data.length === 0) { + return null + } + // transform data into expected shape + data = transformData(data) + // need to grab when + const when = router.query.when + + return ( + + + + + + + + {barNames?.map((v, i) => + )} + {areaNames?.map((v, i) => + )} + {lineNames?.map((v, i) => + )} + + + ) +} diff --git a/fragments/notifications.js b/fragments/notifications.js index c7f43172..58482a55 100644 --- a/fragments/notifications.js +++ b/fragments/notifications.js @@ -37,6 +37,9 @@ export const NOTIFICATIONS = gql` tips } } + ... on Referral { + sortTime + } ... on Reply { sortTime item { diff --git a/lib/constants.js b/lib/constants.js index ec7a9582..185cb5a4 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -1,4 +1,4 @@ -export const NOFOLLOW_LIMIT = 1000 +export const NOFOLLOW_LIMIT = 100 export const BOOST_MIN = 5000 export const UPLOAD_SIZE_MAX = 2 * 1024 * 1024 export const IMAGE_PIXELS_MAX = 35000000 diff --git a/lib/format.js b/lib/format.js index d312eb9e..405ea09a 100644 --- a/lib/format.js +++ b/lib/format.js @@ -16,3 +16,10 @@ export const msatsToSats = msats => { } return Number(BigInt(msats) / 1000n) } + +export const msatsToSatsDecimal = msats => { + if (msats === null || msats === undefined) { + return null + } + return fixedDecimal(msats / 1000.0, 3) +} diff --git a/middleware.js b/middleware.js new file mode 100644 index 00000000..b76a96a1 --- /dev/null +++ b/middleware.js @@ -0,0 +1,18 @@ +import { NextResponse } from 'next/server' + +export function middleware (request) { + const regex = /(\/.*)?\/r\/([\w_]+)/ + const m = regex.exec(request.nextUrl.pathname) + + const url = new URL(m[1] || '/', request.url) + url.search = request.nextUrl.search + url.hash = request.nextUrl.hash + + const resp = NextResponse.redirect(url) + resp.cookies.set('sn_referrer', m[2]) + return resp +} + +export const config = { + matcher: ['/(.*/|)r/([\\w_]+)([?#]?.*)'] +} diff --git a/pages/api/auth/[...nextauth].js b/pages/api/auth/[...nextauth].js index e104714a..7beffc55 100644 --- a/pages/api/auth/[...nextauth].js +++ b/pages/api/auth/[...nextauth].js @@ -5,9 +5,7 @@ import prisma from '../../../api/models' import nodemailer from 'nodemailer' import { getSession } from 'next-auth/client' -export default (req, res) => NextAuth(req, res, options) - -const options = { +export default (req, res) => NextAuth(req, res, { callbacks: { /** * @param {object} token Decrypted JSON Web Token @@ -26,22 +24,32 @@ const options = { token.user = { id: Number(user.id) } } - // sign them up for the newsletter - if (isNewUser && profile.email) { - fetch(process.env.LIST_MONK_URL + '/api/subscribers', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: 'Basic ' + Buffer.from(process.env.LIST_MONK_AUTH).toString('base64') - }, - body: JSON.stringify({ - email: profile.email, - name: 'blank', - lists: [2], - status: 'enabled', - preconfirm_subscriptions: true - }) - }).then(async r => console.log(await r.json())).catch(console.log) + if (isNewUser) { + // if referrer exists, set on user + if (req.cookies.sn_referrer && user?.id) { + const referrer = await prisma.user.findUnique({ where: { name: req.cookies.sn_referrer } }) + if (referrer) { + await prisma.user.update({ where: { id: user.id }, data: { referrerId: referrer.id } }) + } + } + + // sign them up for the newsletter + if (profile.email) { + fetch(process.env.LIST_MONK_URL + '/api/subscribers', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: 'Basic ' + Buffer.from(process.env.LIST_MONK_AUTH).toString('base64') + }, + body: JSON.stringify({ + email: profile.email, + name: 'blank', + lists: [2], + status: 'enabled', + preconfirm_subscriptions: true + }) + }).then(async r => console.log(await r.json())).catch(console.log) + } } return token @@ -130,7 +138,7 @@ const options = { pages: { signIn: '/login' } -} +}) function sendVerificationRequest ({ identifier: email, diff --git a/pages/invites/index.js b/pages/invites/index.js index b401b2e9..e78d74e3 100644 --- a/pages/invites/index.js +++ b/pages/invites/index.js @@ -120,7 +120,7 @@ export default function Invites () {

invite links

- send these to people you trust somewhat, e.g. group chats or DMs + send these to people you trust, e.g. group chats or DMs {active.length > 0 && } diff --git a/pages/referrals/[when].js b/pages/referrals/[when].js new file mode 100644 index 00000000..942d6a6a --- /dev/null +++ b/pages/referrals/[when].js @@ -0,0 +1,75 @@ +import { gql } from 'apollo-server-micro' +import Link from 'next/link' +import { useRouter } from 'next/router' +import { getGetServerSideProps } from '../../api/ssrApollo' +import { CopyInput, Form, Select } from '../../components/form' +import LayoutCenter from '../../components/layout-center' +import { useMe } from '../../components/me' +import { WhenComposedChart } from '../../components/when-charts' + +export const getServerSideProps = getGetServerSideProps( + gql` + query Referrals($when: String!) + { + referrals(when: $when) { + totalSats + totalReferrals + stats { + time + data { + name + value + } + } + } + }`) + +export default function Referrals ({ data: { referrals: { totalSats, totalReferrals, stats } } }) { + const router = useRouter() + const me = useMe() + return ( + +
+

+ {totalReferrals} referrals & {totalSats} sats in the last +