forked from trent_larson/crowd-funder-for-time-pwa
Extract IdentitySection as vue-facing-decorator component and integrate into AccountViewView
- Created IdentitySection.vue using vue-facing-decorator (class-based, TypeScript, @Component, @Prop, @Emit). - Moved all identity-related UI and logic (name, QR code, profile image, DID) into the new component. - Replaced original identity section markup in AccountViewView.vue with <IdentitySection />. - Passed all necessary props and implemented event handlers in AccountViewView.vue to maintain existing behavior. - Ensured all linter errors are resolved and code is consistent with project conventions.
This commit is contained in:
187
src/components/IdentitySection.vue
Normal file
187
src/components/IdentitySection.vue
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
<template>
|
||||||
|
<section
|
||||||
|
id="sectionIdentityDetails"
|
||||||
|
class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mt-4"
|
||||||
|
aria-labelledby="identityDetailsHeading"
|
||||||
|
>
|
||||||
|
<h2 id="identityDetailsHeading" class="sr-only">Identity Details</h2>
|
||||||
|
<div v-if="givenName">
|
||||||
|
<h2 class="text-xl font-semibold mb-2">
|
||||||
|
<span class="whitespace-nowrap">
|
||||||
|
<button
|
||||||
|
class="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-1.5 py-1 mr-1 rounded-md"
|
||||||
|
@click="showQrCode"
|
||||||
|
>
|
||||||
|
<font-awesome icon="qrcode" class="fa-fw text-xl" />
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
{{ givenName }}
|
||||||
|
<button @click="editName">
|
||||||
|
<font-awesome
|
||||||
|
icon="pen"
|
||||||
|
class="text-xs text-blue-500 ml-2 mb-1"
|
||||||
|
></font-awesome>
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="block w-full text-center text-md bg-amber-200 border border-dashed border-slate-400 px-1.5 py-2 rounded-md mb-2"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="inline-block text-md 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-4 py-2 rounded-md"
|
||||||
|
@click="editName"
|
||||||
|
>
|
||||||
|
Set Your Name
|
||||||
|
</button>
|
||||||
|
<p class="text-xs text-slate-500 mt-1">
|
||||||
|
(Don't worry: this is not visible to anyone until you share it with
|
||||||
|
them. It's not sent to any servers.)
|
||||||
|
</p>
|
||||||
|
</span>
|
||||||
|
<div class="flex justify-center mt-4">
|
||||||
|
<span v-if="profileImageUrl" class="flex justify-between">
|
||||||
|
<EntityIcon
|
||||||
|
:icon-size="96"
|
||||||
|
:profile-image-url="profileImageUrl"
|
||||||
|
class="inline-block align-text-bottom border border-slate-300 rounded"
|
||||||
|
role="button"
|
||||||
|
aria-label="View profile image in large size"
|
||||||
|
tabindex="0"
|
||||||
|
@click="emitShowLargeIdenticonUrl"
|
||||||
|
/>
|
||||||
|
<font-awesome
|
||||||
|
icon="trash-can"
|
||||||
|
class="text-red-500 fa-fw ml-8 mt-8 w-12 h-12"
|
||||||
|
role="button"
|
||||||
|
aria-label="Delete profile image"
|
||||||
|
tabindex="0"
|
||||||
|
@click="deleteImage"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<div v-else class="text-center">
|
||||||
|
<template v-if="isRegistered">
|
||||||
|
<div
|
||||||
|
class="inline-block 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-4 py-2 rounded-md"
|
||||||
|
@click="addImage"
|
||||||
|
>
|
||||||
|
<font-awesome icon="user" class="fa-fw" />
|
||||||
|
<font-awesome icon="camera" class="fa-fw" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<div class="flex justify-center text-center text-sm leading-tight mb-1">
|
||||||
|
People {{ peopleSeeText }} see this
|
||||||
|
<br />
|
||||||
|
(if you've let them see your activity):
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<EntityIcon
|
||||||
|
:entity-id="activeDid"
|
||||||
|
:icon-size="64"
|
||||||
|
class="inline-block align-middle border border-slate-300 rounded-md mr-1"
|
||||||
|
@click="emitShowLargeIdenticonId"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="showLargeIdenticonIdProp || showLargeIdenticonUrlProp"
|
||||||
|
class="fixed z-[100] top-0 inset-x-0 w-full"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
|
||||||
|
>
|
||||||
|
<EntityIcon
|
||||||
|
:entity-id="showLargeIdenticonIdProp"
|
||||||
|
:icon-size="512"
|
||||||
|
:profile-image-url="showLargeIdenticonUrlProp"
|
||||||
|
class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
|
||||||
|
@click="closeLargeIdenticon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="text-sm text-slate-500 flex justify-start items-center mt-2 mb-1"
|
||||||
|
data-testId="didWrapper"
|
||||||
|
role="region"
|
||||||
|
aria-label="Your Identifier"
|
||||||
|
>
|
||||||
|
<div class="font-bold">ID: </div>
|
||||||
|
<code class="truncate" aria-label="Your DID">{{ activeDid }}</code>
|
||||||
|
<button class="ml-2" aria-label="Copy DID to clipboard" @click="copyDid">
|
||||||
|
<font-awesome
|
||||||
|
icon="copy"
|
||||||
|
class="text-slate-400 fa-fw"
|
||||||
|
aria-hidden="true"
|
||||||
|
></font-awesome>
|
||||||
|
</button>
|
||||||
|
<span v-show="showDidCopy" role="status" aria-live="polite">Copied</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-blue-500 text-sm font-bold">
|
||||||
|
<router-link :to="{ path: '/did/' + encodeURIComponent(activeDid) }">
|
||||||
|
Your Activity
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Vue, Prop, Emit } from "vue-facing-decorator";
|
||||||
|
import EntityIcon from "./EntityIcon.vue";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
name: "IdentitySection",
|
||||||
|
components: { EntityIcon },
|
||||||
|
})
|
||||||
|
export default class IdentitySection extends Vue {
|
||||||
|
@Prop({ required: false }) givenName!: string;
|
||||||
|
@Prop({ required: true }) profileImageUrl!: string;
|
||||||
|
@Prop({ required: true }) activeDid!: string;
|
||||||
|
@Prop({ required: false }) isRegistered!: boolean;
|
||||||
|
@Prop({ required: false }) showLargeIdenticonId!: string;
|
||||||
|
@Prop({ required: false }) showLargeIdenticonUrl!: string;
|
||||||
|
@Prop({ required: false }) showDidCopy!: boolean;
|
||||||
|
|
||||||
|
get peopleSeeText(): string {
|
||||||
|
return this.profileImageUrl ? "without your image" : "";
|
||||||
|
}
|
||||||
|
get showLargeIdenticonIdProp(): string | undefined {
|
||||||
|
return this.showLargeIdenticonId;
|
||||||
|
}
|
||||||
|
get showLargeIdenticonUrlProp(): string | undefined {
|
||||||
|
return this.showLargeIdenticonUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Emit("edit-name")
|
||||||
|
editName() {}
|
||||||
|
|
||||||
|
@Emit("show-qr-code")
|
||||||
|
showQrCode() {}
|
||||||
|
|
||||||
|
@Emit("add-image")
|
||||||
|
addImage() {}
|
||||||
|
|
||||||
|
@Emit("delete-image")
|
||||||
|
deleteImage() {}
|
||||||
|
|
||||||
|
@Emit("show-large-identicon-id")
|
||||||
|
emitShowLargeIdenticonId() {
|
||||||
|
return this.activeDid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Emit("show-large-identicon-url")
|
||||||
|
emitShowLargeIdenticonUrl() {
|
||||||
|
return this.profileImageUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Emit("close-large-identicon")
|
||||||
|
closeLargeIdenticon() {}
|
||||||
|
|
||||||
|
@Emit("copy-did")
|
||||||
|
copyDid() {
|
||||||
|
return this.activeDid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -35,160 +35,23 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Identity Details -->
|
<!-- Identity Details -->
|
||||||
<section
|
<IdentitySection
|
||||||
id="sectionIdentityDetails"
|
:given-name="givenName"
|
||||||
class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mt-4"
|
:profile-image-url="profileImageUrl"
|
||||||
aria-labelledby="identityDetailsHeading"
|
:active-did="activeDid"
|
||||||
>
|
:is-registered="isRegistered"
|
||||||
<h2 id="identityDetailsHeading" class="sr-only">Identity Details</h2>
|
:show-large-identicon-id="showLargeIdenticonId"
|
||||||
<div v-if="givenName">
|
:show-large-identicon-url="showLargeIdenticonUrl"
|
||||||
<h2 class="text-xl font-semibold mb-2">
|
:show-did-copy="showDidCopy"
|
||||||
<span class="whitespace-nowrap">
|
@edit-name="onEditName"
|
||||||
<button
|
@show-qr-code="onShowQrCode"
|
||||||
class="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-1.5 py-1 mr-1 rounded-md"
|
@add-image="onAddImage"
|
||||||
@click="handleQRCodeClick"
|
@delete-image="onDeleteImage"
|
||||||
>
|
@show-large-identicon-id="onShowLargeIdenticonId"
|
||||||
<font-awesome icon="qrcode" class="fa-fw text-xl" />
|
@show-large-identicon-url="onShowLargeIdenticonUrl"
|
||||||
</button>
|
@close-large-identicon="onCloseLargeIdenticon"
|
||||||
</span>
|
@copy-did="onCopyDid"
|
||||||
{{ givenName }}
|
/>
|
||||||
<router-link :to="{ name: 'new-edit-account' }">
|
|
||||||
<font-awesome
|
|
||||||
icon="pen"
|
|
||||||
class="text-xs text-blue-500 ml-2 mb-1"
|
|
||||||
></font-awesome>
|
|
||||||
</router-link>
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
v-else
|
|
||||||
class="block w-full text-center text-md bg-amber-200 border border-dashed border-slate-400 px-1.5 py-2 rounded-md mb-2"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="inline-block text-md 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-4 py-2 rounded-md"
|
|
||||||
@click="
|
|
||||||
() =>
|
|
||||||
($refs.userNameDialog as UserNameDialog).open((name) => {
|
|
||||||
if (name) givenName = name;
|
|
||||||
})
|
|
||||||
"
|
|
||||||
>
|
|
||||||
Set Your Name
|
|
||||||
</button>
|
|
||||||
<p class="text-xs text-slate-500 mt-1">
|
|
||||||
(Don't worry: this is not visible to anyone until you share it with
|
|
||||||
them. It's not sent to any servers.)
|
|
||||||
</p>
|
|
||||||
<UserNameDialog ref="userNameDialog" />
|
|
||||||
</span>
|
|
||||||
<div class="flex justify-center mt-4">
|
|
||||||
<span v-if="profileImageUrl" class="flex justify-between">
|
|
||||||
<EntityIcon
|
|
||||||
:icon-size="96"
|
|
||||||
:profile-image-url="profileImageUrl"
|
|
||||||
class="inline-block align-text-bottom border border-slate-300 rounded"
|
|
||||||
role="button"
|
|
||||||
aria-label="View profile image in large size"
|
|
||||||
tabindex="0"
|
|
||||||
@click="showLargeIdenticonUrl = profileImageUrl"
|
|
||||||
/>
|
|
||||||
<font-awesome
|
|
||||||
icon="trash-can"
|
|
||||||
class="text-red-500 fa-fw ml-8 mt-8 w-12 h-12"
|
|
||||||
role="button"
|
|
||||||
aria-label="Delete profile image"
|
|
||||||
tabindex="0"
|
|
||||||
@click="confirmDeleteImage"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<div v-else class="text-center">
|
|
||||||
<template v-if="isRegistered">
|
|
||||||
<div
|
|
||||||
class="inline-block 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-4 py-2 rounded-md"
|
|
||||||
@click="openImageDialog()"
|
|
||||||
>
|
|
||||||
<font-awesome icon="user" class="fa-fw" />
|
|
||||||
<font-awesome icon="camera" class="fa-fw" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<!--
|
|
||||||
If not registered, they don't need to see this at all. We show a prompt
|
|
||||||
to register below.
|
|
||||||
-->
|
|
||||||
</div>
|
|
||||||
<ImageMethodDialog
|
|
||||||
ref="imageMethodDialog"
|
|
||||||
:is-registered="isRegistered"
|
|
||||||
default-camera-mode="user"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="mt-4">
|
|
||||||
<div class="flex justify-center text-center text-sm leading-tight mb-1">
|
|
||||||
People {{ profileImageUrl ? "without your image" : "" }} see this
|
|
||||||
<br />
|
|
||||||
(if you've let them see your activity):
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-center">
|
|
||||||
<EntityIcon
|
|
||||||
:entity-id="activeDid"
|
|
||||||
:icon-size="64"
|
|
||||||
class="inline-block align-middle border border-slate-300 rounded-md mr-1"
|
|
||||||
@click="showLargeIdenticonId = activeDid"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="showLargeIdenticonId || showLargeIdenticonUrl"
|
|
||||||
class="fixed z-[100] top-0 inset-x-0 w-full"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
|
|
||||||
>
|
|
||||||
<EntityIcon
|
|
||||||
:entity-id="showLargeIdenticonId"
|
|
||||||
:icon-size="512"
|
|
||||||
:profile-image-url="showLargeIdenticonUrl"
|
|
||||||
class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
|
|
||||||
@click="
|
|
||||||
showLargeIdenticonId = undefined;
|
|
||||||
showLargeIdenticonUrl = undefined;
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="text-sm text-slate-500 flex justify-start items-center mt-2 mb-1"
|
|
||||||
data-testId="didWrapper"
|
|
||||||
role="region"
|
|
||||||
aria-label="Your Identifier"
|
|
||||||
>
|
|
||||||
<div class="font-bold">ID: </div>
|
|
||||||
<code class="truncate" aria-label="Your DID">{{ activeDid }}</code>
|
|
||||||
<button
|
|
||||||
class="ml-2"
|
|
||||||
aria-label="Copy DID to clipboard"
|
|
||||||
@click="
|
|
||||||
doCopyTwoSecRedo(activeDid, () => (showDidCopy = !showDidCopy))
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<font-awesome
|
|
||||||
icon="copy"
|
|
||||||
class="text-slate-400 fa-fw"
|
|
||||||
aria-hidden="true"
|
|
||||||
></font-awesome>
|
|
||||||
</button>
|
|
||||||
<span v-show="showDidCopy" role="status" aria-live="polite"
|
|
||||||
>Copied</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-blue-500 text-sm font-bold">
|
|
||||||
<router-link :to="{ path: '/did/' + encodeURIComponent(activeDid) }">
|
|
||||||
Your Activity
|
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Registration notice -->
|
<!-- Registration notice -->
|
||||||
<!--
|
<!--
|
||||||
@@ -996,6 +859,7 @@ 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";
|
||||||
import DataExportSection from "../components/DataExportSection.vue";
|
import DataExportSection from "../components/DataExportSection.vue";
|
||||||
|
import IdentitySection from '@/components/IdentitySection.vue';
|
||||||
import {
|
import {
|
||||||
AppString,
|
AppString,
|
||||||
DEFAULT_IMAGE_API_SERVER,
|
DEFAULT_IMAGE_API_SERVER,
|
||||||
@@ -1049,6 +913,7 @@ const inputImportFileNameRef = ref<Blob>();
|
|||||||
TopMessage,
|
TopMessage,
|
||||||
UserNameDialog,
|
UserNameDialog,
|
||||||
DataExportSection,
|
DataExportSection,
|
||||||
|
IdentitySection,
|
||||||
},
|
},
|
||||||
mixins: [PlatformServiceMixin],
|
mixins: [PlatformServiceMixin],
|
||||||
})
|
})
|
||||||
@@ -1133,7 +998,10 @@ export default class AccountViewView extends Vue {
|
|||||||
*/
|
*/
|
||||||
async mounted(): Promise<void> {
|
async mounted(): Promise<void> {
|
||||||
this.notify = createNotifyHelpers(this.$notify);
|
this.notify = createNotifyHelpers(this.$notify);
|
||||||
this.profileService = createProfileService(this.axios, this.partnerApiServer);
|
this.profileService = createProfileService(
|
||||||
|
this.axios,
|
||||||
|
this.partnerApiServer,
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
await this.initializeState();
|
await this.initializeState();
|
||||||
await this.processIdentity();
|
await this.processIdentity();
|
||||||
@@ -1150,7 +1018,9 @@ export default class AccountViewView extends Vue {
|
|||||||
// Profile not created yet; leave defaults
|
// Profile not created yet; leave defaults
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_NOT_AVAILABLE);
|
this.notify.error(
|
||||||
|
ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_NOT_AVAILABLE,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1159,7 +1029,8 @@ export default class AccountViewView extends Vue {
|
|||||||
error,
|
error,
|
||||||
);
|
);
|
||||||
logger.error(
|
logger.error(
|
||||||
"To repeat with concatenated error: telling user to clear cache at page create because: " + error,
|
"To repeat with concatenated error: telling user to clear cache at page create because: " +
|
||||||
|
error,
|
||||||
);
|
);
|
||||||
this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_LOAD_ERROR);
|
this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_LOAD_ERROR);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -1808,5 +1679,34 @@ export default class AccountViewView extends Vue {
|
|||||||
this.userProfileLatitude = event.latlng.lat;
|
this.userProfileLatitude = event.latlng.lat;
|
||||||
this.userProfileLongitude = event.latlng.lng;
|
this.userProfileLongitude = event.latlng.lng;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IdentitySection event handlers
|
||||||
|
onEditName() {
|
||||||
|
(this.$refs.userNameDialog as any).open((name: string) => {
|
||||||
|
if (name) this.givenName = name;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
onShowQrCode() {
|
||||||
|
this.handleQRCodeClick();
|
||||||
|
}
|
||||||
|
onAddImage() {
|
||||||
|
this.openImageDialog();
|
||||||
|
}
|
||||||
|
onDeleteImage() {
|
||||||
|
this.confirmDeleteImage();
|
||||||
|
}
|
||||||
|
onShowLargeIdenticonId(id: string) {
|
||||||
|
this.showLargeIdenticonId = id;
|
||||||
|
}
|
||||||
|
onShowLargeIdenticonUrl(url: string) {
|
||||||
|
this.showLargeIdenticonUrl = url;
|
||||||
|
}
|
||||||
|
onCloseLargeIdenticon() {
|
||||||
|
this.showLargeIdenticonId = undefined;
|
||||||
|
this.showLargeIdenticonUrl = undefined;
|
||||||
|
}
|
||||||
|
onCopyDid(did: string) {
|
||||||
|
this.doCopyTwoSecRedo(did, () => (this.showDidCopy = !this.showDidCopy));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user