forked from jsnbuchanan/crowd-funder-for-time-pwa
add a prompt for things for which to express gratitude
This commit is contained in:
@@ -1,9 +1,14 @@
|
|||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
|
|
||||||
|
- fix timeSafari.org cert renewals
|
||||||
- anchor hash into BTC
|
- anchor hash into BTC
|
||||||
|
- add more detail on TimeSafari.org
|
||||||
|
- add button to front page to prompt for ideas for gratitude
|
||||||
|
- checkboxes: show in order, show non-person-oriented messages, show only contacts, show only projects
|
||||||
|
- show previous on "Your" screen
|
||||||
|
- allow user to add a time when they want their daily notification
|
||||||
- prompt for the name directly when they visit the QR scan page
|
- prompt for the name directly when they visit the QR scan page
|
||||||
- bug - user on new phone did not prompt him to install
|
|
||||||
- image on give
|
- image on give
|
||||||
- Show a camera to take a picture
|
- Show a camera to take a picture
|
||||||
- Scale the image to a reasonable size
|
- Scale the image to a reasonable size
|
||||||
|
|||||||
214
src/components/GiftedPrompts.vue
Normal file
214
src/components/GiftedPrompts.vue
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="visible" class="dialog-overlay">
|
||||||
|
<div class="dialog">
|
||||||
|
<h1 class="text-xl font-bold text-center mb-4">Here's one:</h1>
|
||||||
|
<span class="flex justify-between h-24">
|
||||||
|
<span
|
||||||
|
class="rounded-l border border-slate-400 bg-slate-200 h-full px-4 py-2 flex h-screen"
|
||||||
|
@click="prevIdea()"
|
||||||
|
>
|
||||||
|
<fa icon="chevron-left" class="m-auto" />
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="m-2">
|
||||||
|
<span v-if="currentIdeaIndex < IDEAS.length">
|
||||||
|
<p class="text-center text-lg font-bold">
|
||||||
|
{{ IDEAS[currentIdeaIndex] }}
|
||||||
|
</p>
|
||||||
|
</span>
|
||||||
|
<div v-if="currentIdeaIndex == IDEAS.length + 0">
|
||||||
|
<p class="text-center text-lg font-bold">
|
||||||
|
Did this person or someone near them do something – maybe a
|
||||||
|
while ago?
|
||||||
|
<br />
|
||||||
|
<span v-if="currentContact?.name == null" class="text-orange-500">
|
||||||
|
That's all your contacts.
|
||||||
|
</span>
|
||||||
|
<span v-else>{{
|
||||||
|
currentContact.name || AppString.NO_CONTACT_NAME
|
||||||
|
}}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span
|
||||||
|
class="rounded-r border border-slate-400 bg-slate-200 h-full px-4 py-2 flex h-screen"
|
||||||
|
@click="nextIdea()"
|
||||||
|
>
|
||||||
|
<fa icon="chevron-right" class="m-auto" />
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mt-4"
|
||||||
|
@click="cancel"
|
||||||
|
>
|
||||||
|
That's it.
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Vue, Component } from "vue-facing-decorator";
|
||||||
|
|
||||||
|
import { AppString } from "@/constants/app";
|
||||||
|
import { db } from "@/db/index";
|
||||||
|
import { Contact } from "@/db/tables/contacts";
|
||||||
|
|
||||||
|
interface Notification {
|
||||||
|
group: string;
|
||||||
|
type: string;
|
||||||
|
title: string;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class GivenPrompts extends Vue {
|
||||||
|
$notify!: (notification: Notification, timeout?: number) => void;
|
||||||
|
|
||||||
|
IDEAS = [
|
||||||
|
"Did anyone fix food for you?",
|
||||||
|
"Did a family member do something for you?",
|
||||||
|
"Did anyone give you a compliment?",
|
||||||
|
"Did you see anyone give to someone else?",
|
||||||
|
"Did anyone give you a good laugh?",
|
||||||
|
"Do you recall anything that was given to you while you were young?",
|
||||||
|
"Do you know of a way an ancestor contributed to your life?",
|
||||||
|
];
|
||||||
|
OTHER_PROMPTS = 1;
|
||||||
|
CONTACT_PROMPT_INDEX = this.IDEAS.length; // expected after other prompts
|
||||||
|
|
||||||
|
currentContact: Contact | undefined = undefined;
|
||||||
|
currentIdeaIndex = 0;
|
||||||
|
numContacts = 0;
|
||||||
|
shownContactDbIndices: number[] = [];
|
||||||
|
visible = false;
|
||||||
|
|
||||||
|
AppString = AppString;
|
||||||
|
|
||||||
|
async open() {
|
||||||
|
this.visible = true;
|
||||||
|
|
||||||
|
await db.open();
|
||||||
|
this.numContacts = await db.contacts.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
// close the dialog but don't change values (just in case some actions are added later)
|
||||||
|
this.visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the next idea.
|
||||||
|
* If it is a contact prompt, loop through.
|
||||||
|
*/
|
||||||
|
async nextIdea() {
|
||||||
|
// if we're incrementing to the contact prompt
|
||||||
|
// or if we're at the contact prompt and there was a previous contact...
|
||||||
|
if (
|
||||||
|
this.currentIdeaIndex == this.CONTACT_PROMPT_INDEX - 1 ||
|
||||||
|
(this.currentIdeaIndex == this.CONTACT_PROMPT_INDEX &&
|
||||||
|
this.currentContact)
|
||||||
|
) {
|
||||||
|
this.currentIdeaIndex = this.CONTACT_PROMPT_INDEX;
|
||||||
|
this.findNextUnshownContact();
|
||||||
|
} else {
|
||||||
|
// we're not at the contact prompt (or we ran out), so increment the idea index
|
||||||
|
this.currentIdeaIndex =
|
||||||
|
(this.currentIdeaIndex + 1) % (this.IDEAS.length + this.OTHER_PROMPTS);
|
||||||
|
// ... and clear out any other prompt info
|
||||||
|
this.currentContact = undefined;
|
||||||
|
this.shownContactDbIndices = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prevIdea() {
|
||||||
|
if (
|
||||||
|
this.currentIdeaIndex ==
|
||||||
|
(this.CONTACT_PROMPT_INDEX + 1) %
|
||||||
|
(this.IDEAS.length + this.OTHER_PROMPTS) ||
|
||||||
|
(this.currentIdeaIndex == this.CONTACT_PROMPT_INDEX &&
|
||||||
|
this.currentContact)
|
||||||
|
) {
|
||||||
|
this.currentIdeaIndex = this.CONTACT_PROMPT_INDEX;
|
||||||
|
this.findNextUnshownContact();
|
||||||
|
} else {
|
||||||
|
// we're not at the contact prompt (or we ran out), so increment the idea index
|
||||||
|
this.currentIdeaIndex--;
|
||||||
|
if (this.currentIdeaIndex < 0) {
|
||||||
|
this.currentIdeaIndex = this.IDEAS.length - 1 + this.OTHER_PROMPTS;
|
||||||
|
}
|
||||||
|
// ... and clear out any other prompt info
|
||||||
|
this.currentContact = undefined;
|
||||||
|
this.shownContactDbIndices = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async findNextUnshownContact() {
|
||||||
|
// get a random contact
|
||||||
|
if (this.shownContactDbIndices.length === this.numContacts) {
|
||||||
|
// no more contacts to show
|
||||||
|
this.currentContact = undefined;
|
||||||
|
} else {
|
||||||
|
// get a random contact that hasn't been shown yet
|
||||||
|
let someContactDbIndex = Math.floor(Math.random() * this.numContacts);
|
||||||
|
// and guarantee that one is found by walking past shown contacts
|
||||||
|
let shownContactIndex =
|
||||||
|
this.shownContactDbIndices.indexOf(someContactDbIndex);
|
||||||
|
while (shownContactIndex !== -1) {
|
||||||
|
// increment both indices until we find a spot where "shown" skips a spot
|
||||||
|
shownContactIndex = (shownContactIndex + 1) % this.numContacts;
|
||||||
|
someContactDbIndex = (someContactDbIndex + 1) % this.numContacts;
|
||||||
|
if (
|
||||||
|
this.shownContactDbIndices[shownContactIndex] !== someContactDbIndex
|
||||||
|
) {
|
||||||
|
// we found a contact that hasn't been shown yet
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// continue
|
||||||
|
// ... and there must be at least one because shownContactDbIndices length < numContacts
|
||||||
|
}
|
||||||
|
this.shownContactDbIndices.push(someContactDbIndex);
|
||||||
|
this.shownContactDbIndices.sort();
|
||||||
|
|
||||||
|
// get the contact at that offset
|
||||||
|
await db.open();
|
||||||
|
this.currentContact = await db.contacts
|
||||||
|
.offset(someContactDbIndex)
|
||||||
|
.first();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
this.currentContact = undefined;
|
||||||
|
this.currentIdeaIndex = 0;
|
||||||
|
this.numContacts = 0;
|
||||||
|
this.shownContactDbIndices = [];
|
||||||
|
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</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>
|
||||||
@@ -11,6 +11,8 @@ export enum AppString {
|
|||||||
PROD_PUSH_SERVER = "https://timesafari.app",
|
PROD_PUSH_SERVER = "https://timesafari.app",
|
||||||
TEST1_PUSH_SERVER = "https://test.timesafari.app",
|
TEST1_PUSH_SERVER = "https://test.timesafari.app",
|
||||||
TEST2_PUSH_SERVER = "https://timesafari-pwa.anomalistlabs.com",
|
TEST2_PUSH_SERVER = "https://timesafari-pwa.anomalistlabs.com",
|
||||||
|
|
||||||
|
NO_CONTACT_NAME = "(no name)",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_ENDORSER_API_SERVER = AppString.TEST_ENDORSER_API_SERVER;
|
export const DEFAULT_ENDORSER_API_SERVER = AppString.TEST_ENDORSER_API_SERVER;
|
||||||
|
|||||||
@@ -98,7 +98,7 @@
|
|||||||
class="inline-block align-text-bottom border border-slate-300 rounded"
|
class="inline-block align-text-bottom border border-slate-300 rounded"
|
||||||
@click="showLargeIdenticon = contact.did"
|
@click="showLargeIdenticon = contact.did"
|
||||||
></EntityIcon>
|
></EntityIcon>
|
||||||
{{ contact.name || "(no name)" }}
|
{{ contact.name || AppString.NO_CONTACT_NAME }}
|
||||||
<button
|
<button
|
||||||
class="text-sm uppercase bg-slate-500 text-white px-1 rounded-md"
|
class="text-sm uppercase bg-slate-500 text-white px-1 rounded-md"
|
||||||
@click="
|
@click="
|
||||||
@@ -289,7 +289,7 @@ import * as R from "ramda";
|
|||||||
import { IIdentifier } from "@veramo/core";
|
import { IIdentifier } from "@veramo/core";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
|
|
||||||
import { NotificationIface } from "@/constants/app";
|
import { AppString, NotificationIface } from "@/constants/app";
|
||||||
import { accountsDB, db } from "@/db/index";
|
import { accountsDB, db } from "@/db/index";
|
||||||
import { Contact } from "@/db/tables/contacts";
|
import { Contact } from "@/db/tables/contacts";
|
||||||
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
||||||
@@ -349,6 +349,7 @@ export default class ContactsView extends Vue {
|
|||||||
showGiveConfirmed = true;
|
showGiveConfirmed = true;
|
||||||
showLargeIdenticon = "";
|
showLargeIdenticon = "";
|
||||||
|
|
||||||
|
AppString = AppString;
|
||||||
libsUtil = libsUtil;
|
libsUtil = libsUtil;
|
||||||
|
|
||||||
async created() {
|
async created() {
|
||||||
|
|||||||
@@ -104,7 +104,15 @@
|
|||||||
|
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<!-- activeDid && isRegistered -->
|
<!-- activeDid && isRegistered -->
|
||||||
<h2 class="text-xl font-bold mb-4">Record Something Given</h2>
|
<div class="flex justify-between mb-4">
|
||||||
|
<h2 class="text-xl font-bold">Record Something Given</h2>
|
||||||
|
<button
|
||||||
|
@click="openGiftedPrompts()"
|
||||||
|
class="block text-center text-md font-bold bg-blue-500 text-white px-2 py-3 rounded-md"
|
||||||
|
>
|
||||||
|
If you want an idea...
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ul class="grid grid-cols-4 gap-x-3 gap-y-5 text-center mb-5">
|
<ul class="grid grid-cols-4 gap-x-3 gap-y-5 text-center mb-5">
|
||||||
<li @click="openDialog()">
|
<li @click="openDialog()">
|
||||||
@@ -160,6 +168,7 @@
|
|||||||
message="Received from"
|
message="Received from"
|
||||||
showGivenToUser="true"
|
showGivenToUser="true"
|
||||||
/>
|
/>
|
||||||
|
<GiftedPrompts ref="giftedPrompts" />
|
||||||
|
|
||||||
<!-- Results List -->
|
<!-- Results List -->
|
||||||
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
|
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
|
||||||
@@ -226,6 +235,7 @@ import { Component, Vue } from "vue-facing-decorator";
|
|||||||
|
|
||||||
import EntityIcon from "@/components/EntityIcon.vue";
|
import EntityIcon from "@/components/EntityIcon.vue";
|
||||||
import GiftedDialog from "@/components/GiftedDialog.vue";
|
import GiftedDialog from "@/components/GiftedDialog.vue";
|
||||||
|
import GiftedPrompts from "@/components/GiftedPrompts.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 TopMessage from "@/components/TopMessage.vue";
|
import TopMessage from "@/components/TopMessage.vue";
|
||||||
@@ -252,6 +262,7 @@ interface Notification {
|
|||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
GiftedDialog,
|
GiftedDialog,
|
||||||
|
GiftedPrompts,
|
||||||
QuickNav,
|
QuickNav,
|
||||||
EntityIcon,
|
EntityIcon,
|
||||||
InfiniteScroll,
|
InfiniteScroll,
|
||||||
@@ -500,5 +511,9 @@ export default class HomeView extends Vue {
|
|||||||
openDialog(giver: GiverInputInfo) {
|
openDialog(giver: GiverInputInfo) {
|
||||||
(this.$refs.customDialog as GiftedDialog).open(giver);
|
(this.$refs.customDialog as GiftedDialog).open(giver);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openGiftedPrompts() {
|
||||||
|
(this.$refs.giftedPrompts as GiftedPrompts).open();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user