Skip to content

Commit

Permalink
feat(onboarding): Add setup steps to onboarding (#291)
Browse files Browse the repository at this point in the history
* feat: Add progress bar

* feat(onboarding): Start adding new onboarding pages with settings

* refactor(progress): Update progress bar colours

* feat: Add onboarding page and header components

* feat: Implement onboarding with settings pages

* chore(i18n): Update Hungarian translations
  • Loading branch information
Hanziness committed Dec 20, 2022
1 parent 2c26968 commit c8a56dc
Show file tree
Hide file tree
Showing 13 changed files with 248 additions and 55 deletions.
17 changes: 17 additions & 0 deletions components/base/uiProgress.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<script setup lang="ts">
const props = defineProps({
progress: {
type: Number,
default: 1.0,
validator: (value: number) => value >= 0.0 && value <= 1.0
}
})
</script>

<template>
<div class="rounded-full h-2 bg-surface-variant dark:bg-surface-darkvariant overflow-hidden ring-1 ring-inset ring-surface-onlight ring-opacity-10 dark:ring-surface-ondark dark:ring-opacity-25">
<div class="rounded-full bg-primary dark:bg-primary-dark w-full h-full transition-all" :style="{ 'translate': `-${100 - 100 * props.progress}% 0` }" />
</div>
</template>
15 changes: 15 additions & 0 deletions components/tutorial/onboarding/onboardingHeader.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script setup lang="ts">
const props = defineProps({
text: {
type: String,
required: true
}
})
</script>

<template>
<div class="px-6 py-3 flex gap-2 text-lg flex-row justify-center items-center">
<slot />
<span v-text="props.text" />
</div>
</template>
5 changes: 5 additions & 0 deletions components/tutorial/onboarding/onboardingPage.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<template>
<div class="overflow-auto px-6 py-2 flex flex-col justify-start items-center gap-2 -mx-6">
<slot />
</div>
</template>
32 changes: 32 additions & 0 deletions components/tutorial/onboarding/page0_welcome.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<script setup lang="ts">
import { LanguageIcon } from 'vue-tabler-icons'
import OnboardingPage from './onboardingPage.vue'
import OnboardingHeader from './onboardingHeader.vue'
import OptionGroup from '~~/components/base/optionGroup.vue'
import { useSettings } from '~~/stores/settings'
const settingsStore = useSettings()
</script>

<template>
<OnboardingPage>
<div class="py-3 flex gap-2 text-3xl flex-col md:flex-row justify-center items-center">
<img class="w-10" src="/favicon.svg" role="note">
<span v-text="$t('tutorials.onboarding.pages.0.title')" />
</div>

<div class="text-center -mt-3 mb-4" v-text="$t('tutorials.onboarding.pages.0.onboarding_invite')" />

<OnboardingHeader :text="$t('tutorials.onboarding.pages.0.heading')">
<LanguageIcon :size="42" />
</OnboardingHeader>

<OptionGroup
class="w-full"
:choices="$languages"
:value="settingsStore.lang"
:override-text="{ title: $languages, description: {} }"
@input="(newLang) => { settingsStore.lang = newLang }"
/>
</OnboardingPage>
</template>
16 changes: 16 additions & 0 deletions components/tutorial/onboarding/page1.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<script setup lang="ts">
import { ClockPlayIcon } from 'vue-tabler-icons'
import OptionGroup from '~~/components/base/optionGroup.vue'
import { useSettings } from '~~/stores/settings';
const settingsStore = useSettings()
</script>

<template>
<div class="h-96 flex flex-col justify-start items-center gap-2">
<div class="px-6 py-3 flex gap-2 text-lg flex-row justify-center items-center">
<ClockPlayIcon :size="42" />
Set your timer
</div>
</div>
</template>
21 changes: 21 additions & 0 deletions components/tutorial/onboarding/page1_timer.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<script setup lang="ts">
import { ClockPlayIcon } from 'vue-tabler-icons'
import OnboardingPage from './onboardingPage.vue'
import OnboardingHeader from './onboardingHeader.vue'
import OptionGroup from '~~/components/base/optionGroup.vue'
import { useSettings } from '~~/stores/settings'
const settingsStore = useSettings()
</script>

<template>
<OnboardingPage>
<OnboardingHeader :text="$t('tutorials.onboarding.pages.1.heading')">
<ClockPlayIcon :size="42" />
</OnboardingHeader>

<div class="text-center -mt-3 mb-4" v-text="$t('tutorials.onboarding.pages.1.text')" />

<OptionGroup :value="settingsStore.getActiveSchedulePreset" :choices="{ 'default': 'default', 'easy': 'easy', 'advanced': 'advanced', 'workaholic': 'workaholic' }" class="w-full" translation-key="timerpreset" @input="(selectedPreset) => { settingsStore.applyPreset(selectedPreset) }" />
</OnboardingPage>
</template>
28 changes: 28 additions & 0 deletions components/tutorial/onboarding/page2_theme.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<script setup lang="ts">
import { BrightnessDownIcon, DeviceWatchIcon } from 'vue-tabler-icons'
import OnboardingPage from './onboardingPage.vue'
import OnboardingHeader from './onboardingHeader.vue'
import OptionGroup from '~~/components/base/optionGroup.vue'
import { TimerType, useSettings } from '~~/stores/settings'
const settingsStore = useSettings()
const currentTheme = computed(() => settingsStore.visuals.darkMode ? 'dark' : 'light')
const currentTimer = computed(() => settingsStore.currentTimer as string)
</script>

<template>
<OnboardingPage>
<OnboardingHeader :text="$t('tutorials.onboarding.pages.2.theme.heading')">
<BrightnessDownIcon :size="42" />
</OnboardingHeader>

<OptionGroup :value="currentTheme" :choices="{ 'light': 'Light', 'dark': 'Dark' }" translation-key="tutorials.onboarding.pages.2.theme.options" class="w-full" @input="(newValue) => settingsStore.visuals.darkMode = (newValue === 'dark')" />

<OnboardingHeader :text="$t('tutorials.onboarding.pages.2.display.heading')">
<DeviceWatchIcon :size="42" />
</OnboardingHeader>

<OptionGroup :value="currentTimer" :choices="{ 'traditional': 'Traditional', 'approximate': 'Approximate', 'percentage': 'Percentage' }" translation-key="settings.values.currentTimer" class="w-full" @input="(newValue) => settingsStore.currentTimer = newValue as TimerType" />
</OnboardingPage>
</template>
9 changes: 9 additions & 0 deletions components/tutorial/onboarding/page2_timers.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script setup lang="ts">
import OptionGroup from '~~/components/base/optionGroup.vue'
</script>

<template>
<div class="h-96 flex flex-col">
<OptionGroup :choices="{'pomodoro': 'Pomodoro', 'short': 'Short times'}" :value="'short'" />
</div>
</template>
15 changes: 15 additions & 0 deletions components/tutorial/onboarding/page3_extras.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script setup lang="ts">
import { InfoCircleIcon } from 'vue-tabler-icons'
import OnboardingHeader from './onboardingHeader.vue'
import OnboardingPage from './onboardingPage.vue'
</script>

<template>
<OnboardingPage>
<OnboardingHeader :text="$t('tutorials.onboarding.pages.3.heading')">
<InfoCircleIcon :size="42" />
</OnboardingHeader>

<div class="text-center" v-text="$t('tutorials.onboarding.pages.3.text')" />
</OnboardingPage>
</template>
17 changes: 17 additions & 0 deletions components/tutorial/onboarding/page4_support.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<script setup lang="ts">
import { HeartHandshakeIcon } from 'vue-tabler-icons'
import OnboardingHeader from './onboardingHeader.vue'
import OnboardingPage from './onboardingPage.vue'
</script>

<template>
<OnboardingPage>
<OnboardingHeader :text="$t('tutorials.onboarding.pages.support.heading')">
<HeartHandshakeIcon :size="42" />
</OnboardingHeader>

<div class="text-center" v-text="$t('tutorials.onboarding.pages.support.text')" />

<div class="text-center" v-text="$t('tutorials.onboarding.pages.support.hint')" />
</OnboardingPage>
</template>
52 changes: 19 additions & 33 deletions components/tutorial/tutorialOnboarding.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
<script setup lang="ts">
import { ClockIcon, MugIcon, SettingsIcon, HeartIcon } from 'vue-tabler-icons'
import { ButtonImportance, ButtonTheme } from '../base/types/button'
import Button from '~~/components/base/uiButton.vue'
import PopupSheet from '@/components/base/popupSheet.vue'
import { ButtonImportance } from '../base/types/button';
import UiProgress from '~~/components/base/uiProgress.vue'
import OnboardingPage0 from '~~/components/tutorial/onboarding/page0_welcome.vue'
import OnboardingPage1 from '~~/components/tutorial/onboarding/page1_timer.vue'
import OnboardingPage2 from '~~/components/tutorial/onboarding/page2_theme.vue'
import OnboardingPage3 from '~~/components/tutorial/onboarding/page3_extras.vue'
import OnboardingPage4 from '~~/components/tutorial/onboarding/page4_support.vue'
const state = reactive({
page: 0
Expand All @@ -11,40 +17,19 @@ const state = reactive({

<template>
<popup-sheet class="fixed w-full max-w-2xl md:p-4" open>
<div class="flex flex-col px-6 pt-6 pb-6 transition bg-white shadow-lg md:pb-3 rounded-t-2xl md:rounded-2xl">
<div class="flex flex-col px-6 pt-6 pb-6 transition bg-surface-light dark:bg-surface-dark md:backdrop-blur-xl md:backdrop-brightness-200 md:backdrop-contrast-50 md:bg-opacity-80 md:dark:bg-opacity-80 shadow-lg md:pb-3 rounded-t-2xl md:rounded-2xl max-h-[90vh] md:h-auto md:min-h-[24rem] ring-1 ring-surface-onlight dark:ring-surface-ondark ring-opacity-20 dark:ring-opacity-20 ring-inset">
<UiProgress :progress="state.page / 4" class="mb-4 flex-shrink-0" />
<transition enter-active-class="transition" enter-from-class="translate-x-4 opacity-0" leave-to-class="-translate-x-4 opacity-0" leave-active-class="transition" mode="out-in">
<!-- Welcome screen -->
<div v-if="state.page === 0" key="page-index" class="flex flex-col">
<div class="flex flex-col items-center gap-2 mb-2 text-center">
<img src="/favicon.svg" class="p-3 bg-work bg-opacity-20 rounded-xl" width="72">
<h2 class="mt-2 text-xl font-bold uppercase" v-text="$t('tutorials.onboarding.pages.0.title')" />
<p v-text="$t('tutorials.onboarding.pages.0.text')" />
</div>
</div>
<div v-else-if="state.page === 1" key="tutorial-1" class="flex flex-col items-center gap-2 text-center">
<ClockIcon size="72" class="p-3 bg-work bg-opacity-20 rounded-xl" />
<h2 class="mt-2 text-xl font-bold uppercase text-work" v-text="$t('tutorials.onboarding.pages.1.title')" />
<p v-text="$t('tutorials.onboarding.pages.1.text')" />
</div>
<div v-else-if="state.page === 2" key="tutorial-2" class="flex flex-col items-center gap-2 text-center">
<MugIcon size="72" class="p-3 bg-shortpause bg-opacity-20 rounded-xl" />
<h2 class="mt-2 text-xl font-bold uppercase text-shortpause" v-text="$t('tutorials.onboarding.pages.2.title')" />
<p v-text="$t('tutorials.onboarding.pages.2.text')" />
</div>
<div v-else-if="state.page === 3" key="tutorial-3" class="flex flex-col items-center gap-2 text-center">
<SettingsIcon size="72" class="p-3 bg-longpause bg-opacity-20 rounded-xl" />
<h2 class="mt-2 text-xl font-bold uppercase text-longpause" v-text="$t('tutorials.onboarding.pages.3.title')" />
<p v-text="$t('tutorials.onboarding.pages.3.text')" />
</div>
<div v-else-if="state.page === 4" key="tutorial-4" class="flex flex-col items-center gap-2 text-center">
<HeartIcon size="72" class="p-3 text-black bg-amber-400 rounded-xl" />
<h2 class="mt-2 text-xl font-bold uppercase text-amber-500" v-text="$t('tutorials.onboarding.pages.support.title')" />
<p v-text="$t('tutorials.onboarding.pages.support.text')" />
</div>
<OnboardingPage0 v-if="state.page === 0" />
<OnboardingPage1 v-else-if="state.page === 1" />
<OnboardingPage2 v-else-if="state.page === 2" />
<OnboardingPage3 v-else-if="state.page === 3" />
<OnboardingPage4 v-else />
</transition>
<div class="flex-grow h-4" />
<div class="flex flex-col gap-2 mt-4 md:flex-row">
<Button class="flex-grow w-full" default-style :importance="ButtonImportance.Tonal" @click="$emit('close')">
<Button class="flex-grow w-full" default-style :theme="ButtonTheme.Primary" :importance="ButtonImportance.Outline" @click="$emit('close')">
{{ $t('tutorials.onboarding.buttons.close') }}
</Button>
<Button v-if="state.page === 0" class="flex-grow w-full" default-style :importance="ButtonImportance.Filled" @click="state.page = 1">
Expand All @@ -56,11 +41,12 @@ const state = reactive({
<Button
v-else-if="state.page === 4"
link
href="https://www.buymeacoffee.com/imreg?utm_source=anotherpomodoro&utm_medium=cta&utm_campaign=onboarding"
href="https://www.buymeacoffee.com/imreg?utm_source=focustide&utm_medium=cta&utm_campaign=onboarding"
target="_blank"
:importance="ButtonImportance.Filled"
:theme="ButtonTheme.Secondary"
class="flex-grow w-full"
bg-class="bg-amber-300 border-amber-300"
@click="$emit('close')"
>
{{ $t('tutorials.onboarding.buttons.support') }}
</Button>
Expand Down
36 changes: 26 additions & 10 deletions i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -468,28 +468,44 @@
"pages": {
"0": {
"title": "Welcome to FocusTide!",
"text": "Take a quick look around on how to use the app effectively."
"onboarding_invite": "Let's set you up for the first time. You can change everything in the settings menu later.",
"heading": "Choose your language"
},
"1": {
"title": "Follow the clock",
"text": "Work or take a break until the timer runs out. Then proceed with your next timer."
"heading": "Set your timers",
"text": "Choose a predefined setup for the lengths of your timers. You can customize them later in the settings."
},
"2": {
"title": "Rest regularly",
"text": "Every few breaks you get more time to rest. Make good use of it."
"theme": {
"heading": "Choose a theme",
"options": {
"_values": {
"light": "Light",
"dark": "Dark"
},
"_valueDescription": {
"light": "",
"dark": ""
}
}
},
"display": {
"heading": "Choose a display"
}
},
"3": {
"title": "Stay flexible",
"text": "Set your own timers, use the task list and notifications to your advantage. Check out the settings menu for more opportunities!"
"heading": "Remember",
"text": "You can always change these (and more) in the settings menu!"
},
"support": {
"title": "Support the project",
"text": "If this project helped you, consider inviting the author to a coffee. You can find the support button in the settings menu, too."
"heading": "Support the project",
"text": "If this project has helped you, consider buying the author a coffee.",
"hint": "You can also find support options in the Settings > About page."
}
},
"buttons": {
"close": "Close",
"start": "Start tutorial",
"start": "Start setup",
"next": "Next",
"support": "Support the project"
}
Expand Down
40 changes: 28 additions & 12 deletions i18n/hu.json
Original file line number Diff line number Diff line change
Expand Up @@ -467,31 +467,47 @@
"onboarding": {
"pages": {
"0": {
"title": "Üdvözöl az FocusTide!",
"text": "Tekints meg egy gyorstalpalót az alkalmazás hatékony használatáról."
"title": "Üdvözöl a FocusTide!",
"onboarding_invite": "Itt beállíthatod az alkalmazást az első használatra.",
"heading": "Válassz nyelvet"
},
"1": {
"title": "Kövesd az időzítőt",
"text": "Dolgozz vagy pihenj, amíg le nem jár az időzítő, majd folytasd a következővel."
"heading": "Válassz időzítőt",
"text": "Válassz az előre beállított időzítők közül. Az egyes időzítők hosszát később a beállításokban is megváltoztathatod."
},
"2": {
"title": "Pihenj rendszeresen",
"text": "Minden néhányadik szünet hosszabb. Használd ki."
"theme": {
"heading": "Válassz témát",
"options": {
"_values": {
"light": "Világos",
"dark": "Sötét"
},
"_valueDescription": {
"light": "",
"dark": ""
}
}
},
"display": {
"heading": "Válassz megjelenést"
}
},
"3": {
"title": "Maradj rugalmas",
"text": "Állítsd be a saját időzítőidet, használd a feladatlistát és az értesítéseket, hogy még hatékonyabb lehess. Nézd meg a beállításokat pár ötletért!"
"heading": "Ne feledd",
"text": "Ezeket (és sok mást is) megváltoztathatsz a beállításokban."
},
"support": {
"title": "Támogasd a projektet",
"text": "Ha segít neked ez a projekt, hívd meg a fejlesztőt egy kávéra. A támogatás gombot a beállításokban is megtalálod."
"heading": "Támogasd a projektet",
"text": "Ha segít neked ez az alkalmazás, hívd meg a fejlesztőt egy kávéra.",
"hint": "Később a Beállítások > Névjegy lapon is megtalálod a támogatási lehetőségeket."
}
},
"buttons": {
"close": "Bezárás",
"start": "Gyorstalpaló indítása",
"start": "Beállítás",
"next": "Következő",
"support": "Támogasd a projektet"
"support": "Projekt támogatása"
}
}
}
Expand Down

0 comments on commit c8a56dc

Please sign in to comment.