diff --git a/api/typeDefs/sub.js b/api/typeDefs/sub.js index 3c5aa496..5b82f5b8 100644 --- a/api/typeDefs/sub.js +++ b/api/typeDefs/sub.js @@ -9,7 +9,8 @@ export default gql` extend type Mutation { upsertSub(name: String!, desc: String, baseCost: Int!, - postTypes: [String!]!, billingType: String!, hash: String, hmac: String): Sub + postTypes: [String!]!, billingType: String!, billingAutoRenew: Boolean!, + hash: String, hmac: String): Sub paySub(name: String!, hash: String, hmac: String): Sub } @@ -23,6 +24,7 @@ export default gql` postTypes: [String!]! billingCost: Int! billingType: String! + billingAutoRenew: Boolean! rankingType: String! billedLastAt: Date! baseCost: Int! diff --git a/components/territory-form.js b/components/territory-form.js index 193062ad..1e222e8b 100644 --- a/components/territory-form.js +++ b/components/territory-form.js @@ -15,9 +15,11 @@ export default function TerritoryForm ({ sub }) { const [upsertSub] = useMutation( gql` mutation upsertSub($name: String!, $desc: String, $baseCost: Int!, - $postTypes: [String!]!, $billingType: String!, $hash: String, $hmac: String) { + $postTypes: [String!]!, $billingType: String!, $billingAutoRenew: Boolean!, + $hash: String, $hmac: String) { upsertSub(name: $name, desc: $desc, baseCost: $baseCost, - postTypes: $postTypes, billingType: $billingType, hash: $hash, hmac: $hmac) { + postTypes: $postTypes, billingType: $billingType, + billingAutoRenew: $billingAutoRenew, hash: $hash, hmac: $hmac) { name } }` @@ -59,7 +61,8 @@ export default function TerritoryForm ({ sub }) { desc: sub?.desc || '', baseCost: sub?.baseCost || 10, postTypes: sub?.postTypes || POST_TYPES, - billingType: sub?.billingType || 'MONTHLY' + billingType: sub?.billingType || 'MONTHLY', + billingAutoRenew: sub?.billingAutoRenew || false }} schema={territorySchema({ client, me })} invoiceable @@ -138,7 +141,11 @@ export default function TerritoryForm ({ sub }) { - name read only : 'billing'} name='billing'> + {(!sub?.billingType || sub.billingType === 'MONTHLY') && } + {billing !== 'once' && + }
{ + if (!sub || sub.billingType === 'ONCE') return null + + const pivot = sub.billingType === 'MONTHLY' + ? { months: 1 } + : { years: 1 } + + pivot.days = grace ? TERRITORY_GRACE_DAYS : 0 + + return datePivot(new Date(sub.billedLastAt), pivot) +} + export default function TerritoryPaymentDue ({ sub }) { const me = useMe() const client = useApolloClient() const [paySub] = useMutation(SUB_PAY) - const dueDate = datePivot( - new Date(sub.billedLastAt), - sub.billingType === 'MONTHLY' - ? { months: 1, days: TERRITORY_GRACE_DAYS } - : { years: 1, days: TERRITORY_GRACE_DAYS }) - const onSubmit = useCallback( async ({ ...variables }) => { const { error } = await paySub({ @@ -33,6 +39,10 @@ export default function TerritoryPaymentDue ({ sub }) { if (!sub || sub.userId !== Number(me?.id) || sub.status === 'ACTIVE') return null + const dueDate = billingDueDate(sub, true) + + if (!dueDate) return null + return ( {sub.status === 'STOPPED' @@ -75,3 +85,17 @@ export default function TerritoryPaymentDue ({ sub }) { ) } + +export function TerritoryBillingLine ({ sub }) { + const me = useMe() + if (!sub || sub.userId !== Number(me?.id)) return null + + const dueDate = billingDueDate(sub, false) + + return ( +
+ billing {sub.billingAutoRenew ? 'automatically renews' : 'due'} on + {dueDate ? timeSince(dueDate) : 'never again'} +
+ ) +} diff --git a/fragments/subs.js b/fragments/subs.js index 57d63d46..cb180608 100644 --- a/fragments/subs.js +++ b/fragments/subs.js @@ -9,6 +9,7 @@ export const SUB_FIELDS = gql` rankingType billingType billingCost + billingAutoRenew billedLastAt baseCost userId diff --git a/lib/time.js b/lib/time.js index 8d827419..84c5fa24 100644 --- a/lib/time.js +++ b/lib/time.js @@ -1,6 +1,6 @@ export function timeSince (timeStamp) { const now = new Date() - const secondsPast = (now.getTime() - timeStamp) / 1000 + const secondsPast = Math.abs(now.getTime() - timeStamp) / 1000 if (secondsPast < 60) { return parseInt(secondsPast) + 's' } diff --git a/pages/~/index.js b/pages/~/index.js index df6698ef..e45ba540 100644 --- a/pages/~/index.js +++ b/pages/~/index.js @@ -15,7 +15,7 @@ import PageLoading from '../../components/page-loading' import CardFooter from 'react-bootstrap/CardFooter' import Hat from '../../components/hat' import styles from '../../components/item.module.css' -import TerritoryPaymentDue from '../../components/territory-payment-due' +import TerritoryPaymentDue, { TerritoryBillingLine } from '../../components/territory-payment-due' import Badge from 'react-bootstrap/Badge' import { numWithUnits } from '../../lib/format' @@ -49,17 +49,16 @@ export default function Sub ({ ssrData }) {
- founded by - + founded by @{sub.user.name}
- post cost - + post cost {numWithUnits(sub.baseCost)}
+
diff --git a/prisma/migrations/20231208191418_territory_renew/migration.sql b/prisma/migrations/20231208191418_territory_renew/migration.sql new file mode 100644 index 00000000..fa91a81c --- /dev/null +++ b/prisma/migrations/20231208191418_territory_renew/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Sub" ADD COLUMN "billingAutoRenew" BOOLEAN NOT NULL DEFAULT false; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 78e0dfff..fab48d87 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -403,15 +403,16 @@ model Sub { parentName String? @db.Citext path Unsupported("ltree")? - postTypes PostType[] - rankingType RankingType - baseCost Int @default(1) - rewardsPct Int @default(50) - desc String? - status Status @default(ACTIVE) - billingType BillingType - billingCost Int - billedLastAt DateTime @default(now()) + postTypes PostType[] + rankingType RankingType + baseCost Int @default(1) + rewardsPct Int @default(50) + desc String? + status Status @default(ACTIVE) + billingType BillingType + billingCost Int + billingAutoRenew Boolean @default(false) + billedLastAt DateTime @default(now()) parent Sub? @relation("ParentChildren", fields: [parentName], references: [name]) children Sub[] @relation("ParentChildren") diff --git a/worker/territory.js b/worker/territory.js index 45245ef4..e593b2b3 100644 --- a/worker/territory.js +++ b/worker/territory.js @@ -10,12 +10,7 @@ export async function territoryBilling ({ data: { subName }, boss, models }) { } }) - try { - const queries = paySubQueries(sub, models) - await serialize(models, ...queries) - } catch (e) { - console.error(e) - + async function territoryStatusUpdate () { await models.sub.update({ where: { name: subName @@ -27,4 +22,17 @@ export async function territoryBilling ({ data: { subName }, boss, models }) { // retry billing in one day await boss.send('territoryBilling', { subName }, { startAfter: datePivot(new Date(), { days: 1 }) }) } + + if (!sub.billingAutoRenew) { + await territoryStatusUpdate() + return + } + + try { + const queries = paySubQueries(sub, models) + await serialize(models, ...queries) + } catch (e) { + console.error(e) + await territoryStatusUpdate() + } }