forked from trent_larson/crowd-funder-for-time-pwa
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.
This commit is contained in:
@@ -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>
|
||||
<div
|
||||
v-if="!isRegistered && show"
|
||||
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 mt-4"
|
||||
role="alert"
|
||||
aria-live="polite"
|
||||
>
|
||||
<p class="mb-4">
|
||||
Before you can publicly announce a new project or time commitment, a
|
||||
friend needs to register you.
|
||||
</p>
|
||||
<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"
|
||||
@click="shareInfo"
|
||||
<div class="my-4">
|
||||
<div
|
||||
id="noticeSomeoneMustRegisterYou"
|
||||
class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3"
|
||||
>
|
||||
Share Your Info
|
||||
</button>
|
||||
<p class="mb-4">{{ message }}</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 {{ passkeysEnabled ? "default" : "your" }} identifier info
|
||||
</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>
|
||||
</template>
|
||||
|
||||
<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 {
|
||||
@Prop({ required: true }) isRegistered!: boolean;
|
||||
@Prop({ required: true }) show!: boolean;
|
||||
$router!: Router;
|
||||
|
||||
@Emit("share-info")
|
||||
shareInfo() {}
|
||||
/**
|
||||
* 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" });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>
|
||||
|
||||
@@ -55,9 +55,10 @@
|
||||
|
||||
<!-- Registration notice -->
|
||||
<RegistrationNotice
|
||||
:is-registered="isRegistered"
|
||||
:show="showRegistrationNotice"
|
||||
@share-info="onShareInfo"
|
||||
v-if="!isRegistered"
|
||||
:passkeys-enabled="PASSKEYS_ENABLED"
|
||||
:given-name="givenName"
|
||||
message="Before you can publicly announce a new project or time commitment, a friend needs to register you."
|
||||
/>
|
||||
|
||||
<!-- Notifications -->
|
||||
@@ -781,6 +782,7 @@ import {
|
||||
DEFAULT_PUSH_SERVER,
|
||||
IMAGE_TYPE_PROFILE,
|
||||
NotificationIface,
|
||||
PASSKEYS_ENABLED,
|
||||
} from "../constants/app";
|
||||
import { Contact } from "../db/tables/contacts";
|
||||
import {
|
||||
@@ -851,6 +853,7 @@ export default class AccountViewView extends Vue {
|
||||
readonly DEFAULT_PUSH_SERVER: string = DEFAULT_PUSH_SERVER;
|
||||
readonly DEFAULT_IMAGE_API_SERVER: string = DEFAULT_IMAGE_API_SERVER;
|
||||
readonly DEFAULT_PARTNER_API_SERVER: string = DEFAULT_PARTNER_API_SERVER;
|
||||
readonly PASSKEYS_ENABLED: boolean = PASSKEYS_ENABLED;
|
||||
|
||||
// Identity and settings properties
|
||||
activeDid: string = "";
|
||||
@@ -1789,20 +1792,6 @@ export default class AccountViewView extends Vue {
|
||||
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() {
|
||||
this.checkLimits();
|
||||
}
|
||||
|
||||
@@ -86,32 +86,14 @@ Raymer * @version 1.0.0 */
|
||||
Identity creation is now handled by router navigation guard.
|
||||
-->
|
||||
<div class="mb-4">
|
||||
<div v-if="!isUserRegistered" class="my-4">
|
||||
<div
|
||||
id="noticeSomeoneMustRegisterYou"
|
||||
class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3"
|
||||
>
|
||||
<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>
|
||||
<RegistrationNotice
|
||||
v-if="!isUserRegistered"
|
||||
:passkeys-enabled="PASSKEYS_ENABLED"
|
||||
:given-name="givenName"
|
||||
message="To share, someone must register you."
|
||||
/>
|
||||
|
||||
<div v-else id="sectionRecordSomethingGiven">
|
||||
<div v-if="isUserRegistered" id="sectionRecordSomethingGiven">
|
||||
<!-- Record Quick-Action -->
|
||||
<div class="mb-6">
|
||||
<div class="flex gap-2 items-center mb-2">
|
||||
@@ -251,8 +233,6 @@ Raymer * @version 1.0.0 */
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<ChoiceButtonDialog ref="choiceButtonDialog" />
|
||||
|
||||
<ImageViewer v-model:is-open="isImageViewerOpen" :image-url="selectedImage" />
|
||||
</template>
|
||||
|
||||
@@ -260,7 +240,6 @@ Raymer * @version 1.0.0 */
|
||||
import { UAParser } from "ua-parser-js";
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
import { Router } from "vue-router";
|
||||
import { Capacitor } from "@capacitor/core";
|
||||
|
||||
//import App from "../App.vue";
|
||||
import EntityIcon from "../components/EntityIcon.vue";
|
||||
@@ -271,10 +250,9 @@ import InfiniteScroll from "../components/InfiniteScroll.vue";
|
||||
import OnboardingDialog from "../components/OnboardingDialog.vue";
|
||||
import QuickNav from "../components/QuickNav.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 ActivityListItem from "../components/ActivityListItem.vue";
|
||||
import RegistrationNotice from "../components/RegistrationNotice.vue";
|
||||
import {
|
||||
AppString,
|
||||
NotificationIface,
|
||||
@@ -382,12 +360,11 @@ interface FeedError {
|
||||
GiftedPrompts,
|
||||
InfiniteScroll,
|
||||
OnboardingDialog,
|
||||
ChoiceButtonDialog,
|
||||
QuickNav,
|
||||
TopMessage,
|
||||
UserNameDialog,
|
||||
ImageViewer,
|
||||
ActivityListItem,
|
||||
RegistrationNotice,
|
||||
},
|
||||
mixins: [PlatformServiceMixin],
|
||||
})
|
||||
@@ -1643,67 +1620,6 @@ export default class HomeView extends Vue {
|
||||
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
|
||||
*
|
||||
@@ -1716,14 +1632,6 @@ export default class HomeView extends Vue {
|
||||
this.isImageViewerOpen = true;
|
||||
}
|
||||
|
||||
private handleQRCodeClick() {
|
||||
if (Capacitor.isNativePlatform()) {
|
||||
this.$router.push({ name: "contact-qr-scan-full" });
|
||||
} else {
|
||||
this.$router.push({ name: "contact-qr" });
|
||||
}
|
||||
}
|
||||
|
||||
openPersonDialog(
|
||||
giver?: GiverReceiverInputInfo | "Unnamed",
|
||||
prompt?: string,
|
||||
|
||||
Reference in New Issue
Block a user