@ -1,24 +1,104 @@
< template >
<!-- CONTENT -- >
< section id = "Content" class = "relativew-[100vw] h-[100vh]" >
< section id = "Content" class = "relative w-[100vw] h-[100vh]" >
< div
class = "absolute inset-x-0 bottom-0 bg-black/50 p-6 pb-[calc(env(safe-area-inset-bottom)+1.5rem)] "
class = "p-6 bg-white w-full max-w-[calc((100vh-env(safe-area-inset-top)-env(safe-area-inset-bottom))*0.4)] mx-auto "
>
< p class = "text-center text-white mb-3" >
Point your camera at a TimeSafari contact QR code to scan it
automatically .
< / p >
< div class = "mb-4" >
< h1 class = "text-xl text-center font-semibold relative" >
<!-- Back -- >
< a
class = "text-lg text-center px-2 py-1 absolute -left-2 -top-1"
@ click = "handleBack"
>
< font -awesome icon = "chevron-left" class = "fa-fw" / >
< / a >
<!-- Quick Help -- >
< a
class = "text-xl text-center text-blue-500 px-2 py-1 absolute -right-2 -top-1"
@ click = "toastQRCodeHelp()"
>
< font -awesome icon = "circle-question" class = "fa-fw" / >
< / a >
< p v-if ="error" class="text-center text-rose-300 mb-3" > {{ error }} < / p >
Share Contact Info
< / h1 >
< / div >
< div class = "flex justify-center items-center" >
< div
v - if = "!givenName"
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"
>
< p class = "mb-2" >
< b > Note : < / b > your identity currently does < b > not < / b > include a name .
< / p >
< button
class = "text-center text-slate-600 leading-none bg-white p-2 rounded-full drop-shadow-lg"
@ click = "handleBack"
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 = "openUserNameDialog "
>
< font -awesome icon = "xmark" class = "size-6" > < / f o n t - a w e s o m e >
Set Your Name
< / button >
< / div >
< UserNameDialog ref = "userNameDialog" / >
< div
v - if = "activeDid && activeDid.startsWith(ETHR_DID_PREFIX)"
class = "block w-full max-w-[calc((100vh-env(safe-area-inset-top)-env(safe-area-inset-bottom))*0.4)] mx-auto mt-4"
@ click = "onCopyUrlToClipboard()"
>
<!--
Play with display options : https : / / q r - c o d e - s t y l i n g . c o m /
See docs : https : / / w w w . n p m j s . c o m / p a c k a g e / q r - c o d e - g e n e r a t o r - v u e 3
-- >
< QRCodeVue3
: value = "qrValue"
: width = "606"
: height = "606"
: corners - square - options = "{ type: 'square' }"
: dots - options = "{ type: 'square', color: '#000' }"
/ >
< / div >
< div v -else -if = " activeDid " class = "text-center mt-4" >
<!-- Not an ETHR DID so force them to paste it . ( Passkey Peer DIDs are too big . ) -- >
< span class = "text-blue-500" @click ="onCopyDidToClipboard()" >
Click here to copy your DID to your clipboard .
< / span >
< span >
Then give it to them so they can paste it in their list of People .
< / span >
< / div >
< div v -else class = "text-center mt-4" >
You have no identitifiers yet , so
< router -link
: to = "{ name: 'start' }"
class = "bg-blue-500 text-white px-1.5 py-1 rounded-md"
>
create your identifier .
< / r o u t e r - l i n k >
< br / >
If you don 't do that first, these contacts won' t see your activity .
< / div >
< / div >
< div
class = "relative w-full max-w-[calc((100vh-env(safe-area-inset-top)-env(safe-area-inset-bottom))*0.4)] mx-auto border border-dashed border-white mt-8 aspect-square"
>
< p
class = "absolute top-0 left-0 right-0 bg-black bg-opacity-50 text-white text-sm text-center py-2 z-10"
>
Position QR code in the frame
< / p >
< p
v - if = "error"
class = "absolute bottom-0 left-0 right-0 bg-black bg-opacity-50 text-sm text-center py-2 z-20 text-rose-400"
>
{ { error } }
< / p >
< / div >
< / section >
< / template >
@ -33,18 +113,29 @@ import { NotificationIface } from "../constants/app";
import { db } from "../db/index" ;
import { Contact } from "../db/tables/contacts" ;
import { getContactJwtFromJwtUrl } from "../libs/crypto" ;
import { decodeEndorserJwt } from "../libs/crypto/vc" ;
import { decodeEndorserJwt , ETHR_DID_PREFIX } from "../libs/crypto/vc" ;
import { retrieveSettingsForActiveAccount } from "../db/index" ;
import { setVisibilityUtil } from "../libs/endorserServer" ;
import { useClipboard } from "@vueuse/core" ;
import QRCodeVue3 from "qr-code-generator-vue3" ;
import UserNameDialog from "../components/UserNameDialog.vue" ;
import { generateEndorserJwtUrlForAccount } from "../libs/endorserServer" ;
import { retrieveAccountMetadata } from "../libs/util" ;
interface QRScanResult {
rawValue ? : string ;
barcode ? : string ;
}
interface IUserNameDialog {
open : ( callback : ( name : string ) => void ) => void ;
}
@ Component ( {
components : {
QuickNav ,
QRCodeVue3 ,
UserNameDialog ,
} ,
} )
export default class ContactQRScan extends Vue {
@ -55,11 +146,14 @@ export default class ContactQRScan extends Vue {
error : string | null = null ;
activeDid = "" ;
apiServer = "" ;
givenName = "" ;
qrValue = "" ;
ETHR_DID_PREFIX = ETHR_DID_PREFIX ;
/ / A d d n e w p r o p e r t i e s t o t r a c k s c a n n i n g s t a t e
private lastScannedValue : string = "" ;
private lastScanTime : number = 0 ;
private readonly SCAN_DEBOUNCE_MS = 2 000; / / P r e v e n t d u p l i c a t e s c a n s w i t h i n 2 s e c o n d s
private readonly SCAN_DEBOUNCE_MS = 5 000; / / I n c r e a s e d f r o m 2 0 0 0 t o 5 0 0 0 m s t o b e t t e r h a n d l e m o b i l e s c a n n i n g
/ / A d d c l e a n u p t r a c k i n g
private isCleaningUp = false ;
@ -70,6 +164,21 @@ export default class ContactQRScan extends Vue {
const settings = await retrieveSettingsForActiveAccount ( ) ;
this . activeDid = settings . activeDid || "" ;
this . apiServer = settings . apiServer || "" ;
this . givenName = settings . firstName || "" ;
const account = await retrieveAccountMetadata ( this . activeDid ) ;
if ( account ) {
const name =
( settings . firstName || "" ) +
( settings . lastName ? ` ${ settings . lastName } ` : "" ) ;
this . qrValue = await generateEndorserJwtUrlForAccount (
account ,
! ! settings . isRegistered ,
name ,
settings . profileImageUrl || "" ,
false ,
) ;
}
} catch ( error ) {
logger . error ( "Error initializing component:" , {
error : error instanceof Error ? error . message : String ( error ) ,
@ -270,14 +379,12 @@ export default class ContactQRScan extends Vue {
notes : contactInfo . notes || "" ,
} ;
/ / A d d c o n t a c t a n d s t o p s c a n n i n g
/ / A d d c o n t a c t b u t k e e p s c a n n i n g
logger . info ( "Adding new contact to database:" , {
did : contact . did ,
name : contact . name ,
} ) ;
await this . addNewContact ( contact ) ;
await this . stopScanning ( ) ;
this . $router . back ( ) ; / / R e t u r n t o p r e v i o u s v i e w a f t e r s u c c e s s f u l s c a n
} catch ( error ) {
logger . error ( "Error processing contact QR code:" , {
error : error instanceof Error ? error . message : String ( error ) ,
@ -420,6 +527,56 @@ export default class ContactQRScan extends Vue {
await this . cleanupScanner ( ) ;
this . $router . back ( ) ;
}
toastQRCodeHelp ( ) {
this . $notify (
{
group : "alert" ,
type : "info" ,
title : "QR Code Help" ,
text : "Click the QR code to copy your contact info to your clipboard." ,
} ,
5000 ,
) ;
}
onCopyUrlToClipboard ( ) {
useClipboard ( )
. copy ( this . qrValue )
. then ( ( ) => {
this . $notify (
{
group : "alert" ,
type : "toast" ,
title : "Copied" ,
text : "Contact URL was copied to clipboard." ,
} ,
2000 ,
) ;
} ) ;
}
onCopyDidToClipboard ( ) {
useClipboard ( )
. copy ( this . activeDid )
. then ( ( ) => {
this . $notify (
{
group : "alert" ,
type : "info" ,
title : "Copied" ,
text : "Your DID was copied to the clipboard. Have them paste it in the box on their 'People' screen to add you." ,
} ,
5000 ,
) ;
} ) ;
}
openUserNameDialog ( ) {
( this . $refs . userNameDialog as IUserNameDialog ) . open ( ( name : string ) => {
this . givenName = name ;
} ) ;
}
}
< / script >