Skip to main content
Glama
ProfilePage.vue11.1 kB
<!-- eslint-disable vue/no-v-html --> <template> <div> <template v-if="loadUserReqStatus.isPending"> <Icon name="loader" size="xl" /> </template> <template v-else-if="loadUserReqStatus.isError"> <ErrorMessage :requestStatus="loadUserReqStatus" /> </template> <template v-else-if="draftUser"> <div class="flex gap-xl"> <div class="w-[35%] flex items-center pl-md"> <Stack> <!-- this text only shows the first time / user is in onboarding --> <template v-if="isOnboarding"> <RichText> <h2>Welcome To System Initiative!</h2> <p>Please enter your profile information.</p> </RichText> </template> <!-- this is the default text --> <template v-else> <RichText> <h2>Update Your Profile</h2> <p>Use this page to update your profile info.</p> </RichText> </template> </Stack> </div> <form class="grow my-md px-md"> <Stack> <ErrorMessage :requestStatus="updateUserReqStatus" /> <VormInput v-if="draftUser.pictureUrl || storeUser?.pictureUrl" label="Profile Image" type="container" > <div v-if="draftUser.pictureUrl" class="flex flex-row items-center gap-sm" > <img :src="draftUser.pictureUrl" class="rounded-full w-xl h-xl" /> <VButton tone="destructive" size="xs" variant="ghost" @click="clearPicture" >Clear Picture </VButton> </div> <div v-else-if="storeUser?.pictureUrl" class="h-xl items-center flex flex-row gap-sm" > <div class="italic text-sm">No image set.</div> <VButton tone="action" size="xs" variant="ghost" @click="restorePicture" >Restore Picture </VButton> </div> </VormInput> <Tiles columns="2" spacing="sm" columnsMobile="1"> <VormInput v-model="draftUser.firstName" label="First Name" autocomplete="given-name" placeholder="Your first name" :regex="ALLOWED_INPUT_REGEX" /> <VormInput v-model="draftUser.lastName" label="Last Name" autocomplete="last-name" placeholder="Your last name" :regex="ALLOWED_INPUT_REGEX" /> </Tiles> <VormInput v-model="draftUser.nickname" label="Nickname" autocomplete="username" required placeholder="This name will be shown in the application" :regex="ALLOWED_INPUT_REGEX" /> <VormInput v-model="draftUser.email" label="Email" type="email" autocomplete="email" required disabled placeholder="ex: yourname@somewhere.com" /> <VormInput v-model="draftUser.discordUsername" label="Discord Username" name="discord_username" placeholder="ex: eggscellent OR eggscellent#1234" :regex="DISCORD_TAG_REGEX" regexMessage="Invalid discord tag" class="pb-xs" > <template #instructions> <div class="text-neutral-700 dark:text-neutral-200 italic"> Entering your username will help us to give you technical support <a href="" class="underline text-action-500">on our Discord</a >. </div> </template> </VormInput> <VormInput v-model="draftUser.githubUsername" label="Github Username" name="github_username" placeholder="ex: devopsdude42" :regex="GITHUB_USERNAME_REGEX" regexMessage="Invalid github username" /> <VButton iconRight="chevron--right" :disabled="validationState.isError || failedEmailVerify" :requestStatus="updateUserReqStatus" loadingText="Saving your profile..." successText="Updated your profile!" tone="action" variant="solid" :label="saveButtonLabel" @click="saveHandler" /> </Stack> </form> </div> </template> </div> </template> <script setup lang="ts"> /* eslint-disable @typescript-eslint/no-non-null-assertion */ import * as _ from "lodash-es"; import { useRouter } from "vue-router"; import { computed, onBeforeMount, ref, watch } from "vue"; import { ErrorMessage, Icon, Tiles, Stack, useValidatedInputGroup, VButton, VormInput, RichText, } from "@si/vue-lib/design-system"; import { useHead } from "@vueuse/head"; import { useAuthStore, User } from "@/store/auth.store"; import { tracker } from "@/lib/posthog"; import { useWorkspacesStore } from "@/store/workspaces.store"; import { ALLOWED_INPUT_REGEX } from "@/lib/validations"; const GITHUB_USERNAME_REGEX = /^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i; const DISCORD_TAG_REGEX = /^(?!(discord|here|everyone))(((?!.*\.\.)(([\w.]{2,32})))|[^@#:]{2,32}#[\d]{4})$/i; const { validationState, validationMethods } = useValidatedInputGroup(); const authStore = useAuthStore(); const workspacesStore = useWorkspacesStore(); // Reactively load the workspaces so that the workspace redirect happens for first time users workspacesStore.refreshWorkspaces(); const router = useRouter(); const loadUserReqStatus = authStore.getRequestStatus("LOAD_USER"); const checkAuthReqStatus = authStore.getRequestStatus("CHECK_AUTH"); const updateUserReqStatus = authStore.getRequestStatus("UPDATE_USER"); const storeUser = computed(() => authStore.user); const draftUser = ref<User>(); const isOnboarding = ref<boolean>(); const refreshAuth0ReqStatus = authStore.getRequestStatus( "REFRESH_AUTH0_PROFILE", ); useHead({ title: "Profile" }); function resetDraftUser() { draftUser.value = _.cloneDeep(storeUser.value!); } watch(storeUser, resetDraftUser, { immediate: true }); function checkUserOnboarding() { if (storeUser.value && isOnboarding.value === undefined) { isOnboarding.value = !storeUser.value.onboardingDetails?.reviewedProfile; } } watch(storeUser, checkUserOnboarding, { immediate: true }); watch([refreshAuth0ReqStatus, storeUser], () => { if (storeUser.value?.emailVerified) { failedEmailVerify.value = false; saveButtonLabel.value = "Save"; } }); onBeforeMount(() => { // normally when landing on this page, we should probably make sure we have the latest profile info // but we already load user info with CHECK_AUTH so can skip if it was just loaded // (this will likely go away if we start fetching more profile info than what gets fetched while checking auth) if (+checkAuthReqStatus.value.lastSuccessAt! > +new Date() - 10000) { return; } // eslint-disable-next-line @typescript-eslint/no-floating-promises authStore.LOAD_USER(); }); const saveHandler = async () => { if (validationMethods.hasError()) return; // Users whose email has not been verified should not be able to continue past here if (!authStore.user?.emailVerified) { await verifyEmail(); if (!authStore.user?.emailVerified) { youMustVerifyYourEmailAddress(); return; } } // Okay now we know that their email has been verified // if this is first time, do this stuff const updateReq = await authStore.UPDATE_USER(draftUser.value!); if (updateReq.result.success && isOnboarding.value) { if ( storeUser.value && storeUser.value.emailVerified && !storeUser.value.auth0Id.startsWith("auth0") ) { // We only want to send this event when a user has signed up and // we captured a verified email for them. We will only ever have a // user with an auth0Id of auth0 if it's not using SSO. So we will // force that user through a manual verification and capture the user // at that stage // This means we won't ever be sending badly formed data to our CRM // or billing // This is also the place we would trigger the creation of a Billing user tracker.trackEvent("initial_profile_set", { email: draftUser.value?.email, githubUsername: draftUser.value?.githubUsername, discordUsername: draftUser.value?.discordUsername, firstName: draftUser.value?.firstName, lastName: draftUser.value?.lastName, }); await authStore.BILLING_INTEGRATION(); } const completeProfileReq = await authStore.COMPLETE_PROFILE({}); if (completeProfileReq.result.success) { // eslint-disable-next-line @typescript-eslint/no-floating-promises router.push({ name: "workspaces" }); } } else { // eslint-disable-next-line @typescript-eslint/no-floating-promises router.push({ name: "workspaces" }); } }; const clearPicture = () => { if (draftUser.value) { draftUser.value.pictureUrl = null; } }; const restorePicture = () => { if (draftUser.value && storeUser.value) { draftUser.value.pictureUrl = storeUser.value.pictureUrl; } }; const saveButtonLabel = ref("Save"); const failedEmailVerify = ref(false); const youMustVerifyYourEmailAddress = () => { saveButtonLabel.value = "You must verify your email address to continue"; failedEmailVerify.value = true; }; const verifyEmail = async () => { // if this is first time, we will take them off profile page after save const verificationReq = await authStore.REFRESH_AUTH0_PROFILE(); if (verificationReq.result.success) { failedEmailVerify.value = false; if (storeUser.value && storeUser.value.emailVerified) { // We only want to send this event when a user has signed up and // we captured a verified email for them // This means we won't ever be sending badly formed data to our CRM // or billing // This is also the place we would trigger the creation of a Billing user tracker.trackEvent("user_email_manually_verified", { email: storeUser.value?.email, githubUsername: storeUser.value?.githubUsername, discordUsername: storeUser.value?.discordUsername, firstName: storeUser.value?.firstName, lastName: storeUser.value?.lastName, }); await authStore.BILLING_INTEGRATION(); } } }; </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