feat(vue-dapp-auth): Add example dapp on Vue 3 (#76)
Co-authored-by: Ben Kremer <contact@bkrem.dev>
This commit is contained in:
parent
882f9d10c8
commit
7e850d66c4
2
dapps/vue-dapp-auth/.env.example
Normal file
2
dapps/vue-dapp-auth/.env.example
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
WALLETCONNECT_PROJECT_ID=...
|
||||||
|
WALLETCONNECT_RELAY_URL=wss://relay.walletconnect.com
|
67
dapps/vue-dapp-auth/.eslintrc
Normal file
67
dapps/vue-dapp-auth/.eslintrc
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
{
|
||||||
|
"extends": "@antfu",
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"argsIgnorePattern": "^_",
|
||||||
|
"destructuredArrayIgnorePattern": "^_"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@typescript-eslint/array-type": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"default": "array"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"camelcase": "off",
|
||||||
|
"import/named": "off",
|
||||||
|
"no-useless-constructor": "off",
|
||||||
|
"no-control-regex": "off",
|
||||||
|
"no-console": "warn",
|
||||||
|
"@typescript-eslint/brace-style": "off",
|
||||||
|
"brace-style": [
|
||||||
|
"error",
|
||||||
|
"1tbs"
|
||||||
|
],
|
||||||
|
"curly": [
|
||||||
|
"error",
|
||||||
|
"all"
|
||||||
|
],
|
||||||
|
"@typescript-eslint/space-before-function-paren": "off",
|
||||||
|
"space-before-function-paren": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"anonymous": "never",
|
||||||
|
"named": "never",
|
||||||
|
"asyncArrow": "always"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"vue/max-attributes-per-line": ["error", {
|
||||||
|
"singleline": {
|
||||||
|
"max": 3
|
||||||
|
},
|
||||||
|
"multiline": {
|
||||||
|
"max": 1
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
"vue/component-tags-order": ["error", {
|
||||||
|
"order": [ "template", "script", "style" ]
|
||||||
|
}],
|
||||||
|
"vue/custom-event-name-casing": ["error", "kebab-case"],
|
||||||
|
"vue/no-deprecated-v-on-native-modifier": "off",
|
||||||
|
"vue/no-deprecated-dollar-listeners-api": "off",
|
||||||
|
"vue/no-deprecated-v-bind-sync": "off",
|
||||||
|
"vue/no-deprecated-dollar-scopedslots-api": "off",
|
||||||
|
"vue/no-deprecated-filter": "off",
|
||||||
|
"vue/require-explicit-emits": "off",
|
||||||
|
"vue/no-deprecated-destroyed-lifecycle": "off",
|
||||||
|
"vue/component-name-in-template-casing": ["error", "kebab-case"],
|
||||||
|
"vue/multiline-html-element-content-newline": ["error", {
|
||||||
|
"ignores": ["pre", "textarea", "nuxt-link", "a", "abbr", "audio", "b", "bdi", "bdo", "canvas", "cite", "code", "data", "del", "dfn", "em", "i", "iframe", "ins", "kbd", "label", "map", "mark", "noscript", "object", "output", "picture", "q", "ruby", "s", "samp", "small", "span", "strong", "sub", "sup", "svg", "time", "u", "var", "video"]
|
||||||
|
}],
|
||||||
|
"vue/singleline-html-element-content-newline": ["error", {
|
||||||
|
"ignores": ["pre", "textarea", "nuxt-link", "a", "abbr", "audio", "b", "bdi", "bdo", "canvas", "cite", "code", "data", "del", "dfn", "em", "i", "iframe", "ins", "kbd", "label", "map", "mark", "noscript", "object", "output", "picture", "q", "ruby", "s", "samp", "small", "span", "strong", "sub", "sup", "svg", "time", "u", "var", "video"]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
96
dapps/vue-dapp-auth/.gitignore
vendored
Normal file
96
dapps/vue-dapp-auth/.gitignore
vendored
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
# Created by .ignore support plugin (hsz.mobi)
|
||||||
|
### Node template
|
||||||
|
# Logs
|
||||||
|
/logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# TypeScript v1 declaration files
|
||||||
|
typings/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!*.example
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# next.js build output
|
||||||
|
.next
|
||||||
|
|
||||||
|
# nuxt.js build output
|
||||||
|
.nuxt
|
||||||
|
|
||||||
|
# Nuxt generate
|
||||||
|
dist
|
||||||
|
.output
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless
|
||||||
|
|
||||||
|
# IDE / Editor
|
||||||
|
.idea
|
||||||
|
.history
|
||||||
|
|
||||||
|
# Service worker
|
||||||
|
sw.*
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Vim swap files
|
||||||
|
*.swp
|
||||||
|
|
||||||
|
node_modules
|
61
dapps/vue-dapp-auth/README.md
Normal file
61
dapps/vue-dapp-auth/README.md
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
# Vue Auth dApp
|
||||||
|
|
||||||
|
### Stack
|
||||||
|
- 💚 Vue 3
|
||||||
|
- ⛰️ Nuxt 3
|
||||||
|
- 🍍 Pinia
|
||||||
|
- 🟦 TypeScript
|
||||||
|
- 💨 TailwindCSS
|
||||||
|
- 🔗 ethers.js
|
||||||
|
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This example aims to demonstrate dapp-facing use cases enabled by WalletConnect Auth Client.
|
||||||
|
|
||||||
|
...And show that you can easily use WalletConnect with any framework.
|
||||||
|
|
||||||
|
## Running locally
|
||||||
|
|
||||||
|
Install the app's dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn
|
||||||
|
```
|
||||||
|
|
||||||
|
Set up your local environment variables by copying the example into your own `.env` file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Your `.env` now contains the following environment variables:
|
||||||
|
|
||||||
|
- `WALLETCONNECT_PROJECT_ID` (placeholder) - You can generate your own ProjectId at https://cloud.walletconnect.com
|
||||||
|
|
||||||
|
Also, the default relay server `WALLETCONNECT_RELAY_URL` is set. You can change it to use your own instance.
|
||||||
|
|
||||||
|
|
||||||
|
## Development Server
|
||||||
|
|
||||||
|
Start the development server on http://localhost:3000
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Production
|
||||||
|
|
||||||
|
Build the application for production:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn build
|
||||||
|
```
|
||||||
|
|
||||||
|
Locally preview production build:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn preview
|
||||||
|
```
|
||||||
|
|
||||||
|
Checkout the [deployment documentation](https://v3.nuxtjs.org/guide/deploy/presets) for more information.
|
17
dapps/vue-dapp-auth/app.config.ts
Normal file
17
dapps/vue-dapp-auth/app.config.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
export default defineAppConfig({
|
||||||
|
nuxtIcon: {
|
||||||
|
aliases: {
|
||||||
|
'close': 'ion:close-outline',
|
||||||
|
'chevron-down': 'ion:chevron-down-outline',
|
||||||
|
'desktop': 'ion:desktop-outline',
|
||||||
|
'sun': 'ion:sunny-outline',
|
||||||
|
'moon': 'ion:moon-outline',
|
||||||
|
'open': 'ion:open-outline',
|
||||||
|
'github': 'ion:logo-github',
|
||||||
|
'link': 'ion:link-outline',
|
||||||
|
'gem': 'ion:diamond-outline',
|
||||||
|
'copy': 'ion:copy-outline',
|
||||||
|
'qr': 'ion:qr-code-outline',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
27
dapps/vue-dapp-auth/app.vue
Normal file
27
dapps/vue-dapp-auth/app.vue
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<template>
|
||||||
|
<main class="tw-py-8 tw-container tw-h-screen tw-flex tw-flex-col">
|
||||||
|
<div class="tw-flex tw-justify-center">
|
||||||
|
<nuxt-link :to="{ name: 'index' }" class="tw-pl-2.5 tw-text-lg tw-inline-flex tw-items-center tw-gap-3">
|
||||||
|
<span class="tw-leading-xs">
|
||||||
|
Example Vue App
|
||||||
|
</span>
|
||||||
|
<wc-label />
|
||||||
|
</nuxt-link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tw-section tw-flex-1">
|
||||||
|
<nuxt-page />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="tw-flex tw-justify-end">
|
||||||
|
<switch-theme />
|
||||||
|
</footer>
|
||||||
|
</main>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const { authClientVersion } = useRuntimeConfig()
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`AuthClient@${authClientVersion}`)
|
||||||
|
</script>
|
22
dapps/vue-dapp-auth/assets/scss/_base.scss
Normal file
22
dapps/vue-dapp-auth/assets/scss/_base.scss
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
@layer base {
|
||||||
|
html {
|
||||||
|
font-size: 16px; // 1rem
|
||||||
|
@apply tw-font-main tw-leading-sm tw-bg-base tw-text-base;
|
||||||
|
}
|
||||||
|
::selection {
|
||||||
|
@apply tw-bg-accent-secondary tw-text-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
@apply tw-cursor-pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
@apply tw-text-divider;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
button {
|
||||||
|
outline: none !important;
|
||||||
|
}
|
||||||
|
}
|
65
dapps/vue-dapp-auth/assets/scss/_components.scss
Normal file
65
dapps/vue-dapp-auth/assets/scss/_components.scss
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
@layer components {
|
||||||
|
.tw-section {
|
||||||
|
@apply tw-py-8;
|
||||||
|
@screen md {
|
||||||
|
@apply tw-py-12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cards
|
||||||
|
.tw-card {
|
||||||
|
@apply tw-shadow-card tw-rounded-lg tw-bg-dim-1 tw-p-8 sm:tw-px-10;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
.tw-clickable {
|
||||||
|
@apply tw-cursor-pointer tw-gap-1 tw-rounded tw-font-medium tw-inline-flex tw-items-center tw-justify-center tw-duration-onhover-fast;
|
||||||
|
@apply active:tw-scale-click;
|
||||||
|
@apply disabled:tw-pointer-events-none disabled:tw-opacity-muted;
|
||||||
|
}
|
||||||
|
.tw-button {
|
||||||
|
@apply tw-clickable;
|
||||||
|
@apply tw-text-custom tw-leading-xs tw-bg-custom tw-bg-opacity-custom;
|
||||||
|
@apply tw-text-sm tw-h-[2.5em] tw-p-[0.75em];
|
||||||
|
@apply tw-relative before:tw-absolute before:tw-inset-0 before:tw-border before:tw-border-solid before:tw-border-custom before:tw-border-opacity-custom;
|
||||||
|
@apply before:tw-duration-fast before:tw-rounded;
|
||||||
|
@apply focus:tw-ring-2 focus:tw-ring-accent-primary focus:tw-ring-opacity-outline;
|
||||||
|
&::before {
|
||||||
|
mask-image: linear-gradient(to right, rgba(white, 0.5), rgba(white, 1), rgba(white, 0.5));
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
--bg-opacity: var(--bg-opacity-hover);
|
||||||
|
&::before {
|
||||||
|
--border-opacity: var(--border-opacity-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tw-button-primary {
|
||||||
|
@apply tw-button;
|
||||||
|
--text-color: var(--c-button-primary-color);
|
||||||
|
--bg-color: var(--c-button-primary-bg);
|
||||||
|
--border-color: var(--c-button-primary-border);
|
||||||
|
--bg-opacity: var(--o-button-primary-bg);
|
||||||
|
--border-opacity: var(--o-button-primary-border);
|
||||||
|
--bg-opacity-hover: var(--o-button-primary-bg-hover);
|
||||||
|
--border-opacity-hover: var(--o-button-primary-border-hover);
|
||||||
|
}
|
||||||
|
.tw-button-secondary {
|
||||||
|
@apply tw-button;
|
||||||
|
--text-color: var(--c-button-secondary-color);
|
||||||
|
--bg-color: var(--c-button-secondary-bg);
|
||||||
|
--border-color: var(--c-button-secondary-border);
|
||||||
|
--bg-opacity: var(--o-button-secondary-bg);
|
||||||
|
--border-opacity: var(--o-button-secondary-border);
|
||||||
|
--bg-opacity-hover: var(--o-button-secondary-bg-hover);
|
||||||
|
--border-opacity-hover: var(--o-button-secondary-border-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
// radio
|
||||||
|
.tw-radio-option {
|
||||||
|
@apply tw-size-6 tw-flex tw-items-center tw-justify-center tw-text-dim-3 hover:tw-text-dim-2 tw-duration-onhover-fast;
|
||||||
|
&.checked {
|
||||||
|
@apply tw-text-dim-1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
dapps/vue-dapp-auth/assets/scss/_palette.scss
Normal file
6
dapps/vue-dapp-auth/assets/scss/_palette.scss
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
@mixin palette {
|
||||||
|
--c-black: 0, 0, 0;
|
||||||
|
--c-white: 255, 255, 255;
|
||||||
|
--c-accent-primary: 51, 150, 255; // #3396FF
|
||||||
|
--c-accent-secondary: 121, 48, 217; // #7930D9
|
||||||
|
}
|
22
dapps/vue-dapp-auth/assets/scss/tailwind.scss
Normal file
22
dapps/vue-dapp-auth/assets/scss/tailwind.scss
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
@use 'themes';
|
||||||
|
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@import 'palette';
|
||||||
|
@import 'base';
|
||||||
|
@import 'components';
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
:root {
|
||||||
|
/* COMMON PALETTE */
|
||||||
|
@include palette;
|
||||||
|
|
||||||
|
// Default: light mode
|
||||||
|
@include themes.light-mode;
|
||||||
|
}
|
||||||
|
.dark-mode {
|
||||||
|
@include themes.dark-mode;
|
||||||
|
}
|
||||||
|
}
|
38
dapps/vue-dapp-auth/assets/scss/themes/_dark.scss
Normal file
38
dapps/vue-dapp-auth/assets/scss/themes/_dark.scss
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
@mixin dark-mode {
|
||||||
|
/* text */
|
||||||
|
--c-text-base: var(--c-white);
|
||||||
|
--c-text-dim-1: 225, 225, 229;
|
||||||
|
--c-text-dim-2: 187, 187, 187;
|
||||||
|
--c-text-dim-3: 124, 124, 124;
|
||||||
|
|
||||||
|
/* bg */
|
||||||
|
--c-bg-base: 30, 30, 30; // #1e1e1e
|
||||||
|
--c-bg-dim-1: 39, 42, 42; // #272a2a
|
||||||
|
--c-bg-placeholder: 20, 20, 20;
|
||||||
|
|
||||||
|
--c-divider: 64, 64, 64;
|
||||||
|
--c-divider-muted: 54, 54, 54;
|
||||||
|
|
||||||
|
--c-state-error: 255, 71, 117;
|
||||||
|
--c-state-success: 71, 255, 197;
|
||||||
|
|
||||||
|
/* buttons */
|
||||||
|
// primary
|
||||||
|
--c-button-primary-color: var(--c-text-base);
|
||||||
|
--c-button-primary-bg: var(--c-accent-primary);
|
||||||
|
--c-button-primary-border: var(--c-accent-primary);
|
||||||
|
--o-button-primary-bg: 1;
|
||||||
|
--o-button-primary-bg-hover: 0.8;
|
||||||
|
--o-button-primary-border: 1;
|
||||||
|
--o-button-primary-border-hover: 1;
|
||||||
|
// secondary
|
||||||
|
--c-button-secondary-color: var(--c-accent-primary);
|
||||||
|
--c-button-secondary-bg: var(--c-accent-primary);
|
||||||
|
--c-button-secondary-border: var(--c-accent-primary);
|
||||||
|
--o-button-secondary-bg: 0.1;
|
||||||
|
--o-button-secondary-bg-hover: 0.2;
|
||||||
|
--o-button-secondary-border: 0.3;
|
||||||
|
--o-button-secondary-border-hover: 0.6;
|
||||||
|
|
||||||
|
--o-outline: 0.3;
|
||||||
|
}
|
2
dapps/vue-dapp-auth/assets/scss/themes/_index.scss
Normal file
2
dapps/vue-dapp-auth/assets/scss/themes/_index.scss
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
@import 'dark';
|
||||||
|
@import 'light';
|
38
dapps/vue-dapp-auth/assets/scss/themes/_light.scss
Normal file
38
dapps/vue-dapp-auth/assets/scss/themes/_light.scss
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
@mixin light-mode {
|
||||||
|
/* text */
|
||||||
|
--c-text-base: 5, 5, 6;
|
||||||
|
--c-text-dim-1: 69, 77, 84;
|
||||||
|
--c-text-dim-2: 133, 144, 153;
|
||||||
|
--c-text-dim-3: 179, 185, 189;
|
||||||
|
|
||||||
|
/* bg */
|
||||||
|
--c-bg-base: 230, 235, 242;
|
||||||
|
--c-bg-dim-1: 249, 249, 255;
|
||||||
|
--c-bg-placeholder: 230, 235, 242;
|
||||||
|
|
||||||
|
--c-divider: 209, 215, 219;
|
||||||
|
--c-divider-muted: 230, 235, 242;
|
||||||
|
|
||||||
|
--c-state-error: 255, 71, 117;
|
||||||
|
--c-state-success: 71, 255, 197;
|
||||||
|
|
||||||
|
/* buttons */
|
||||||
|
// primary
|
||||||
|
--c-button-primary-color: var(--c-white);
|
||||||
|
--c-button-primary-bg: var(--c-accent-primary);
|
||||||
|
--c-button-primary-border: var(--c-accent-primary);
|
||||||
|
--o-button-primary-bg: 1;
|
||||||
|
--o-button-primary-bg-hover: 0.9;
|
||||||
|
--o-button-primary-border: 1;
|
||||||
|
--o-button-primary-border-hover: 1;
|
||||||
|
// secondary
|
||||||
|
--c-button-secondary-color: var(--c-accent-primary);
|
||||||
|
--c-button-secondary-bg: var(--c-accent-primary);
|
||||||
|
--c-button-secondary-border: var(--c-accent-primary);
|
||||||
|
--o-button-secondary-bg: 0.2;
|
||||||
|
--o-button-secondary-bg-hover: 0.25;
|
||||||
|
--o-button-secondary-border: 0.5;
|
||||||
|
--o-button-secondary-border-hover: 1;
|
||||||
|
|
||||||
|
--o-outline: 0.1;
|
||||||
|
}
|
39
dapps/vue-dapp-auth/components/AccountCard.vue
Normal file
39
dapps/vue-dapp-auth/components/AccountCard.vue
Normal file
@ -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>
|
18
dapps/vue-dapp-auth/components/AvatarImage.vue
Normal file
18
dapps/vue-dapp-auth/components/AvatarImage.vue
Normal file
@ -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>
|
56
dapps/vue-dapp-auth/components/ConnectView.vue
Normal file
56
dapps/vue-dapp-auth/components/ConnectView.vue
Normal file
@ -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>
|
6
dapps/vue-dapp-auth/components/ConnectedBadge.vue
Normal file
6
dapps/vue-dapp-auth/components/ConnectedBadge.vue
Normal file
@ -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>
|
17
dapps/vue-dapp-auth/components/EthBalance.vue
Normal file
17
dapps/vue-dapp-auth/components/EthBalance.vue
Normal file
@ -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
dapps/vue-dapp-auth/components/QrView.vue
Normal file
60
dapps/vue-dapp-auth/components/QrView.vue
Normal 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>
|
33
dapps/vue-dapp-auth/components/SwitchTheme.vue
Normal file
33
dapps/vue-dapp-auth/components/SwitchTheme.vue
Normal file
@ -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>
|
12
dapps/vue-dapp-auth/components/WcLabel.vue
Normal file
12
dapps/vue-dapp-auth/components/WcLabel.vue
Normal file
@ -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>
|
50
dapps/vue-dapp-auth/components/lib/Radio.vue
Normal file
50
dapps/vue-dapp-auth/components/lib/Radio.vue
Normal file
@ -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>
|
7
dapps/vue-dapp-auth/components/loading/Spinner.vue
Normal file
7
dapps/vue-dapp-auth/components/loading/Spinner.vue
Normal file
@ -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>
|
34
dapps/vue-dapp-auth/composables/useAccount.ts
Normal file
34
dapps/vue-dapp-auth/composables/useAccount.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import type { MaybeRef } from '@vueuse/shared'
|
||||||
|
import { providers } from 'ethers'
|
||||||
|
|
||||||
|
export const useAccount = (address: MaybeRef<string>) => {
|
||||||
|
const balance = ref<number | null>(null)
|
||||||
|
const avatar = ref<string | null>(null)
|
||||||
|
const isLoading = ref(false)
|
||||||
|
|
||||||
|
const updateAccountInfo = async () => {
|
||||||
|
const addressValue = unref(address)
|
||||||
|
if (addressValue) {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
const provider = providers.getDefaultProvider()
|
||||||
|
balance.value = (await provider.getBalance(addressValue)).toNumber()
|
||||||
|
avatar.value = await provider.getAvatar(addressValue)
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
} else {
|
||||||
|
avatar.value = null
|
||||||
|
balance.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register watcher for a ref
|
||||||
|
// or just call the effect once for regular value
|
||||||
|
if (isRef(address)) {
|
||||||
|
watchEffect(updateAccountInfo)
|
||||||
|
} else {
|
||||||
|
updateAccountInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
return { balance, avatar, isLoading }
|
||||||
|
}
|
9
dapps/vue-dapp-auth/composables/useThemes.ts
Normal file
9
dapps/vue-dapp-auth/composables/useThemes.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export enum Theme {
|
||||||
|
light = 'light',
|
||||||
|
dark = 'dark',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const themeModes = [
|
||||||
|
'system',
|
||||||
|
...Object.values(Theme),
|
||||||
|
]
|
29
dapps/vue-dapp-auth/nuxt.config.ts
Normal file
29
dapps/vue-dapp-auth/nuxt.config.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { version as authClientVersion } from '@walletconnect/auth-client/package.json'
|
||||||
|
|
||||||
|
// https://v3.nuxtjs.org/api/configuration/nuxt.config
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
modules: [
|
||||||
|
'@nuxtjs/tailwindcss',
|
||||||
|
'@nuxtjs/color-mode',
|
||||||
|
'nuxt-icon',
|
||||||
|
'@vueuse/nuxt',
|
||||||
|
'@pinia/nuxt',
|
||||||
|
],
|
||||||
|
|
||||||
|
tailwindcss: {
|
||||||
|
cssPath: '~/assets/scss/tailwind.scss',
|
||||||
|
},
|
||||||
|
|
||||||
|
runtimeConfig: {
|
||||||
|
public: {
|
||||||
|
/**
|
||||||
|
* Avoiding framework-specific prefixes for env vars
|
||||||
|
* see https://v3.nuxtjs.org/guide/features/runtime-config/#environment-variables
|
||||||
|
*/
|
||||||
|
WALLETCONNECT_PROJECT_ID: process.env.WALLETCONNECT_PROJECT_ID ?? '',
|
||||||
|
WALLETCONNECT_RELAY_URL: process.env.WALLETCONNECT_RELAY_URL ?? 'wss://relay.walletconnect.com',
|
||||||
|
|
||||||
|
authClientVersion,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
34
dapps/vue-dapp-auth/package.json
Normal file
34
dapps/vue-dapp-auth/package.json
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "nuxt build",
|
||||||
|
"dev": "nuxt dev",
|
||||||
|
"generate": "nuxt generate",
|
||||||
|
"preview": "nuxt preview",
|
||||||
|
"postinstall": "nuxt prepare",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"lintfix": "nr lint --fix"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@headlessui/vue": "^1.7.4",
|
||||||
|
"@nuxtjs/color-mode": "^3.1.5",
|
||||||
|
"@nuxtjs/tailwindcss": "^6.1.3",
|
||||||
|
"@pinia/nuxt": "^0.4.3",
|
||||||
|
"@vueuse/nuxt": "^9.1.1",
|
||||||
|
"@walletconnect/auth-client": "1.0.1",
|
||||||
|
"ethers": "^5.7.0",
|
||||||
|
"nuxt-icon": "^0.1.7",
|
||||||
|
"pinia": "^2.0.23",
|
||||||
|
"qrcode.vue": "^3.3.3",
|
||||||
|
"sass": "^1.43.3",
|
||||||
|
"smart-truncate": "^1.0.1",
|
||||||
|
"typescript": "^4.5.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@antfu/eslint-config": "^0.29.4",
|
||||||
|
"@types/smart-truncate": "^1.0.2",
|
||||||
|
"eslint": "^8.27.0",
|
||||||
|
"nuxt": "3.0.0-rc.13"
|
||||||
|
}
|
||||||
|
}
|
29
dapps/vue-dapp-auth/pages/404.vue
Normal file
29
dapps/vue-dapp-auth/pages/404.vue
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tw-h-full tw-flex tw-items-center tw-justify-center">
|
||||||
|
<div class="tw-items-baseline tw-divide-x tw-divide-base tw-divide-opacity-muted tw-gap-4">
|
||||||
|
<div class="status-code tw-px-3">
|
||||||
|
404
|
||||||
|
</div>
|
||||||
|
<h2 class="tw-px-3 tw-font-thin">
|
||||||
|
Not found
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="css">
|
||||||
|
.status-code {
|
||||||
|
@apply tw-h1 tw-text-accent-primary tw-bg-no-repeat tw-relative;
|
||||||
|
background: url('/img/auth.png');
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
color: transparent;
|
||||||
|
background-size: 200%;
|
||||||
|
background-position: center center;
|
||||||
|
filter: drop-shadow(0 0 6px var(--c-accent-secondary));
|
||||||
|
&::after {
|
||||||
|
@apply tw-absolute tw-blur-xs tw-opacity-muted tw-top-0 tw-left-1/2 tw--translate-x-1/2 tw-text-accent-primary;
|
||||||
|
content: "404";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
20
dapps/vue-dapp-auth/pages/index.vue
Normal file
20
dapps/vue-dapp-auth/pages/index.vue
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tw-h-full tw-flex tw-items-center tw-justify-center">
|
||||||
|
<client-only>
|
||||||
|
<account-card v-if="address" :address="address" />
|
||||||
|
<qr-view v-else-if="connectUri" :uri="connectUri" />
|
||||||
|
<connect-view v-else />
|
||||||
|
</client-only>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { useConnectionStore } from '../stores'
|
||||||
|
|
||||||
|
const connectionStore = useConnectionStore()
|
||||||
|
const { connectUri, address } = storeToRefs(connectionStore)
|
||||||
|
|
||||||
|
// Init auth client
|
||||||
|
onMounted(connectionStore.init)
|
||||||
|
</script>
|
5
dapps/vue-dapp-auth/plugins/globals.client.ts
Normal file
5
dapps/vue-dapp-auth/plugins/globals.client.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// Adjustment for AuthClient
|
||||||
|
// relying on the `global` field that webpack exposes (but vite doesn't)
|
||||||
|
window.global ||= window
|
||||||
|
|
||||||
|
export default {}
|
BIN
dapps/vue-dapp-auth/public/favicon.ico
Normal file
BIN
dapps/vue-dapp-auth/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
BIN
dapps/vue-dapp-auth/public/img/auth.png
Normal file
BIN
dapps/vue-dapp-auth/public/img/auth.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.2 KiB |
BIN
dapps/vue-dapp-auth/public/img/eth.png
Normal file
BIN
dapps/vue-dapp-auth/public/img/eth.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
dapps/vue-dapp-auth/public/img/wc-bg.png
Normal file
BIN
dapps/vue-dapp-auth/public/img/wc-bg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
BIN
dapps/vue-dapp-auth/public/img/wc.png
Normal file
BIN
dapps/vue-dapp-auth/public/img/wc.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 368 B |
1
dapps/vue-dapp-auth/stores/index.ts
Normal file
1
dapps/vue-dapp-auth/stores/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './useConnectionStore'
|
100
dapps/vue-dapp-auth/stores/useConnectionStore.ts
Normal file
100
dapps/vue-dapp-auth/stores/useConnectionStore.ts
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import { acceptHMRUpdate, defineStore, skipHydrate } from 'pinia'
|
||||||
|
import AuthClient, { generateNonce } from '@walletconnect/auth-client'
|
||||||
|
|
||||||
|
export const useConnectionStore = defineStore('connection', () => {
|
||||||
|
const address = useLocalStorage<string | null>('address', null)
|
||||||
|
const connectUri = ref<string | null>(null)
|
||||||
|
|
||||||
|
const client = ref<AuthClient | null>(null)
|
||||||
|
const initialized = computed(() => !!client.value)
|
||||||
|
|
||||||
|
// reactive auth error
|
||||||
|
const error = ref<string | null>(null)
|
||||||
|
|
||||||
|
const setError = (e: unknown) => {
|
||||||
|
error.value = e?.toString() ?? null
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init client and listen to auth events
|
||||||
|
const config = useRuntimeConfig()
|
||||||
|
const init = () => {
|
||||||
|
AuthClient.init({
|
||||||
|
relayUrl: config.WALLETCONNECT_RELAY_URL,
|
||||||
|
projectId: config.WALLETCONNECT_PROJECT_ID,
|
||||||
|
metadata: {
|
||||||
|
name: 'vue-dapp-auth',
|
||||||
|
description: 'Vue 3 Example Dapp for Auth',
|
||||||
|
url: window.location.host,
|
||||||
|
icons: [`${window.location.origin}/img/wc-bg.png`],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((authClient) => {
|
||||||
|
client.value = authClient
|
||||||
|
})
|
||||||
|
.catch(setError)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(client, () => {
|
||||||
|
if (!client.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client.value.on('auth_response', ({ params }) => {
|
||||||
|
if ('code' in params) {
|
||||||
|
return setError(params.message)
|
||||||
|
}
|
||||||
|
if ('error' in params) {
|
||||||
|
return setError(params.error)
|
||||||
|
}
|
||||||
|
address.value = params.result.p.iss.split(':')[4]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const reset = () => {
|
||||||
|
address.value = null
|
||||||
|
connectUri.value = null
|
||||||
|
error.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// In case if the address is removed from the localStorage side,
|
||||||
|
// clear all the connection vars
|
||||||
|
watch(address, (newAddress, oldAddress) => {
|
||||||
|
if (oldAddress && !newAddress) {
|
||||||
|
reset()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const requestConnection = async () => {
|
||||||
|
if (!client.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const { uri } = await client.value.request({
|
||||||
|
aud: window.location.href,
|
||||||
|
domain: window.location.hostname.split('.').slice(-2).join('.'),
|
||||||
|
chainId: 'eip155:1',
|
||||||
|
type: 'eip4361',
|
||||||
|
nonce: generateNonce(),
|
||||||
|
statement: 'Sign in with wallet.',
|
||||||
|
})
|
||||||
|
connectUri.value = uri
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Skip hydration from init state
|
||||||
|
// as need to init only on the client side from localStorage
|
||||||
|
address: skipHydrate(address),
|
||||||
|
|
||||||
|
connectUri,
|
||||||
|
initialized,
|
||||||
|
error,
|
||||||
|
|
||||||
|
init,
|
||||||
|
reset,
|
||||||
|
|
||||||
|
requestConnection,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.accept(acceptHMRUpdate(useConnectionStore, import.meta.hot))
|
||||||
|
}
|
190
dapps/vue-dapp-auth/tailwind.config.js
Normal file
190
dapps/vue-dapp-auth/tailwind.config.js
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
const plugin = require('tailwindcss/plugin')
|
||||||
|
const addHeaders = require('./tailwind/headers')
|
||||||
|
|
||||||
|
// get formatted color, by color and opacity
|
||||||
|
function c(color, opacityValue) {
|
||||||
|
return opacityValue === undefined
|
||||||
|
? `rgb(var(${color}))`
|
||||||
|
: `rgba(var(${color}), ${opacityValue})`
|
||||||
|
}
|
||||||
|
|
||||||
|
// get color-maker method receiving an opacity as the input, by color
|
||||||
|
function co(color) {
|
||||||
|
return ({ opacityValue }) => c(color, opacityValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read more about tailwindcss configuration: https://tailwindcss.com/docs/configuration
|
||||||
|
module.exports = {
|
||||||
|
mode: 'jit',
|
||||||
|
prefix: 'tw-',
|
||||||
|
safelist: [
|
||||||
|
'light-mode',
|
||||||
|
'dark-mode',
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
// structure
|
||||||
|
container: {
|
||||||
|
center: true,
|
||||||
|
padding: {
|
||||||
|
DEFAULT: '1.5rem',
|
||||||
|
md: '2rem',
|
||||||
|
lg: '2.5rem',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
main: [
|
||||||
|
'-apple-system',
|
||||||
|
'"Segoe UI"',
|
||||||
|
'Helvetica',
|
||||||
|
'Arial',
|
||||||
|
'sans-serif',
|
||||||
|
'"Apple Color Emoji"',
|
||||||
|
'"Segoe UI Emoji"',
|
||||||
|
'"Segoe UI Symbol"',
|
||||||
|
],
|
||||||
|
mono: ['monospace'],
|
||||||
|
},
|
||||||
|
lineHeight: {
|
||||||
|
none: 1,
|
||||||
|
xs: 1.1,
|
||||||
|
sm: 1.15,
|
||||||
|
},
|
||||||
|
|
||||||
|
// colors
|
||||||
|
colors: {
|
||||||
|
black: co('--c-black'),
|
||||||
|
white: co('--c-white'),
|
||||||
|
divider: co('--c-divider'),
|
||||||
|
muted: co('--c-divider-muted'),
|
||||||
|
accent: {
|
||||||
|
primary: co('--c-accent-primary'),
|
||||||
|
secondary: co('--c-accent-secondary'),
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
error: co('--c-state-error'),
|
||||||
|
success: co('--c-state-success'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
textColor: theme => ({
|
||||||
|
...theme('colors'),
|
||||||
|
'custom': co('--text-color'),
|
||||||
|
'base': co('--c-text-base'),
|
||||||
|
'dim-1': co('--c-text-dim-1'),
|
||||||
|
'dim-2': co('--c-text-dim-2'),
|
||||||
|
'dim-3': co('--c-text-dim-3'),
|
||||||
|
'bg': co('--c-bg-base'),
|
||||||
|
}),
|
||||||
|
backgroundColor: theme => ({
|
||||||
|
...theme('colors'),
|
||||||
|
'custom': co('--bg-color'),
|
||||||
|
'base': co('--c-bg-base'),
|
||||||
|
'dim-1': co('--c-bg-dim-1'),
|
||||||
|
'placeholder': co('--c-bg-placeholder'),
|
||||||
|
}),
|
||||||
|
borderColor: theme => ({
|
||||||
|
...theme('colors'),
|
||||||
|
base: co('--c-text-base'),
|
||||||
|
transparent: 'transparent',
|
||||||
|
custom: co('--border-color'),
|
||||||
|
}),
|
||||||
|
fill: theme => ({
|
||||||
|
...theme('backgroundColor'),
|
||||||
|
}),
|
||||||
|
stroke: theme => ({
|
||||||
|
...theme('borderColor'),
|
||||||
|
}),
|
||||||
|
|
||||||
|
// opacity
|
||||||
|
opacity: {
|
||||||
|
0: '0',
|
||||||
|
muted: '0.5',
|
||||||
|
soft: '0.8',
|
||||||
|
full: '1',
|
||||||
|
outline: 'var(--o-outline)',
|
||||||
|
},
|
||||||
|
textOpacity: theme => ({
|
||||||
|
...theme('opacity'),
|
||||||
|
custom: 'var(--text-opacity)',
|
||||||
|
}),
|
||||||
|
backgroundOpacity: theme => ({
|
||||||
|
...theme('opacity'),
|
||||||
|
custom: 'var(--bg-opacity)',
|
||||||
|
}),
|
||||||
|
borderOpacity: theme => ({
|
||||||
|
...theme('opacity'),
|
||||||
|
custom: 'var(--border-opacity)',
|
||||||
|
}),
|
||||||
|
|
||||||
|
transitionDuration: {
|
||||||
|
fast: '250ms',
|
||||||
|
normal: '500ms',
|
||||||
|
slow: '750ms',
|
||||||
|
},
|
||||||
|
zIndex: {
|
||||||
|
muted: '-1',
|
||||||
|
1: '1',
|
||||||
|
},
|
||||||
|
borderRadius: {
|
||||||
|
0: '0',
|
||||||
|
sm: '0.75rem',
|
||||||
|
DEFAULT: '1rem',
|
||||||
|
lg: '1.5rem',
|
||||||
|
full: '9999px',
|
||||||
|
},
|
||||||
|
scale: {
|
||||||
|
0: '0',
|
||||||
|
click: '0.975',
|
||||||
|
normal: '1',
|
||||||
|
},
|
||||||
|
|
||||||
|
extend: {
|
||||||
|
blur: {
|
||||||
|
px: '1px',
|
||||||
|
xs: '2px',
|
||||||
|
},
|
||||||
|
fontSize: {
|
||||||
|
'1/2': '0.5em',
|
||||||
|
'5/8': '0.625em',
|
||||||
|
'3/4': '0.75em',
|
||||||
|
'7/8': '0.875em',
|
||||||
|
'9/8': '1.125em',
|
||||||
|
'5/4': '1.25em',
|
||||||
|
'3/2': '1.5em',
|
||||||
|
},
|
||||||
|
boxShadow: {
|
||||||
|
// shadows for dialogs, popups etc
|
||||||
|
card: '0 0 2rem -1.75rem rgb(var(--c-accent-secondary))',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
plugin(addHeaders),
|
||||||
|
({ addUtilities, matchUtilities, theme }) => {
|
||||||
|
const size = value => ({
|
||||||
|
height: value,
|
||||||
|
minHeight: value,
|
||||||
|
width: value,
|
||||||
|
minWidth: value,
|
||||||
|
})
|
||||||
|
matchUtilities(
|
||||||
|
{
|
||||||
|
size,
|
||||||
|
circle: value => ({
|
||||||
|
...size(value),
|
||||||
|
borderRadius: theme('borderRadius.full'),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{ values: theme('height') },
|
||||||
|
)
|
||||||
|
|
||||||
|
addUtilities({
|
||||||
|
'.duration-onhover-fast': {
|
||||||
|
'transitionDuration': theme('transitionDuration.normal'),
|
||||||
|
'&:hover': {
|
||||||
|
transitionDuration: theme('transitionDuration.fast'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
59
dapps/vue-dapp-auth/tailwind/headers.js
Normal file
59
dapps/vue-dapp-auth/tailwind/headers.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
module.exports = ({ addBase, theme, addUtilities }) => {
|
||||||
|
const commonHeaderStyles = {
|
||||||
|
display: 'inline-block',
|
||||||
|
}
|
||||||
|
const headerStyles = {
|
||||||
|
...commonHeaderStyles,
|
||||||
|
fontFamily: theme('fontFamily.display'),
|
||||||
|
fontWeight: theme('fontWeight.bold'),
|
||||||
|
lineHeight: theme('lineHeight.sm'),
|
||||||
|
}
|
||||||
|
const headerStylesStrong = {
|
||||||
|
...commonHeaderStyles,
|
||||||
|
fontFamily: theme('fontFamily.display'),
|
||||||
|
fontWeight: theme('fontWeight.black'),
|
||||||
|
lineHeight: theme('lineHeight.none'),
|
||||||
|
textShadow: theme('dropShadow.title'),
|
||||||
|
}
|
||||||
|
const headers = {
|
||||||
|
h1: {
|
||||||
|
...headerStylesStrong,
|
||||||
|
fontSize: theme('fontSize.4xl'),
|
||||||
|
marginBottom: '1em',
|
||||||
|
},
|
||||||
|
h2: {
|
||||||
|
...headerStylesStrong,
|
||||||
|
fontSize: theme('fontSize.3xl'),
|
||||||
|
marginBottom: '0.75em',
|
||||||
|
},
|
||||||
|
h3: {
|
||||||
|
...headerStylesStrong,
|
||||||
|
fontSize: theme('fontSize.2xl'),
|
||||||
|
marginBottom: '0.75em',
|
||||||
|
},
|
||||||
|
h4: {
|
||||||
|
...headerStyles,
|
||||||
|
fontSize: theme('fontSize.xl'),
|
||||||
|
marginBottom: '0.625em',
|
||||||
|
},
|
||||||
|
h5: {
|
||||||
|
...headerStyles,
|
||||||
|
fontSize: theme('fontSize.lg'),
|
||||||
|
marginBottom: '0.5em',
|
||||||
|
},
|
||||||
|
h6: {
|
||||||
|
...headerStyles,
|
||||||
|
fontSize: theme('fontSize.normal'),
|
||||||
|
marginBottom: '0.375em',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// bind styles to tags
|
||||||
|
addBase(headers)
|
||||||
|
|
||||||
|
// create .h# utils (e.g. `tw-h6` etc)
|
||||||
|
addUtilities(Object.entries(headers).reduce((utils, [tag, styles]) => ({
|
||||||
|
...utils,
|
||||||
|
[`.${tag}`]: styles,
|
||||||
|
}), {}))
|
||||||
|
}
|
4
dapps/vue-dapp-auth/tsconfig.json
Normal file
4
dapps/vue-dapp-auth/tsconfig.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
// https://v3.nuxtjs.org/concepts/typescript
|
||||||
|
"extends": "./.nuxt/tsconfig.json"
|
||||||
|
}
|
7492
dapps/vue-dapp-auth/yarn.lock
Normal file
7492
dapps/vue-dapp-auth/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user