Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(): Onboarding Checklist #2863

Closed
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions src/components/TimelineOnboarding.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<script setup lang="ts">
import { computed, watch } from 'vue';
import { useI18n } from '@/composables/useI18n';
import { useOnboardingChecklist } from '@/composables/useOnboardingChecklist';
import { useStorage } from '@vueuse/core';

const props = defineProps<{
web3Account: string;
}>();

const { t } = useI18n();
const { onboardingChecklist } = useOnboardingChecklist();

const hideOnboarding = useStorage(
`snapshot.hideOnboarding.${props.web3Account.slice(0, 8).toLowerCase()}`,
false
);

const checkListCompleted = computed(() => {
let completed = true;

onboardingChecklist.value.forEach(checkListItem => {
if (!checkListItem.checked) {
completed = false;
return;
}
});

return completed;
});

watch(checkListCompleted, completed => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of auto hiding it on completion we could have a share on twitter button

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it could be a good idea. I think it should be also discussed with @bonustrack.

It will ask some UX thinking about how to handle the closing of this panel.

if (completed) {
setTimeout(() => {
hideOnboarding.value = true;
}, 2000);
}
});

function closeOnboarding() {
hideOnboarding.value = true;
}
</script>

<template>
<transition name="fade">
<div
v-if="!hideOnboarding"
class="border-skin-border bg-skin-block-bg text-white md:rounded-lg md:border"
>
<div class="p-4">
<div class="flex">
<div class="flex-grow">
<h3 class="text-bold">
samuveth marked this conversation as resolved.
Show resolved Hide resolved
{{ t('onboarding.title') }}
</h3>
<p>{{ t('onboarding.subtitle') }}</p>
</div>
<div class="mt-2">
<BaseButtonIcon @click="closeOnboarding">
<i-ho-x class="text-base" />
</BaseButtonIcon>
</div>
</div>

<ul class="mt-4 flex-col space-y-2">
<li
v-for="checkListItem in onboardingChecklist"
:key="checkListItem.name"
class="flex items-center"
>
<div class="w-4">
<i-ho-check
v-if="checkListItem.checked"
class="text-[17px] text-green"
/>
<div v-else class="h-3 w-3 border"></div>
</div>
{{ checkListItem.name }}
</li>
</ul>
<transition name="fade">
<div v-if="checkListCompleted" class="mt-4">
<h3>🎉 {{ t('onboarding.congratulations') }}</h3>
</div>
</transition>
</div>
</div>
</transition>
</template>
85 changes: 85 additions & 0 deletions src/composables/useOnboardingChecklist.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { computed, ref, watch } from 'vue';
import { useProfiles } from '@/composables/useProfiles';
import { useWeb3 } from '@/composables/useWeb3';
import { useI18n } from '@/composables/useI18n';
import { useApolloQuery } from '@/composables/useApolloQuery';
import { PROPOSALS_QUERY } from '@/helpers/queries';
import { ACTIVITY_VOTES_QUERY } from '@/helpers/queries';
import { useFollowSpace } from '@/composables/useFollowSpace';

const { t } = useI18n();

const { apolloQuery } = useApolloQuery();
const { profiles } = useProfiles();
const { web3Account } = useWeb3();
const { followingSpaces } = useFollowSpace();

const profile = computed(() => profiles.value[web3Account.value]);

const hasSetPublicProfile = computed(() =>
profile.value?.name ? true : false
);
const hasFollowedSpace = computed(() => followingSpaces.value.length);
const hasVoted = ref(false);
const hasCreatedProposal = ref(false);

const onboardingChecklist = ref([
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be hard for new users figuring out how to complete each list item. Maybe each item should be collapsable with additional links and information. @ManuBernard do you have a designer with who we could try some things out?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have followed the design provided by @bonustrack :
https://www.figma.com/file/GS2AS8G3YCYjeH1dFTFpIn/Snapshot---onboarding-section?node-id=0%3A1

Not sure how to move forward, maybe you should talk together and see if we want to improve this in this PR or create a new one later?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see. Yeah I wasn't aware of this design sorry for the confusion :)

{
checked: hasSetPublicProfile,
name: t('onboarding.profile')
},
{
checked: hasFollowedSpace,
name: t('onboarding.space')
},
{
checked: hasVoted,
name: t('onboarding.vote')
},
{
checked: hasCreatedProposal,
name: t('onboarding.proposal')
}
]);

async function loadVotes() {
const votes = await apolloQuery(
{
query: ACTIVITY_VOTES_QUERY,
variables: {
voter: profile.value.id
}
},
'votes'
);

hasVoted.value = votes.length ? true : false;
}

async function loadProposals() {
const proposals = await apolloQuery(
{
query: PROPOSALS_QUERY,
variables: {
first: 1,
skip: 0,
state: 'all',
author_in: [profile.value.id]
}
},
'proposals'
);

hasCreatedProposal.value = proposals.length ? true : false;
}

watch(profiles, p => {
if (profile.value) {
loadVotes();
loadProposals();
}
});

export function useOnboardingChecklist() {
return { onboardingChecklist };
}
9 changes: 9 additions & 0 deletions src/locales/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -612,5 +612,14 @@
"empty": "There are no assets in this contract"
},
"24hChange": "24h change"
},
"onboarding": {
"title": "New to Snapshot?",
"subtitle": "Here is a quick check list to get started:",
"profile": "Set your public profile",
"space": "Join your favorite space",
"vote": "Cast your first vote",
"proposal": "Create a proposal",
"congratulations": "Congratulations"
}
}
9 changes: 9 additions & 0 deletions src/locales/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -602,5 +602,14 @@
"empty": "There are no assets in this contract"
},
"24hChange": "24h change"
},
"onboarding": {
"title": "Nouveau sur Snapshot ?",
"subtitle": "Voici une checklist rapide pour commencer :",
"profile": "Définir votre profil public",
"space": "Rejoindre votre espace préféré",
"vote": "Votre premier vote",
"proposal": "Créer une proposition",
"congratulations": "Félicitations"
}
}
7 changes: 7 additions & 0 deletions src/views/TimelineView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,13 @@ function selectState(e) {
</template>
</BaseDropdown>
</div>

<TimelineOnboarding
v-if="web3Account"
class="mb-4"
:web3-account="web3Account"
/>

<div class="border-skin-border bg-skin-block-bg md:rounded-lg md:border">
<LoadingRow
v-if="
Expand Down