From 3a4843fb4652b6ea69425cace33d7848e8e79590 Mon Sep 17 00:00:00 2001 From: Geometrically <18202329+Geometrically@users.noreply.github.com> Date: Thu, 15 Aug 2024 23:21:30 -0700 Subject: [PATCH] Billing / plus frontend (#2130) * [wip] initial * [wip] subscriptions/plus frontend * [wip] finish payment flow * Charges page * finish most subscriptions work * Finish * update eslint * Fix issues * fix intl extract * fix omorphia locale extract * fix responsiveness * fix lint --- apps/app-frontend/.env | 3 - apps/app-frontend/package.json | 2 +- apps/frontend/nuxt.config.ts | 14 +- apps/frontend/package.json | 5 +- apps/frontend/src/assets/styles/layout.scss | 1 - apps/frontend/src/assets/styles/utils.scss | 4 - apps/frontend/src/composables/auth.js | 10 + apps/frontend/src/composables/country.js | 6 + apps/frontend/src/composables/user.js | 16 +- apps/frontend/src/layouts/default.vue | 33 + apps/frontend/src/locales/en-US/index.json | 96 +++ apps/frontend/src/pages/[type]/[id].vue | 15 +- apps/frontend/src/pages/auth/sign-in.vue | 8 +- apps/frontend/src/pages/auth/sign-up.vue | 8 - apps/frontend/src/pages/auth/welcome.vue | 2 +- apps/frontend/src/pages/collection/[id].vue | 2 +- .../src/pages/dashboard/collections.vue | 4 +- apps/frontend/src/pages/organization/[id].vue | 2 +- apps/frontend/src/pages/plus.vue | 159 +++++ .../src/pages/search/[searchProjectType].vue | 3 +- apps/frontend/src/pages/settings.vue | 7 + .../src/pages/settings/billing/charges.vue | 88 +++ .../src/pages/settings/billing/index.vue | 668 ++++++++++++++++++ apps/frontend/src/pages/user/[id].vue | 2 +- apps/frontend/src/utils/common-messages.ts | 4 + apps/frontend/tailwind.config.js | 4 + packages/assets/branding/modrinth-plus.svg | 11 + packages/assets/icons/card.svg | 1 + packages/assets/icons/receipt-text.svg | 1 + packages/assets/icons/sparkles.svg | 1 + packages/assets/index.ts | 8 + packages/assets/styles/classes.scss | 16 +- packages/eslint-config-custom/nuxt.js | 13 +- packages/eslint-config-custom/vue.js | 1 + packages/ui/package.json | 11 +- .../ui/src/components/base/ButtonStyled.vue | 197 ++++++ .../src/components/billing/PurchaseModal.vue | 586 +++++++++++++++ packages/ui/src/components/index.ts | 6 +- packages/ui/src/components/modal/NewModal.vue | 170 +++++ .../ui/src/components/modal/ReportModal.vue | 127 ---- .../ui/{ => src}/locales/en-US/index.json | 39 + packages/utils/billing.ts | 129 ++++ packages/utils/index.ts | 1 + pnpm-lock.yaml | 70 +- 44 files changed, 2353 insertions(+), 201 deletions(-) delete mode 100644 apps/app-frontend/.env create mode 100644 apps/frontend/src/composables/country.js create mode 100644 apps/frontend/src/pages/plus.vue create mode 100644 apps/frontend/src/pages/settings/billing/charges.vue create mode 100644 apps/frontend/src/pages/settings/billing/index.vue create mode 100644 packages/assets/branding/modrinth-plus.svg create mode 100644 packages/assets/icons/card.svg create mode 100644 packages/assets/icons/receipt-text.svg create mode 100644 packages/assets/icons/sparkles.svg create mode 100644 packages/ui/src/components/base/ButtonStyled.vue create mode 100644 packages/ui/src/components/billing/PurchaseModal.vue create mode 100644 packages/ui/src/components/modal/NewModal.vue delete mode 100644 packages/ui/src/components/modal/ReportModal.vue rename packages/ui/{ => src}/locales/en-US/index.json (62%) create mode 100644 packages/utils/billing.ts diff --git a/apps/app-frontend/.env b/apps/app-frontend/.env deleted file mode 100644 index 6684c4059..000000000 --- a/apps/app-frontend/.env +++ /dev/null @@ -1,3 +0,0 @@ -BASE_URL=https://staging-api.modrinth.com/v2/ -BROWSER_BASE_URL=https://staging-api.modrinth.com/v2/ -BASE_URL= diff --git a/apps/app-frontend/package.json b/apps/app-frontend/package.json index e7d5ec9a7..94ef6d549 100644 --- a/apps/app-frontend/package.json +++ b/apps/app-frontend/package.json @@ -23,7 +23,7 @@ "tauri-plugin-window-state-api": "github:tauri-apps/tauri-plugin-window-state#v1", "vite-svg-loader": "^5.1.0", "vue": "^3.4.21", - "vue-multiselect": "3.0.0-beta.3", + "vue-multiselect": "3.0.0", "vue-router": "4.3.0", "vue-typed-virtual-list": "^1.0.10" }, diff --git a/apps/frontend/nuxt.config.ts b/apps/frontend/nuxt.config.ts index 3088e7885..b5f32c734 100644 --- a/apps/frontend/nuxt.config.ts +++ b/apps/frontend/nuxt.config.ts @@ -77,6 +77,13 @@ export default defineNuxtConfig({ title: "Modrinth mods", }, ], + script: [ + { + src: "https://js.stripe.com/v3/", + defer: true, + async: true, + }, + ], }, }, vite: { @@ -125,6 +132,7 @@ export default defineNuxtConfig({ homePageProjects?: any[]; homePageSearch?: any[]; homePageNotifs?: any[]; + products?: any[]; } = {}; try { @@ -165,6 +173,7 @@ export default defineNuxtConfig({ homePageProjects, homePageSearch, homePageNotifs, + products, ] = await Promise.all([ $fetch(`${API_URL}tag/category`, headers), $fetch(`${API_URL}tag/loader`, headers), @@ -174,6 +183,8 @@ export default defineNuxtConfig({ $fetch(`${API_URL}projects_random?count=60`, headers), $fetch(`${API_URL}search?limit=3&query=leave&index=relevance`, headers), $fetch(`${API_URL}search?limit=3&query=&index=updated`, headers), + // TODO: dehardcode + $fetch(`${API_URL.replace("/v2/", "/_internal/")}billing/products`, headers), ]); state.categories = categories; @@ -184,6 +195,7 @@ export default defineNuxtConfig({ state.homePageProjects = homePageProjects; state.homePageSearch = homePageSearch; state.homePageNotifs = homePageNotifs; + state.products = products; await fs.writeFile("./src/generated/state.json", JSON.stringify(state)); @@ -236,7 +248,7 @@ export default defineNuxtConfig({ const omorphiaLocales: string[] = []; const omorphiaLocaleSets = new Map(); - for await (const localeDir of globIterate("node_modules/omorphia/locales/*", { + for await (const localeDir of globIterate("node_modules/@modrinth/ui/src/locales/*", { posix: true, })) { const tag = basename(localeDir); diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 914d33bd0..fca6cc271 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -10,9 +10,10 @@ "postinstall": "nuxi prepare", "lint": "eslint . && prettier --check .", "fix": "eslint . --fix && prettier --write .", - "intl:extract": "formatjs extract \"{,components,composables,layouts,middleware,modules,pages,plugins,utils}/**/*.{vue,ts,tsx,js,jsx,mts,cts,mjs,cjs}\" --ignore '**/*.d.ts' --ignore 'node_modules' --out-file locales/en-US/index.json --format crowdin --preserve-whitespace" + "intl:extract": "formatjs extract \"{,src/components,src/composables,src/layouts,src/middleware,src/modules,src/pages,src/plugins,src/utils}/**/*.{vue,ts,tsx,js,jsx,mts,cts,mjs,cjs}\" --ignore '**/*.d.ts' --ignore 'node_modules' --out-file src/locales/en-US/index.json --format crowdin --preserve-whitespace" }, "devDependencies": { + "@formatjs/cli": "^6.2.12", "@nuxt/devtools": "^1.3.3", "@nuxtjs/turnstile": "^0.8.0", "@types/node": "^20.1.0", @@ -49,7 +50,7 @@ "pathe": "^1.1.2", "qrcode.vue": "^3.4.0", "semver": "^7.5.4", - "vue-multiselect": "3.0.0-alpha.2", + "vue-multiselect": "3.0.0", "vue3-apexcharts": "^1.5.2", "xss": "^1.0.14" } diff --git a/apps/frontend/src/assets/styles/layout.scss b/apps/frontend/src/assets/styles/layout.scss index c33bd8f5b..d6c02a4f0 100644 --- a/apps/frontend/src/assets/styles/layout.scss +++ b/apps/frontend/src/assets/styles/layout.scss @@ -116,6 +116,5 @@ .normal-page__content { max-width: calc(60rem - 0.75rem); - overflow-x: hidden; } } diff --git a/apps/frontend/src/assets/styles/utils.scss b/apps/frontend/src/assets/styles/utils.scss index afeeb8b89..23c6ccc41 100644 --- a/apps/frontend/src/assets/styles/utils.scss +++ b/apps/frontend/src/assets/styles/utils.scss @@ -2,10 +2,6 @@ display: none !important; } -.w-100 { - width: 100%; -} - body { overflow-y: scroll; overflow-x: hidden; diff --git a/apps/frontend/src/composables/auth.js b/apps/frontend/src/composables/auth.js index e5483cf2b..606e401e5 100644 --- a/apps/frontend/src/composables/auth.js +++ b/apps/frontend/src/composables/auth.js @@ -39,6 +39,16 @@ export const initAuth = async (oldToken = null) => { authCookie.value = route.query.code; } + if (route.fullPath.includes("new_account=true") && route.path !== "/auth/welcome") { + const redirect = route.path.startsWith("/auth/") ? null : route.fullPath; + + await navigateTo( + `/auth/welcome?authToken=${route.query.code}${ + redirect ? `&redirect=${encodeURIComponent(redirect)}` : "" + }`, + ); + } + if (authCookie.value) { auth.token = authCookie.value; diff --git a/apps/frontend/src/composables/country.js b/apps/frontend/src/composables/country.js new file mode 100644 index 000000000..19a0aca27 --- /dev/null +++ b/apps/frontend/src/composables/country.js @@ -0,0 +1,6 @@ +export const useUserCountry = () => + useState("userCountry", () => { + const headers = useRequestHeaders(["cf-ipcountry"]); + + return headers["cf-ipcountry"] ?? "US"; + }); diff --git a/apps/frontend/src/composables/user.js b/apps/frontend/src/composables/user.js index e28b96938..fc28156d1 100644 --- a/apps/frontend/src/composables/user.js +++ b/apps/frontend/src/composables/user.js @@ -12,20 +12,27 @@ export const initUser = async () => { const auth = (await useAuth()).value; const user = { - notifications: [], + collections: [], follows: [], + subscriptions: [], lastUpdated: 0, }; if (auth.user && auth.user.id) { try { - const [follows, collections] = await Promise.all([ - useBaseFetch(`user/${auth.user.id}/follows`), - useBaseFetch(`user/${auth.user.id}/collections`, { apiVersion: 3 }), + const headers = { + Authorization: auth.token, + }; + + const [follows, collections, subscriptions] = await Promise.all([ + useBaseFetch(`user/${auth.user.id}/follows`, { headers }, true), + useBaseFetch(`user/${auth.user.id}/collections`, { apiVersion: 3, headers }, true), + useBaseFetch(`billing/subscriptions`, { internal: true, headers }, true), ]); user.collections = collections; user.follows = follows; + user.subscriptions = subscriptions; user.lastUpdated = Date.now(); } catch (err) { console.error(err); @@ -170,6 +177,5 @@ export const logout = async () => { await useAuth("none"); useCookie("auth-token").value = null; - await navigateTo("/"); stopLoading(); }; diff --git a/apps/frontend/src/layouts/default.vue b/apps/frontend/src/layouts/default.vue index 863ad8b31..19cfdf48d 100644 --- a/apps/frontend/src/layouts/default.vue +++ b/apps/frontend/src/layouts/default.vue @@ -18,6 +18,21 @@ +
+ {{ formatMessage(subscriptionPaymentFailedBannerMessages.title) }} + + + {{ formatMessage(subscriptionPaymentFailedBannerMessages.action) }} + +
-