mirror of
https://github.com/PretendoNetwork/website.git
synced 2026-08-28 19:46:35 -05:00
feat: account pages
This commit is contained in:
12
README.md
12
README.md
@@ -68,16 +68,18 @@ The website is currently in a refactor, here is what is still left on the fronte
|
||||
- [x] Styling for progress page
|
||||
- [x] Styling for progress on main page
|
||||
- [X] Account: Mii editor
|
||||
- [ ] Account: View account
|
||||
- [ ] Account: Delete account
|
||||
- [ ] Account: Link discord account
|
||||
- [ ] Account: Edit server environment
|
||||
- [ ] Donations: stripe checkout frontend
|
||||
- [X] Account: View account
|
||||
- [X] Account: Delete account
|
||||
- [X] Account: Link discord account
|
||||
- [X] Account: Edit server environment
|
||||
- [ ] Account: Edit account info
|
||||
- [X] Donations: stripe checkout frontend
|
||||
- [ ] Auth: Login
|
||||
- [ ] Auth: Logout
|
||||
- [ ] Auth: Register
|
||||
- [ ] Auth: Forgot password flow
|
||||
- [ ] Content: Term pages
|
||||
- [ ] April fools'
|
||||
- [X] Content: Documentation pages
|
||||
- [x] Content: Blog
|
||||
|
||||
|
||||
20
package-lock.json
generated
20
package-lock.json
generated
@@ -14,7 +14,7 @@
|
||||
"@nuxt/content": "^3.4.0",
|
||||
"@nuxt/eslint": "^1.3.0",
|
||||
"@nuxt/fonts": "^0.14.0",
|
||||
"@nuxt/icon": "^2.4.1",
|
||||
"@nuxt/icon": "^2.5.0",
|
||||
"@nuxtjs/i18n": "^10.6.0",
|
||||
"@pinia/nuxt": "^1.0.1",
|
||||
"@pretendonetwork/error-codes": "^1.2.2",
|
||||
@@ -1685,9 +1685,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@iconify/collections": {
|
||||
"version": "1.0.719",
|
||||
"resolved": "https://registry.npmjs.org/@iconify/collections/-/collections-1.0.719.tgz",
|
||||
"integrity": "sha512-v3I7sBgR0UWOr6y4bXGiuNrOOl9d+tRDPg1d7vfQHofQ4nTt3AU15S2UsePjHztYAc7ptSPUTT74Io2M7Y1CZg==",
|
||||
"version": "1.0.724",
|
||||
"resolved": "https://registry.npmjs.org/@iconify/collections/-/collections-1.0.724.tgz",
|
||||
"integrity": "sha512-wl26lugNV8a3z1bsJyNceAnGeOCrvkXdZmw1Yvs40qLlz90EzMYPYA2OLFITzC9ALx6Ar3vyzEKo7ZNNKw0zjg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@iconify/types": "*"
|
||||
@@ -3281,17 +3281,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@nuxt/icon": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@nuxt/icon/-/icon-2.4.1.tgz",
|
||||
"integrity": "sha512-fOY3kiT6OoL4bWXVmMLc1VgoCm1PxIGSBmsSaLO090NEgtd1ub441dZ7MEoOD5UIilWzba1kCWdwJcAkGCOwLg==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@nuxt/icon/-/icon-2.5.0.tgz",
|
||||
"integrity": "sha512-EBczSfd3QmAD7GoSmJ5jCPvQ5zzNaPPuWPU9DSMcBuBM10A3JIvjaMLEOEniHQRz4iVPb05cJRO/JdC60oiiwg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@iconify/collections": "^1.0.715",
|
||||
"@iconify/collections": "^1.0.721",
|
||||
"@iconify/types": "^2.0.0",
|
||||
"@iconify/utils": "^3.1.4",
|
||||
"@iconify/vue": "^5.0.1",
|
||||
"@nuxt/devtools-kit": "^3.3.1",
|
||||
"@nuxt/kit": "^4.5.0",
|
||||
"@nuxt/devtools-kit": "^3.4.1",
|
||||
"@nuxt/kit": "^4.5.2",
|
||||
"consola": "^3.4.2",
|
||||
"local-pkg": "^1.2.1",
|
||||
"mlly": "^1.8.2",
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"@nuxt/content": "^3.4.0",
|
||||
"@nuxt/eslint": "^1.3.0",
|
||||
"@nuxt/fonts": "^0.14.0",
|
||||
"@nuxt/icon": "^2.4.1",
|
||||
"@nuxt/icon": "^2.5.0",
|
||||
"@nuxtjs/i18n": "^10.6.0",
|
||||
"@pinia/nuxt": "^1.0.1",
|
||||
"@pretendonetwork/error-codes": "^1.2.2",
|
||||
|
||||
@@ -10,13 +10,19 @@ export default defineEventHandler(async (event): Promise<GetApiAuthMe> => {
|
||||
pid: data.pid,
|
||||
username: data.username,
|
||||
accessLevel: data.accessLevel,
|
||||
birthday: data.birthday,
|
||||
gender: data.gender,
|
||||
country: data.country,
|
||||
timezone: data.timezone,
|
||||
emailAddress: data.emailAddress,
|
||||
serverAccessLevel: data.serverAccessLevel as any,
|
||||
discordId: data.connections?.discord?.id ?? null,
|
||||
stripeTier: data.connections?.stripe?.subscriptionId
|
||||
? {
|
||||
subscriptionId: data.connections.stripe.subscriptionId,
|
||||
priceId: data.connections.stripe.priceId ?? '',
|
||||
tierName: data.connections.stripe.tierName ?? ''
|
||||
tierName: data.connections.stripe.tierName ?? '',
|
||||
tierLevel: data.connections.stripe.tierLevel ?? ''
|
||||
}
|
||||
: null,
|
||||
mii: data.mii
|
||||
|
||||
@@ -4,12 +4,18 @@ export type GetApiAuthMe = {
|
||||
pid: number;
|
||||
username: string;
|
||||
accessLevel: number;
|
||||
birthday: string;
|
||||
gender: string;
|
||||
country: string;
|
||||
timezone: string;
|
||||
emailAddress: string;
|
||||
serverAccessLevel: 'dev' | 'test' | 'prod';
|
||||
discordId: string | null;
|
||||
stripeTier: {
|
||||
subscriptionId: string;
|
||||
priceId: string;
|
||||
tierName: string;
|
||||
tierLevel: string | number;
|
||||
} | null;
|
||||
mii: {
|
||||
imageUrl: string;
|
||||
|
||||
@@ -140,7 +140,7 @@ div.modal-wrapper {
|
||||
align-items: center;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
|
||||
z-index: 10;
|
||||
z-index: 100;
|
||||
}
|
||||
div.modal-wrapper.hidden {
|
||||
display: none;
|
||||
@@ -152,34 +152,37 @@ div.modal {
|
||||
text-align: left;
|
||||
width: min(660px, 90%);
|
||||
box-sizing: border-box;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
div.modal h1 {
|
||||
div.modal h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
p.modal-caption {
|
||||
color: var(--text-shade-1);
|
||||
}
|
||||
p.modal-caption span,
|
||||
p.switch-tier-modal-caption span {
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
|
||||
.modal-caption span span {
|
||||
color: var(--text-shade-3);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.modal-button-wrapper {
|
||||
margin-top: 24px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.modal-button-wrapper button {
|
||||
margin-left: 12px;
|
||||
width: fit-content;
|
||||
}
|
||||
.modal-button-wrapper button.cancel {
|
||||
background: none;
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
gap: 1em;
|
||||
}
|
||||
.modal-button-wrapper button {
|
||||
padding: 12px 24px;
|
||||
}
|
||||
.modal-button-wrapper button.cancel {
|
||||
background: var(--bg-shade-4);
|
||||
}
|
||||
.modal-button-wrapper button.alert {
|
||||
background: var(--red-shade-1);
|
||||
}
|
||||
.modal-button-wrapper button.action {
|
||||
background: var(--green-shade-0);
|
||||
}
|
||||
|
||||
.purple-card {
|
||||
border-radius: 10px;
|
||||
@@ -270,6 +273,31 @@ input[type="range"]:focus::-moz-range-thumb {
|
||||
background-color: var(--accent-shade-3);
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 12px;
|
||||
margin: 1rem 0;
|
||||
border-radius: 6px;
|
||||
background: var(--bg-shade-3);
|
||||
}
|
||||
.progress-bar-real,
|
||||
.progress-bar-capped {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
border-radius: 6px;
|
||||
width: var(--progress-bar-width);
|
||||
}
|
||||
.progress-bar-capped {
|
||||
background-color: var(--accent-shade-0);
|
||||
max-width: 100%;
|
||||
}
|
||||
.progress-bar-real {
|
||||
background-color: var(--green-shade-1);
|
||||
width: calc(var(--progress-bar-width) - 6px);
|
||||
left: 6px;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
width: 95%;
|
||||
@@ -293,6 +321,10 @@ input[type="range"]:focus::-moz-range-thumb {
|
||||
.hide-on-desktop {
|
||||
display: unset !important;
|
||||
}
|
||||
|
||||
.modal-button-wrapper {
|
||||
grid-auto-flow: row;
|
||||
}
|
||||
}
|
||||
|
||||
/* Scrollbar styling 'cause it's fancy */
|
||||
|
||||
@@ -1,15 +1,39 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogOverlay,
|
||||
AlertDialogPortal,
|
||||
AlertDialogRoot,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger, ToastDescription, ToastProvider, ToastRoot, ToastViewport
|
||||
} from 'reka-ui';
|
||||
import type { ApiAccountUpdateRequest } from '~~/shared/api-types';
|
||||
|
||||
definePageMeta({
|
||||
needsAuth: true
|
||||
});
|
||||
|
||||
const upgradeSuccess = ref(useRoute().query.upgrade_success === 'true');
|
||||
const upgradeError = ref(useRoute().query.upgrade_success === 'false');
|
||||
const showToast = computed(() => upgradeSuccess.value || upgradeError.value);
|
||||
if (showToast.value) {
|
||||
useRouter().replace({ query: {} });
|
||||
}
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const { data: profile, refresh } = await useApiFetch('/api/auth/me');
|
||||
const { data: connections, refresh: refreshConnections } = await useApiFetch('/api/auth/me-connections');
|
||||
|
||||
async function updateServerEnvironment(env: ApiAccountUpdateRequest['environment']) {
|
||||
const selectedServerEnv = ref(profile.value?.serverAccessLevel);
|
||||
const dialogContainer = ref(null);
|
||||
const modalIsOpen = ref(false);
|
||||
|
||||
async function updateServerEnvironment(
|
||||
env: ApiAccountUpdateRequest['environment']
|
||||
) {
|
||||
try {
|
||||
await apiFetch('/api/account/update', {
|
||||
method: 'PATCH',
|
||||
@@ -62,66 +86,738 @@ async function unlinkDiscord() {
|
||||
}
|
||||
}
|
||||
|
||||
async function updateMii() {
|
||||
try {
|
||||
await apiFetch('/api/account/update', {
|
||||
method: 'PATCH',
|
||||
body: {
|
||||
mii: { name: 'steve', primary: 'Y', data: 'AwAAQOlVognnx0GC2qjhdwOzuI0n2QAAAGBzAHQAZQB2AGUAAAAAAAAAAAAAAEBAAAAhAQJoRBgmNEYUgRIXaA0AACkAUkhQAAAAAAAAAAAAAAAAAAAAAAAAAAAAANeC' }
|
||||
} satisfies ApiAccountUpdateRequest
|
||||
});
|
||||
await refresh();
|
||||
} catch (error: unknown) {
|
||||
const err = getApiError(error);
|
||||
alert(err.code);
|
||||
}
|
||||
}
|
||||
useHead({
|
||||
title: `Account`
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="profile">
|
||||
<h1>Hello {{ profile.username }}</h1>
|
||||
<p>TODO: account stub page</p>
|
||||
<div :style="{ border: '1px solid white', margin: '2rem 0' }">
|
||||
<p>Environment ({{ profile.serverAccessLevel }})</p>
|
||||
<button @click="updateServerEnvironment('prod')">
|
||||
Prod
|
||||
</button>
|
||||
<button @click="updateServerEnvironment('test')">
|
||||
Test
|
||||
</button>
|
||||
<button @click="updateServerEnvironment('dev')">
|
||||
Dev
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
v-if="profile.discordId"
|
||||
@click="unlinkDiscord()"
|
||||
>
|
||||
Unlink discord: <span v-if="connections?.discord">
|
||||
<img
|
||||
:style="{ height: '25px', width: '25px', borderRadius: '100px'}"
|
||||
:src="connections.discord.avatarUrl ?? '#'"
|
||||
<div
|
||||
v-if="profile"
|
||||
class="account-wrapper"
|
||||
>
|
||||
<div class="account-sidebar">
|
||||
<div class="user">
|
||||
<a
|
||||
href="/account/miieditor"
|
||||
class="mii"
|
||||
>
|
||||
{{ connections.discord.username }}
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
@click="linkDiscord()"
|
||||
<img
|
||||
:src="profile.mii?.imageUrl"
|
||||
alt="Mii image"
|
||||
>
|
||||
</a>
|
||||
<p class="miiname">
|
||||
{{ profile.mii?.name }}
|
||||
</p>
|
||||
<p
|
||||
class="username"
|
||||
:value="profile.username"
|
||||
>
|
||||
PNID: {{ profile.username }}
|
||||
</p>
|
||||
<p
|
||||
v-if="profile.stripeTier?.tierName"
|
||||
:class="`tier-name access-level-${profile.accessLevel}`"
|
||||
:value="profile.stripeTier?.tierName"
|
||||
>
|
||||
{{ profile.stripeTier?.tierName }}
|
||||
</p>
|
||||
<p
|
||||
v-else-if="profile.accessLevel !== -1"
|
||||
:class="`tier-name access-level-${profile.accessLevel}`"
|
||||
:value="$t(`account.accountLevel[${profile.accessLevel}]`)"
|
||||
>
|
||||
{{ $t(`account.accountLevel[${profile.accessLevel}]`) }}
|
||||
</p>
|
||||
|
||||
<p
|
||||
v-else
|
||||
class="tier-name access-level-banned"
|
||||
:value="$t('account.banned')"
|
||||
>
|
||||
{{ $t("account.banned") }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<NuxtLink
|
||||
id="account-upgrade"
|
||||
class="button secondary"
|
||||
to="/account/upgrade"
|
||||
>
|
||||
<p class="caption">
|
||||
{{ $t("account.settings.upgrade") }}
|
||||
</p>
|
||||
</NuxtLink>
|
||||
<AlertDialogRoot v-model:open="modalIsOpen">
|
||||
<AlertDialogTrigger
|
||||
id="account-delete"
|
||||
class="secondary"
|
||||
>
|
||||
{{ $t("account.settings.delete.button") }}
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogPortal :to="dialogContainer">
|
||||
<AlertDialogOverlay />
|
||||
<AlertDialogContent class="modal">
|
||||
<AlertDialogTitle>{{ $t("account.settings.delete.modalTitle") }}?</AlertDialogTitle>
|
||||
<AlertDialogDescription class="modal-caption">
|
||||
<p
|
||||
style="white-space: pre-line;"
|
||||
>
|
||||
{{ $t('account.settings.delete.modalDescription') }}
|
||||
</p>
|
||||
<p class="noundo">
|
||||
{{ $t('account.settings.delete.modalCaution') }}
|
||||
</p>
|
||||
</AlertDialogDescription>
|
||||
<div class="modal-button-wrapper">
|
||||
<AlertDialogCancel class="cancel">
|
||||
{{ $t("modals.cancel") }}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
class="alert"
|
||||
@click="deleteAccount"
|
||||
>
|
||||
{{ $t("account.settings.delete.modalConfirm") }}
|
||||
</AlertDialogAction>
|
||||
</div>
|
||||
</AlertDialogContent>
|
||||
</AlertDialogPortal>
|
||||
</AlertDialogRoot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-wrapper">
|
||||
<h2
|
||||
id="user-settings"
|
||||
class="section-header"
|
||||
>
|
||||
{{ $t("account.settings.settingCards.userSettings") }}
|
||||
</h2>
|
||||
<div class="setting-card">
|
||||
<h2 class="header">
|
||||
{{ $t("account.settings.settingCards.profile") }}
|
||||
</h2>
|
||||
<a
|
||||
href="/account/edit/profile"
|
||||
class="edit"
|
||||
>
|
||||
<Icon
|
||||
name="ph:pencil"
|
||||
size="26"
|
||||
/>
|
||||
</a>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<p class="label">
|
||||
{{ $t("account.settings.settingCards.nickname") }}
|
||||
</p>
|
||||
<p class="value">
|
||||
{{ profile.mii?.name }}
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p class="label">
|
||||
{{ $t("account.settings.settingCards.birthDate") }}
|
||||
</p>
|
||||
<p class="value">
|
||||
{{ profile.birthday }}
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p class="label">
|
||||
{{ $t("account.settings.settingCards.gender") }}
|
||||
</p>
|
||||
<p class="value">
|
||||
{{ profile.gender }}
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p class="label">
|
||||
{{ $t("account.settings.settingCards.country") }}
|
||||
</p>
|
||||
<p class="value">
|
||||
{{ profile.country }}
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p class="label">
|
||||
{{ $t("account.settings.settingCards.timezone") }}
|
||||
</p>
|
||||
<p class="value">
|
||||
{{ profile.timezone }}
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="setting-card">
|
||||
<h2 class="header">
|
||||
{{ $t("account.settings.settingCards.serverEnv") }}
|
||||
</h2>
|
||||
<fieldset :disabled="profile.accessLevel < 1">
|
||||
<form
|
||||
id="server"
|
||||
class="server-selection"
|
||||
>
|
||||
<input
|
||||
id="prod"
|
||||
v-model="selectedServerEnv"
|
||||
type="radio"
|
||||
value="prod"
|
||||
>
|
||||
<label for="prod">
|
||||
<Icon
|
||||
name="ph:cube"
|
||||
size="36"
|
||||
/>
|
||||
<h2>{{ $t("account.settings.settingCards.production") }}</h2>
|
||||
</label>
|
||||
<input
|
||||
id="test"
|
||||
v-model="selectedServerEnv"
|
||||
type="radio"
|
||||
value="test"
|
||||
>
|
||||
|
||||
<label for="test">
|
||||
<Icon
|
||||
name="ph:flask"
|
||||
size="36"
|
||||
/>
|
||||
<h2>{{ $t("account.settings.settingCards.beta") }}</h2>
|
||||
</label>
|
||||
</form>
|
||||
</fieldset>
|
||||
|
||||
<button
|
||||
v-if="
|
||||
profile.accessLevel >= 1 &&
|
||||
selectedServerEnv !== profile.serverAccessLevel
|
||||
"
|
||||
id="save-server-selection"
|
||||
class="button secondary"
|
||||
@click.prevent="() => updateServerEnvironment(selectedServerEnv)"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
<p
|
||||
v-html="
|
||||
profile.accessLevel < 1
|
||||
? $t('account.settings.settingCards.upgradePrompt')
|
||||
: $t('account.settings.settingCards.hasAccessPrompt')
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h2
|
||||
id="security"
|
||||
class="section-header"
|
||||
>
|
||||
{{ $t("account.settings.settingCards.signInSecurity") }}
|
||||
</h2>
|
||||
<div class="setting-card">
|
||||
<h2 class="header">
|
||||
{{ $t("account.account") }}
|
||||
</h2>
|
||||
<a
|
||||
href="/account/edit/login-info"
|
||||
class="edit"
|
||||
>
|
||||
<Icon
|
||||
name="ph:pencil"
|
||||
size="26"
|
||||
/>
|
||||
</a>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<p class="label">
|
||||
{{ $t("account.settings.settingCards.email") }}
|
||||
</p>
|
||||
<p class="value">
|
||||
{{ profile.emailAddress }}
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p class="label">
|
||||
{{ $t("account.settings.settingCards.password") }}
|
||||
</p>
|
||||
<p class="value">
|
||||
●●●●●●●●
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>{{ $t("account.settings.settingCards.passwordResetNotice") }}</p>
|
||||
</div>
|
||||
|
||||
<div class="setting-card sign-in-history">
|
||||
<h2 class="header">
|
||||
{{ $t("account.settings.settingCards.signInHistory") }}
|
||||
</h2>
|
||||
<p>{{ $t("account.settings.settingCards.no_signins_notice") }}</p>
|
||||
</div>
|
||||
|
||||
<h2
|
||||
id="other"
|
||||
class="section-header"
|
||||
>
|
||||
{{ $t("account.settings.settingCards.otherSettings") }}
|
||||
</h2>
|
||||
<div class="setting-card">
|
||||
<h2 class="header">
|
||||
{{ $t("account.settings.settingCards.discord") }}
|
||||
</h2>
|
||||
|
||||
<p
|
||||
v-if="profile.discordId"
|
||||
class="discord-profile"
|
||||
>
|
||||
{{ $t("account.settings.settingCards.connectedToDiscord") }}
|
||||
<img
|
||||
:style="{ height: '25px', width: '25px', borderRadius: '100px'}"
|
||||
:src="connections?.discord?.avatarUrl ?? '#'"
|
||||
>@{{
|
||||
connections?.discord?.username
|
||||
}}.
|
||||
</p>
|
||||
|
||||
<button
|
||||
v-if="profile.discordId"
|
||||
id="remove-discord-connection"
|
||||
class="button secondary"
|
||||
@click="unlinkDiscord"
|
||||
>
|
||||
{{ $t("account.settings.settingCards.removeDiscord") }}
|
||||
</button>
|
||||
<p v-else>
|
||||
{{ $t("account.settings.settingCards.noDiscordLinked") }}
|
||||
<NuxtLink
|
||||
:style="{cursor: 'pointer'}"
|
||||
@click="linkDiscord"
|
||||
>
|
||||
{{ $t("account.settings.settingCards.linkDiscord") }}
|
||||
</NuxtLink>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="setting-card">
|
||||
<h2 class="header">
|
||||
{{ $t("account.settings.settingCards.newsletter") }}
|
||||
</h2>
|
||||
<p>{{ $t("account.settings.settingCards.no_newsletter_notice") }}</p>
|
||||
<!--
|
||||
<form id="other">
|
||||
<input type="checkbox" id="marketing" name="marketing" {{#if account.flags.marketing}}checked{{/if}}>
|
||||
<label for="marketing">{{ locale.account.settings.settingCards.newsletterPrompt }}</label>
|
||||
</form>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="delete-account"
|
||||
:class="{ 'modal-wrapper': true, hidden: !modalIsOpen }"
|
||||
>
|
||||
Link discord
|
||||
</button>
|
||||
<button
|
||||
@click="updateMii()"
|
||||
<div ref="dialogContainer" />
|
||||
</div>
|
||||
<div
|
||||
v-if="showToast"
|
||||
:class="{'banner-notice': true, success: upgradeSuccess, error: upgradeError }"
|
||||
>
|
||||
Update Mii
|
||||
</button>
|
||||
<button
|
||||
:style="{ color: 'red' }"
|
||||
@click="deleteAccount()"
|
||||
>
|
||||
Delete account
|
||||
</button>
|
||||
<ToastProvider>
|
||||
<ToastRoot as="div">
|
||||
<ToastDescription
|
||||
v-if="upgradeSuccess"
|
||||
as="p"
|
||||
>
|
||||
Account upgraded successfully
|
||||
</ToastDescription>
|
||||
<ToastDescription
|
||||
v-if="upgradeError"
|
||||
as="p"
|
||||
>
|
||||
Account upgrade failed
|
||||
</ToastDescription>
|
||||
</ToastRoot>
|
||||
|
||||
<ToastViewport as="p" />
|
||||
</ToastProvider>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Removing until it's done */
|
||||
.sign-in-history a {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.account-wrapper {
|
||||
display: grid;
|
||||
column-gap: 48px;
|
||||
margin-top: 80px;
|
||||
color: var(--text-shade-1);
|
||||
}
|
||||
|
||||
/* Account settings sidebar */
|
||||
.account-sidebar .user {
|
||||
margin: 55px auto auto;
|
||||
width: fit-content;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
}
|
||||
.account-sidebar .user .miiname {
|
||||
font-size: 1.2rem;
|
||||
color: var(--text-shade-3);
|
||||
margin: 8px 0 4px;
|
||||
}
|
||||
.account-sidebar .user .username {
|
||||
margin: 0;
|
||||
}
|
||||
.account-sidebar .user .tier-name {
|
||||
margin: 12px 0;
|
||||
line-height: 1.2em;
|
||||
border-radius: 1.2em;
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
padding: 4px 16px;
|
||||
}
|
||||
|
||||
.account-sidebar .user .tier-level-0,
|
||||
.account-sidebar .user .access-level-0 {
|
||||
background: #2a2f50;
|
||||
color: var(--text-shade-1);
|
||||
border-color: #383f6b;
|
||||
}
|
||||
.account-sidebar .user .tier-level-1 {
|
||||
background: rgba(255, 132, 132, 0.2);
|
||||
color: #ff8484;
|
||||
border-color: rgba(255, 132, 132, 0.8);
|
||||
}
|
||||
.account-sidebar .user .tier-level-2 {
|
||||
background: rgba(89, 201, 165, 0.3);
|
||||
color: #59c9a5;
|
||||
border-color: #59c9a5;
|
||||
}
|
||||
.account-sidebar .user .tier-level-3 {
|
||||
background: rgba(202, 177, 251, 0.3);
|
||||
color: var(--accent-shade-3);
|
||||
border-color: var(--accent-shade-3);
|
||||
}
|
||||
.account-sidebar .user .access-level-banned {
|
||||
background: rgba(255, 63, 0, 0.1);
|
||||
color: #ff3f00;
|
||||
border-color: rgba(255, 63, 0, 0.8);
|
||||
}
|
||||
.account-sidebar .user .access-level-1 {
|
||||
background: rgba(100, 247, 239, 0.3);
|
||||
color: #64f7ef;
|
||||
border-color: #64f7ef;
|
||||
}
|
||||
.account-sidebar .user .access-level-2 {
|
||||
background: rgba(255, 199, 89, 0.3);
|
||||
color: #ffc759;
|
||||
border-color: #ffc759;
|
||||
}
|
||||
.account-sidebar .user .access-level-3 {
|
||||
background: rgba(90, 255, 21, 0.3);
|
||||
color: #5aff15;
|
||||
border-color: #5aff15;
|
||||
}
|
||||
|
||||
.account-sidebar .user a.mii {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
overflow: hidden;
|
||||
border-radius: 100%;
|
||||
background: var(--bg-shade-3);
|
||||
}
|
||||
.account-sidebar .user a.mii::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background:
|
||||
no-repeat center/50% url("@/public/assets/images/edit.svg"),
|
||||
rgba(55, 60, 101, 0.7);
|
||||
opacity: 0;
|
||||
transition: opacity 150ms;
|
||||
}
|
||||
.account-sidebar .user a.mii:hover::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.account-sidebar .user .mii {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.account-sidebar .buttons {
|
||||
display: grid;
|
||||
grid-auto-flow: row;
|
||||
gap: 16px;
|
||||
}
|
||||
.account-sidebar .buttons a {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
margin: 20px 0 0 0;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
}
|
||||
.account-sidebar .buttons a svg {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.account-sidebar .buttons a p.caption {
|
||||
margin: 0;
|
||||
}
|
||||
.account-sidebar .buttons p.cemu-warning {
|
||||
margin: 4px 0 0;
|
||||
font-size: 0.7rem;
|
||||
color: var(--text-shade-1);
|
||||
}
|
||||
|
||||
.account-sidebar .buttons #account-delete {
|
||||
background-color: #f44336;
|
||||
}
|
||||
|
||||
.modal p.noundo {
|
||||
font-weight: bold;
|
||||
color: var(--text-shade-3)
|
||||
}
|
||||
|
||||
/* Settings */
|
||||
.settings-wrapper {
|
||||
display: grid;
|
||||
grid-column-start: 2;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
column-gap: 20px;
|
||||
}
|
||||
.settings-wrapper a {
|
||||
color: var(--accent-shade-1);
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
.settings-wrapper a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.settings-wrapper h2.section-header {
|
||||
margin-top: 40px;
|
||||
grid-column: 1 / 3;
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
|
||||
.setting-card {
|
||||
display: grid;
|
||||
grid-template-rows: 35px repeat(2, auto);
|
||||
row-gap: 24px;
|
||||
position: relative;
|
||||
border-radius: 10px;
|
||||
background: var(--bg-shade-2);
|
||||
padding: 48px 60px;
|
||||
}
|
||||
.setting-card * {
|
||||
margin: 0;
|
||||
}
|
||||
.setting-card .edit {
|
||||
color: var(--text-shade-1);
|
||||
background: var(--bg-shade-3);
|
||||
border-radius: 100%;
|
||||
position: absolute;
|
||||
top: 42px;
|
||||
right: 48px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.setting-card .edit:hover {
|
||||
background: var(--bg-shade-3);
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
.setting-card .edit svg {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.setting-card .header {
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
|
||||
.setting-card .setting-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, auto);
|
||||
gap: 24px;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.setting-card .setting-list p.label {
|
||||
color: var(--text-shade-3);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
position: relative;
|
||||
height: min-content;
|
||||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.setting-card .server-selection {
|
||||
display: flex;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
background: var(--bg-shade-3);
|
||||
}
|
||||
.setting-card .server-selection input {
|
||||
display: none;
|
||||
}
|
||||
.server-selection input + label {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
flex: 50%;
|
||||
color: var(--text-shade-1);
|
||||
padding: 40px;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
}
|
||||
.server-selection input + label h2 {
|
||||
margin-top: 12px;
|
||||
color: var(--text-shade-1);
|
||||
}
|
||||
.server-selection input:checked + label,
|
||||
.server-selection input:checked + label h2 {
|
||||
background: var(--accent-shade-0);
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
|
||||
.setting-card #link-discord-account {
|
||||
width: 100%;
|
||||
padding: 12px 48px;
|
||||
cursor: pointer;
|
||||
background: var(--bg-shade-3);
|
||||
}
|
||||
|
||||
.setting-card .discord-profile {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.setting-card button {
|
||||
width: 100%;
|
||||
height: fit-content;
|
||||
padding: 12px 48px;
|
||||
align-self: flex-end;
|
||||
cursor: pointer;
|
||||
background: var(--bg-shade-3);
|
||||
}
|
||||
|
||||
.setting-card.span-both-columns {
|
||||
grid-column: 1 / span 2;
|
||||
}
|
||||
|
||||
@keyframes banner-notice {
|
||||
0% {
|
||||
top: -150px;
|
||||
}
|
||||
20% {
|
||||
top: 35px;
|
||||
}
|
||||
80% {
|
||||
top: 35px;
|
||||
}
|
||||
100% {
|
||||
top: -150px;
|
||||
}
|
||||
}
|
||||
.banner-notice {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
top: -150px;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
animation: banner-notice 5s;
|
||||
z-index: 100;
|
||||
color: var(--text-shade-3)
|
||||
}
|
||||
.banner-notice div {
|
||||
padding: 4px 36px;
|
||||
border-radius: 5px;
|
||||
z-index: 3;
|
||||
}
|
||||
.banner-notice.success div {
|
||||
background: var(--green-shade-0);
|
||||
}
|
||||
.banner-notice.error div {
|
||||
background: var(--red-shade-1);
|
||||
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 80px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1300px) {
|
||||
.account-wrapper {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.settings-wrapper {
|
||||
grid-column-start: 1;
|
||||
}
|
||||
|
||||
.account-sidebar {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.account-sidebar .user .mii {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
.settings-wrapper {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.setting-card {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.setting-card {
|
||||
padding: 24px;
|
||||
width: calc(100vw - 48px);
|
||||
margin-left: -5vw;
|
||||
margin-right: -2.5vw;
|
||||
border-radius: 0;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.setting-card .edit {
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
transform: scale(0.85);
|
||||
}
|
||||
|
||||
.setting-card .server-selection {
|
||||
flex-flow: column;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 350px) {
|
||||
.setting-card .setting-list {
|
||||
grid-template-columns: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,13 +1,46 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogOverlay,
|
||||
AlertDialogPortal,
|
||||
AlertDialogRoot,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger
|
||||
} from 'reka-ui';
|
||||
import type { ApiAccountCheckoutRequest } from '~~/shared/api-types';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
definePageMeta({
|
||||
needsAuth: true
|
||||
});
|
||||
|
||||
const { data: tierData } = await useApiFetch('/api/account/tiers');
|
||||
const { data: profile } = await useApiFetch('/api/auth/me');
|
||||
const sortedTiers = computed(() => [...tierData.value?.tiers ?? []].sort((a, b) => a.tierLevel - b.tierLevel));
|
||||
const sortedTiers = computed(() =>
|
||||
[...(tierData.value?.tiers ?? [])].sort((a, b) => a.tierLevel - b.tierLevel)
|
||||
);
|
||||
|
||||
const selectedTier = ref(profile.value?.stripeTier);
|
||||
const selectedTierPriceId = computed(() => selectedTier?.value?.priceId || '');
|
||||
const modalIsOpen = ref(false);
|
||||
|
||||
const dialogContainer = ref(null);
|
||||
|
||||
const progress = await useFetch('/api/progress');
|
||||
const donations = computed(() => progress.data.value?.donations);
|
||||
const goalPercentage = computed(() => Math.floor((donations.value?.currentCents ?? 0) / (donations.value?.goalCents ?? 0) * 100));
|
||||
|
||||
const goalText = computed(() => {
|
||||
return t('donation.progress', {
|
||||
totd: Math.round((donations.value?.currentCents ?? 0) / 100).toString(),
|
||||
goald: Math.round((donations.value?.goalCents ?? 0) / 100).toString(),
|
||||
perc: goalPercentage.value.toString()
|
||||
});
|
||||
});
|
||||
|
||||
async function unsubscribe() {
|
||||
try {
|
||||
@@ -37,43 +70,568 @@ async function checkout(priceId: string) {
|
||||
}
|
||||
|
||||
function hasSubscription(priceId: string) {
|
||||
return profile.value?.stripeTier && priceId === profile.value.stripeTier.priceId;
|
||||
return (
|
||||
profile.value?.stripeTier && priceId === profile.value.stripeTier.priceId
|
||||
);
|
||||
}
|
||||
|
||||
useHead({
|
||||
title: `Upgrade`
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1>Tiers</h1>
|
||||
<div
|
||||
v-for="tier of sortedTiers"
|
||||
:key="tier.priceId"
|
||||
:style="{ border: '1px solid white'}"
|
||||
<div class="account-form-wrapper">
|
||||
<a
|
||||
href="/account"
|
||||
class="back-arrow"
|
||||
>
|
||||
<h2>{{ tier.name }}</h2>
|
||||
<p>{{ tier.description }}</p>
|
||||
<p v-if="tier.perks.beta">
|
||||
Perk: Beta access
|
||||
</p>
|
||||
<p v-if="tier.perks.discordRead">
|
||||
Perk: Read discord channels
|
||||
</p>
|
||||
<img
|
||||
v-if="tier.thumbnailUrl"
|
||||
:src="tier.thumbnailUrl"
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="feather feather-arrow-left"
|
||||
>
|
||||
<p>Cost: {{ tier.priceCents }} cents</p>
|
||||
<button
|
||||
v-if="hasSubscription(tier.priceId)"
|
||||
@click="unsubscribe()"
|
||||
<line
|
||||
x1="19"
|
||||
y1="12"
|
||||
x2="5"
|
||||
y2="12"
|
||||
/>
|
||||
<polyline points="12 19 5 12 12 5" />
|
||||
</svg>
|
||||
<span>{{ $t("upgrade.back") }}</span>
|
||||
</a>
|
||||
|
||||
<a
|
||||
class="logotype"
|
||||
href="/"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="120"
|
||||
height="39.876"
|
||||
>
|
||||
Unsubscribe
|
||||
</button>
|
||||
<button
|
||||
<g
|
||||
id="logo_type"
|
||||
data-name="logo type"
|
||||
transform="translate(-553 -467)"
|
||||
>
|
||||
<g
|
||||
id="logo"
|
||||
transform="translate(553 467)"
|
||||
>
|
||||
<rect
|
||||
id="XMLID_158_"
|
||||
width="39.876"
|
||||
height="39.876"
|
||||
fill="#9d6ff3"
|
||||
opacity="0"
|
||||
/>
|
||||
<g
|
||||
id="XMLID_6_"
|
||||
transform="translate(8.222 1.418)"
|
||||
>
|
||||
<path
|
||||
id="XMLID_15_"
|
||||
d="M69.149,28.312c-1.051.553-.129,2.139.922,1.585a12.365,12.365,0,0,1,8.794-.571,10.829,10.829,0,0,1,6.342,4.166c.645,1,2.231.074,1.585-.922C83.308,27.169,74.7,25.436,69.149,28.312Z"
|
||||
transform="translate(-64.246 -23.389)"
|
||||
fill="#9d6ff3"
|
||||
/>
|
||||
<path
|
||||
id="XMLID_14_"
|
||||
d="M82.64,14.608A15.565,15.565,0,0,0,73.5,8.45a17.535,17.535,0,0,0-12.647.9c-1.051.553-.129,2.139.922,1.585,3.411-1.788,7.6-1.714,11.209-.719,3.1.848,6.268,2.544,8.038,5.309C81.681,16.543,83.267,15.622,82.64,14.608Z"
|
||||
transform="translate(-57.476 -7.693)"
|
||||
fill="#9d6ff3"
|
||||
/>
|
||||
<path
|
||||
id="XMLID_9_"
|
||||
d="M55.68,47.8a10.719,10.719,0,0,0-6.71,2.3H45.983A1.336,1.336,0,0,0,44.6,51.376V75.84a1.431,1.431,0,0,0,1.383,1.383h3.023a1.367,1.367,0,0,0,1.309-1.383V68.392A10.993,10.993,0,1,0,55.68,47.8Zm0,17.182a6.213,6.213,0,1,1,6.213-6.213A6.216,6.216,0,0,1,55.68,64.982Z"
|
||||
transform="translate(-44.6 -40.406)"
|
||||
fill="#9d6ff3"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<text
|
||||
id="Pretendo"
|
||||
transform="translate(593 492)"
|
||||
fill="#fff"
|
||||
font-size="17"
|
||||
font-family="Poppins-Bold, Poppins"
|
||||
font-weight="700"
|
||||
>
|
||||
<tspan
|
||||
x="0"
|
||||
y="0"
|
||||
>Pretendo</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<h1 class="title dot">
|
||||
{{ $t("upgrade.title") }}
|
||||
</h1>
|
||||
<p class="caption">
|
||||
{{ $t("upgrade.description") }}
|
||||
</p>
|
||||
|
||||
<div class="progress-bar-wrapper">
|
||||
<div
|
||||
class="progress-bar"
|
||||
:style="{
|
||||
'--progress-bar-width': goalPercentage + '%',
|
||||
}"
|
||||
>
|
||||
<div
|
||||
v-if="goalPercentage >= 100"
|
||||
class="progress-bar-real"
|
||||
/>
|
||||
<div class="progress-bar-capped" />
|
||||
</div>
|
||||
<p
|
||||
class="localeReplace"
|
||||
v-html="goalText"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<form method="post">
|
||||
<template
|
||||
v-for="tier of sortedTiers"
|
||||
:key="tier.priceId"
|
||||
>
|
||||
<input
|
||||
:id="tier.priceId"
|
||||
v-model="selectedTierPriceId"
|
||||
type="radio"
|
||||
class="tier-radio"
|
||||
:data-tier-name="tier.name"
|
||||
:value="tier.priceId"
|
||||
@click="selectedTier = tier"
|
||||
>
|
||||
<label
|
||||
class="tier"
|
||||
:for="tier.priceId"
|
||||
>
|
||||
<div class="tier-thumbnail">
|
||||
<img
|
||||
:src="tier.thumbnailUrl"
|
||||
width="100%"
|
||||
height="auto"
|
||||
alt="Tier icon"
|
||||
>
|
||||
</div>
|
||||
<div class="tier-text">
|
||||
<p class="tier-name">{{ tier.name }}</p>
|
||||
<div class="tier-perks">
|
||||
<p v-if="tier.perks.beta">
|
||||
<Icon
|
||||
name="ph:flask"
|
||||
size="20"
|
||||
style="color: var(--green-shade-1)"
|
||||
/>
|
||||
Beta server access
|
||||
</p>
|
||||
<p v-if="tier.perks.discordRead">
|
||||
<Icon
|
||||
name="ph:chats"
|
||||
size="20"
|
||||
style="color: var(--accent-shade-2)"
|
||||
/>
|
||||
Read-only access to select dev channels on Discord
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="price">
|
||||
<span>${{ tier.priceCents / 100 }}</span> /
|
||||
{{ $t("upgrade.month") }}
|
||||
</p>
|
||||
</label>
|
||||
</template>
|
||||
<div
|
||||
v-if="hasSubscription(selectedTier?.priceId || '')"
|
||||
class="button-wrapper"
|
||||
>
|
||||
<AlertDialogRoot v-model:open="modalIsOpen">
|
||||
<AlertDialogTrigger>
|
||||
{{ $t("upgrade.unsub") }}
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogPortal :to="dialogContainer">
|
||||
<AlertDialogOverlay />
|
||||
<AlertDialogContent class="modal">
|
||||
<AlertDialogTitle>{{ $t("upgrade.unsub") }}?</AlertDialogTitle>
|
||||
<AlertDialogDescription class="modal-caption">
|
||||
<span v-html="$t('upgrade.unsubPrompt').replace('tiername', profile?.stripeTier?.tierName)" />
|
||||
</AlertDialogDescription>
|
||||
<div class="modal-button-wrapper">
|
||||
<AlertDialogCancel class="cancel">
|
||||
{{ $t("modals.cancel") }}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
class="alert"
|
||||
@click="unsubscribe"
|
||||
>
|
||||
{{ $t("upgrade.unsubConfirm") }}
|
||||
</AlertDialogAction>
|
||||
</div>
|
||||
</AlertDialogContent>
|
||||
</AlertDialogPortal>
|
||||
</AlertDialogRoot>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="profile?.stripeTier"
|
||||
class="button-wrapper"
|
||||
>
|
||||
<AlertDialogRoot v-model:open="modalIsOpen">
|
||||
<AlertDialogTrigger>
|
||||
{{ $t("upgrade.changeTier") }}
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogPortal :to="dialogContainer">
|
||||
<AlertDialogOverlay />
|
||||
<AlertDialogContent class="modal">
|
||||
<AlertDialogTitle>{{ $t("upgrade.changeTier") }}?</AlertDialogTitle>
|
||||
<AlertDialogDescription class="modal-caption">
|
||||
<span v-html="$t('upgrade.changeTierPrompt').replace('oldtiername', profile?.stripeTier?.tierName).replace('newtiername', selectedTier?.name)" />
|
||||
</AlertDialogDescription>
|
||||
<div class="modal-button-wrapper">
|
||||
<AlertDialogCancel class="cancel">
|
||||
{{ $t("modals.cancel") }}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
class="action"
|
||||
@click="() => checkout(selectedTier?.priceId || '')"
|
||||
>
|
||||
{{ $t("modals.confirm") }}
|
||||
</AlertDialogAction>
|
||||
</div>
|
||||
</AlertDialogContent>
|
||||
</AlertDialogPortal>
|
||||
</AlertDialogRoot>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="selectedTier?.priceId"
|
||||
class="button-wrapper"
|
||||
>
|
||||
<button
|
||||
|
||||
@click.prevent="checkout(selectedTier.priceId)"
|
||||
>
|
||||
Subscribe to {{ selectedTier.name }}
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
@click="checkout(tier.priceId)"
|
||||
class="button-wrapper"
|
||||
>
|
||||
Checkout
|
||||
</button>
|
||||
<button
|
||||
disabled
|
||||
>
|
||||
{{ $t('upgrade.tierSelectPrompt') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div
|
||||
id="unsub"
|
||||
:class="{ 'modal-wrapper': true, hidden: !modalIsOpen }"
|
||||
>
|
||||
<div ref="dialogContainer" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.back-arrow {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
top: 48px;
|
||||
left: 0;
|
||||
width: fit-content;
|
||||
padding: 6px 10px;
|
||||
background: var(--bg-shade-3);
|
||||
border-radius: 24px;
|
||||
|
||||
transition: filter 150ms;
|
||||
text-decoration: none;
|
||||
color: var(--text-shade-3);
|
||||
z-index: 5;
|
||||
}
|
||||
.back-arrow:hover {
|
||||
filter: brightness(1.5);
|
||||
}
|
||||
.back-arrow svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
.back-arrow span {
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.account-form-wrapper {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
width: min(1200px, 100%);
|
||||
color: var(--text-shade-1);
|
||||
margin: 0 auto 48px;
|
||||
text-align: center;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.account-form-wrapper .logotype {
|
||||
margin: 36px auto 0;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
h1.title {
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
p.caption {
|
||||
width: min(100%, 500px);
|
||||
margin: 0 auto 36px;
|
||||
}
|
||||
|
||||
.account-form-wrapper .progress-bar-wrapper {
|
||||
justify-content: center;
|
||||
width: min(100%, 500px);
|
||||
margin: 0 auto 72px;
|
||||
padding: 24px;
|
||||
border-radius: 6px;
|
||||
background: var(--bg-shade-2);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.account-form-wrapper .progress-bar-wrapper p {
|
||||
text-align: left;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.account-form-wrapper .progress-bar-wrapper p span {
|
||||
color: var(--text-shade-3);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.account-form-wrapper .progress-bar {
|
||||
height: 8px;
|
||||
border-radius: 4px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
form {
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 1.3rem;
|
||||
}
|
||||
|
||||
form .tier-radio {
|
||||
display: none;
|
||||
}
|
||||
form .tier-radio:checked + label::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-shadow: inset 0 0 0 4px var(--accent-shade-1);
|
||||
border-radius: 10px;
|
||||
}
|
||||
form .tier-radio:checked + label::after {
|
||||
content: url(/assets/images/check.svg);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background: var(--accent-shade-1);
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 100%;
|
||||
position: absolute;
|
||||
top: -16px;
|
||||
right: -16px;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
label.tier {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
position: relative;
|
||||
border-radius: 10px;
|
||||
align-items: center;
|
||||
padding-top: calc(50px + 1rem);
|
||||
background: var(--bg-shade-3);
|
||||
cursor: pointer;
|
||||
transition: all 150ms;
|
||||
margin-top: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
label.tier p {
|
||||
margin: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
label.tier .tier-thumbnail {
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
position: absolute;
|
||||
top: -50px;
|
||||
z-index: 2;
|
||||
background: var(--bg-shade-4);
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
form .tier-radio:checked + label .tier-thumbnail::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-shadow: inset 0 0 0 4px var(--accent-shade-1);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
label.tier .tier-text {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
label.tier .tier-name {
|
||||
color: var(--text-shade-3);
|
||||
font-weight: bold;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
label.tier .tier-perks {
|
||||
text-align: left;
|
||||
width: 70%;
|
||||
margin: 24px auto 48px;
|
||||
}
|
||||
|
||||
label.tier .tier-perks p span {
|
||||
position: relative;
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
label.tier .tier-perks div {
|
||||
display: grid;
|
||||
grid-template-columns: 16px auto;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
label.tier p.price {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: var(--bg-shade-4);
|
||||
margin: 0;
|
||||
padding: 1.5rem 1rem;
|
||||
box-sizing: border-box;
|
||||
border-radius: 0 0 10px 10px;
|
||||
}
|
||||
label.tier p.price span {
|
||||
font-size: 2rem;
|
||||
color: var(--text-shade-3);
|
||||
font-weight: bold;
|
||||
margin-right: 0.5ch;
|
||||
}
|
||||
|
||||
form .button-wrapper {
|
||||
grid-column: 2 / span 1;
|
||||
position: relative;
|
||||
margin-top: 24px;
|
||||
}
|
||||
button {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
display: block;
|
||||
font-family: Poppins, Arial, Helvetica, sans-serif;
|
||||
font-size: 1rem;
|
||||
height: fit-content;
|
||||
|
||||
background: var(--accent-shade-0);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
color: var(--text-shade-3);
|
||||
width: 100%;
|
||||
|
||||
transition: filter 300ms;
|
||||
pointer-events: all;
|
||||
cursor: pointer;
|
||||
filter: none;
|
||||
}
|
||||
form button:disabled {
|
||||
pointer-events: none;
|
||||
filter: brightness(0.75) saturate(0.75) !important; /* not using opacity here 'cause in the mobile layout you would see the cards under it */
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.alertDialogRoot {
|
||||
position: absolute;
|
||||
background-color: #f00;
|
||||
}
|
||||
|
||||
form button.unsubscribe {
|
||||
position: relative;
|
||||
background: none;
|
||||
color: var(--text-shade-1);
|
||||
margin-top: 12px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.account-form-wrapper {
|
||||
width: min(500px, 100%);
|
||||
margin-bottom: 172px;
|
||||
}
|
||||
|
||||
form {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2.4rem;
|
||||
}
|
||||
|
||||
form button {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
form .button-wrapper {
|
||||
grid-column: 1 / span 1;
|
||||
position: fixed;
|
||||
bottom: 24px;
|
||||
width: min(500px, 90%);
|
||||
z-index: 5;
|
||||
}
|
||||
form .button-wrapper::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -24px;
|
||||
left: -100vw;
|
||||
width: 200vw;
|
||||
height: 300%;
|
||||
background: var(--bg-shade-0);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 380px) {
|
||||
label.tier .tier-perks {
|
||||
width: 80%;
|
||||
}
|
||||
.back-arrow {
|
||||
padding: 6px;
|
||||
}
|
||||
.back-arrow span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,8 +4,6 @@ const { data: error } = await useAsyncData(`${code}`, () => {
|
||||
return queryCollection('errorcodes').where('stem', '=', code).first();
|
||||
});
|
||||
|
||||
console.log(error.value);
|
||||
|
||||
if (!error.value) {
|
||||
throw createError({ statusCode: 404, statusMessage: 'Page Not Found' });
|
||||
}
|
||||
|
||||
@@ -7,6 +7,16 @@ const { data: termContent } = await useAsyncData(`terms-${slug}`, () => {
|
||||
if (!termContent.value) {
|
||||
throw createError({ statusCode: 404, statusMessage: 'Page Not Found' });
|
||||
}
|
||||
|
||||
useHead({
|
||||
title: `${termContent.value.title} | Docs`,
|
||||
meta: [
|
||||
{ property: 'description', content: termContent.value.description },
|
||||
{ property: 'og:description', content: termContent.value.description },
|
||||
{ property: 'og:image:alt', content: '' },
|
||||
{ name: 'twitter:description', content: termContent.value.description }
|
||||
]
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="#fff" d="M5 19h1.4l8.625-8.625l-1.4-1.4L5 17.6ZM19.3 8.925l-4.25-4.2l1.4-1.4q.575-.575 1.413-.575q.837 0 1.412.575l1.4 1.4q.575.575.6 1.388q.025.812-.55 1.387ZM4 21q-.425 0-.712-.288Q3 20.425 3 20v-2.825q0-.2.075-.387q.075-.188.225-.338l10.3-10.3l4.25 4.25l-10.3 10.3q-.15.15-.337.225q-.188.075-.388.075ZM14.325 9.675l-.7-.7l1.4 1.4Z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#fff" viewBox="0 0 256 256"><path d="M227.31,73.37,182.63,28.68a16,16,0,0,0-22.63,0L36.69,152A15.86,15.86,0,0,0,32,163.31V208a16,16,0,0,0,16,16H92.69A15.86,15.86,0,0,0,104,219.31L227.31,96a16,16,0,0,0,0-22.63ZM51.31,160,136,75.31,152.69,92,68,176.68ZM48,179.31,76.69,208H48Zm48,25.38L79.31,188,164,103.31,180.69,120Zm96-96L147.31,64l24-24L216,84.68Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 438 B After Width: | Height: | Size: 433 B |
Reference in New Issue
Block a user