Browse Source

Refactor: simplify RegistrationNotice component interface

- Centralize all registration notice logic within upgraded RegistrationNotice component
- Clean up unused methods and imports in HomeView and AccountViewView
- Customizable message to maintain original contextual wordings unique to each view

Component is now more focused and follows Vue.js best practices
for conditional rendering.
onboard-alert-component
Jose Olarte III 20 hours ago
parent
commit
47a7b03cca
  1. 159
      src/components/RegistrationNotice.vue
  2. 23
      src/views/AccountViewView.vue
  3. 110
      src/views/HomeView.vue

159
src/components/RegistrationNotice.vue

@ -1,33 +1,156 @@
/** * @file RegistrationNotice.vue * @description Reusable component for
displaying user registration status and related actions. * Shows registration
notice when user is not registered, with options to show identifier info * or
access advanced options. * * @author Matthew Raymer * @version 1.0.0 * @created
2025-08-21T17:25:28-08:00 */
<template> <template>
<div class="my-4">
<div <div
v-if="!isRegistered && show" id="noticeSomeoneMustRegisterYou"
id="noticeBeforeAnnounce" class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3"
class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 mt-4"
role="alert"
aria-live="polite"
> >
<p class="mb-4"> <p class="mb-4">{{ message }}</p>
Before you can publicly announce a new project or time commitment, a <div class="grid grid-cols-1 gap-2 sm:flex sm:justify-center">
friend needs to register you.
</p>
<button <button
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" class="inline-block text-md font-bold 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="shareInfo" @click="showNameThenIdDialog"
> >
Share Your Info Show them {{ passkeysEnabled ? "default" : "your" }} identifier info
</button> </button>
<button
class="inline-block 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-4 py-2 rounded-md"
@click="openAdvancedOptions"
>
See advanced options
</button>
</div>
</div>
<UserNameDialog ref="userNameDialog" />
<ChoiceButtonDialog ref="choiceButtonDialog" />
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue, Prop, Emit } from "vue-facing-decorator"; import { Component, Vue, Prop } from "vue-facing-decorator";
import { Router } from "vue-router";
import { Capacitor } from "@capacitor/core";
import UserNameDialog from "./UserNameDialog.vue";
import ChoiceButtonDialog from "./ChoiceButtonDialog.vue";
@Component({ name: "RegistrationNotice" }) /**
* RegistrationNotice Component
*
* Displays registration status notice and provides actions for unregistered users.
* Handles all registration-related flows internally without requiring parent component intervention.
*
* Template Usage:
* ```vue
* <RegistrationNotice
* v-if="!isUserRegistered"
* :passkeys-enabled="PASSKEYS_ENABLED"
* :given-name="givenName"
* message="Custom registration message here"
* />
* ```
*
* Component Dependencies:
* - UserNameDialog: Dialog for entering user name
* - ChoiceButtonDialog: Dialog for sharing method selection
*/
@Component({
name: "RegistrationNotice",
components: {
UserNameDialog,
ChoiceButtonDialog,
},
})
export default class RegistrationNotice extends Vue { export default class RegistrationNotice extends Vue {
@Prop({ required: true }) isRegistered!: boolean; $router!: Router;
@Prop({ required: true }) show!: boolean;
/**
* Whether passkeys are enabled in the application
*/
@Prop({ required: true })
passkeysEnabled!: boolean;
/**
* User's given name for dialog pre-population
*/
@Prop({ required: true })
givenName!: string;
/**
* Custom message to display in the registration notice
* Defaults to "To share, someone must register you."
*/
@Prop({ default: "To share, someone must register you." })
message!: string;
/**
* Shows name input dialog if needed
* Handles the full flow internally without requiring parent component intervention
*/
showNameThenIdDialog() {
this.openUserNameDialog(() => {
this.promptForShareMethod();
});
}
/**
* Opens advanced options page
* Navigates directly to the start page
*/
openAdvancedOptions() {
this.$router.push({ name: "start" });
}
/**
* Shows dialog for sharing method selection
* Provides options for different sharing scenarios
*/
promptForShareMethod() {
(this.$refs.choiceButtonDialog as ChoiceButtonDialog).open({
title: "How can you share your info?",
text: "",
option1Text: "We are in a meeting together",
option2Text: "We are nearby with cameras",
option3Text: "We will share some other way",
onOption1: () => {
this.$router.push({ name: "onboard-meeting-list" });
},
onOption2: () => {
this.handleQRCodeClick();
},
onOption3: () => {
this.$router.push({ name: "share-my-contact-info" });
},
});
}
@Emit("share-info") /**
shareInfo() {} * Handles QR code sharing based on platform
* Navigates to appropriate QR code page
*/
private handleQRCodeClick() {
if (Capacitor.isNativePlatform()) {
this.$router.push({ name: "contact-qr-scan-full" });
} else {
this.$router.push({ name: "contact-qr" });
}
}
/**
* Opens the user name dialog if needed
*
* @param callback Function to call after name is entered
*/
openUserNameDialog(callback: () => void) {
if (!this.givenName) {
(this.$refs.userNameDialog as UserNameDialog).open(callback);
} else {
callback();
}
}
} }
</script> </script>

23
src/views/AccountViewView.vue

@ -55,9 +55,10 @@
<!-- Registration notice --> <!-- Registration notice -->
<RegistrationNotice <RegistrationNotice
:is-registered="isRegistered" v-if="!isRegistered"
:show="showRegistrationNotice" :passkeys-enabled="PASSKEYS_ENABLED"
@share-info="onShareInfo" :given-name="givenName"
message="Before you can publicly announce a new project or time commitment, a friend needs to register you."
/> />
<!-- Notifications --> <!-- Notifications -->
@ -781,6 +782,7 @@ import {
DEFAULT_PUSH_SERVER, DEFAULT_PUSH_SERVER,
IMAGE_TYPE_PROFILE, IMAGE_TYPE_PROFILE,
NotificationIface, NotificationIface,
PASSKEYS_ENABLED,
} from "../constants/app"; } from "../constants/app";
import { Contact } from "../db/tables/contacts"; import { Contact } from "../db/tables/contacts";
import { import {
@ -851,6 +853,7 @@ export default class AccountViewView extends Vue {
readonly DEFAULT_PUSH_SERVER: string = DEFAULT_PUSH_SERVER; readonly DEFAULT_PUSH_SERVER: string = DEFAULT_PUSH_SERVER;
readonly DEFAULT_IMAGE_API_SERVER: string = DEFAULT_IMAGE_API_SERVER; readonly DEFAULT_IMAGE_API_SERVER: string = DEFAULT_IMAGE_API_SERVER;
readonly DEFAULT_PARTNER_API_SERVER: string = DEFAULT_PARTNER_API_SERVER; readonly DEFAULT_PARTNER_API_SERVER: string = DEFAULT_PARTNER_API_SERVER;
readonly PASSKEYS_ENABLED: boolean = PASSKEYS_ENABLED;
// Identity and settings properties // Identity and settings properties
activeDid: string = ""; activeDid: string = "";
@ -1789,20 +1792,6 @@ export default class AccountViewView extends Vue {
this.doCopyTwoSecRedo(did, () => (this.showDidCopy = !this.showDidCopy)); this.doCopyTwoSecRedo(did, () => (this.showDidCopy = !this.showDidCopy));
} }
get showRegistrationNotice(): boolean {
// Show the notice if not registered and any other conditions you want
return !this.isRegistered;
}
onShareInfo() {
// Navigate to QR code sharing page - mobile uses full scan, web uses basic
if (Capacitor.isNativePlatform()) {
this.$router.push({ name: "contact-qr-scan-full" });
} else {
this.$router.push({ name: "contact-qr" });
}
}
onRecheckLimits() { onRecheckLimits() {
this.checkLimits(); this.checkLimits();
} }

110
src/views/HomeView.vue

@ -86,32 +86,14 @@ Raymer * @version 1.0.0 */
Identity creation is now handled by router navigation guard. Identity creation is now handled by router navigation guard.
--> -->
<div class="mb-4"> <div class="mb-4">
<div v-if="!isUserRegistered" class="my-4"> <RegistrationNotice
<div v-if="!isUserRegistered"
id="noticeSomeoneMustRegisterYou" :passkeys-enabled="PASSKEYS_ENABLED"
class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3" :given-name="givenName"
> message="To share, someone must register you."
<p class="mb-4">To share, someone must register you.</p> />
<div class="grid grid-cols-1 gap-2 sm:flex sm:justify-center">
<button
class="inline-block text-md font-bold 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="showNameThenIdDialog()"
>
Show them {{ PASSKEYS_ENABLED ? "default" : "your" }} identifier
info
</button>
<router-link
:to="{ name: 'start' }"
class="inline-block 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-4 py-2 rounded-md"
>
See advanced options
</router-link>
</div>
</div>
<UserNameDialog ref="userNameDialog" />
</div>
<div v-else id="sectionRecordSomethingGiven"> <div v-if="isUserRegistered" id="sectionRecordSomethingGiven">
<!-- Record Quick-Action --> <!-- Record Quick-Action -->
<div class="mb-6"> <div class="mb-6">
<div class="flex gap-2 items-center mb-2"> <div class="flex gap-2 items-center mb-2">
@ -251,8 +233,6 @@ Raymer * @version 1.0.0 */
</div> </div>
</section> </section>
<ChoiceButtonDialog ref="choiceButtonDialog" />
<ImageViewer v-model:is-open="isImageViewerOpen" :image-url="selectedImage" /> <ImageViewer v-model:is-open="isImageViewerOpen" :image-url="selectedImage" />
</template> </template>
@ -260,7 +240,6 @@ Raymer * @version 1.0.0 */
import { UAParser } from "ua-parser-js"; import { UAParser } from "ua-parser-js";
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { Router } from "vue-router"; import { Router } from "vue-router";
import { Capacitor } from "@capacitor/core";
//import App from "../App.vue"; //import App from "../App.vue";
import EntityIcon from "../components/EntityIcon.vue"; import EntityIcon from "../components/EntityIcon.vue";
@ -271,10 +250,9 @@ import InfiniteScroll from "../components/InfiniteScroll.vue";
import OnboardingDialog from "../components/OnboardingDialog.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 ChoiceButtonDialog from "../components/ChoiceButtonDialog.vue";
import ImageViewer from "../components/ImageViewer.vue"; import ImageViewer from "../components/ImageViewer.vue";
import ActivityListItem from "../components/ActivityListItem.vue"; import ActivityListItem from "../components/ActivityListItem.vue";
import RegistrationNotice from "../components/RegistrationNotice.vue";
import { import {
AppString, AppString,
NotificationIface, NotificationIface,
@ -382,12 +360,11 @@ interface FeedError {
GiftedPrompts, GiftedPrompts,
InfiniteScroll, InfiniteScroll,
OnboardingDialog, OnboardingDialog,
ChoiceButtonDialog,
QuickNav, QuickNav,
TopMessage, TopMessage,
UserNameDialog,
ImageViewer, ImageViewer,
ActivityListItem, ActivityListItem,
RegistrationNotice,
}, },
mixins: [PlatformServiceMixin], mixins: [PlatformServiceMixin],
}) })
@ -1643,67 +1620,6 @@ export default class HomeView extends Vue {
return known ? "text-slate-500" : "text-slate-100"; return known ? "text-slate-500" : "text-slate-100";
} }
/**
* Shows name input dialog if needed
*
* @public
* @callGraph
* Called by: Template
* Calls:
* - UserNameDialog.open()
* - promptForShareMethod()
*
* @chain
* Template -> showNameThenIdDialog() -> promptForShareMethod()
*
* @requires
* - this.$refs.userNameDialog
* - this.givenName
*/
showNameThenIdDialog() {
if (!this.givenName) {
(this.$refs.userNameDialog as UserNameDialog).open(() => {
this.promptForShareMethod();
});
} else {
this.promptForShareMethod();
}
}
/**
* Shows dialog for sharing method selection
*
* @internal
* @callGraph
* Called by: showNameThenIdDialog()
* Calls: ChoiceButtonDialog.open()
*
* @chain
* Template -> showNameThenIdDialog() -> promptForShareMethod()
*
* @requires
* - this.$refs.choiceButtonDialog
* - this.$router
*/
promptForShareMethod() {
(this.$refs.choiceButtonDialog as ChoiceButtonDialog).open({
title: "How can you share your info?",
text: "",
option1Text: "We are in a meeting together",
option2Text: "We are nearby with cameras",
option3Text: "We will share some other way",
onOption1: () => {
this.$router.push({ name: "onboard-meeting-list" });
},
onOption2: () => {
this.handleQRCodeClick();
},
onOption3: () => {
this.$router.push({ name: "share-my-contact-info" });
},
});
}
/** /**
* Opens image viewer dialog * Opens image viewer dialog
* *
@ -1716,14 +1632,6 @@ export default class HomeView extends Vue {
this.isImageViewerOpen = true; this.isImageViewerOpen = true;
} }
private handleQRCodeClick() {
if (Capacitor.isNativePlatform()) {
this.$router.push({ name: "contact-qr-scan-full" });
} else {
this.$router.push({ name: "contact-qr" });
}
}
openPersonDialog( openPersonDialog(
giver?: GiverReceiverInputInfo | "Unnamed", giver?: GiverReceiverInputInfo | "Unnamed",
prompt?: string, prompt?: string,

Loading…
Cancel
Save