Skip to main content
Glama
LegalAgreementPage.vue7.19 kB
<!-- eslint-disable vue/no-v-html --> <template> <div> <!-- <div class="legal-markdown" v-html="authStore.tosDetails?.html" /> --> <RichText class="mb-xl"> <template v-if="viewOnlyMode"> <h1>System Initiative Legal Documents</h1> <p> <i>Last updated {{ currentVersion }}</i> </p> </template> <template v-else> <h1>Review our legal docs:</h1> <p> In order to use System Initiative, we need you to review and agree to our terms: </p> </template> </RichText> <div class="flex gap-lg"> <div class="flex-none w-[220px]"> <div class="sticky top-md flex flex-col gap-md"> <div v-for="doc in currentDocs" :key="doc.fileName" :class=" clsx( 'cursor-pointer flex items-center gap-xs', doc.slug === activeDocSlug && '', ) " @click="scrollToDoc(doc.slug)" > <a :class=" clsx( 'underline-link w-auto', doc.slug === activeDocSlug && '--active', ) " href="#" @click.prevent > {{ doc.title }} </a> </div> <div v-if="!viewOnlyMode" class="cursor-pointer" @click="scrollToDoc('agree')" > <a :class=" clsx( 'underline-link w-auto', activeDocSlug === 'agree' && '--active', ) " href="#" @click.prevent > Agree and Continue </a> </div> </div> </div> <div class="grow border-l border-neutral-300 dark:border-neutral-700 pl-lg flex flex-col gap-xl" > <div v-for="(doc, key) in currentDocs" :key="key" :data-doc-slug="doc.slug" > <RichText class="text-sm"> <Component :is="doc.component" /> </RichText> <div class="mt-md"> <VButton :linkTo="{ name: 'print-legal', params: { docVersion: currentVersion, docSlug: doc.slug }, }" icon="download" size="sm" target="_blank" tone="shade" variant="soft" >Print / Download </VButton> </div> </div> <Stack v-if="!viewOnlyMode" data-doc-slug="agree"> <VormInput v-model="userAgreed" type="checkbox" >I have read and agree to the terms above </VormInput> <VButton :disabled="disableContinueButton" :requestStatus="agreeTosReqStatus" icon="arrow--right" variant="solid" @click="agreeButtonHandler" > Agree & Continue </VButton> </Stack> </div> </div> </div> </template> <script lang="ts" setup> import * as _ from "lodash-es"; import { useRoute, useRouter } from "vue-router"; import { computed, onBeforeUnmount, onMounted, reactive, ref, watch, } from "vue"; import { RichText, Stack, userOverrideTheme, VButton, VormInput, } from "@si/vue-lib/design-system"; import clsx from "clsx"; import { useHead } from "@vueuse/head"; import { TosVersion } from "@si/ts-lib/src/terms-of-service"; import { useAuthStore } from "@/store/auth.store"; import { LEGAL_DOCS_CONTENT } from "./load-docs"; const authStore = useAuthStore(); const router = useRouter(); const route = useRoute(); // this page handles 2 modes, public view-only and review/agreement const viewOnlyMode = route.name === "legal"; const agreeTosReqStatus = authStore.getRequestStatus("AGREE_TOS"); const currentVersion = computed(() => TosVersion.v20240925); const currentDocs = computed(() => LEGAL_DOCS_CONTENT[currentVersion.value]); const userAgreed = ref(false); useHead({ title: "Legal" }); const disableContinueButton = computed(() => { if (!userAgreed.value) return true; if (agreeTosReqStatus.value.isPending) return true; return false; }); async function loadTosDetails() { if (import.meta.env.SSR) return; if (viewOnlyMode) return; if (authStore.user?.needsTosUpdate === false) { return router.push({ name: "login-success" }); } } watch(() => authStore.user?.needsTosUpdate, loadTosDetails, { immediate: true, }); async function agreeButtonHandler() { const isFirstAgreement = !authStore.user?.agreedTosVersion; const agreeReq = await authStore.AGREE_TOS(currentVersion.value); if (agreeReq.result.success) { await router.push({ name: isFirstAgreement ? "profile" : "login-success" }); } } const scrollingToSlug = ref<string | undefined>(undefined); function scrollToDoc(slug: string) { const el = document.querySelector(`[data-doc-slug="${slug}"]`); el?.scrollIntoView({ behavior: "smooth" }); activeDocSlug.value = slug; scrollingToSlug.value = slug; } // track all intersecting sections, and current one should be the last in the list const intersectingDocs = reactive<Record<string, boolean>>({}); const activeDocSlug = ref("tos"); const observer = new IntersectionObserver( (entries) => { const entry = entries[0]; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const slug = entry.target.getAttribute("data-doc-slug")!; if (entry.isIntersecting && slug !== "agree") { intersectingDocs[slug] = true; } else { intersectingDocs[slug] = false; } activeDocSlug.value = _.last(_.keys(_.pickBy(intersectingDocs))) || activeDocSlug.value; }, { threshold: [0] }, ); watch(activeDocSlug, () => { /* eslint-disable @typescript-eslint/no-floating-promises */ router.replace({ ...route, hash: `#${activeDocSlug.value}` }); }); const enableObserver = () => { const sectionEls = document.querySelectorAll("[data-doc-slug]"); sectionEls.forEach((el) => { observer.observe(el); }); }; let title = document.title; const theme = userOverrideTheme.value; const setPrintTitle = () => { title = document.title; document.title = `${currentVersion.value}_SI-Legal-Documents`; userOverrideTheme.value = "light"; }; const clearPrintTitle = () => { document.title = title; userOverrideTheme.value = theme; }; onMounted(() => { enableObserver(); window.addEventListener("beforeprint", setPrintTitle); window.addEventListener("afterprint", clearPrintTitle); window.addEventListener("scrollend", () => { if (scrollingToSlug.value) { activeDocSlug.value = scrollingToSlug.value; scrollingToSlug.value = undefined; } }); // if url refers to a specific doc, we'll scroll to it right away if (route.hash) { scrollToDoc(route.hash.replace("#", "")); } }); onBeforeUnmount(() => { window.removeEventListener("beforeprint", setPrintTitle); window.removeEventListener("afterprint", clearPrintTitle); observer.disconnect(); }); </script>

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/systeminit/si'

If you have feedback or need assistance with the MCP directory API, please join our Discord server