Merge branch 'master' into contact-gifting-current-user

This commit is contained in:
Jose Olarte III
2025-08-26 16:51:09 +08:00
241 changed files with 20051 additions and 3992 deletions

View File

@@ -288,8 +288,7 @@ export default class ActivityListItem extends Vue {
}
get fetchAmount(): string {
const claim =
(this.record.fullClaim as any)?.claim || this.record.fullClaim;
const claim = this.record.fullClaim;
const amount = claim.object?.amountOfThisGood
? this.displayAmount(claim.object.unitCode, claim.object.amountOfThisGood)
@@ -299,8 +298,7 @@ export default class ActivityListItem extends Vue {
}
get description(): string {
const claim =
(this.record.fullClaim as any)?.claim || this.record.fullClaim;
const claim = this.record.fullClaim;
return `${claim?.description || ""}`;
}

View File

@@ -1,7 +1,7 @@
<!-- similar to UserNameDialog -->
<template>
<div v-if="visible" :class="overlayClasses">
<div :class="dialogClasses">
<div v-if="visible" class="dialog-overlay">
<div class="dialog">
<h1 :class="titleClasses">{{ title }}</h1>
{{ message }}
Note that their name is only stored on this device.
@@ -61,20 +61,6 @@ export default class ContactNameDialog extends Vue {
title = "Contact Name";
visible = false;
/**
* CSS classes for the modal overlay backdrop
*/
get overlayClasses(): string {
return "z-index-50 fixed top-0 left-0 right-0 bottom-0 bg-black/50 flex justify-center items-center p-6";
}
/**
* CSS classes for the modal dialog container
*/
get dialogClasses(): string {
return "bg-white p-4 rounded-lg w-full max-w-[500px]";
}
/**
* CSS classes for the dialog title
*/

View File

@@ -171,6 +171,8 @@ export default class DataExportSection extends Vue {
* @throws {Error} If export fails
*/
public async exportDatabase(): Promise<void> {
// Note that similar code is in ContactsView.vue exportContactData()
if (this.isExporting) {
return; // Prevent multiple simultaneous exports
}

View File

@@ -101,6 +101,7 @@ import {
import { Router } from "vue-router";
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
import { logger } from "@/utils/logger";
@Component({
components: {
@@ -119,11 +120,13 @@ export default class FeedFilters extends Vue {
isNearby = false;
settingChanged = false;
visible = false;
activeDid = "";
async open(onCloseIfChanged: () => void) {
async open(onCloseIfChanged: () => void, activeDid: string) {
this.onCloseIfChanged = onCloseIfChanged;
this.activeDid = activeDid;
const settings = await this.$settings();
const settings = await this.$accountSettings(activeDid);
this.hasVisibleDid = !!settings.filterFeedByVisible;
this.isNearby = !!settings.filterFeedByNearby;
if (settings.searchBoxes && settings.searchBoxes.length > 0) {
@@ -137,6 +140,7 @@ export default class FeedFilters extends Vue {
async toggleHasVisibleDid() {
this.settingChanged = true;
this.hasVisibleDid = !this.hasVisibleDid;
await this.$updateSettings({
filterFeedByVisible: this.hasVisibleDid,
});
@@ -145,9 +149,18 @@ export default class FeedFilters extends Vue {
async toggleNearby() {
this.settingChanged = true;
this.isNearby = !this.isNearby;
logger.debug("[FeedFilters] 🔄 Toggling nearby filter:", {
newValue: this.isNearby,
settingChanged: this.settingChanged,
activeDid: this.activeDid,
});
await this.$updateSettings({
filterFeedByNearby: this.isNearby,
});
logger.debug("[FeedFilters] ✅ Nearby filter updated in settings");
}
async clearAll() {
@@ -179,43 +192,27 @@ export default class FeedFilters extends Vue {
}
close() {
logger.debug("[FeedFilters] 🚪 Closing dialog:", {
settingChanged: this.settingChanged,
hasCallback: !!this.onCloseIfChanged,
});
if (this.settingChanged) {
logger.debug("[FeedFilters] 🔄 Settings changed, calling callback");
this.onCloseIfChanged();
}
this.visible = false;
}
done() {
logger.debug("[FeedFilters] ✅ Done button clicked");
this.close();
}
}
</script>
<style>
.dialog-overlay {
z-index: 50;
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;
}
#dialogFeedFilters.dialog-overlay {
z-index: 100;
overflow: scroll;
}
.dialog {
background-color: white;
padding: 1rem;
border-radius: 0.5rem;
width: 100%;
max-width: 500px;
}
</style>

View File

@@ -608,7 +608,10 @@ export default class GiftedDialog extends Vue {
* Handle edit entity request from GiftDetailsStep
* @param data - Object containing entityType and currentEntity
*/
handleEditEntity(data: { entityType: string; currentEntity: any }) {
handleEditEntity(data: {
entityType: string;
currentEntity: { did: string; name: string };
}) {
this.goBackToStep1(data.entityType);
}
@@ -648,27 +651,3 @@ export default class GiftedDialog extends Vue {
}
}
</script>
<style>
.dialog-overlay {
z-index: 50;
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>

View File

@@ -291,27 +291,3 @@ export default class GivenPrompts extends Vue {
}
}
</script>
<style>
.dialog-overlay {
z-index: 50;
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>

View File

@@ -1,9 +1,6 @@
<template>
<div
v-if="isOpen"
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
>
<div class="bg-white rounded-lg p-6 max-w-2xl w-full mx-4">
<div v-if="isOpen" class="dialog-overlay">
<div class="dialog">
<!-- Header -->
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold capitalize">{{ roleName }} Details</h2>

View File

@@ -1,90 +0,0 @@
<template>
<svg
v-if="iconData"
:class="svgClass"
:fill="fill"
:stroke="stroke"
:viewBox="viewBox"
xmlns="http://www.w3.org/2000/svg"
>
<path v-for="(path, index) in iconData.paths" :key="index" v-bind="path" />
</svg>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-facing-decorator";
import icons from "../assets/icons.json";
import { logger } from "../utils/logger";
/**
* Icon path interface
*/
interface IconPath {
d: string;
fillRule?: string;
clipRule?: string;
strokeLinecap?: string;
strokeLinejoin?: string;
strokeWidth?: string | number;
fill?: string;
stroke?: string;
}
/**
* Icon data interface
*/
interface IconData {
paths: IconPath[];
}
/**
* Icons JSON structure
*/
interface IconsJson {
[key: string]: IconPath | IconData;
}
/**
* Icon Renderer Component
*
* This component loads SVG icon definitions from a JSON file and renders them
* as SVG elements. It provides a clean way to use icons without cluttering
* templates with long SVG path definitions.
*
* @author Matthew Raymer
* @version 1.0.0
* @since 2024
*/
@Component({
name: "IconRenderer",
})
export default class IconRenderer extends Vue {
@Prop({ required: true }) readonly iconName!: string;
@Prop({ default: "h-5 w-5" }) readonly svgClass!: string;
@Prop({ default: "none" }) readonly fill!: string;
@Prop({ default: "currentColor" }) readonly stroke!: string;
@Prop({ default: "0 0 24 24" }) readonly viewBox!: string;
/**
* Get the icon data for the specified icon name
*
* @returns {IconData | null} The icon data object or null if not found
*/
get iconData(): IconData | null {
const icon = (icons as IconsJson)[this.iconName];
if (!icon) {
logger.warn(`Icon "${this.iconName}" not found in icons.json`);
return null;
}
// Convert single path to array format for consistency
if ("d" in icon) {
return {
paths: [icon as IconPath],
};
}
return icon as IconData;
}
}
</script>

View File

@@ -1,5 +1,5 @@
<template>
<div v-if="visible" class="dialog-overlay z-[60]">
<div v-if="visible" class="dialog-overlay">
<div class="dialog relative">
<div class="text-lg text-center font-bold relative">
<h1 id="ViewHeading" class="text-center font-bold">
@@ -931,32 +931,6 @@ export default class ImageMethodDialog extends Vue {
</script>
<style>
.dialog-overlay {
z-index: 50;
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: 700px;
max-height: 90vh;
overflow: hidden;
display: flex;
flex-direction: column;
}
/* Add styles for diagnostic panel */
.diagnostic-panel {
font-family: monospace;

View File

@@ -93,27 +93,3 @@ export default class InviteDialog extends Vue {
}
}
</script>
<style>
.dialog-overlay {
z-index: 50;
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>

View File

@@ -312,28 +312,3 @@ export default class OfferDialog extends Vue {
}
}
</script>
<style scoped>
.dialog-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 50;
}
.dialog {
background: white;
padding: 1.5rem;
border-radius: 0.5rem;
max-width: 500px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
}
</style>

View File

@@ -307,27 +307,3 @@ export default class OnboardingDialog extends Vue {
}
}
</script>
<style>
.dialog-overlay {
z-index: 40;
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>

View File

@@ -10,7 +10,7 @@ Comprehensive error handling * * @author Matthew Raymer * @version 1.0.0 * @file
PhotoDialog.vue */
<template>
<div v-if="visible" class="dialog-overlay z-[60]">
<div v-if="visible" class="dialog-overlay">
<div class="dialog relative">
<div class="text-lg text-center font-light relative z-50">
<div id="ViewHeading" :class="headingClasses">
@@ -628,34 +628,6 @@ export default class PhotoDialog extends Vue {
</script>
<style>
/* Dialog overlay styling */
.dialog-overlay {
z-index: 60;
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 container styling */
.dialog {
background-color: white;
padding: 1rem;
border-radius: 0.5rem;
width: 100%;
max-width: 700px;
max-height: 90vh;
overflow: hidden;
display: flex;
flex-direction: column;
}
/* Camera preview styling */
.camera-preview {
flex: 1;

View File

@@ -2,7 +2,7 @@
<!-- QUICK NAV -->
<nav
id="QuickNav"
class="fixed bottom-0 left-0 right-0 bg-slate-200 z-50 pb-[env(safe-area-inset-bottom)]"
class="fixed bottom-0 left-0 right-0 bg-slate-200 z-50 pb-[max(env(safe-area-inset-bottom),var(--safe-area-inset-bottom,0px))]"
>
<ul class="flex text-2xl px-6 py-2 gap-1 max-w-3xl mx-auto">
<!-- Home Feed -->

View File

@@ -1,33 +1,154 @@
/** * @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 Jose Olarte III * @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"
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 my-4"
>
<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"
>
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" />
</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 nearby with cameras",
option2Text: "Someone created a meeting room",
option3Text: "We will share some other way",
onOption1: () => {
this.handleQRCodeClick();
},
onOption2: () => {
this.$router.push({ name: "onboard-meeting-list" });
},
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>

View File

@@ -1,5 +1,7 @@
<template>
<div class="absolute right-5 top-[max(0.75rem,env(safe-area-inset-top))]">
<div
class="absolute right-5 top-[max(0.75rem,env(safe-area-inset-top),var(--safe-area-inset-top,0px))]"
>
<span class="align-center text-red-500 mr-2">{{ message }}</span>
<span class="ml-2">
<router-link

View File

@@ -83,6 +83,7 @@
<script lang="ts">
import { Component, Vue, Prop } from "vue-facing-decorator";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { EndorserRateLimits, ImageRateLimits } from "@/interfaces/limits";
@Component({
name: "UsageLimitsSection",
@@ -94,8 +95,8 @@ export default class UsageLimitsSection extends Vue {
@Prop({ required: true }) loadingLimits!: boolean;
@Prop({ required: true }) limitsMessage!: string;
@Prop({ required: false }) activeDid?: string;
@Prop({ required: false }) endorserLimits?: any;
@Prop({ required: false }) imageLimits?: any;
@Prop({ required: false }) endorserLimits?: EndorserRateLimits;
@Prop({ required: false }) imageLimits?: ImageRateLimits;
@Prop({ required: true }) onRecheckLimits!: () => void;
mounted() {

View File

@@ -134,27 +134,3 @@ export default class UserNameDialog extends Vue {
}
}
</script>
<style>
.dialog-overlay {
z-index: 50;
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>