feat(vue-dapp-auth): Add example dapp on Vue 3 (#76)

Co-authored-by: Ben Kremer <contact@bkrem.dev>
This commit is contained in:
Pavel Yankovski
2022-11-14 18:13:55 +01:00
committed by GitHub
co-authored by Ben Kremer
parent 882f9d10c8
commit 7e850d66c4
41 changed files with 8767 additions and 0 deletions
@@ -0,0 +1,39 @@
<template>
<div class="tw-card tw-w-full tw-max-w-xs tw-space-y-6">
<div class="tw-space-y-6">
<div class="tw-flex tw-justify-between tw-items-start tw-gap-2">
<avatar-image class="tw--ml-2" :src="avatar" :loading="isLoading" />
<connected-badge />
</div>
<h3>{{ formattedAddress }}</h3>
</div>
<hr>
<div class="tw-text-dim-1 tw-text-xl tw-flex tw-items-center tw-gap-4">
<p class="tw-flex-1">
Balance
</p>
<eth-balance :value="balance" :loading="isLoading" />
</div>
<button class="tw-button-secondary tw-text-lg tw-w-full" @click="resetConnection()">
Sign Out
</button>
</div>
</template>
<script setup lang="ts">
import truncate from 'smart-truncate'
import { useConnectionStore } from '../stores'
const props = defineProps<{
address: string
}>()
const { address } = toRefs(props)
const formattedAddress = computed(() => truncate(address.value, 12, { position: 7 }))
const { balance, avatar, isLoading } = useAccount(address)
const { reset: resetConnection } = useConnectionStore()
</script>
@@ -0,0 +1,18 @@
<template>
<div class="tw-border tw-border-muted tw-bg-placeholder tw-circle-24 tw-flex tw-items-center tw-justify-center">
<loading-spinner v-if="loading" class="tw-text-2xl" />
<img v-else-if="src" :src="src" :alt="alt">
</div>
</template>
<script setup lang="ts">
interface Props {
src?: string | null
loading?: boolean
alt?: string
}
withDefaults(defineProps<Props>(), {
alt: 'Avatar',
})
</script>
@@ -0,0 +1,56 @@
<template>
<div class="tw-w-full tw-relative tw-flex tw-flex-col tw-items-center">
<img
src="/img/auth.png"
alt="WalletConnect Auth Client logo"
class="tw-absolute tw--top-14 tw-size-20 tw-blur-md"
>
<img
src="/img/auth.png"
alt="WalletConnect Auth Client logo"
class="tw-absolute tw--top-14 tw-size-20 tw-blur-px"
>
<div class="tw-card tw-text-center tw-w-full tw-max-w-xs">
<h2 class="tw-mt-2.5">
Sign in
</h2>
<button
class="tw-button-primary tw-text-xl tw-justify-between tw-w-full sm:tw-min-w-[11em]"
:disabled="!initialized || isLoading"
@click="requestConnection()"
>
<img src="/img/wc.png" alt="WalletConnect logo" class="tw-h-4 tw-w-auto tw-mx-auto">
<span class="tw-flex-1 tw-text-center tw-hidden sm:tw-inline">
<template v-if="initialized">
WalletConnect
</template>
<template v-else>
Initializing...
</template>
</span>
</button>
<p v-if="error" class="tw-text-sm tw-text-state-error">
{{ error }}
</p>
</div>
</div>
</template>
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useConnectionStore } from '../stores'
const connectionStore = useConnectionStore()
const { error, initialized } = storeToRefs(connectionStore)
const isLoading = ref(false)
const requestConnection = async () => {
isLoading.value = true
await connectionStore.requestConnection()
isLoading.value = false
}
</script>
@@ -0,0 +1,6 @@
<template>
<div class="tw-text-dim-2 tw-inline-flex tw-items-center tw-gap-2">
<span class="tw-relative tw-circle-[0.5em] tw-bg-state-success tw-inline-flex before:tw-circle-[0.5em] before:tw-scale-[1.2] before:tw-bg-state-success before:tw-animate-ping before:tw-opacity-soft before:tw-absolute" />
Connected
</div>
</template>
@@ -0,0 +1,17 @@
<template>
<loading-spinner v-if="loading" class="tw-h-8" />
<div v-else class="tw-h-8 tw-leading-xs tw-inline-flex tw-items-center tw-gap-2">
<img src="/img/eth.png" alt="ETH" class="tw-size-8">
{{ value }} ETH
</div>
</template>
<script setup lang="ts">
interface Props {
value?: number | null
loading?: boolean
}
withDefaults(defineProps<Props>(), {
value: 0,
})
</script>
+60
View File
@@ -0,0 +1,60 @@
<template>
<div class="tw-card tw-text-center tw-space-y-8 tw-w-full tw-max-w-sm">
<qr-code
background="transparent"
:foreground="foreground"
class="qr-code tw-py-4"
:value="uri"
:size="400"
/>
<hr class="tw-mx-12">
<div class="tw-space-y-4 tw-flex tw-flex-col tw-items-center">
<div class="tw-text-center">
<h3 class="tw-text-dim-1">
Scan with your phone
</h3>
<p class="tw-text-dim-2">
Open your camera app or mobile wallet and scan the code to connect
</p>
</div>
<button class="tw-button-secondary tw-text-lg tw-w-full sm:tw-min-w-[7em]" @click="copyLink()">
<icon name="copy" />
{{ message }}
</button>
</div>
</div>
</template>
<script setup lang="ts">
import QrCode from 'qrcode.vue'
const props = defineProps<{
uri: string
}>()
const colorMode = useColorMode()
const foreground = computed(
() => colorMode.value === Theme.dark
? '#fff'
: '#000',
)
const message = refAutoReset('Copy to clipboard', 1000)
const copyLink = async () => {
await navigator.clipboard.writeText(props.uri)
message.value = 'Copied!'
}
</script>
<style scoped>
.qr-code {
@apply tw-mx-auto;
width: 100% !important;
height: auto !important;
max-width: theme('maxWidth.xs');
}
</style>
@@ -0,0 +1,33 @@
<template>
<lib-radio
v-model="$colorMode.preference"
:options="themeModes"
>
<template #option="{ option, checked }">
<a
v-if="option === 'system'"
role="button"
class="tw-radio-option"
:class="{ checked }"
>
<icon name="desktop" />
</a>
<a
v-else-if="option === 'light'"
role="button"
class="tw-radio-option"
:class="{ checked }"
>
<icon name="sun" class="tw-scale-[1.2]" />
</a>
<a
v-else-if="option === 'dark'"
role="button"
class="tw-radio-option"
:class="{ checked }"
>
<icon name="moon" />
</a>
</template>
</lib-radio>
</template>
@@ -0,0 +1,12 @@
<template>
<div class="tw-p-2.5 tw-rounded tw-inline-flex tw-items-center tw-gap-2 tw-bg-dim-1">
<img src="/img/wc-bg.png" alt="WalletConnect logo" class="tw-size-8">
<span class="tw-px-0.5">
v{{ authClientVersion }}
</span>
</div>
</template>
<script setup lang="ts">
const { authClientVersion } = useRuntimeConfig()
</script>
@@ -0,0 +1,50 @@
<template>
<radio-group v-model="model" class="tw-inline-flex tw-bg-dim-1 tw-p-3 tw-rounded tw-gap-2">
<radio-group-option
v-for="option, index of options"
:key="getKey(option, index)"
v-slot="{ checked }"
:value="getValue(option)"
>
<slot name="option" v-bind="{ option, checked }">
<div class="tw-radio-option tw-clickable" :class="{ checked }">
<radio-group-label as="div" class="tw-z-1">
<span class="tw-font-medium">
{{ option }}
</span>
</radio-group-label>
</div>
</slot>
</radio-group-option>
</radio-group>
</template>
<script setup lang="ts">
import {
RadioGroup,
RadioGroupLabel,
RadioGroupOption,
} from '@headlessui/vue'
type Value = any
type Option = any
interface Props {
modelValue: Value
options?: Option[]
// How to render list of the options
getKey?: (_option: Option, _index: number) => string | number
// Getter to a value to use as a model
getValue?: (_option: Option) => Value
}
const props = withDefaults(defineProps<Props>(), {
options: () => [],
getKey: (_option: Option, index: number) => index,
getValue: (option: Option) => option,
})
const model = useVModel(props)
</script>
@@ -0,0 +1,7 @@
<template>
<div class="tw-flex tw-justify-center tw-items-center">
<div class="tw-spinner-border tw-animate-spin tw-inline-block tw-circle-[1em] tw-border-[3px] tw-border-base tw-border-opacity-muted tw-border-l-transparent" role="status">
<span class="tw-hidden">Loading...</span>
</div>
</div>
</template>