Skip to content

Commit

Permalink
Merge pull request #4206 from systeminit/fix(web)--Rework-the-first-t…
Browse files Browse the repository at this point in the history
…ime-modal-tutorial-experience

fix(web): Rework the first time modal tutorial experience
  • Loading branch information
stack72 authored Jul 24, 2024
2 parents 5239bbc + 92419fc commit ead263a
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 81 deletions.
81 changes: 2 additions & 79 deletions app/web/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,52 +24,16 @@
class="fixed w-full h-full top-0 left-0 pointer-events-none z-100"
></canvas>
</Teleport>
<Modal
ref="firstTimeModalRef"
noExit
size="2xl"
title="Welcome To System Initiative!"
>
<!-- TODO(Wendy) - PLACEHOLDER VIDEO, please replace with our video before pushing to prod! -->
<iframe
class="aspect-video"
src="https://www.youtube.com/embed/dQw4w9WgXcQ?si=vdaJeJEq66s6wYTO"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen
></iframe>
<div class="flex flex-row gap-sm mt-xs">
<VormInput
v-model="firstTimeModalCheckbox"
class="flex flex-row-reverse gap-0 italic"
type="checkbox"
label="Don't show me this video again."
inlineLabel
/>
<VButton
class="flex-grow"
label="Let's Get Started!"
@click="closeFirstTimeModal"
/>
</div>
</Modal>
</template>
</div>
</template>

<script lang="ts" setup>
import { computed, ref, watch } from "vue";
import { computed } from "vue";
import "floating-vue/dist/style.css";

import { tw } from "@si/vue-lib";
import {
Modal,
VButton,
VormInput,
useThemeContainer,
} from "@si/vue-lib/design-system";
import { useThemeContainer } from "@si/vue-lib/design-system";
import SiLogoUrlLight from "@si/vue-lib/brand-assets/si-logo-symbol-white-bg.svg?url";
import SiLogoUrlDark from "@si/vue-lib/brand-assets/si-logo-symbol-black-bg.svg?url";
import { useHead } from "@vueuse/head";
Expand All @@ -80,9 +44,6 @@ import { useWorkspacesStore } from "./store/workspaces.store";
import { useRealtimeStore } from "./store/realtime/realtime.store";
import RealtimeConnectionStatus from "./components/RealtimeConnectionStatus.vue";
import CachedAppNotification from "./components/CachedAppNotification.vue";
import { useFeatureFlagsStore } from "./store/feature_flags.store";

const featureFlagsStore = useFeatureFlagsStore();

useCustomFontsLoadedProvider();

Expand Down Expand Up @@ -128,44 +89,6 @@ const reconnectAuthReqStatus = authStore.getRequestStatus("AUTH_RECONNECT");
const workspacesStore = useWorkspacesStore();
const selectedWorkspace = computed(() => workspacesStore.selectedWorkspace);

const firstTimeModalFired = ref(false);
const firstTimeModalRef = ref<InstanceType<typeof Modal>>();

watch(restoreAuthReqStatus, async () => {
if (!featureFlagsStore.FIRST_TIME_TUTORIAL_MODAL) return;

if (restoreAuthReqStatus.value.isSuccess) {
if (authStore.user) {
const res = await fetch(
`${import.meta.env.VITE_AUTH_API_URL}/users/${
authStore.user.pk
}/firstTimeModal`,
);

const data = await res.json();

if (!firstTimeModalFired.value && data.firstTimeModal) {
firstTimeModalRef.value?.open();
firstTimeModalFired.value = true;
}
}
}
});

const firstTimeModalCheckbox = ref(false);

const closeFirstTimeModal = () => {
if (authStore.user && firstTimeModalCheckbox.value) {
fetch(
`${import.meta.env.VITE_AUTH_API_URL}/users/${
authStore.user.pk
}/dismissFirstTimeModal`,
{ method: "POST" },
);
}
firstTimeModalRef.value?.close();
};

// initialize the realtime store - which will watch for auth and open/close websocket
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const realtimeStore = useRealtimeStore();
Expand Down
68 changes: 66 additions & 2 deletions app/web/src/pages/WorkspaceSinglePage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,57 @@
class="flex-none"
/>
</template>
<Modal
ref="firstTimeModalRef"
noExit
size="2xl"
title="Welcome To System Initiative!"
>
<!-- TODO(Wendy) - PLACEHOLDER VIDEO, please replace with our video before pushing to prod! -->
<iframe
class="aspect-video"
src="https://www.youtube.com/embed/7vrIJmP49IE?si=Kknr-Qm5DDBDXjTu"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen
></iframe>
<div class="flex flex-row gap-sm mt-xs">
<VormInput
v-model="firstTimeModalCheckbox"
class="flex flex-row-reverse gap-0 italic"
type="checkbox"
label="Don't show me this video again."
inlineLabel
/>
<VButton
class="flex-grow"
label="Let's Get Started!"
@click="closeFirstTimeModal"
/>
</div>
</Modal>
</template>
</AppLayout>
</template>

<script lang="ts" setup>
import { computed, PropType, watch } from "vue";
import { computed, onMounted, PropType, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import * as _ from "lodash-es";
import { ErrorMessage, Icon, LoadingMessage } from "@si/vue-lib/design-system";
import storage from "local-storage-fallback";
import {
ErrorMessage,
Icon,
LoadingMessage,
Modal,
VButton,
VormInput,
} from "@si/vue-lib/design-system";
import { useChangeSetsStore } from "@/store/change_sets.store";
import { useWorkspacesStore } from "@/store/workspaces.store";
import { useAuthStore } from "@/store/auth.store";
import AppLayout from "@/components/layout/AppLayout.vue";
import Navbar from "@/components/layout/navbar/Navbar.vue";
import StatusBar from "@/components/StatusBar/StatusBar.vue";
Expand All @@ -96,6 +136,7 @@ const route = useRoute();
const workspacesStore = useWorkspacesStore();
const changeSetsStore = useChangeSetsStore();
const authStore = useAuthStore();
const workspacesReqStatus = workspacesStore.getRequestStatus(
"FETCH_USER_WORKSPACES",
Expand All @@ -109,6 +150,29 @@ const changeSetsReqStatus =
// this page is the parent of many child routes so we watch the route rather than use mounted hooks
watch([route, changeSetsReqStatus], handleUrlChange, { immediate: true });
const firstTimeModalFired = ref(false);
const firstTimeModalRef = ref<InstanceType<typeof Modal>>();
onMounted(async () => {
if (authStore.user) {
const showModal = await authStore.CHECK_FIRST_MODAL(authStore.user.pk);
const hasServedModal = storage.getItem("SI_FIRST_TIME_MODAL_SHOWN");
if (!firstTimeModalFired.value && showModal && !hasServedModal) {
firstTimeModalRef.value?.open();
firstTimeModalFired.value = true;
storage.setItem("SI_FIRST_TIME_MODAL_SHOWN", "1");
}
}
});
const firstTimeModalCheckbox = ref(false);
const closeFirstTimeModal = () => {
if (authStore.user && firstTimeModalCheckbox.value) {
authStore.DISMISS_FIRST_TIME_MODAL(authStore.user.pk);
}
firstTimeModalRef.value?.close();
};
// TODO: this logic needs some work
function handleUrlChange() {
changeSetsStore.creatingChangeSet = false;
Expand Down
14 changes: 14 additions & 0 deletions app/web/src/store/auth.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { User } from "@/api/sdf/dal/user";
import { Workspace } from "@/api/sdf/dal/workspace";
import { useWorkspacesStore } from "./workspaces.store";
import handleStoreError from "./errors";
import { AuthApiRequest } from ".";

export type UserId = string;

Expand Down Expand Up @@ -98,6 +99,19 @@ export const useAuthStore = defineStore("auth", {
});
},

async CHECK_FIRST_MODAL(userPk: string) {
return new AuthApiRequest<boolean>({
url: `/users/${userPk}/firstTimeModal`,
});
},

async DISMISS_FIRST_TIME_MODAL(userPk: string) {
return new AuthApiRequest<boolean>({
method: "post",
url: `/users/${userPk}/dismissFirstTimeModal`,
});
},

// uses existing auth token (jwt) to re-fetch and initialize workspace/user from auth api
// this is needed if user is still logged inbut the running SI instance DB is empty
// for example after updating containers via the launcher
Expand Down

0 comments on commit ead263a

Please sign in to comment.