Browse Source

add basic page-by-page onboarding help

master
Trent Larson 3 weeks ago
parent
commit
1bc58ae928
  1. 4
      package-lock.json
  2. 230
      src/components/OnboardingDialog.vue
  3. 1
      src/db/tables/settings.ts
  4. 8
      src/libs/util.ts
  5. 11
      src/views/DiscoverView.vue
  6. 4
      src/views/HelpOnboardingView.vue
  7. 20
      src/views/HelpView.vue
  8. 21
      src/views/HomeView.vue
  9. 17
      src/views/ProjectsView.vue

4
package-lock.json

@ -1,12 +1,12 @@
{ {
"name": "TimeSafari", "name": "TimeSafari",
"version": "0.3.29", "version": "0.3.30-beta",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "TimeSafari", "name": "TimeSafari",
"version": "0.3.29", "version": "0.3.30-beta",
"dependencies": { "dependencies": {
"@dicebear/collection": "^5.4.1", "@dicebear/collection": "^5.4.1",
"@dicebear/core": "^5.4.1", "@dicebear/core": "^5.4.1",

230
src/components/OnboardingDialog.vue

@ -0,0 +1,230 @@
<!-- similar to ContactNameDialog -->
<template>
<div v-if="visible" class="dialog-overlay">
<div v-if="page === OnboardPage.Home" class="dialog">
<h1 class="text-xl font-bold text-center mb-4">
Welcome to Time Safari
<br />
Showcasing Gratitude & Magnifing Time
</h1>
<p v-if="isRegistered" class="mt-4">
You can now log things that you've received or witnessed:
<span v-if="numContacts > 0">
click on {{ firstContactName }}'s name or
</span>
click on "Unnamed" to express your appreciation for... whatever -- like
thanks for showing you all these fascinating stories of
<em>gratitude</em>.
</p>
<p v-else class="mt-4">
The feed underneath this pop-up shows the latest gifts recognized by
others. Once someone registers you, you'll be able to log your
appreciation, too.
</p>
<p class="mt-4 flex items-center">
The more you illuminate cool things people are doing, the more you
attract people to work together with you.
</p>
<p class="mt-4 flex items-center">
The
<fa
icon="house-chimney"
class="ml-1 mr-1 text-lg text-white bg-slate-400 px-2 py-2 rounded"
/>
button below brings you back to this feed screen.
</p>
<div class="mt-8">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
<button
type="button"
class="block w-full text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md mb-2"
@click="onClickClose(true)"
>
That's enough help, thanks.
</button>
<button
type="button"
class="block w-full text-center text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md mb-2"
@click="$router.push({ name: 'discover' })"
>
Show me more!
</button>
</div>
</div>
<p class="mt-4 flex items-center">
To see these instructions and more, click above on
<span
class="ml-1 mr-1 text-xs uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-1 rounded-md"
>
Help
</span>
</p>
</div>
<div v-if="page === OnboardPage.Discover" class="dialog">
<h1 class="text-xl font-bold text-center mb-4">
Find Interesting Events & Projects
</h1>
<p>
It turns out that people in groups have more to be grateful for and get
more accomplished, so here you'll find others who are doing interesting
things. Some may be in your neighborhood. Search for a topic, or search
around your location under "Nearby".
</p>
<p class="mt-4 flex items-center">
The
<fa
icon="magnifying-glass"
class="ml-1 mr-1 text-lg text-white bg-slate-400 px-2 py-2 rounded"
/>
button below brings you to this discovery screen.
</p>
<div class="mt-8">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
<button
type="button"
class="block w-full text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md mb-2"
@click="onClickClose(true)"
>
No more help needed.
</button>
<button
type="button"
class="block w-full text-center text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md mb-2"
@click="$router.push({ name: 'projects' })"
>
Show me even more.
</button>
</div>
</div>
</div>
<div v-if="page === OnboardPage.Create" class="dialog">
<h1 class="text-xl font-bold text-center mb-4">
Fish for Others with Projects of Your Own
</h1>
<p>
Now you can take a turn: throw out projects of your own... anything
you'd like to see happen. If your first idea doesn't attract any fish,
try, try again... and let others know that this is a good place to find
help.
</p>
<p class="mt-4 flex items-center">
The
<fa
icon="hand"
class="ml-1 mr-1 text-lg text-white bg-slate-400 px-2 py-2 rounded"
/>
button below brings you here to your ideas.
</p>
<div class="mt-8">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
<button
type="button"
class="block w-full text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md mb-2"
@click="onClickClose()"
>
Let's go!
</button>
<button
type="button"
class="block w-full text-center text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md mb-2"
@click="$router.push({ name: 'help' })"
>
Take me to more Help.
</button>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from "vue-facing-decorator";
import { NotificationIface } from "@/constants/app";
import {db, retrieveSettingsForActiveAccount, updateAccountSettings} from "@/db/index";
import { OnboardPage } from "@/libs/util";
@Component({
computed: {
OnboardPage() {
return OnboardPage;
},
},
components: { OnboardPage },
})
export default class OnboardingDialog extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void;
activeDid = "";
firstContactName = null;
givenName = "";
isRegistered = false;
numContacts = 0;
page = OnboardPage.Home;
visible = false;
async open(page: OnboardPage) {
this.page = page;
const settings = await retrieveSettingsForActiveAccount();
this.activeDid = settings.activeDid || "";
this.isRegistered = !!settings.isRegistered;
const contacts = await db.contacts.toArray();
this.numContacts = contacts.length;
if (this.numContacts > 0) {
this.firstContactName = contacts[0].name;
}
this.visible = true;
if (this.page === OnboardPage.Create) {
// we'll assume that they've been through all the other pages
await updateAccountSettings(this.activeDid, {
finishedOnboarding: true,
});
}
}
async onClickClose(done?: boolean) {
this.visible = false;
if (done) {
await updateAccountSettings(this.activeDid, {
finishedOnboarding: true,
});
}
}
}
</script>
<style>
.dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
padding: 1.5rem;
}
.dialog {
background-color: white;
padding: 1rem;
border-radius: 0.5rem;
width: 100%;
max-width: 500px;
}
</style>

1
src/db/tables/settings.ts

@ -24,6 +24,7 @@ export type Settings = {
filterFeedByNearby?: boolean; // filter by nearby filterFeedByNearby?: boolean; // filter by nearby
filterFeedByVisible?: boolean; // filter by visible users ie. anyone not hidden filterFeedByVisible?: boolean; // filter by visible users ie. anyone not hidden
finishedOnboarding?: boolean; // the user has completed the onboarding process
firstName?: string; // user's full name, may be null if unwanted for a particular account firstName?: string; // user's full name, may be null if unwanted for a particular account
hideRegisterPromptOnNewContact?: boolean; hideRegisterPromptOnNewContact?: boolean;

8
src/libs/util.ts

@ -32,6 +32,14 @@ export interface GiverReceiverInputInfo {
name?: string; name?: string;
} }
export enum OnboardPage {
Home = "HOME",
Discover = "DISCOVER",
Create = "CREATE",
Contact = "CONTACT",
Account = "ACCOUNT",
}
export const PRIVACY_MESSAGE = export const PRIVACY_MESSAGE =
"The data you send will be visible to the world -- except: your IDs and the IDs of anyone you tag will stay private, only visible to them and others you explicitly allow."; "The data you send will be visible to the world -- except: your IDs and the IDs of anyone you tag will stay private, only visible to them and others you explicitly allow.";
export const SHARED_PHOTO_BASE64_KEY = "shared-photo-base64"; export const SHARED_PHOTO_BASE64_KEY = "shared-photo-base64";

11
src/views/DiscoverView.vue

@ -9,6 +9,8 @@
Discover Projects Discover Projects
</h1> </h1>
<OnboardingDialog ref="onboardingDialog" />
<!-- Quick Search --> <!-- Quick Search -->
<div <div
id="QuickSearch" id="QuickSearch"
@ -144,16 +146,19 @@ import { Router } from "vue-router";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
import InfiniteScroll from "@/components/InfiniteScroll.vue"; import InfiniteScroll from "@/components/InfiniteScroll.vue";
import ProjectIcon from "@/components/ProjectIcon.vue"; import ProjectIcon from "@/components/ProjectIcon.vue";
import OnboardingDialog from "@/components/OnboardingDialog.vue";
import TopMessage from "@/components/TopMessage.vue"; import TopMessage from "@/components/TopMessage.vue";
import { NotificationIface } from "@/constants/app"; import { NotificationIface } from "@/constants/app";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index"; import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import { BoundingBox } from "@/db/tables/settings"; import { BoundingBox } from "@/db/tables/settings";
import { didInfo, getHeaders, PlanData } from "@/libs/endorserServer"; import { didInfo, getHeaders, PlanData } from "@/libs/endorserServer";
import { OnboardPage } from "@/libs/util";
@Component({ @Component({
components: { components: {
InfiniteScroll, InfiniteScroll,
OnboardingDialog,
ProjectIcon, ProjectIcon,
QuickNav, QuickNav,
TopMessage, TopMessage,
@ -192,6 +197,12 @@ export default class DiscoverView extends Vue {
this.searchTerms = (this.$route as Router).query["searchText"] || ""; this.searchTerms = (this.$route as Router).query["searchText"] || "";
if (!settings.finishedOnboarding) {
(this.$refs.onboardingDialog as OnboardingDialog).open(
OnboardPage.Discover,
);
}
if (this.searchBox) { if (this.searchBox) {
await this.searchLocal(); await this.searchLocal();
} else { } else {

4
src/views/HelpOnboardingView.vue

@ -29,10 +29,10 @@
</p> </p>
<h1 class="mt-4 font-bold text-xl">Next Steps</h1> <h1 class="mt-4 font-bold text-xl">Next Steps</h1>
Although not totally necessary, these are important to understand. Although not totally necessary, backups are important to understand.
<div class="ml-4"> <div class="ml-4">
<h1 class="font-bold text-xl">Backups</h1> <h1 class="font-bold text-xl">Without a backup, you can lose data.</h1>
<div> <div>
<p> <p>
Exporting backups (from the Account <fa icon="circle-user" /> screen) Exporting backups (from the Account <fa icon="circle-user" /> screen)

20
src/views/HelpView.vue

@ -27,6 +27,14 @@
This app focuses on gifts & gratitude, using them to build cool things together with your network. This app focuses on gifts & gratitude, using them to build cool things together with your network.
</p> </p>
<p class="ml-4">
If you'd like to see the page-by-page help,
<span
@click="unsetFinishedOnboarding()"
class="text-blue-500 cursor-pointer"
>click here</span>.
</p>
<h2 class="text-xl font-semibold">What is the idea here?</h2> <h2 class="text-xl font-semibold">What is the idea here?</h2>
<p> <p>
We are building networks of people who want to grow good society from the ground up, using modern We are building networks of people who want to grow good society from the ground up, using modern
@ -548,11 +556,13 @@
<script lang="ts"> <script lang="ts">
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { Router } from "vue-router";
import { useClipboard } from "@vueuse/core"; import { useClipboard } from "@vueuse/core";
import * as Package from "../../package.json"; import * as Package from "../../package.json";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
import { NotificationIface } from "@/constants/app"; import { NotificationIface } from "@/constants/app";
import {retrieveSettingsForActiveAccount, updateAccountSettings} from "@/db/index";
@Component({ components: { QuickNav } }) @Component({ components: { QuickNav } })
export default class Help extends Vue { export default class Help extends Vue {
@ -575,5 +585,15 @@ export default class Help extends Vue {
.copy(text) .copy(text)
.then(() => setTimeout(fn, 2000)); .then(() => setTimeout(fn, 2000));
} }
async unsetFinishedOnboarding() {
const settings = await retrieveSettingsForActiveAccount();
if (settings.activeDid) {
await updateAccountSettings(settings.activeDid || "", {
finishedOnboarding: false,
});
}
(this.$router as Router).push({ name: "home" });
}
} }
</script> </script>

21
src/views/HomeView.vue

@ -8,6 +8,8 @@
{{ AppString.APP_NAME }} {{ AppString.APP_NAME }}
</h1> </h1>
<OnboardingDialog ref="onboardingDialog" />
<!-- prompt to install notifications with notificationsSupported, which we're making an advanced feature --> <!-- prompt to install notifications with notificationsSupported, which we're making an advanced feature -->
<div class="mb-8 mt-8"> <div class="mb-8 mt-8">
<div <div
@ -326,6 +328,7 @@ import GiftedDialog from "@/components/GiftedDialog.vue";
import GiftedPrompts from "@/components/GiftedPrompts.vue"; import GiftedPrompts from "@/components/GiftedPrompts.vue";
import FeedFilters from "@/components/FeedFilters.vue"; import FeedFilters from "@/components/FeedFilters.vue";
import InfiniteScroll from "@/components/InfiniteScroll.vue"; import InfiniteScroll from "@/components/InfiniteScroll.vue";
import OnboardingDialog from "@/components/OnboardingDialog.vue";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
import TopMessage from "@/components/TopMessage.vue"; import TopMessage from "@/components/TopMessage.vue";
import UserNameDialog from "@/components/UserNameDialog.vue"; import UserNameDialog from "@/components/UserNameDialog.vue";
@ -335,10 +338,10 @@ import {
PASSKEYS_ENABLED, PASSKEYS_ENABLED,
} from "@/constants/app"; } from "@/constants/app";
import { import {
db,
accountsDB, accountsDB,
updateAccountSettings, db,
retrieveSettingsForActiveAccount, retrieveSettingsForActiveAccount,
updateAccountSettings,
} from "@/db/index"; } from "@/db/index";
import { Contact } from "@/db/tables/contacts"; import { Contact } from "@/db/tables/contacts";
import { import {
@ -358,6 +361,7 @@ import {
import { import {
generateSaveAndActivateIdentity, generateSaveAndActivateIdentity,
GiverReceiverInputInfo, GiverReceiverInputInfo,
OnboardPage,
registerSaveAndActivatePasskey, registerSaveAndActivatePasskey,
} from "@/libs/util"; } from "@/libs/util";
@ -384,12 +388,13 @@ interface GiveRecordWithContactInfo extends GiveSummaryRecord {
}, },
}, },
components: { components: {
EntityIcon,
FeedFilters,
GiftedDialog, GiftedDialog,
GiftedPrompts, GiftedPrompts,
FeedFilters,
QuickNav,
EntityIcon,
InfiniteScroll, InfiniteScroll,
OnboardingDialog,
QuickNav,
TopMessage, TopMessage,
UserNameDialog, UserNameDialog,
}, },
@ -448,6 +453,12 @@ export default class HomeView extends Vue {
this.isAnyFeedFilterOn = isAnyFeedFilterOn(settings); this.isAnyFeedFilterOn = isAnyFeedFilterOn(settings);
if (!settings.finishedOnboarding) {
(this.$refs.onboardingDialog as OnboardingDialog).open(
OnboardPage.Home,
);
}
// someone may have have registered after sharing contact info, so recheck // someone may have have registered after sharing contact info, so recheck
if (!this.isRegistered && this.activeDid) { if (!this.isRegistered && this.activeDid) {
try { try {

17
src/views/ProjectsView.vue

@ -6,6 +6,8 @@
<!-- Heading --> <!-- Heading -->
<h1 id="ViewHeading" class="text-4xl text-center font-light">Your Ideas</h1> <h1 id="ViewHeading" class="text-4xl text-center font-light">Your Ideas</h1>
<OnboardingDialog ref="onboardingDialog" />
<!-- Result Tabs --> <!-- Result Tabs -->
<div class="text-center text-slate-500 border-b border-slate-300 mt-8"> <div class="text-center text-slate-500 border-b border-slate-300 mt-8">
<ul class="flex flex-wrap justify-center gap-4 -mb-px"> <ul class="flex flex-wrap justify-center gap-4 -mb-px">
@ -261,10 +263,14 @@ import { Router } from "vue-router";
import { NotificationIface } from "@/constants/app"; import { NotificationIface } from "@/constants/app";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index"; import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index";
import * as libsUtil from "@/libs/util"; import * as libsUtil from "@/libs/util";
import EntityIcon from "@/components/EntityIcon.vue";
import InfiniteScroll from "@/components/InfiniteScroll.vue"; import InfiniteScroll from "@/components/InfiniteScroll.vue";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
import OnboardingDialog from "@/components/OnboardingDialog.vue";
import ProjectIcon from "@/components/ProjectIcon.vue"; import ProjectIcon from "@/components/ProjectIcon.vue";
import TopMessage from "@/components/TopMessage.vue"; import TopMessage from "@/components/TopMessage.vue";
import UserNameDialog from "@/components/UserNameDialog.vue";
import { Contact } from "@/db/tables/contacts";
import { import {
didInfo, didInfo,
getHeaders, getHeaders,
@ -272,15 +278,14 @@ import {
OfferSummaryRecord, OfferSummaryRecord,
PlanData, PlanData,
} from "@/libs/endorserServer"; } from "@/libs/endorserServer";
import EntityIcon from "@/components/EntityIcon.vue"; import { OnboardPage } from "@/libs/util";
import { Contact } from "@/db/tables/contacts";
import UserNameDialog from "@/components/UserNameDialog.vue";
@Component({ @Component({
components: { components: {
EntityIcon, EntityIcon,
InfiniteScroll, InfiniteScroll,
QuickNav, QuickNav,
OnboardingDialog,
ProjectIcon, ProjectIcon,
TopMessage, TopMessage,
UserNameDialog, UserNameDialog,
@ -325,6 +330,12 @@ export default class ProjectsView extends Vue {
const allAccounts = await accountsDB.accounts.toArray(); const allAccounts = await accountsDB.accounts.toArray();
this.allMyDids = allAccounts.map((acc) => acc.did); this.allMyDids = allAccounts.map((acc) => acc.did);
if (!settings.finishedOnboarding) {
(this.$refs.onboardingDialog as OnboardingDialog).open(
OnboardPage.Create,
);
}
if (allAccounts.length === 0) { if (allAccounts.length === 0) {
console.error("No accounts found."); console.error("No accounts found.");
this.errNote("You need an identifier to load your projects."); this.errNote("You need an identifier to load your projects.");

Loading…
Cancel
Save