|
|
|
<template>
|
|
|
|
<QuickNav />
|
|
|
|
<TopMessage />
|
|
|
|
|
|
|
|
<!-- CONTENT -->
|
|
|
|
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
|
|
|
|
<!-- Back -->
|
|
|
|
<div class="text-lg text-center font-light relative px-7">
|
|
|
|
<h1
|
|
|
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
|
|
|
@click="cancel()"
|
|
|
|
>
|
|
|
|
<fa icon="chevron-left" class="fa-fw"></fa>
|
|
|
|
</h1>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- Heading -->
|
|
|
|
<h1 class="text-4xl text-center font-light px-4 mb-4">What Was Given</h1>
|
|
|
|
|
|
|
|
<h1 class="text-xl font-bold text-center mb-4">
|
|
|
|
{{ message }} {{ giverName || "somebody not named" }}
|
|
|
|
</h1>
|
|
|
|
<textarea
|
|
|
|
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
|
|
|
|
placeholder="What was received"
|
|
|
|
v-model="description"
|
|
|
|
/>
|
|
|
|
<div class="flex flex-row justify-center">
|
|
|
|
<span
|
|
|
|
class="rounded-l border border-r-0 border-slate-400 bg-slate-200 text-center text-blue-500 px-2 py-2 w-20"
|
|
|
|
@click="changeUnitCode()"
|
|
|
|
>
|
|
|
|
{{ libsUtil.UNIT_SHORT[unitCode] }}
|
|
|
|
</span>
|
|
|
|
<div
|
|
|
|
class="border border-r-0 border-slate-400 bg-slate-200 px-4 py-2"
|
|
|
|
@click="amountInput === '0' ? null : decrement()"
|
|
|
|
>
|
|
|
|
<fa icon="chevron-left" />
|
|
|
|
</div>
|
|
|
|
<input
|
|
|
|
type="number"
|
|
|
|
class="border border-r-0 border-slate-400 px-2 py-2 text-center"
|
|
|
|
v-model="amountInput"
|
|
|
|
/>
|
|
|
|
<div
|
|
|
|
class="rounded-r border border-slate-400 bg-slate-200 px-4 py-2"
|
|
|
|
@click="increment()"
|
|
|
|
>
|
|
|
|
<fa icon="chevron-right" />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="flex justify-center mb-4 mt-4">
|
|
|
|
<span v-if="imageUrl">
|
|
|
|
Includes Image:
|
|
|
|
<a :href="imageUrl" target="_blank" class="text-blue-500 ml-4">View</a>
|
|
|
|
<fa
|
|
|
|
icon="trash-can"
|
|
|
|
@click="confirmDeleteImage"
|
|
|
|
class="text-red-500 fa-fw ml-8"
|
|
|
|
/>
|
|
|
|
</span>
|
|
|
|
<span v-else>
|
|
|
|
<router-link
|
|
|
|
:to="{ name: 'gifted-photo' }"
|
|
|
|
class="bg-blue-500 text-white px-1.5 py-1 rounded-md"
|
|
|
|
>
|
|
|
|
<fa icon="camera" class="fa-fw" />
|
|
|
|
</router-link>
|
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
<div class="mt-4">
|
|
|
|
<input type="checkbox" class="h-6 w-6 mr-2" v-model="givenToUser" />
|
|
|
|
<label class="text-sm">Given to you</label>
|
|
|
|
</div>
|
|
|
|
<div class="mt-4">
|
|
|
|
<input type="checkbox" class="h-6 w-6 mr-2" v-model="isTrade" />
|
|
|
|
<label class="text-sm">Trade (not a gift)</label>
|
|
|
|
</div>
|
|
|
|
<p class="text-center mb-2 mt-6 italic">
|
|
|
|
Sign & Send to publish to the world
|
|
|
|
</p>
|
|
|
|
<button
|
|
|
|
class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
|
|
|
|
@click="confirm"
|
|
|
|
>
|
|
|
|
Sign & Send
|
|
|
|
</button>
|
|
|
|
<button
|
|
|
|
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
|
|
|
|
@click="cancel"
|
|
|
|
>
|
|
|
|
Cancel
|
|
|
|
</button>
|
|
|
|
</section>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script lang="ts">
|
|
|
|
import { Component, Vue } from "vue-facing-decorator";
|
|
|
|
|
|
|
|
import { DEFAULT_IMAGE_API_SERVER, NotificationIface } from "@/constants/app";
|
|
|
|
import QuickNav from "@/components/QuickNav.vue";
|
|
|
|
import TopMessage from "@/components/TopMessage.vue";
|
|
|
|
import { db } from "@/db/index";
|
|
|
|
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
|
|
|
import { createAndSubmitGive } from "@/libs/endorserServer";
|
|
|
|
import * as libsUtil from "@/libs/util";
|
|
|
|
import { accessToken } from "@/libs/crypto";
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
components: {
|
|
|
|
QuickNav,
|
|
|
|
TopMessage,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
export default class GiftedDetails extends Vue {
|
|
|
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
|
|
|
|
|
|
|
activeDid = "";
|
|
|
|
apiServer = "";
|
|
|
|
|
|
|
|
amountInput = "0";
|
|
|
|
description = "";
|
|
|
|
givenToUser = false;
|
|
|
|
giverDid: string | undefined;
|
|
|
|
giverName = "";
|
|
|
|
imageUrl = "";
|
|
|
|
isTrade = false;
|
|
|
|
message = "";
|
|
|
|
offerId = "";
|
|
|
|
projectId = "";
|
|
|
|
unitCode = "HUR";
|
|
|
|
|
|
|
|
libsUtil = libsUtil;
|
|
|
|
|
|
|
|
async mounted() {
|
|
|
|
this.amountInput = this.$route.query.amountInput as string;
|
|
|
|
this.description = this.$route.query.description as string;
|
|
|
|
this.giverDid = this.$route.query.giverDid as string;
|
|
|
|
this.giverName = this.$route.query.giverName as string;
|
|
|
|
this.imageUrl = localStorage.getItem("imageUrl") || "";
|
|
|
|
this.message = this.$route.query.message as string;
|
|
|
|
this.offerId = this.$route.query.offerId as string;
|
|
|
|
this.projectId = this.$route.query.projectId as string;
|
|
|
|
this.unitCode = this.$route.query.unitCode as string;
|
|
|
|
|
|
|
|
try {
|
|
|
|
await db.open();
|
|
|
|
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
|
|
|
|
this.apiServer = settings?.apiServer || "";
|
|
|
|
this.activeDid = settings?.activeDid || "";
|
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
|
} catch (err: any) {
|
|
|
|
console.error("Error retrieving settings from database:", err);
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: "alert",
|
|
|
|
type: "danger",
|
|
|
|
title: "Error",
|
|
|
|
text: err.message || "There was an error retrieving your settings.",
|
|
|
|
},
|
|
|
|
-1,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
changeUnitCode() {
|
|
|
|
const units = Object.keys(this.libsUtil.UNIT_SHORT);
|
|
|
|
const index = units.indexOf(this.unitCode);
|
|
|
|
this.unitCode = units[(index + 1) % units.length];
|
|
|
|
}
|
|
|
|
|
|
|
|
increment() {
|
|
|
|
this.amountInput = `${(parseFloat(this.amountInput) || 0) + 1}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
decrement() {
|
|
|
|
this.amountInput = `${Math.max(
|
|
|
|
0,
|
|
|
|
(parseFloat(this.amountInput) || 1) - 1,
|
|
|
|
)}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
cancel() {
|
|
|
|
this.deleteImage(); // not awaiting, so they'll go back immediately
|
|
|
|
this.$router.back();
|
|
|
|
}
|
|
|
|
|
|
|
|
confirmDeleteImage() {
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: "modal",
|
|
|
|
type: "confirm",
|
|
|
|
title: "Are you sure you want to delete the image?",
|
|
|
|
text: "",
|
|
|
|
onYes: this.deleteImage,
|
|
|
|
},
|
|
|
|
-1,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
async deleteImage() {
|
|
|
|
const identity = await libsUtil.getIdentity(this.activeDid);
|
|
|
|
const token = await accessToken(identity);
|
|
|
|
const response = await this.axios.delete(
|
|
|
|
DEFAULT_IMAGE_API_SERVER + "/image/" + encodeURIComponent(this.imageUrl),
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
Authorization: `Bearer ${token}`,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
);
|
|
|
|
if (response.status === 204) {
|
|
|
|
// don't bother with a notification
|
|
|
|
// (either they'll simply continue or they're canceling and going back)
|
|
|
|
} else {
|
|
|
|
console.error("Error deleting image:", response);
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: "alert",
|
|
|
|
type: "danger",
|
|
|
|
title: "Error",
|
|
|
|
text: "There was an error deleting the image.",
|
|
|
|
},
|
|
|
|
5000,
|
|
|
|
);
|
|
|
|
// keep the imageUrl in localStorage so the user can try again if they want
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
localStorage.removeItem("imageUrl");
|
|
|
|
this.imageUrl = "";
|
|
|
|
}
|
|
|
|
|
|
|
|
async confirm() {
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: "alert",
|
|
|
|
type: "toast",
|
|
|
|
text: "Recording the give...",
|
|
|
|
title: "",
|
|
|
|
},
|
|
|
|
1000,
|
|
|
|
);
|
|
|
|
// this is asynchronous, but we don't need to wait for it to complete
|
|
|
|
await this.recordGive();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param giverDid may be null
|
|
|
|
* @param description may be an empty string
|
|
|
|
* @param amountInput may be 0
|
|
|
|
* @param unitCode may be omitted, defaults to "HUR"
|
|
|
|
*/
|
|
|
|
public async recordGive() {
|
|
|
|
if (!this.activeDid) {
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: "alert",
|
|
|
|
type: "danger",
|
|
|
|
title: "Error",
|
|
|
|
text: "You must select an identifier before you can record a give.",
|
|
|
|
},
|
|
|
|
-1,
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!this.description && !this.amountInput) {
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: "alert",
|
|
|
|
type: "danger",
|
|
|
|
title: "Error",
|
|
|
|
text: `You must enter a description or some number of ${
|
|
|
|
this.libsUtil.UNIT_LONG[this.unitCode]
|
|
|
|
}.`,
|
|
|
|
},
|
|
|
|
-1,
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
const identity = await libsUtil.getIdentity(this.activeDid);
|
|
|
|
const result = await createAndSubmitGive(
|
|
|
|
this.axios,
|
|
|
|
this.apiServer,
|
|
|
|
identity,
|
|
|
|
this.giverDid,
|
|
|
|
this.givenToUser ? this.activeDid : undefined,
|
|
|
|
this.description,
|
|
|
|
parseFloat(this.amountInput),
|
|
|
|
this.unitCode,
|
|
|
|
this.projectId,
|
|
|
|
this.offerId,
|
|
|
|
this.isTrade,
|
|
|
|
this.imageUrl,
|
|
|
|
);
|
|
|
|
|
|
|
|
if (
|
|
|
|
result.type === "error" ||
|
|
|
|
this.isGiveCreationError(result.response)
|
|
|
|
) {
|
|
|
|
const errorMessage = this.getGiveCreationErrorMessage(result);
|
|
|
|
console.error("Error with give creation result:", result);
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: "alert",
|
|
|
|
type: "danger",
|
|
|
|
title: "Error",
|
|
|
|
text: errorMessage || "There was an error creating the give.",
|
|
|
|
},
|
|
|
|
-1,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: "alert",
|
|
|
|
type: "success",
|
|
|
|
title: "Success",
|
|
|
|
text: `That ${this.isTrade ? "trade" : "gift"} was recorded.`,
|
|
|
|
},
|
|
|
|
5000,
|
|
|
|
);
|
|
|
|
localStorage.removeItem("imageUrl");
|
|
|
|
this.$router.back();
|
|
|
|
}
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
|
} catch (error: any) {
|
|
|
|
console.error("Error with give recordation caught:", error);
|
|
|
|
const message =
|
|
|
|
error.userMessage ||
|
|
|
|
error.response?.data?.error?.message ||
|
|
|
|
"There was an error recording the give.";
|
|
|
|
this.$notify(
|
|
|
|
{
|
|
|
|
group: "alert",
|
|
|
|
type: "danger",
|
|
|
|
title: "Error",
|
|
|
|
text: message,
|
|
|
|
},
|
|
|
|
-1,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Helper functions for readability
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param result response "data" from the server
|
|
|
|
* @returns true if the result indicates an error
|
|
|
|
*/
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
|
isGiveCreationError(result: any) {
|
|
|
|
return result.status !== 201 || result.data?.error;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param result direct response eg. ErrorResult or SuccessResult (potentially with embedded "data")
|
|
|
|
* @returns best guess at an error message
|
|
|
|
*/
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
|
getGiveCreationErrorMessage(result: any) {
|
|
|
|
return (
|
|
|
|
result.error?.userMessage ||
|
|
|
|
result.error?.error ||
|
|
|
|
result.response?.data?.error?.message
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</script>
|