diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fd5cb54d3..4e0701511 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,13 +48,13 @@ jobs: var { addedDiff: diff } = require('deep-object-diff'); var { inspect } = require('util'); - var source = JSON.parse(fs.readFileSync('./translations/source.json')); + var source = JSON.parse(fs.readFileSync('./translations/en.json')); var translations = fs.readdirSync('./translations') .map(f => path.join('./translations', f)) .map(j => ({ path: j, json: JSON.parse(fs.readFileSync(j)) })); for (var json of translations) { - const added = diff(source, json.json) + var added = diff(source, json.json) if (Object.keys(added).length) { console.log(inspect(added, undefined, 100, true), json.path) diff --git a/.gitignore b/.gitignore index d6f720c48..3c9056d15 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ package-lock.json Pictures/Thumbs.db Pictures/Splash/Thumbs.db Pictures/TransparentPics/Thumbs.db + +**/.DS_Store diff --git a/Pictures/PseudoShop/ascended-monthly-sub.png b/Pictures/PseudoShop/ascended-monthly-sub.png new file mode 100644 index 000000000..622eb5356 Binary files /dev/null and b/Pictures/PseudoShop/ascended-monthly-sub.png differ diff --git a/Pictures/PseudoShop/omega-monthly-sub.png b/Pictures/PseudoShop/omega-monthly-sub.png new file mode 100644 index 000000000..0183e0944 Binary files /dev/null and b/Pictures/PseudoShop/omega-monthly-sub.png differ diff --git a/Pictures/PseudoShop/reincarnated-monthly-sub.png b/Pictures/PseudoShop/reincarnated-monthly-sub.png new file mode 100644 index 000000000..3beceeaef Binary files /dev/null and b/Pictures/PseudoShop/reincarnated-monthly-sub.png differ diff --git a/Pictures/PseudoShop/transcended-monthly-sub.png b/Pictures/PseudoShop/transcended-monthly-sub.png new file mode 100644 index 000000000..cfb08e817 Binary files /dev/null and b/Pictures/PseudoShop/transcended-monthly-sub.png differ diff --git a/Pictures/ascended-monthly-sub.png b/Pictures/ascended-monthly-sub.png new file mode 100644 index 000000000..622eb5356 Binary files /dev/null and b/Pictures/ascended-monthly-sub.png differ diff --git a/Pictures/Jar of PseudoCoins.png b/Pictures/jar-pseudocoins.png similarity index 100% rename from Pictures/Jar of PseudoCoins.png rename to Pictures/jar-pseudocoins.png diff --git a/Pictures/Jug of PseudoCoins.png b/Pictures/jug-pseudocoins.png similarity index 100% rename from Pictures/Jug of PseudoCoins.png rename to Pictures/jug-pseudocoins.png diff --git a/Pictures/Magic Pot of PseudoCoins.png b/Pictures/magic-pot-pseudocoins.png similarity index 100% rename from Pictures/Magic Pot of PseudoCoins.png rename to Pictures/magic-pot-pseudocoins.png diff --git a/Pictures/omega-monthly-sub.png b/Pictures/omega-monthly-sub.png new file mode 100644 index 000000000..0183e0944 Binary files /dev/null and b/Pictures/omega-monthly-sub.png differ diff --git a/Pictures/Piggy Bank of PseudoCoins.png b/Pictures/piggy-bank-pseudocoins.png similarity index 100% rename from Pictures/Piggy Bank of PseudoCoins.png rename to Pictures/piggy-bank-pseudocoins.png diff --git a/Pictures/reincarnated-monthly-sub.png b/Pictures/reincarnated-monthly-sub.png new file mode 100644 index 000000000..3beceeaef Binary files /dev/null and b/Pictures/reincarnated-monthly-sub.png differ diff --git a/Pictures/Roll of PseudoCoins.png b/Pictures/roll-pseudocoins.png similarity index 100% rename from Pictures/Roll of PseudoCoins.png rename to Pictures/roll-pseudocoins.png diff --git a/Pictures/transcended-monthly-sub.png b/Pictures/transcended-monthly-sub.png new file mode 100644 index 000000000..cfb08e817 Binary files /dev/null and b/Pictures/transcended-monthly-sub.png differ diff --git a/Synergism.css b/Synergism.css index eb0c43094..e018a391a 100644 --- a/Synergism.css +++ b/Synergism.css @@ -3995,6 +3995,30 @@ form input:hover { margin: 0 auto; } +.pseudoSubscriptionContainer { + display: flex; + flex-direction: column; + align-items: center; +} + +.pseudoSubscriptionContainer > form { + display: flex; + flex-direction: column; + justify-content: center; + margin-top: 10px; +} + +.pseudoSubscriptionContainer > form > input[type="submit"] { + border: 2px solid gold; + width: 200px; + height: 40px; +} + +.pseudoSubscriptionContainer > #sub-section-holder { + display: grid; + grid-template-columns: repeat(4, 1fr); +} + .pseudoCoinContainer { margin: 10px 10px 0 0; border: 1px solid #ccc; @@ -4002,10 +4026,22 @@ form input:hover { text-align: center; } +.subscriptionContainer { + margin: 10px 10px 0 0; + border: 1px solid lightgoldenrodyellow; + border: 2px solid gold; + text-align: center; +} + #pseudoCoins > #productContainer > section > * > .pseudoCoinImage { margin-top: 8px; } +.pseudoCoinSubImage { + height: 48px; + width: 48px; +} + .pseudoCoinText { width: 90%; height: 80px; @@ -4017,8 +4053,18 @@ form input:hover { margin: 5px auto; } +.pseudoSubscriptionText { + width: 100%; + height: 80px; + padding: 10px; + box-sizing: border-box; + text-align: center; + box-shadow: 0 0 10px rgb(0 0 0 / 10%); + margin: 5px auto; +} + .pseudoCoinButton { - width: 50%; + width: 75%; padding: 10px; background-color: black; color: white; diff --git a/index.html b/index.html index d8cc14cf7..0b18b22cd 100644 --- a/index.html +++ b/index.html @@ -4696,12 +4696,17 @@

Artists

> +
+
+
+
PseudoCoins diff --git a/package.json b/package.json index 6d2daf917..38dcc1d08 100644 --- a/package.json +++ b/package.json @@ -38,8 +38,8 @@ "csslint": "npx stylelint Synergism.css", "csslint:fix": "npx stylelint Synergism.css --fix", "htmllint": "npx htmlhint index.html", - "build:esbuild": "npx esbuild src/Synergism.ts --bundle --minify --target=\"chrome58,firefox57,safari11,edge29\" --outfile=\"./dist/out.js\"", - "watch:esbuild": "npx esbuild src/Synergism.ts --bundle --minify --target=\"chrome58,firefox57,safari11,edge29\" --outfile=\"./dist/out.js\" --watch --sourcemap", + "build:esbuild": "npx esbuild src/Synergism.ts --bundle --minify --target=\"chrome58,firefox57,safari11,edge29\" --outfile=\"./dist/out.js\" --define:PROD=true", + "watch:esbuild": "npx esbuild src/Synergism.ts --bundle --minify --target=\"chrome58,firefox57,safari11,edge29\" --outfile=\"./dist/out.js\" --watch --sourcemap --define:PROD=false", "check:tsc": "npx -p typescript tsc", "watch:tsc": "npx -p typescript tsc --watch", "prepare": "husky install", diff --git a/scripts/update-last-updated.js b/scripts/update-last-updated.js index 4da248eda..d7fd6fe8a 100644 --- a/scripts/update-last-updated.js +++ b/scripts/update-last-updated.js @@ -26,14 +26,3 @@ try { console.error(e) process.exit(1) } - -try { - console.log('Update like prod, maaaan') - const config = readFileSync(`${__dirname}/../src/Config.ts`, 'utf-8') - .replace('const prod: boolean = false', 'const prod: boolean = true') - - writeFileSync(`${__dirname}/../src/Config.ts`, config) -} catch (e) { - console.log('maaaaaan that sucks!', e) - process.exit(1) -} diff --git a/src/Calculate.ts b/src/Calculate.ts index 881b2de2b..0fed84a29 100644 --- a/src/Calculate.ts +++ b/src/Calculate.ts @@ -2780,7 +2780,7 @@ export const calculateAscensionScore = () => { ? 1 : 0 bonusLevel += +player.singularityChallenges.oneChallengeCap.rewards.freeCorruptionLevel - + // Init Arrays with challenge values :) const challengeScoreArrays1 = [0, 8, 10, 12, 15, 20, 60, 80, 120, 180, 300] const challengeScoreArrays2 = [0, 10, 12, 15, 20, 30, 80, 120, 180, 300, 450] diff --git a/src/Config.ts b/src/Config.ts index a372d0be7..b10999af2 100644 --- a/src/Config.ts +++ b/src/Config.ts @@ -1,3 +1,5 @@ +declare const PROD: boolean | undefined + export const version = '3.1.1 January 15, 2025 pt 1.4: The Bakery Update' /** @@ -13,4 +15,4 @@ export const lastUpdated = new Date('##LAST_UPDATED##') /** * CHANGE THIS ONE INSTEAD */ -export const prod: boolean = false +export const prod = typeof PROD === 'undefined' ? false : PROD diff --git a/src/PseudoCoinUpgrades.ts b/src/PseudoCoinUpgrades.ts index adbb77ff4..30301ad27 100644 --- a/src/PseudoCoinUpgrades.ts +++ b/src/PseudoCoinUpgrades.ts @@ -1,6 +1,7 @@ import i18next from 'i18next' import { displayProperLoadoutCount } from './BlueberryUpgrades' import { corruptionLoadoutTableCreate, updateCorruptionLoadoutNames } from './Corruptions' +import { CartTab } from './purchases/CartTab' import { format } from './Synergism' export type PseudoCoinUpgradeNames = @@ -20,28 +21,6 @@ export type PseudoCoinUpgradeNames = export type PseudoCoinUpgrades = Record export type PseudoCoinUpgradeEffects = Record -interface Upgrades { - upgradeId: number - maxLevel: number - name: string - description: string - internalName: string - level: number - cost: number -} - -interface PlayerUpgrades { - level: number - upgradeId: number - internalName: PseudoCoinUpgradeNames -} - -interface UpgradesResponse { - coins: number - upgrades: Upgrades[] - playerUpgrades: PlayerUpgrades[] -} - // TODO?: Something more robust to injections? export const PCoinUpgrades: PseudoCoinUpgrades = { @@ -75,8 +54,7 @@ export const PCoinUpgradeEffects: PseudoCoinUpgradeEffects = { } export const initializePCoinCache = async () => { - const response = await fetch('https://synergism.cc/stripe/upgrades') - const upgradesList = await response.json() as UpgradesResponse + const upgradesList = await CartTab.fetchUpgrades() // Reset Cache for (const key of Object.keys(PCoinUpgrades)) { diff --git a/src/SingularityChallenges.ts b/src/SingularityChallenges.ts index d85a8b087..9aad8f6fa 100644 --- a/src/SingularityChallenges.ts +++ b/src/SingularityChallenges.ts @@ -347,8 +347,7 @@ export const singularityChallengeData: Record< singularityRequirement: (baseReq: number, completions: number) => { if (completions < 10) { return baseReq + 13 * completions - } - else { + } else { return baseReq + 13 * 9 + 10 * (completions - 9) } }, diff --git a/src/Statistics.ts b/src/Statistics.ts index bd75e8a59..55747cf39 100644 --- a/src/Statistics.ts +++ b/src/Statistics.ts @@ -1,4 +1,5 @@ import Decimal from 'break_infinity.js' +import i18next from 'i18next' import { DOMCacheGetOrSet } from './Cache/DOM' import { calculateAllCubeMultiplier, @@ -43,9 +44,8 @@ import { import { calculateSingularityDebuff } from './singularity' import { format, formatTimeShort, player } from './Synergism' import type { GlobalVariables } from './types/Synergism' -import { Globals as G } from './Variables' import { sumContents } from './Utility' -import i18next from 'i18next' +import { Globals as G } from './Variables' const associated = new Map([ ['kMisc', 'miscStats'], @@ -1780,7 +1780,7 @@ export const loadStatisticsAmbrosiaGeneration = () => { 9: { acc: 4, desc: 'No Ambrosia Upgrades EXALT' }, 10: { acc: 4, desc: 'Cube Upgrade Cx26' }, 11: { acc: 4, desc: 'Cash-Grab ULTIMATE' }, - 12: { acc: 4, desc: 'Event Bonus' }, + 12: { acc: 4, desc: 'Event Bonus' } } for (let i = 0; i < arr.length; i++) { const statAGenMi = DOMCacheGetOrSet(`statAGenM${i + 1}`) diff --git a/src/Tabs.ts b/src/Tabs.ts index 55f75bbd6..e19d6a9f9 100644 --- a/src/Tabs.ts +++ b/src/Tabs.ts @@ -285,22 +285,29 @@ const subtabInfo: Record = { }, buttonID: 'cartSubTab1' }, + { + subTabID: 'subscriptionContainer', + get unlocked () { + return isLoggedIn() || !prod + }, + buttonID: 'cartSubTab2' + }, { subTabID: 'upgradesContainer', unlocked: true, - buttonID: 'cartSubTab2' + buttonID: 'cartSubTab3' }, { subTabID: 'cartContainer', get unlocked () { return isLoggedIn() || !prod }, - buttonID: 'cartSubTab3' + buttonID: 'cartSubTab4' }, { subTabID: 'merchContainer', unlocked: true, - buttonID: 'cartSubTab4' + buttonID: 'cartSubTab5' } ] } diff --git a/src/UpdateVisuals.ts b/src/UpdateVisuals.ts index 223f7aa2b..84d3b4142 100644 --- a/src/UpdateVisuals.ts +++ b/src/UpdateVisuals.ts @@ -1633,7 +1633,7 @@ export const visualUpdateAmbrosia = () => { } ) - if (player.cubeUpgrades[76] > 0) { + if (player.cubeUpgrades[76] > 0) { DOMCacheGetOrSet('cubeUpgradeThresholds').style.display = 'block' DOMCacheGetOrSet('cubeUpgradeThresholds').innerHTML = i18next.t( 'ambrosia.cubeUpgradeThresholds', @@ -1642,8 +1642,7 @@ export const visualUpdateAmbrosia = () => { percent: player.cubeUpgrades[76] * calculateNumberOfThresholds() } ) - } - else { + } else { DOMCacheGetOrSet('cubeUpgradeThresholds').style.display = 'none' } @@ -1656,8 +1655,7 @@ export const visualUpdateAmbrosia = () => { luck: format(Math.min(100, player.cubeUpgradeRedBarFilled / 50), 2, true) } ) - } - else { + } else { DOMCacheGetOrSet('cubeUpgradeRedBarFills').style.display = 'none' } } diff --git a/src/purchases/CartTab.ts b/src/purchases/CartTab.ts index b8c21d6b9..4d8ec962c 100644 --- a/src/purchases/CartTab.ts +++ b/src/purchases/CartTab.ts @@ -8,20 +8,29 @@ import { setEmptyProductMap } from './CartUtil' import { clearCheckoutTab, toggleCheckoutTab } from './CheckoutTab' import { clearMerchSubtab, toggleMerchSubtab } from './MerchTab' import { clearProductPage, toggleProductPage } from './ProductSubtab' -import { clearUpgradeSubtab, toggleUpgradeSubtab } from './UpgradesSubtab' +import { clearSubscriptionPage, toggleSubscriptionPage } from './SubscriptionsSubtab' +import { clearUpgradeSubtab, toggleUpgradeSubtab, type UpgradesResponse } from './UpgradesSubtab' export type Product = { name: string - id: number + id: string price: number coins: number + subscription: boolean + description: string } +export const products: Product[] = [] +export let coinProducts: Product[] = [] +export let subscriptionProducts: Product[] = [] +export let upgradeResponse: UpgradesResponse + const cartSubTabs = { Coins: 0, - Upgrades: 1, - Checkout: 2, - Merch: 3 + Subscriptions: 1, + Upgrades: 2, + Checkout: 3, + Merch: 4 } as const const tab = document.getElementById('pseudoCoins')! @@ -34,9 +43,9 @@ function* yieldQuerySelectorAll (selector: string) { } } -class CartTab { +export class CartTab { static #productsFetch: DeferredPromise | undefined - static #products: Product[] = [] + static #upgradesFetch: DeferredPromise | undefined constructor () { this.#updateSubtabs() @@ -49,18 +58,45 @@ class CartTab { CartTab.#productsFetch = createDeferredPromise() + const url = !prod ? 'https://synergism.cc/stripe/test/products' : 'https://synergism.cc/stripe/products' + // TODO: move this fetch to the products page. - fetch('https://synergism.cc/stripe/products') - .then((response) => response.json() as Promise) - .then((products) => { - CartTab.#products.push(...products) - setEmptyProductMap(products) + fetch(url) + .then((response) => response.json()) + .then((productsList: Product[]) => { + products.push(...productsList) + setEmptyProductMap(productsList) + coinProducts = products.filter((product) => !product.subscription) + subscriptionProducts = products.filter((product) => product.subscription) + + // The Subscriptions do not naturally sort themselves by price + subscriptionProducts.sort((a, b) => a.price - b.price) CartTab.#productsFetch?.resolve(undefined) }, CartTab.#productsFetch.reject) return CartTab.#productsFetch.promise } + static fetchUpgrades () { + if (CartTab.#upgradesFetch) { + return CartTab.#upgradesFetch.promise + } + + CartTab.#upgradesFetch = createDeferredPromise() + + const url = !prod ? 'https://synergism.cc/stripe/test/upgrades' : 'https://synergism.cc/stripe/upgrades' + + // TODO: move this fetch to the products page. + fetch(url) + .then((response) => response.json()) + .then((upgrades: UpgradesResponse) => { + CartTab.#upgradesFetch!.resolve(upgrades) + upgradeResponse = upgrades + }, CartTab.#upgradesFetch.reject) + + return CartTab.#upgradesFetch.promise + } + static applySubtabListeners () { for (const [index, element] of yieldQuerySelectorAll('.subtabSwitcher button')) { element.addEventListener('click', () => { @@ -83,6 +119,7 @@ class CartTab { } clearProductPage() + clearSubscriptionPage() clearUpgradeSubtab() clearCheckoutTab() clearMerchSubtab() @@ -91,17 +128,28 @@ class CartTab { case cartSubTabs.Coins: CartTab.fetchProducts().then(() => { if (player.subtabNumber === cartSubTabs.Coins) { - toggleProductPage(CartTab.#products) + toggleProductPage() + } + }) + break + case cartSubTabs.Subscriptions: + CartTab.fetchProducts().then(() => { + if (player.subtabNumber === cartSubTabs.Subscriptions) { + toggleSubscriptionPage() } }) break case cartSubTabs.Upgrades: - toggleUpgradeSubtab() + CartTab.fetchUpgrades().then(() => { + if (player.subtabNumber === cartSubTabs.Upgrades) { + toggleUpgradeSubtab() + } + }) break case cartSubTabs.Checkout: CartTab.fetchProducts().then(() => { if (player.subtabNumber === cartSubTabs.Checkout) { - toggleCheckoutTab(CartTab.#products) + toggleCheckoutTab() } }) break diff --git a/src/purchases/CartUtil.ts b/src/purchases/CartUtil.ts index 8e9de05c4..8bfe88800 100644 --- a/src/purchases/CartUtil.ts +++ b/src/purchases/CartUtil.ts @@ -1,7 +1,7 @@ import type { Product } from './CartTab' /** A map of all products in the cart. */ -const cartMap = new Map }>() +const cartMap = new Map }>() /** Total number of items in the cart */ let inCart = 0 @@ -36,7 +36,7 @@ export const setEmptyProductMap = (products: Product[]) => { } } -export const addToCart = (id: number) => { +export const addToCart = (id: string) => { const itemInCart = cartMap.get(id)! itemInCart.quantity++ inCart++ @@ -44,7 +44,7 @@ export const addToCart = (id: number) => { updateInCartCount() } -export const removeFromCart = (id: number) => { +export const removeFromCart = (id: string) => { const itemInCart = cartMap.get(id)! itemInCart.quantity-- inCart-- @@ -67,7 +67,7 @@ export const getPrice = () => { return totalPrice } -export const getQuantity = (id: number) => { +export const getQuantity = (id: string) => { return cartMap.get(id)!.quantity } diff --git a/src/purchases/CheckoutTab.ts b/src/purchases/CheckoutTab.ts index 880b95ac1..a25c1e9b5 100644 --- a/src/purchases/CheckoutTab.ts +++ b/src/purchases/CheckoutTab.ts @@ -1,7 +1,8 @@ +import { prod } from '../Config' import { changeSubTab, Tabs } from '../Tabs' import { Alert, Notification } from '../UpdateHTML' import { memoize } from '../Utility' -import type { Product } from './CartTab' +import { products, subscriptionProducts } from './CartTab' import { addToCart, getPrice, getProductsInCart, getQuantity, removeFromCart } from './CartUtil' const tab = document.querySelector('#pseudoCoins > #cartContainer')! @@ -19,7 +20,7 @@ const formatter = Intl.NumberFormat('en-US', { currency: 'USD' }) -export const initializeCheckoutTab = memoize((products: Product[]) => { +export const initializeCheckoutTab = memoize(() => { closeCart?.addEventListener('click', () => { changeSubTab(Tabs.Purchase, { page: 0 }) }) @@ -35,7 +36,7 @@ export const initializeCheckoutTab = memoize((products: Product[]) => {
@@ -52,14 +53,18 @@ export const initializeCheckoutTab = memoize((products: Product[]) => { const fd = new FormData() for (const product of getProductsInCart()) { - fd.set(`product-${product.id}`, `${product.quantity}`) + fd.set(product.id, `${product.quantity}`) } fd.set('tosAgree', tosAgreed ? 'on' : 'off') checkout.setAttribute('disabled', '') - fetch('https://synergism.cc/stripe/create-checkout-session', { + const url = !prod + ? 'https://synergism.cc/stripe/test/create-checkout-session' + : 'https://synergism.cc/stripe/create-checkout-session' + + fetch(url, { method: 'POST', body: fd }).then((response) => response.json()) @@ -79,11 +84,14 @@ export const initializeCheckoutTab = memoize((products: Product[]) => { function addItem (e: MouseEvent) { e.preventDefault() - const key = Number((e.target as HTMLButtonElement).closest('div[key]')?.getAttribute('key')) + const key = (e.target as HTMLButtonElement).closest('div[key]')?.getAttribute('key') - if (Number.isNaN(key) || !Number.isSafeInteger(key)) { + if (key == null || !products.some((product) => product.id === key)) { Alert('Stop fucking touching the html! We do server-side validation!') return + } else if (subscriptionProducts.some((product) => getQuantity(product.id) !== 0)) { + Alert('You can only subscribe to 1 subscription tier!') + return } addToCart(key) @@ -94,9 +102,9 @@ function addItem (e: MouseEvent) { function removeItem (e: MouseEvent) { e.preventDefault() - const key = Number((e.target as HTMLButtonElement).closest('div[key]')?.getAttribute('key')) + const key = (e.target as HTMLButtonElement).closest('div[key]')?.getAttribute('key') - if (Number.isNaN(key) || !Number.isSafeInteger(key)) { + if (key == null || !products.some((product) => product.id === key)) { Alert('Stop fucking touching the html! We do server-side validation!') return } @@ -118,7 +126,7 @@ function updateItemList () { ${product.quantity > 0 ? `x${product.quantity}` : ''} - +
`)).join('') @@ -128,8 +136,8 @@ function updateItemList () { }) } -export const toggleCheckoutTab = (products: Product[]) => { - initializeCheckoutTab(products) +export const toggleCheckoutTab = () => { + initializeCheckoutTab() updateTotalPriceInCart() updateItemList() diff --git a/src/purchases/ProductSubtab.ts b/src/purchases/ProductSubtab.ts index 5d4738c89..f4d455b35 100644 --- a/src/purchases/ProductSubtab.ts +++ b/src/purchases/ProductSubtab.ts @@ -1,7 +1,7 @@ import { format } from '../Synergism' import { Alert, Notification } from '../UpdateHTML' import { memoize } from '../Utility' -import type { Product } from './CartTab' +import { coinProducts } from './CartTab' import { addToCart } from './CartUtil' const productContainer = document.querySelector('#pseudoCoins > #productContainer') @@ -12,10 +12,10 @@ const formatter = Intl.NumberFormat('en-US', { }) const clickHandler = (e: HTMLElementEventMap['click']) => { - const productId = Number((e.target as HTMLButtonElement).getAttribute('data-id')) + const productId = (e.target as HTMLButtonElement).getAttribute('data-id') const productName = (e.target as HTMLButtonElement).getAttribute('data-name') - if (Number.isNaN(productId) || !Number.isSafeInteger(productId)) { + if (productId === null || !coinProducts.some((product) => product.id === productId)) { Alert('Stop fucking touching the html! We do server-side validation!') return } @@ -24,11 +24,11 @@ const clickHandler = (e: HTMLElementEventMap['click']) => { Notification(`Added ${productName} to the cart!`) } -export const initializeProductPage = memoize((products: Product[]) => { - productContainer!.innerHTML = products.map((product) => (` +export const initializeProductPage = memoize(() => { + productContainer!.innerHTML = coinProducts.map((product) => (`
- ${product.name} + ${product.name}

${product.name} [${format(product.coins)} PseudoCoins]

@@ -50,7 +50,7 @@ export const clearProductPage = () => { productContainer!.style.display = 'none' } -export const toggleProductPage = (products: Product[]) => { - initializeProductPage(products) +export const toggleProductPage = () => { + initializeProductPage() productContainer!.style.display = 'grid' } diff --git a/src/purchases/SubscriptionsSubtab.ts b/src/purchases/SubscriptionsSubtab.ts new file mode 100644 index 000000000..2f9febfd2 --- /dev/null +++ b/src/purchases/SubscriptionsSubtab.ts @@ -0,0 +1,184 @@ +import { prod } from '../Config' +import { Alert, Confirm, Notification } from '../UpdateHTML' +import { memoize } from '../Utility' +import { type Product, subscriptionProducts, upgradeResponse } from './CartTab' +import { addToCart, getQuantity } from './CartUtil' + +const subscriptionsContainer = document.querySelector('#pseudoCoins > #subscriptionsContainer')! +const subscriptionSectionHolder = subscriptionsContainer.querySelector('#sub-section-holder')! + +const formatter = Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD' +}) + +const tierCosts = [0, 300, 600, 1000, 2000] + +async function changeSubscription (productId: string, type: 'upgrade' | 'downgrade') { + const tier = upgradeResponse.tier + const existingCosts = tierCosts[tier] ?? 0 + const newSub = subscriptionProducts.find((v) => v.id === productId) + const newSubPrice = newSub!.price + const newSubName = newSub!.name + const confirm = (type === 'downgrade') + ? await Confirm( + `You are downgrading to ${newSubName}, which costs ${ + formatter.format((existingCosts - newSubPrice) / 100) + } less per month. New cost: ${ + formatter.format(newSubPrice / 100) + } per month. Downgrading takes effect immediately!` + ) + : await Confirm( + `You are upgrading to ${newSubName}, which costs ${ + formatter.format((newSubPrice - existingCosts) / 100) + } more per month. New cost: ${formatter.format(newSubPrice / 100)} per month` + ) + + if (!confirm) { + return + } + + const link = !prod + ? `https://synergism.cc/stripe/test/subscription/${type}` + : `https://synergism.cc/stripe/subscription/${type}` + const url = new URL(link) + url.searchParams.set('key', productId) + + const response = await fetch(url, { + method: 'POST' + }) + console.log(response, response.text()) + return Alert(`You are now subscribed to ${newSubName}!`) +} + +function clickHandler (this: HTMLButtonElement, e: HTMLElementEventMap['click']) { + const productId = (e.target as HTMLButtonElement).getAttribute('data-id') + const productName = (e.target as HTMLButtonElement).getAttribute('data-name') + + if (productId === null || !subscriptionProducts.some((product) => product.id === productId)) { + Alert('Stop fucking touching the html! We do server-side validation!') + return + } else if (subscriptionProducts.some((product) => getQuantity(product.id) !== 0)) { + Alert('You can only subscribe to 1 subscription tier!') + return + } + + if (upgradeResponse.tier !== 0) { + if (this.hasAttribute('data-downgrade')) { + changeSubscription(productId, 'downgrade') + return + } else if (this.hasAttribute('data-upgrade')) { + changeSubscription(productId, 'upgrade') + return + } + } + + addToCart(productId) + Notification(`Added ${productName} to the cart!`) +} + +const constructDescriptions = (description: string) => { + const [firstLine, secondLine] = description.split(' and ') + // capitalize the first letter of the second line + const secondLineCap = secondLine.charAt(0).toUpperCase() + secondLine.slice(1) + + return `${firstLine} +
+ ${secondLineCap}` +} + +export const createIndividualSubscriptionHTML = (product: Product, existingCosts: number) => { + if (product.price < existingCosts) { + return ` +
+
+ ${product.name} +

+ ${product.name.split(' - ').join('
')} +

+

+ ${constructDescriptions(product.description)} +

+ +
+
+ ` + } else if (product.price === existingCosts) { + return ` +
+
+ ${product.name} +

+ ${product.name.split(' - ').join('
')} +

+

+ ${constructDescriptions(product.description)} +

+ +
+
+ ` + } else { + return ` +
+
+ ${product.name} +

+ ${product.name.split(' - ').join('
')} +

+

+ ${constructDescriptions(product.description)} +

+ +
+
+ ` + } +} + +export const initializeSubscriptionPage = memoize(() => { + // Manage subscription button + { + const form = document.createElement('form') + form.action = !prod + ? 'https://synergism.cc/stripe/test/manage-subscription' + : 'https://synergism.cc/stripe/manage-subscription' + + const submit = document.createElement('input') + submit.type = 'submit' + submit.value = 'Manage Subscription' + form.appendChild(submit) + + subscriptionsContainer.prepend(form) + } + + const tier = upgradeResponse.tier + const existingCosts = tierCosts[tier] ?? 0 + + subscriptionSectionHolder.innerHTML = subscriptionProducts.map((product) => + createIndividualSubscriptionHTML(product, existingCosts) + ).join('') + + subscriptionSectionHolder!.style.display = 'grid' + + document.querySelectorAll('.subscriptionContainer > div > button[data-id]').forEach( + (element) => { + element.addEventListener('click', clickHandler) + } + ) +}) + +export const clearSubscriptionPage = () => { + subscriptionsContainer.style.display = 'none' +} + +export const toggleSubscriptionPage = () => { + initializeSubscriptionPage() + subscriptionsContainer.style.display = 'flex' +} diff --git a/src/purchases/UpgradesSubtab.ts b/src/purchases/UpgradesSubtab.ts index f9bec6860..be1e63ab6 100644 --- a/src/purchases/UpgradesSubtab.ts +++ b/src/purchases/UpgradesSubtab.ts @@ -9,6 +9,7 @@ import { } from '../PseudoCoinUpgrades' import { Alert } from '../UpdateHTML' import { memoize } from '../Utility' +import { upgradeResponse } from './CartTab' interface Upgrades { upgradeId: number @@ -32,10 +33,11 @@ type UpgradesList = Omit & { playerLevel: number } -interface UpgradesResponse { +export interface UpgradesResponse { coins: number upgrades: Upgrades[] playerUpgrades: PlayerUpgrades[] + tier: number } interface CoinsResponse { @@ -137,67 +139,62 @@ async function purchaseUpgrade (upgrades: Map) { } const initializeUpgradeSubtab = memoize(() => { - ;(async () => { - const response = await fetch('https://synergism.cc/stripe/upgrades') - const upgradesList = await response.json() as UpgradesResponse - - DOMCacheGetOrSet('currentCoinBalance').innerHTML = `${ - i18next.t('pseudoCoins.coinCount', { amount: Intl.NumberFormat().format(upgradesList.coins) }) - }` - const grouped = upgradesList.upgrades.reduce((map, upgrade) => { - const current = map.get(upgrade.upgradeId) - const playerUpgrade = upgradesList.playerUpgrades.find((v) => v.upgradeId === upgrade.upgradeId) - - if (!current) { - map.set(upgrade.upgradeId, { - ...upgrade, - cost: [upgrade.cost], - level: [upgrade.level], - playerLevel: playerUpgrade?.level ?? 0 - }) - } else { - current.maxLevel = Math.max(current.maxLevel, upgrade.maxLevel) - current.cost.push(upgrade.cost) - current.level.push(upgrade.level) + DOMCacheGetOrSet('currentCoinBalance').innerHTML = `${ + i18next.t('pseudoCoins.coinCount', { amount: Intl.NumberFormat().format(upgradeResponse.coins) }) + }` + const grouped = upgradeResponse.upgrades.reduce((map, upgrade) => { + const current = map.get(upgrade.upgradeId) + const playerUpgrade = upgradeResponse.playerUpgrades.find((v) => v.upgradeId === upgrade.upgradeId) + + if (!current) { + map.set(upgrade.upgradeId, { + ...upgrade, + cost: [upgrade.cost], + level: [upgrade.level], + playerLevel: playerUpgrade?.level ?? 0 + }) + } else { + current.maxLevel = Math.max(current.maxLevel, upgrade.maxLevel) + current.cost.push(upgrade.cost) + current.level.push(upgrade.level) + } + + return map + }, new Map()) + + tab.querySelector('#upgradeGrid')!.innerHTML = [...grouped.values()].map((u) => ` +
+ ${u.internalName} +

${u.playerLevel}/${u.maxLevel}

+ ${u.playerLevel === u.maxLevel ? '

✔️

' : '

'} +
+ `).join('') + + const upgradesInGrid = tab.querySelectorAll('#upgradeGrid > div[data-id]') + upgradesInGrid.forEach((element) => { + element.addEventListener('click', (e) => { + const upgradeId = Number((e.target as HTMLElement).closest('div')?.getAttribute('data-id')) + + if (Number.isNaN(upgradeId) || !Number.isSafeInteger(upgradeId)) { + Alert('Stop touching the fucking html! We do server-side validations!') + return } - return map - }, new Map()) - - tab.querySelector('#upgradeGrid')!.innerHTML = [...grouped.values()].map((u) => ` -
- ${u.internalName} -

${u.playerLevel}/${u.maxLevel}

- ${u.playerLevel === u.maxLevel ? '

✔️

' : '

'} -
- `).join('') - - const upgradesInGrid = tab.querySelectorAll('#upgradeGrid > div[data-id]') - upgradesInGrid.forEach((element) => { - element.addEventListener('click', (e) => { - const upgradeId = Number((e.target as HTMLElement).closest('div')?.getAttribute('data-id')) - - if (Number.isNaN(upgradeId) || !Number.isSafeInteger(upgradeId)) { - Alert('Stop touching the fucking html! We do server-side validations!') - return - } - - setActiveUpgrade([...grouped.values()].find((u) => u.upgradeId === upgradeId)) - - // Setting an active class here turns the border white due to a CSS rule - upgradesInGrid.forEach((u) => u.classList.remove('active')) - element.classList.add('active') - }) - }) + setActiveUpgrade([...grouped.values()].find((u) => u.upgradeId === upgradeId)) - DOMCacheGetOrSet('buy').addEventListener('click', () => { - purchaseUpgrade(grouped) + // Setting an active class here turns the border white due to a CSS rule + upgradesInGrid.forEach((u) => u.classList.remove('active')) + element.classList.add('active') }) - })() + }) + + DOMCacheGetOrSet('buy').addEventListener('click', () => { + purchaseUpgrade(grouped) + }) }) export const toggleUpgradeSubtab = () => { diff --git a/src/singularity.ts b/src/singularity.ts index d0875f848..349a4868c 100644 --- a/src/singularity.ts +++ b/src/singularity.ts @@ -1945,7 +1945,7 @@ export const singularityPerks: SingularityPerk[] = [ 218, 228, 238, - 248, + 248 ], description: (n: number, levels: number[]) => { for (let i = levels.length - 1; i >= 0; i--) { diff --git a/translations/en.json b/translations/en.json index 8aa2c5176..f42405c7f 100644 --- a/translations/en.json +++ b/translations/en.json @@ -2537,6 +2537,7 @@ }, "pseudocoins": { "buy": "Purchase Coins", + "subscriptions": "Subscriptions", "upgrades": "Buy Upgrades", "merch": "Purchase Merch" }