PronounsPage/layouts/default.vue
2025-06-28 12:48:37 +02:00

222 lines
6.2 KiB
Vue

<script setup lang="ts">
import { useStorage } from '@vueuse/core';
import sorter from 'avris-sorter';
import { useCookie } from 'nuxt/app';
import useConfig from '~/composables/useConfig.ts';
import useDark from '~/composables/useDark.ts';
import useDialogue from '~/composables/useDialogue.ts';
import { longtimeCookieSetting } from '~/src/cookieSettings.ts';
import { LoadScriptError } from '~/src/errors.ts';
import { executeUnlessPrerendering, sha256 } from '~/src/helpers.ts';
import { useMainStore } from '~/store/index.ts';
// no need to be super secure, just a sign that the page is not public
const TESTER_PASSWORD_HASH = '3754c03824c4ea3b13e7b4b2d7ad35992dbf64f9ee680493fc7093bf176f49e5';
declare global {
interface Window {
dataLayer: unknown[];
gnshbrequest: {
cmd: any[];
forceInternalRequest: () => void;
};
adpushup: {
que: any[];
};
}
}
const { $translator: translator, $isGranted: isGranted, $locales: locales, $loadScript: loadScript } = useNuxtApp();
const config = useConfig();
const runtimeConfig = useRuntimeConfig();
const store = useMainStore();
useDark();
const testerPasswordCookie = useCookie('tester-password', longtimeCookieSetting);
const testerPassword = ref('');
const requiresLogin = computed((): boolean => {
if (config.macrolanguage?.enabled) {
return false;
}
if (runtimeConfig.public.env === 'test') {
return true;
}
if (config.locale !== '_' && !locales[config.locale]?.published) {
return true;
}
return false;
});
const testerPasswordValid = ref<boolean | undefined>();
const checkTesterPassword = async () => {
if (testerPassword.value) {
testerPasswordCookie.value = testerPassword.value;
}
if (testerPasswordCookie.value) {
testerPasswordValid.value = await sha256(testerPasswordCookie.value) === TESTER_PASSWORD_HASH;
}
};
if (requiresLogin.value) {
await checkTesterPassword();
}
onMounted(executeUnlessPrerendering(() => {
sorter();
confirmAge();
loadAds();
let needsRefresh = false;
const bc = new BroadcastChannel('account_switch');
bc.onmessage = (ev): void => {
if (ev.data !== store.user?.username) {
needsRefresh = true;
if (document.hasFocus()) {
needsRefresh = false;
window.location.reload();
}
}
};
window.onfocus = (): void => {
if (needsRefresh) {
needsRefresh = false;
window.location.reload();
}
};
}));
const dialogue = useDialogue();
const confirmAge = async (): Promise<void> => {
if (!translator.has('footer.ageLimit') || localStorage.getItem('ageConfirmed')) {
return;
}
await dialogue.alert(translator.translate('footer.ageLimit'));
localStorage.setItem('ageConfirmed', '1');
};
const provider = useStorage<string | null>('ads-provider', null);
if (provider.value === null) {
provider.value = Math.random() < 0.7 ? 'publift' : 'pushup';
}
const adsEnabled = computed((): boolean => {
if (isGranted() && provider.value === 'none') {
return false;
}
return !!config.ads?.enabled && import.meta.env?.APP_ENV === 'production';
});
const loadAdsForProvider = {
publift: async (): Promise<void> => {
await loadScript(
'publift',
'https://cdn.fuseplatform.net/publift/tags/2/3329/fuse.js',
undefined,
'head',
);
},
geniee: async (): Promise<void> => {
window.gnshbrequest = window.gnshbrequest || { cmd: [] };
window.gnshbrequest.cmd.push(function () {
window.gnshbrequest.forceInternalRequest();
});
await loadScript(
'gpt',
'https://securepubads.g.doubleclick.net/tag/js/gpt.js',
undefined,
'head',
);
await loadScript(
'geniee',
'https://cpt.geniee.jp/hb/v1/222988/2860/wrapper.min.js',
undefined,
'head',
);
},
pushup: async (): Promise<void> => {
await loadScript(
'adpushup',
'https://cdn.adpushup.com/47589/adpushup.js',
undefined,
'head',
);
},
};
const loadAds = async (): Promise<void> => {
if (!adsEnabled.value) {
return;
}
try {
console.log('loading', provider.value);
const key = provider.value as keyof typeof loadAdsForProvider;
if (!Object.hasOwn(loadAdsForProvider, key)) {
throw new Error(`Invalid provider: ${key}`);
}
await loadAdsForProvider[key]();
} catch (error) {
if (error instanceof LoadScriptError) {
return;
}
throw error;
}
};
</script>
<template>
<div v-if="requiresLogin && !testerPasswordValid" class="body">
<div class="container">
<div class="alert alert-warning m-3 text-center">
<Icon v="exclamation-triangle" />
This is a test server
</div>
<div class="m-3">
<div class="input-group py-1">
<input v-model="testerPassword" class="form-control" type="password" placeholder="Password" @keydown.enter.prevent="checkTesterPassword">
<button type="button" class="btn btn-primary btn-sm border" @click.prevent="checkTesterPassword">
<Icon v="sign-in" />
Sign in
</button>
</div>
<p v-if="testerPasswordCookie && testerPasswordValid === false" class="small text-danger">
<Icon v="exclamation-triangle" />
Password invalid
</p>
</div>
</div>
</div>
<div v-else class="body">
<div class="flex-grow-1 vh">
<Header />
<slot></slot>
<TranslationMode />
<ScrollButton />
</div>
<Footer />
<Lightbox />
</div>
</template>
<style lang="scss">
@import "assets/variables";
@import "~avris-sorter/dist/Sorter.min.css";
.vh {
min-height: calc(100vh - #{$header-height});
}
</style>