Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c0881fe14 | ||
|
|
2a7c858662 | ||
|
|
41d8df2238 | ||
|
|
71546ea605 | ||
|
|
487997b87c | ||
|
|
ba85663048 | ||
|
|
9d566fa977 | ||
|
|
3440f28121 | ||
|
|
150b35c4c7 | ||
|
|
39c931cde9 | ||
|
|
f858a4d29a | ||
|
|
f021fcdb1c | ||
|
|
6325bcbe35 | ||
|
|
0ee35e4946 | ||
|
|
f5a2d71ed3 | ||
|
|
c43fdcbb7f | ||
|
|
3034d66a5d | ||
|
|
ba14fd4242 | ||
|
|
236c1c2836 | ||
|
|
d2cea34242 | ||
|
|
3687e5e282 | ||
|
|
65381e103c | ||
|
|
07f763e167 | ||
|
|
c9919987ca | ||
|
|
9f94aad88a | ||
|
|
b4557c3596 | ||
|
|
05e969fbc4 | ||
|
|
4a407b43ae | ||
|
|
c6d0473fab | ||
|
|
607230b51c | ||
|
|
c9d5ab82fd | ||
|
|
3ac8f911ac | ||
|
|
9232afb5af | ||
|
|
2c57bbf4ee | ||
|
|
c239906a96 | ||
|
|
0fa0936c59 | ||
|
|
aad6b8273b | ||
|
|
571fd241aa | ||
|
|
e0a3f92211 | ||
|
|
b58c0ce820 | ||
|
|
cbad2e7308 | ||
|
|
84af5287de | ||
|
|
39f2d73007 | ||
|
|
ed23317b0f | ||
|
|
3c843b2f16 | ||
|
|
617de58a92 | ||
|
|
290a13fbf2 | ||
|
|
5c14275a75 | ||
|
|
3c388da22d | ||
|
|
ba143dfccd | ||
|
|
037fb09d82 | ||
|
|
3357ee08eb | ||
|
|
65d4efb936 | ||
|
|
f28e2123b1 | ||
|
|
301b96ef3a | ||
|
|
6a0ddb15e5 | ||
|
|
7cb2821e76 | ||
|
|
7d707e47f4 | ||
|
|
a75a745b8b | ||
|
|
813a874d12 | ||
|
|
c1acee63bf | ||
|
|
acbf1b062b | ||
|
|
303f22b60d | ||
|
|
ff826a81f3 | ||
|
|
099dbbdad8 | ||
|
|
ea668a87d4 | ||
|
|
2243458a67 | ||
|
|
4952e17fa4 |
@@ -0,0 +1,4 @@
|
|||||||
|
> 1%
|
||||||
|
last 2 versions
|
||||||
|
not dead
|
||||||
|
not ie 11
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
extends: [
|
||||||
|
"plugin:vue/vue3-essential",
|
||||||
|
"eslint:recommended",
|
||||||
|
"@vue/typescript/recommended",
|
||||||
|
"plugin:prettier/recommended",
|
||||||
|
],
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||||
|
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||||
|
"@typescript-eslint/no-unnecessary-type-constraint": "off",
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,22 +1,21 @@
|
|||||||
# Logs
|
.DS_Store
|
||||||
logs
|
node_modules
|
||||||
*.log
|
/dist
|
||||||
|
|
||||||
|
*~
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Log files
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
pnpm-debug.log*
|
pnpm-debug.log*
|
||||||
lerna-debug.log*
|
|
||||||
|
|
||||||
node_modules
|
|
||||||
dist
|
|
||||||
dist-ssr
|
|
||||||
*.local
|
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode/*
|
|
||||||
!.vscode/extensions.json
|
|
||||||
.idea
|
.idea
|
||||||
.DS_Store
|
.vscode
|
||||||
*.suo
|
*.suo
|
||||||
*.ntvs*
|
*.ntvs*
|
||||||
*.njsproj
|
*.njsproj
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
nodejs 16.18.0
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
|
|
||||||
}
|
|
||||||
@@ -1,18 +1,131 @@
|
|||||||
# Vue 3 + TypeScript + Vite
|
# kickstart-for-time-pwa
|
||||||
|
|
||||||
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
## Project setup
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
## Recommended IDE Setup
|
### Compiles and hot-reloads for development
|
||||||
|
```
|
||||||
|
npm run serve
|
||||||
|
```
|
||||||
|
|
||||||
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
|
### Compiles and minifies for production
|
||||||
|
```
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
## Type Support For `.vue` Imports in TS
|
### Lints and fixes files
|
||||||
|
```
|
||||||
|
npm run lint
|
||||||
|
```
|
||||||
|
|
||||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
|
### Customize configuration
|
||||||
|
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||||
|
|
||||||
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
|
|
||||||
|
|
||||||
1. Disable the built-in TypeScript Extension
|
|
||||||
1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette
|
## Other
|
||||||
2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
|
|
||||||
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
|
```
|
||||||
|
// reference material from https://github.com/trentlarson/endorser-mobile/blob/8dc8e0353e0cc80ffa7ed89ded15c8b0da92726b/src/utility/idUtility.ts#L83
|
||||||
|
|
||||||
|
// Import an existing ID
|
||||||
|
export const importAndStoreIdentifier = async (mnemonic: string, mnemonicPassword: string, toLowercase: boolean, previousIdentifiers: Array<IIdentifier>) => {
|
||||||
|
|
||||||
|
// just to get rid of variability that might cause an error
|
||||||
|
mnemonic = mnemonic.trim().toLowerCase()
|
||||||
|
|
||||||
|
/**
|
||||||
|
// an approach I pieced together
|
||||||
|
// requires: yarn add elliptic
|
||||||
|
// ... plus:
|
||||||
|
// const EC = require('elliptic').ec
|
||||||
|
// const secp256k1 = new EC('secp256k1')
|
||||||
|
//
|
||||||
|
const keyHex: string = bip39.mnemonicToEntropy(mnemonic)
|
||||||
|
// returns a KeyPair from the elliptic.ec library
|
||||||
|
const keyPair = secp256k1.keyFromPrivate(keyHex, 'hex')
|
||||||
|
// this code is from did-provider-eth createIdentifier
|
||||||
|
const privateHex = keyPair.getPrivate('hex')
|
||||||
|
const publicHex = keyPair.getPublic('hex')
|
||||||
|
const address = didJwt.toEthereumAddress(publicHex)
|
||||||
|
**/
|
||||||
|
|
||||||
|
/**
|
||||||
|
// from https://github.com/uport-project/veramo/discussions/346#discussioncomment-302234
|
||||||
|
// ... which almost works but the didJwt.toEthereumAddress is wrong
|
||||||
|
// requires: yarn add bip32
|
||||||
|
// ... plus: import * as bip32 from 'bip32'
|
||||||
|
//
|
||||||
|
const seed: Buffer = await bip39.mnemonicToSeed(mnemonic)
|
||||||
|
const root = bip32.fromSeed(seed)
|
||||||
|
const node = root.derivePath(UPORT_ROOT_DERIVATION_PATH)
|
||||||
|
const privateHex = node.privateKey.toString("hex")
|
||||||
|
const publicHex = node.publicKey.toString("hex")
|
||||||
|
const address = didJwt.toEthereumAddress('0x' + publicHex)
|
||||||
|
**/
|
||||||
|
|
||||||
|
/**
|
||||||
|
// from https://github.com/uport-project/veramo/discussions/346#discussioncomment-302234
|
||||||
|
// requires: yarn add @ethersproject/hdnode
|
||||||
|
// ... plus: import { HDNode } from '@ethersproject/hdnode'
|
||||||
|
**/
|
||||||
|
const hdnode: HDNode = HDNode.fromMnemonic(mnemonic)
|
||||||
|
const rootNode: HDNode = hdnode.derivePath(UPORT_ROOT_DERIVATION_PATH)
|
||||||
|
const privateHex = rootNode.privateKey.substring(2) // original starts with '0x'
|
||||||
|
const publicHex = rootNode.publicKey.substring(2) // original starts with '0x'
|
||||||
|
let address = rootNode.address
|
||||||
|
|
||||||
|
const prevIds = previousIdentifiers || [];
|
||||||
|
|
||||||
|
if (toLowercase) {
|
||||||
|
const foundEqual = R.find(
|
||||||
|
(id) => utility.rawAddressOfDid(id.did) === address,
|
||||||
|
prevIds
|
||||||
|
)
|
||||||
|
if (foundEqual) {
|
||||||
|
// They're trying to create a lowercase version of one that exists in normal case.
|
||||||
|
// (We really should notify the user.)
|
||||||
|
appStore.dispatch(appSlice.actions.addLog({log: true, msg: "Will create a normal-case version of the DID since a regular version exists."}))
|
||||||
|
} else {
|
||||||
|
address = address.toLowerCase()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// They're not trying to convert to lowercase.
|
||||||
|
const foundLower = R.find((id) =>
|
||||||
|
utility.rawAddressOfDid(id.did) === address.toLowerCase(),
|
||||||
|
prevIds
|
||||||
|
)
|
||||||
|
if (foundLower) {
|
||||||
|
// They're trying to create a normal case version of one that exists in lowercase.
|
||||||
|
// (We really should notify the user.)
|
||||||
|
appStore.dispatch(appSlice.actions.addLog({log: true, msg: "Will create a lowercase version of the DID since a lowercase version exists."}))
|
||||||
|
address = address.toLowerCase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
appStore.dispatch(appSlice.actions.addLog({log: false, msg: "... derived keys and address..."}))
|
||||||
|
|
||||||
|
const newId = newIdentifier(address, publicHex, privateHex, UPORT_ROOT_DERIVATION_PATH)
|
||||||
|
appStore.dispatch(appSlice.actions.addLog({log: false, msg: "... created new ID..."}))
|
||||||
|
|
||||||
|
// awaiting because otherwise the UI may not see that a mnemonic was created
|
||||||
|
const savedId = await storeIdentifier(newId, mnemonic, mnemonicPassword)
|
||||||
|
appStore.dispatch(appSlice.actions.addLog({log: false, msg: "... stored new ID..."}))
|
||||||
|
return savedId
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a totally new ID
|
||||||
|
export const createAndStoreIdentifier = async (mnemonicPassword) => {
|
||||||
|
|
||||||
|
// This doesn't give us the entropy/seed.
|
||||||
|
//const id = await agent.didManagerCreate()
|
||||||
|
|
||||||
|
const entropy = crypto.randomBytes(32)
|
||||||
|
const mnemonic = bip39.entropyToMnemonic(entropy)
|
||||||
|
appStore.dispatch(appSlice.actions.addLog({log: false, msg: "... generated mnemonic..."}))
|
||||||
|
|
||||||
|
return importAndStoreIdentifier(mnemonic, mnemonicPassword, false, [])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: ["@vue/cli-plugin-babel/preset"],
|
||||||
|
};
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>Vite + Vue + TS</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="app"></div>
|
|
||||||
<script type="module" src="/src/main.ts"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
You can create a JWT using a library or by encoding the header and payload base64Url and signing it with a secret using a ES256K algorithm. Here is an example of how you can create a JWT using the jq and openssl command line utilities:
|
||||||
|
|
||||||
|
Here is an example of how you can use openssl to sign a JWT with the ES256K algorithm:
|
||||||
|
|
||||||
|
Generate an ECDSA key pair using the secp256k1 curve:
|
||||||
|
|
||||||
|
openssl ecparam -name secp256k1 -genkey -noout -out private.pem
|
||||||
|
openssl ec -in private.pem -pubout -out public.pem
|
||||||
|
|
||||||
|
First, create a header object as a JSON object containing the alg (algorithm) and typ (type) fields. For example:
|
||||||
|
|
||||||
|
header='{"alg":"ES256K", "issuer": "", "typ":"JWT"}'
|
||||||
|
|
||||||
|
Next, create a payload object as a JSON object containing the claims you want to include in the JWT. For example schema.org :
|
||||||
|
|
||||||
|
payload='{"@context": "http://schema.org", "@type": "PlanAction", "identifier": "did:ethr:0xb86913f83A867b5Ef04902419614A6FF67466c12", "name": "Test", "description": "Me"}'
|
||||||
|
|
||||||
|
Encode the header and payload objects as base64Url strings. You can use the jq command line utility to do this:
|
||||||
|
|
||||||
|
header_b64=$(echo -n "$header" | jq -c -M . | tr -d '\n')
|
||||||
|
payload_b64=$(echo -n "$payload" | jq -c -M . | tr -d '\n')
|
||||||
|
|
||||||
|
Concatenate the encoded header, payload, and a secret to create the signing input:
|
||||||
|
|
||||||
|
signing_input="$header_b64.$payload_b64"
|
||||||
|
|
||||||
|
Create the signature by signing the signing input with a ES256K algorithm and your secret. You can use the openssl command line utility to do this:
|
||||||
|
|
||||||
|
signature=$(echo -n "$signing_input" | openssl dgst -sha256 -sign private.pem)
|
||||||
|
|
||||||
|
Finally, encode the signature as a base64Url string and concatenate it with the signing input to create the JWT:
|
||||||
|
|
||||||
|
signature_b64=$(echo -n "$signature" | base64 | tr -d '=' | tr '+' '-' | tr '/' '_')
|
||||||
|
jwt="$signing_input.$signature_b64"
|
||||||
|
|
||||||
|
This JWT can then be passed in the Authorization header of a HTTP request as a bearer token, for example:
|
||||||
|
|
||||||
|
Authorization: Bearer $jwt
|
||||||
|
|
||||||
|
To verify the JWT, you can use the openssl utility with the public key:
|
||||||
|
|
||||||
|
openssl dgst -sha256 -verify public.pem -signature <(echo -n "$signature") "$signing_input"
|
||||||
|
|
||||||
|
This will verify the signature and output Verified OK if the signature is valid. If the signature is not valid, it will output an error.
|
||||||
|
|
||||||
@@ -1,20 +1,73 @@
|
|||||||
{
|
{
|
||||||
"name": "timesafari",
|
"name": "kickstart-for-time-pwa",
|
||||||
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"serve": "vue-cli-service serve",
|
||||||
"build": "vue-tsc && vite build",
|
"build": "vue-cli-service build",
|
||||||
"preview": "vite preview"
|
"lint": "vue-cli-service lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"vue": "^3.3.11"
|
"@ethersproject/hdnode": "^5.7.0",
|
||||||
|
"@fortawesome/fontawesome-svg-core": "^6.2.1",
|
||||||
|
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
||||||
|
"@fortawesome/vue-fontawesome": "^3.0.2",
|
||||||
|
"@pvermeer/dexie-encrypted-addon": "^2.0.2",
|
||||||
|
"@veramo/core": "^4.1.1",
|
||||||
|
"@veramo/credential-w3c": "^4.1.1",
|
||||||
|
"@veramo/data-store": "^4.1.1",
|
||||||
|
"@veramo/did-manager": "^4.1.1",
|
||||||
|
"@veramo/did-provider-ethr": "^4.1.2",
|
||||||
|
"@veramo/did-resolver": "^4.1.1",
|
||||||
|
"@veramo/key-manager": "^4.1.1",
|
||||||
|
"@vueuse/core": "^9.6.0",
|
||||||
|
"@zxing/text-encoding": "^0.9.0",
|
||||||
|
"axios": "^1.2.2",
|
||||||
|
"class-transformer": "^0.5.1",
|
||||||
|
"core-js": "^3.26.1",
|
||||||
|
"dexie": "^3.2.2",
|
||||||
|
"did-jwt": "^6.9.0",
|
||||||
|
"ethereum-cryptography": "^1.1.2",
|
||||||
|
"ethereumjs-util": "^7.1.5",
|
||||||
|
"ethr-did-resolver": "^8.0.0",
|
||||||
|
"js-generate-password": "^0.1.7",
|
||||||
|
"localstorage-slim": "^2.3.0",
|
||||||
|
"luxon": "^3.1.1",
|
||||||
|
"merkletreejs": "^0.3.9",
|
||||||
|
"papaparse": "^5.3.2",
|
||||||
|
"pina": "^0.20.2204228",
|
||||||
|
"pinia-plugin-persistedstate": "^3.0.1",
|
||||||
|
"ramda": "^0.28.0",
|
||||||
|
"readable-stream": "^4.2.0",
|
||||||
|
"reflect-metadata": "^0.1.13",
|
||||||
|
"register-service-worker": "^1.7.2",
|
||||||
|
"vue": "^3.2.45",
|
||||||
|
"vue-axios": "^3.5.2",
|
||||||
|
"vue-class-component": "^8.0.0-0",
|
||||||
|
"vue-property-decorator": "^9.1.2",
|
||||||
|
"vue-router": "^4.1.6",
|
||||||
|
"web-did-resolver": "^2.0.21"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^4.5.2",
|
"@types/ramda": "^0.28.20",
|
||||||
"typescript": "^5.2.2",
|
"@typescript-eslint/eslint-plugin": "^5.44.0",
|
||||||
"vite": "^5.0.8",
|
"@typescript-eslint/parser": "^5.44.0",
|
||||||
"vue-tsc": "^1.8.25"
|
"@vue/cli-plugin-babel": "~5.0.8",
|
||||||
|
"@vue/cli-plugin-eslint": "~5.0.8",
|
||||||
|
"@vue/cli-plugin-pwa": "~5.0.8",
|
||||||
|
"@vue/cli-plugin-router": "~5.0.8",
|
||||||
|
"@vue/cli-plugin-typescript": "~5.0.8",
|
||||||
|
"@vue/cli-plugin-vuex": "~5.0.8",
|
||||||
|
"@vue/cli-service": "~5.0.8",
|
||||||
|
"@vue/eslint-config-typescript": "^11.0.2",
|
||||||
|
"autoprefixer": "^10.4.13",
|
||||||
|
"eslint": "^8.28.0",
|
||||||
|
"eslint-config-prettier": "^8.5.0",
|
||||||
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
|
"eslint-plugin-vue": "^9.8.0",
|
||||||
|
"postcss": "^8.4.19",
|
||||||
|
"prettier": "^2.8.0",
|
||||||
|
"tailwindcss": "^3.2.4",
|
||||||
|
"typescript": "~4.9.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
- top screens from img/screens.pdf :
|
||||||
|
- discover
|
||||||
|
- view
|
||||||
|
- new
|
||||||
|
- commit
|
||||||
|
blocks: ref:https://raw.githubusercontent.com/trentlarson/lives-of-gifts/master/project.yaml#kickstarter%20for%20time
|
||||||
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 799 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M8.00251 14.9297L0 1.07422H6.14651L8.00251 4.27503L9.84583 1.07422H16L8.00251 14.9297Z" fill="black"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 215 B |
@@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||||
|
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||||
|
</noscript>
|
||||||
|
<div id="app"></div>
|
||||||
|
<!-- built files will be auto injected -->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
User-agent: *
|
||||||
|
Disallow:
|
||||||
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,30 +1,7 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import HelloWorld from './components/HelloWorld.vue'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<router-view />
|
||||||
<a href="https://vitejs.dev" target="_blank">
|
|
||||||
<img src="/vite.svg" class="logo" alt="Vite logo" />
|
|
||||||
</a>
|
|
||||||
<a href="https://vuejs.org/" target="_blank">
|
|
||||||
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<HelloWorld msg="Vite + Vue" />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style></style>
|
||||||
.logo {
|
|
||||||
height: 6em;
|
<script lang="ts"></script>
|
||||||
padding: 1.5em;
|
|
||||||
will-change: filter;
|
|
||||||
transition: filter 300ms;
|
|
||||||
}
|
|
||||||
.logo:hover {
|
|
||||||
filter: drop-shadow(0 0 2em #646cffaa);
|
|
||||||
}
|
|
||||||
.logo.vue:hover {
|
|
||||||
filter: drop-shadow(0 0 2em #42b883aa);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
|
After Width: | Height: | Size: 6.7 KiB |
@@ -0,0 +1,11 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Work+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&display=swap');
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
html {
|
||||||
|
font-family: 'Work Sans', ui-sans-serif, system-ui, sans-serif !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 496 B |
@@ -1,38 +1,150 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
defineProps<{ msg: string }>()
|
|
||||||
|
|
||||||
const count = ref(0)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<div class="hello">
|
||||||
<h1>{{ msg }}</h1>
|
<h1>{{ msg }}</h1>
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<button type="button" @click="count++">count is {{ count }}</button>
|
|
||||||
<p>
|
<p>
|
||||||
Edit
|
For a guide and recipes on how to configure / customize this project,<br />
|
||||||
<code>components/HelloWorld.vue</code> to test HMR
|
check out the
|
||||||
|
<a href="https://cli.vuejs.org" target="_blank" rel="noopener"
|
||||||
|
>vue-cli documentation</a
|
||||||
|
>.
|
||||||
</p>
|
</p>
|
||||||
|
<h3>Installed CLI Plugins</h3>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>babel</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwa"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>pwa</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>router</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-vuex"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>vuex</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>eslint</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>typescript</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<h3>Essential Links</h3>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://forum.vuejs.org" target="_blank" rel="noopener"
|
||||||
|
>Forum</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://chat.vuejs.org" target="_blank" rel="noopener"
|
||||||
|
>Community Chat</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://twitter.com/vuejs" target="_blank" rel="noopener"
|
||||||
|
>Twitter</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<h3>Ecosystem</h3>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="https://router.vuejs.org" target="_blank" rel="noopener"
|
||||||
|
>vue-router</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="https://github.com/vuejs/vue-devtools#vue-devtools"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>vue-devtools</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener"
|
||||||
|
>vue-loader</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="https://github.com/vuejs/awesome-vue"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>awesome-vue</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p>
|
|
||||||
Check out
|
|
||||||
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
|
|
||||||
>create-vue</a
|
|
||||||
>, the official Vue + Vite starter
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Install
|
|
||||||
<a href="https://github.com/vuejs/language-tools" target="_blank">Volar</a>
|
|
||||||
in your IDE for a better DX
|
|
||||||
</p>
|
|
||||||
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
|
||||||
|
@Options({
|
||||||
|
props: {
|
||||||
|
msg: String,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export default class HelloWorld extends Vue {
|
||||||
|
msg!: string;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.read-the-docs {
|
h3 {
|
||||||
color: #888;
|
margin: 40px 0 0;
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: #42b983;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* Generic strings that could be used throughout the app.
|
||||||
|
*/
|
||||||
|
export enum AppString {
|
||||||
|
APP_NAME = "Kickstart for time",
|
||||||
|
VERSION = "0.1",
|
||||||
|
DEFAULT_ENDORSER_API_SERVER = "https://test.endorser.ch:8000",
|
||||||
|
DEFAULT_ENDORSER_VIEW_SERVER = "https://test.endorser.ch",
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import BaseDexie from "dexie";
|
||||||
|
import { encrypted, Encryption } from "@pvermeer/dexie-encrypted-addon";
|
||||||
|
import { accountsSchema, AccountsTable } from "./tables/accounts";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In order to make the next line be acceptable, the program needs to have its linter suppress a rule:
|
||||||
|
* https://typescript-eslint.io/rules/no-unnecessary-type-constraint/
|
||||||
|
*
|
||||||
|
* and change *any* to *unknown*
|
||||||
|
*
|
||||||
|
* https://9to5answer.com/how-to-bypass-warning-unexpected-any-specify-a-different-type-typescript-eslint-no-explicit-any
|
||||||
|
*/
|
||||||
|
type DexieTables = AccountsTable;
|
||||||
|
export type Dexie<T extends unknown = DexieTables> = BaseDexie & T;
|
||||||
|
export const db = new BaseDexie("kickStarter") as Dexie;
|
||||||
|
const schema = Object.assign({}, accountsSchema);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Needed to enable a special webpack setting to allow *await* below:
|
||||||
|
* https://stackoverflow.com/questions/72474803/error-the-top-level-await-experiment-is-not-enabled-set-experiments-toplevelaw
|
||||||
|
*/
|
||||||
|
|
||||||
|
// create password and place password in localStorage
|
||||||
|
const secret =
|
||||||
|
localStorage.getItem("secret") || Encryption.createRandomEncryptionKey();
|
||||||
|
|
||||||
|
if (localStorage.getItem("secret") == null) {
|
||||||
|
localStorage.setItem("secret", secret);
|
||||||
|
}
|
||||||
|
console.log(secret);
|
||||||
|
encrypted(db, { secretKey: secret });
|
||||||
|
db.version(1).stores(schema);
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { Table } from "dexie";
|
||||||
|
|
||||||
|
export type Account = {
|
||||||
|
id?: number;
|
||||||
|
publicKey: string;
|
||||||
|
mnemonic: string;
|
||||||
|
identity: string;
|
||||||
|
dateCreated: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AccountsTable = {
|
||||||
|
accounts: Table<Account>;
|
||||||
|
};
|
||||||
|
|
||||||
|
// mark encrypted field by starting with a $ character
|
||||||
|
export const accountsSchema = {
|
||||||
|
accounts: "++id, publicKey, $mnemonic, $identity, dateCreated",
|
||||||
|
};
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
import { IIdentifier } from "@veramo/core";
|
||||||
|
import { DEFAULT_DID_PROVIDER_NAME } from "../veramo/setup";
|
||||||
|
import { getRandomBytesSync } from "ethereum-cryptography/random";
|
||||||
|
import { entropyToMnemonic } from "ethereum-cryptography/bip39";
|
||||||
|
import { wordlist } from "ethereum-cryptography/bip39/wordlists/english";
|
||||||
|
import { HDNode } from "@ethersproject/hdnode";
|
||||||
|
import * as didJwt from "did-jwt";
|
||||||
|
import * as u8a from "uint8arrays";
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {string} address
|
||||||
|
* @param {string} publicHex
|
||||||
|
* @param {string} privateHex
|
||||||
|
* @param {string} derivationPath
|
||||||
|
* @return {*} {Omit<IIdentifier, 'provider'>}
|
||||||
|
*/
|
||||||
|
export const newIdentifier = (
|
||||||
|
address: string,
|
||||||
|
publicHex: string,
|
||||||
|
privateHex: string,
|
||||||
|
derivationPath: string
|
||||||
|
): Omit<IIdentifier, keyof "provider"> => {
|
||||||
|
return {
|
||||||
|
did: DEFAULT_DID_PROVIDER_NAME + ":" + address,
|
||||||
|
keys: [
|
||||||
|
{
|
||||||
|
kid: publicHex,
|
||||||
|
kms: "local",
|
||||||
|
meta: { derivationPath: derivationPath },
|
||||||
|
privateKeyHex: privateHex,
|
||||||
|
publicKeyHex: publicHex,
|
||||||
|
type: "Secp256k1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
provider: DEFAULT_DID_PROVIDER_NAME,
|
||||||
|
services: [],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {string} mnemonic
|
||||||
|
* @return {*} {[string, string, string, string]}
|
||||||
|
*/
|
||||||
|
export const deriveAddress = (
|
||||||
|
mnemonic: string
|
||||||
|
): [string, string, string, string] => {
|
||||||
|
const UPORT_ROOT_DERIVATION_PATH = "m/7696500'/0'/0'/0'";
|
||||||
|
mnemonic = mnemonic.trim().toLowerCase();
|
||||||
|
|
||||||
|
const hdnode: HDNode = HDNode.fromMnemonic(mnemonic);
|
||||||
|
const rootNode: HDNode = hdnode.derivePath(UPORT_ROOT_DERIVATION_PATH);
|
||||||
|
const privateHex = rootNode.privateKey.substring(2); // original starts with '0x'
|
||||||
|
const publicHex = rootNode.publicKey.substring(2); // original starts with '0x'
|
||||||
|
const address = rootNode.address;
|
||||||
|
|
||||||
|
return [address, privateHex, publicHex, UPORT_ROOT_DERIVATION_PATH];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @return {*} {string}
|
||||||
|
*/
|
||||||
|
export const createIdentifier = (): string => {
|
||||||
|
const entropy: Uint8Array = getRandomBytesSync(32);
|
||||||
|
const mnemonic = entropyToMnemonic(entropy, wordlist);
|
||||||
|
|
||||||
|
return mnemonic;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retreive an access token
|
||||||
|
*
|
||||||
|
* @param {IIdentifier} identifier
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
export const accessToken = async (identifier: IIdentifier) => {
|
||||||
|
const did: string = identifier.did;
|
||||||
|
const privateKeyHex: string = identifier.keys[0].privateKeyHex as string;
|
||||||
|
|
||||||
|
const signer = SimpleSigner(privateKeyHex);
|
||||||
|
|
||||||
|
const nowEpoch = Math.floor(Date.now() / 1000);
|
||||||
|
const endEpoch = nowEpoch + 60; // add one minute
|
||||||
|
|
||||||
|
const uportTokenPayload = { exp: endEpoch, iat: nowEpoch, iss: did };
|
||||||
|
const alg = undefined; // defaults to 'ES256K', more standardized but harder to verify vs ES256K-R
|
||||||
|
const jwt: string = await didJwt.createJWT(uportTokenPayload, {
|
||||||
|
alg,
|
||||||
|
issuer: did,
|
||||||
|
signer,
|
||||||
|
});
|
||||||
|
return jwt;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sign = async (privateKeyHex: string) => {
|
||||||
|
const signer = SimpleSigner(privateKeyHex);
|
||||||
|
|
||||||
|
return signer;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copied out of did-jwt since it's deprecated in that library.
|
||||||
|
*
|
||||||
|
* The SimpleSigner returns a configured function for signing data.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const signer = SimpleSigner(process.env.PRIVATE_KEY)
|
||||||
|
* signer(data, (err, signature) => {
|
||||||
|
* ...
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* @param {String} hexPrivateKey a hex encoded private key
|
||||||
|
* @return {Function} a configured signer function
|
||||||
|
*/
|
||||||
|
export function SimpleSigner(hexPrivateKey: string): didJwt.Signer {
|
||||||
|
const signer = didJwt.ES256KSigner(didJwt.hexToBytes(hexPrivateKey), true);
|
||||||
|
return async (data) => {
|
||||||
|
const signature = (await signer(data)) as string;
|
||||||
|
return fromJose(signature);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// from did-jwt/util; see SimpleSigner above
|
||||||
|
export function fromJose(signature: string): {
|
||||||
|
r: string;
|
||||||
|
s: string;
|
||||||
|
recoveryParam?: number;
|
||||||
|
} {
|
||||||
|
const signatureBytes: Uint8Array = didJwt.base64ToBytes(signature);
|
||||||
|
if (signatureBytes.length < 64 || signatureBytes.length > 65) {
|
||||||
|
throw new TypeError(
|
||||||
|
`Wrong size for signature. Expected 64 or 65 bytes, but got ${signatureBytes.length}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const r = bytesToHex(signatureBytes.slice(0, 32));
|
||||||
|
const s = bytesToHex(signatureBytes.slice(32, 64));
|
||||||
|
const recoveryParam =
|
||||||
|
signatureBytes.length === 65 ? signatureBytes[64] : undefined;
|
||||||
|
return { r, s, recoveryParam };
|
||||||
|
}
|
||||||
|
|
||||||
|
// from did-jwt/util; see SimpleSigner above
|
||||||
|
export function bytesToHex(b: Uint8Array): string {
|
||||||
|
return u8a.toString(b, "base16");
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
/* import * as R from "ramda";
|
||||||
|
import { configureStore, createSlice } from "@reduxjs/toolkit";
|
||||||
|
import { IIdentifier } from "@veramo/core";
|
||||||
|
|
||||||
|
import { Contact } from "../entity/contact";
|
||||||
|
import { Settings } from "../entity/settings";
|
||||||
|
import * as utility from "../utility/utility";
|
||||||
|
|
||||||
|
const MAX_LOG_LENGTH = 2000000;
|
||||||
|
|
||||||
|
export const DEFAULT_ENDORSER_API_SERVER = "https://endorser.ch:3000";
|
||||||
|
export const DEFAULT_ENDORSER_VIEW_SERVER = "https://endorser.ch";
|
||||||
|
export const LOCAL_ENDORSER_API_SERVER = "http://127.0.0.1:3000";
|
||||||
|
export const LOCAL_ENDORSER_VIEW_SERVER = "http://127.0.0.1:3001";
|
||||||
|
export const TEST_ENDORSER_API_SERVER = "https://test.endorser.ch:8000";
|
||||||
|
export const TEST_ENDORSER_VIEW_SERVER = "https://test.endorser.ch:8080";
|
||||||
|
|
||||||
|
// for contents set in reducers
|
||||||
|
interface Payload<T> {
|
||||||
|
type: string;
|
||||||
|
payload: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LogMsg {
|
||||||
|
log: boolean;
|
||||||
|
msg: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const appSlice = createSlice({
|
||||||
|
name: "app",
|
||||||
|
initialState: {
|
||||||
|
// This is nullable because it is cached state from the DB...
|
||||||
|
// it'll be null if we haven't even loaded from the DB yet.
|
||||||
|
settings: null as Settings,
|
||||||
|
|
||||||
|
// This is nullable because it is cached state from the DB...
|
||||||
|
// it'll be null if we haven't even loaded from the DB yet.
|
||||||
|
identifiers: null as Array<IIdentifier> | null,
|
||||||
|
|
||||||
|
// This is nullable because it is cached state from the DB...
|
||||||
|
// it'll be null if we haven't even loaded from the DB yet.
|
||||||
|
contacts: null as Array<Contact> | null,
|
||||||
|
|
||||||
|
viewServer: DEFAULT_ENDORSER_VIEW_SERVER,
|
||||||
|
|
||||||
|
logMessage: "",
|
||||||
|
|
||||||
|
advancedMode: false,
|
||||||
|
testMode: false,
|
||||||
|
},
|
||||||
|
reducers: {
|
||||||
|
addIdentifier: (state, contents: Payload<IIdentifier>) => {
|
||||||
|
state.identifiers = state.identifiers.concat([contents.payload]);
|
||||||
|
},
|
||||||
|
addLog: (state, contents: Payload<LogMsg>) => {
|
||||||
|
if (state.logMessage.length > MAX_LOG_LENGTH) {
|
||||||
|
state.logMessage =
|
||||||
|
"<truncated>\n..." +
|
||||||
|
state.logMessage.substring(
|
||||||
|
state.logMessage.length - MAX_LOG_LENGTH / 2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (contents.payload.log) {
|
||||||
|
console.log(contents.payload.msg);
|
||||||
|
state.logMessage += "\n" + contents.payload.msg;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setAdvancedMode: (state, contents: Payload<boolean>) => {
|
||||||
|
state.advancedMode = contents.payload;
|
||||||
|
},
|
||||||
|
setContacts: (state, contents: Payload<Array<Contact>>) => {
|
||||||
|
state.contacts = contents.payload;
|
||||||
|
},
|
||||||
|
setContact: (state, contents: Payload<Contact>) => {
|
||||||
|
const index = R.findIndex(
|
||||||
|
(c) => c.did === contents.payload.did,
|
||||||
|
state.contacts
|
||||||
|
);
|
||||||
|
state.contacts[index] = contents.payload;
|
||||||
|
},
|
||||||
|
setHomeScreen: (state, contents: Payload<string>) => {
|
||||||
|
state.settings.homeScreen = contents.payload;
|
||||||
|
},
|
||||||
|
setIdentifiers: (state, contents: Payload<Array<IIdentifier>>) => {
|
||||||
|
state.identifiers = contents.payload;
|
||||||
|
},
|
||||||
|
setSettings: (state, contents: Payload<Settings>) => {
|
||||||
|
state.settings = contents.payload;
|
||||||
|
},
|
||||||
|
setTestMode: (state, contents: Payload<boolean>) => {
|
||||||
|
state.testMode = contents.payload;
|
||||||
|
},
|
||||||
|
setViewServer: (state, contents: Payload<string>) => {
|
||||||
|
state.viewServer = contents.payload;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const appStore = configureStore({ reducer: appSlice.reducer });
|
||||||
|
*/
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
// Created from the setup in https://veramo.io/docs/guides/react_native
|
||||||
|
|
||||||
|
// Core interfaces
|
||||||
|
/* import {
|
||||||
|
createAgent,
|
||||||
|
IDIDManager,
|
||||||
|
IResolver,
|
||||||
|
IDataStore,
|
||||||
|
IKeyManager,
|
||||||
|
} from "@veramo/core";
|
||||||
|
*/
|
||||||
|
// Core identity manager plugin
|
||||||
|
//import { DIDManager } from "@veramo/did-manager";
|
||||||
|
|
||||||
|
// Ethr did identity provider
|
||||||
|
//import { EthrDIDProvider } from "@veramo/did-provider-ethr";
|
||||||
|
|
||||||
|
// Core key manager plugin
|
||||||
|
//import { KeyManager } from "@veramo/key-manager";
|
||||||
|
|
||||||
|
// Custom key management system for RN
|
||||||
|
//import { KeyManagementSystem } from '@veramo/kms-local-react-native'
|
||||||
|
|
||||||
|
// Custom resolver
|
||||||
|
// Custom resolvers
|
||||||
|
//import { DIDResolverPlugin } from "@veramo/did-resolver";
|
||||||
|
/* import { Resolver } from "did-resolver";
|
||||||
|
import { getResolver as ethrDidResolver } from "ethr-did-resolver";
|
||||||
|
import { getResolver as webDidResolver } from "web-did-resolver";
|
||||||
|
*/
|
||||||
|
// for VCs and VPs https://veramo.io/docs/api/credential-w3c
|
||||||
|
//import { CredentialIssuer } from '@veramo/credential-w3c'
|
||||||
|
|
||||||
|
// Storage plugin using TypeOrm
|
||||||
|
/* import {
|
||||||
|
Entities,
|
||||||
|
KeyStore,
|
||||||
|
DIDStore,
|
||||||
|
IDataStoreORM,
|
||||||
|
} from "@veramo/data-store";
|
||||||
|
*/
|
||||||
|
// TypeORM is installed with @veramo/typeorm
|
||||||
|
//import { createConnection } from 'typeorm'
|
||||||
|
|
||||||
|
//import * as R from "ramda";
|
||||||
|
|
||||||
|
/*
|
||||||
|
import { Contact } from '../entity/contact'
|
||||||
|
import { Settings } from '../entity/settings'
|
||||||
|
import { PrivateData } from '../entity/privateData'
|
||||||
|
|
||||||
|
import { Initial1616938713828 } from '../migration/1616938713828-initial'
|
||||||
|
import { SettingsContacts1616967972293 } from '../migration/1616967972293-settings-contacts'
|
||||||
|
import { EncryptedSeed1637856484788 } from '../migration/1637856484788-EncryptedSeed'
|
||||||
|
import { HomeScreenConfig1639947962124 } from '../migration/1639947962124-HomeScreenConfig'
|
||||||
|
import { HandlePublicKeys1652142819353 } from '../migration/1652142819353-HandlePublicKeys'
|
||||||
|
import { LastClaimsSeen1656811846836 } from '../migration/1656811846836-LastClaimsSeen'
|
||||||
|
import { ContactRegistered1662256903367 }from '../migration/1662256903367-ContactRegistered'
|
||||||
|
import { PrivateData1663080623479 } from '../migration/1663080623479-PrivateData'
|
||||||
|
|
||||||
|
const ALL_ENTITIES = Entities.concat([Contact, Settings, PrivateData])
|
||||||
|
|
||||||
|
// Create react native DB connection configured by ormconfig.js
|
||||||
|
|
||||||
|
export const dbConnection = createConnection({
|
||||||
|
database: 'endorser-mobile.sqlite',
|
||||||
|
entities: ALL_ENTITIES,
|
||||||
|
location: 'default',
|
||||||
|
logging: ['error', 'info', 'warn'],
|
||||||
|
migrations: [ Initial1616938713828, SettingsContacts1616967972293, EncryptedSeed1637856484788, HomeScreenConfig1639947962124, HandlePublicKeys1652142819353, LastClaimsSeen1656811846836, ContactRegistered1662256903367, PrivateData1663080623479 ],
|
||||||
|
migrationsRun: true,
|
||||||
|
type: 'react-native',
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
function didProviderName(netName: string) {
|
||||||
|
return "did:ethr" + (netName === "mainnet" ? "" : ":" + netName);
|
||||||
|
}
|
||||||
|
|
||||||
|
//const NETWORK_NAMES = ["mainnet", "rinkeby"];
|
||||||
|
|
||||||
|
const DEFAULT_DID_PROVIDER_NETWORK_NAME = "mainnet";
|
||||||
|
|
||||||
|
export const DEFAULT_DID_PROVIDER_NAME = didProviderName(
|
||||||
|
DEFAULT_DID_PROVIDER_NETWORK_NAME
|
||||||
|
);
|
||||||
|
|
||||||
|
export const HANDY_APP = false;
|
||||||
|
|
||||||
|
// this is used as the object in RegisterAction claims
|
||||||
|
export const SERVICE_ID = "endorser.ch";
|
||||||
|
|
||||||
|
//const INFURA_PROJECT_ID = "INFURA_PROJECT_ID";
|
||||||
|
/*
|
||||||
|
const providers = {}
|
||||||
|
NETWORK_NAMES.forEach((networkName) => {
|
||||||
|
providers[didProviderName(networkName)] = new EthrDIDProvider({
|
||||||
|
defaultKms: 'local',
|
||||||
|
network: networkName,
|
||||||
|
rpcUrl: 'https://' + networkName + '.infura.io/v3/' + INFURA_PROJECT_ID,
|
||||||
|
gas: 1000001,
|
||||||
|
ttl: 60 * 60 * 24 * 30 * 12 + 1,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const didManager = new DIDManager({
|
||||||
|
store: new DIDStore(dbConnection),
|
||||||
|
defaultProvider: DEFAULT_DID_PROVIDER_NAME,
|
||||||
|
providers: providers,
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* const basicDidResolvers = NETWORK_NAMES.map((networkName) => [
|
||||||
|
networkName,
|
||||||
|
new Resolver({
|
||||||
|
ethr: ethrDidResolver({
|
||||||
|
networks: [
|
||||||
|
{
|
||||||
|
name: networkName,
|
||||||
|
rpcUrl:
|
||||||
|
"https://" + networkName + ".infura.io/v3/" + INFURA_PROJECT_ID,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).ethr,
|
||||||
|
web: webDidResolver().web,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const basicResolverMap = R.fromPairs(basicDidResolvers)
|
||||||
|
|
||||||
|
export const DEFAULT_BASIC_RESOLVER = basicResolverMap[DEFAULT_DID_PROVIDER_NETWORK_NAME]
|
||||||
|
|
||||||
|
const agentDidResolvers = NETWORK_NAMES.map((networkName) => {
|
||||||
|
return new DIDResolverPlugin({
|
||||||
|
resolver: basicResolverMap[networkName],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
let allPlugins = [
|
||||||
|
new CredentialIssuer(),
|
||||||
|
new KeyManager({
|
||||||
|
store: new KeyStore(dbConnection),
|
||||||
|
kms: {
|
||||||
|
local: new KeyManagementSystem(),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
didManager,
|
||||||
|
].concat(agentDidResolvers)
|
||||||
|
*/
|
||||||
|
|
||||||
|
//export const agent = createAgent<IDIDManager & IKeyManager & IDataStore & IDataStoreORM & IResolver>({ plugins: allPlugins })
|
||||||
@@ -1,5 +1,59 @@
|
|||||||
import { createApp } from 'vue'
|
import { createPinia } from "pinia";
|
||||||
import './style.css'
|
import { createApp } from "vue";
|
||||||
import App from './App.vue'
|
import App from "./App.vue";
|
||||||
|
import "./registerServiceWorker";
|
||||||
|
import router from "./router";
|
||||||
|
import axios from "axios";
|
||||||
|
import VueAxios from "vue-axios";
|
||||||
|
|
||||||
createApp(App).mount('#app')
|
import "./assets/styles/tailwind.css";
|
||||||
|
|
||||||
|
import { library } from "@fortawesome/fontawesome-svg-core";
|
||||||
|
import {
|
||||||
|
faChevronLeft,
|
||||||
|
faHouseChimney,
|
||||||
|
faMagnifyingGlass,
|
||||||
|
faFolderOpen,
|
||||||
|
faHand,
|
||||||
|
faCircleUser,
|
||||||
|
faCopy,
|
||||||
|
faShareNodes,
|
||||||
|
faQrcode,
|
||||||
|
faUser,
|
||||||
|
faPen,
|
||||||
|
faPlus,
|
||||||
|
faTrashCan,
|
||||||
|
faCalendar,
|
||||||
|
faEllipsisVertical,
|
||||||
|
faSpinner,
|
||||||
|
faCircleCheck,
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
|
library.add(
|
||||||
|
faChevronLeft,
|
||||||
|
faHouseChimney,
|
||||||
|
faMagnifyingGlass,
|
||||||
|
faFolderOpen,
|
||||||
|
faHand,
|
||||||
|
faCircleUser,
|
||||||
|
faCopy,
|
||||||
|
faShareNodes,
|
||||||
|
faQrcode,
|
||||||
|
faUser,
|
||||||
|
faPen,
|
||||||
|
faPlus,
|
||||||
|
faTrashCan,
|
||||||
|
faCalendar,
|
||||||
|
faEllipsisVertical,
|
||||||
|
faSpinner,
|
||||||
|
faCircleCheck
|
||||||
|
);
|
||||||
|
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||||
|
|
||||||
|
createApp(App)
|
||||||
|
.component("fa", FontAwesomeIcon)
|
||||||
|
.use(createPinia())
|
||||||
|
.use(VueAxios, axios)
|
||||||
|
.use(router)
|
||||||
|
.mount("#app");
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
|
||||||
|
import { register } from "register-service-worker";
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === "production") {
|
||||||
|
register(`${process.env.BASE_URL}service-worker.js`, {
|
||||||
|
ready() {
|
||||||
|
console.log(
|
||||||
|
"App is being served from cache by a service worker.\n" +
|
||||||
|
"For more details, visit https://goo.gl/AFskqB"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
registered() {
|
||||||
|
console.log("Service worker has been registered.");
|
||||||
|
},
|
||||||
|
cached() {
|
||||||
|
console.log("Content has been cached for offline use.");
|
||||||
|
},
|
||||||
|
updatefound() {
|
||||||
|
console.log("New content is downloading.");
|
||||||
|
},
|
||||||
|
updated() {
|
||||||
|
console.log("New content is available; please refresh.");
|
||||||
|
},
|
||||||
|
offline() {
|
||||||
|
console.log(
|
||||||
|
"No internet connection found. App is running in offline mode."
|
||||||
|
);
|
||||||
|
},
|
||||||
|
error(error) {
|
||||||
|
console.error("Error during service worker registration:", error);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
|
||||||
|
import { useAppStore } from "../store/app";
|
||||||
|
|
||||||
|
const routes: Array<RouteRecordRaw> = [
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
name: "home",
|
||||||
|
component: () =>
|
||||||
|
import(/* webpackChunkName: "start" */ "../views/DiscoverView.vue"),
|
||||||
|
beforeEnter: (to, from, next) => {
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const isAuthenticated = appStore.condition === "registered";
|
||||||
|
if (isAuthenticated) {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
next({ name: "start" });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/about",
|
||||||
|
name: "about",
|
||||||
|
component: () =>
|
||||||
|
import(/* webpackChunkName: "about" */ "../views/AboutView.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/start",
|
||||||
|
name: "start",
|
||||||
|
component: () =>
|
||||||
|
import(/* webpackChunkName: "start" */ "../views/StartView.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/account",
|
||||||
|
name: "account",
|
||||||
|
component: () =>
|
||||||
|
import(/* webpackChunkName: "account" */ "../views/AccountViewView.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/confirm-contact",
|
||||||
|
name: "confirm-contact",
|
||||||
|
component: () =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "confirm-contact" */ "../views/ConfirmContactView.vue"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/scan-contact",
|
||||||
|
name: "scan-contact",
|
||||||
|
component: () =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "scan-contact" */ "../views/ContactScanView.vue"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/discover",
|
||||||
|
name: "discover",
|
||||||
|
component: () =>
|
||||||
|
import(/* webpackChunkName: "discover" */ "../views/DiscoverView.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/import-account",
|
||||||
|
name: "import-account",
|
||||||
|
component: () =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "import-account" */ "../views/ImportAccountView.vue"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/new-edit-account",
|
||||||
|
name: "new-edit-account",
|
||||||
|
component: () =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "new-edit-account" */ "../views/NewEditAccountView.vue"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/new-edit-commitment",
|
||||||
|
name: "new-edit-commitment",
|
||||||
|
component: () =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "new-edit-commitment" */ "../views/NewEditCommitmentView.vue"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/project",
|
||||||
|
name: "project",
|
||||||
|
component: () =>
|
||||||
|
import(/* webpackChunkName: "project" */ "../views/ProjectViewView.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/new-edit-project",
|
||||||
|
name: "new-edit-project",
|
||||||
|
component: () =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "new-edit-project" */ "../views/NewEditProjectView.vue"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/projects",
|
||||||
|
name: "projects",
|
||||||
|
component: () =>
|
||||||
|
import(/* webpackChunkName: "projects" */ "../views/ProjectsView.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/commitments",
|
||||||
|
name: "commitments",
|
||||||
|
component: () =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "commitments" */ "../views/CommitmentsView.vue"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/** @type {*} */
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(process.env.BASE_URL),
|
||||||
|
routes,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
declare module '*.vue' {
|
||||||
|
import type { DefineComponent } from 'vue'
|
||||||
|
const component: DefineComponent<{}, {}, any>
|
||||||
|
export default component
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// @ts-check
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
|
export const useAccountStore = defineStore({
|
||||||
|
id: "account",
|
||||||
|
state: () => ({
|
||||||
|
account: JSON.parse(
|
||||||
|
typeof localStorage["account"] == "undefined"
|
||||||
|
? null
|
||||||
|
: localStorage["account"]
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
getters: {
|
||||||
|
firstName: (state) => state.account.firstName,
|
||||||
|
lastName: (state) => state.account.lastName,
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
reset() {
|
||||||
|
localStorage.removeItem("account");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// @ts-check
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
|
export const useAppStore = defineStore({
|
||||||
|
id: "app",
|
||||||
|
state: () => ({
|
||||||
|
_condition:
|
||||||
|
typeof localStorage["condition"] == "undefined"
|
||||||
|
? "uninitialized"
|
||||||
|
: localStorage["condition"],
|
||||||
|
_lastView:
|
||||||
|
typeof localStorage["lastView"] == "undefined"
|
||||||
|
? "/start"
|
||||||
|
: localStorage["lastView"],
|
||||||
|
_projectId:
|
||||||
|
typeof localStorage.getItem("projectId") === "undefined"
|
||||||
|
? ""
|
||||||
|
: localStorage.getItem("projectId"),
|
||||||
|
}),
|
||||||
|
getters: {
|
||||||
|
condition: (state) => state._condition,
|
||||||
|
projectId: (state): string => state._projectId as string,
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
reset() {
|
||||||
|
localStorage.removeItem("condition");
|
||||||
|
},
|
||||||
|
setCondition(newCondition: string) {
|
||||||
|
localStorage.setItem("condition", newCondition);
|
||||||
|
},
|
||||||
|
setProjectId(newProjectId: string) {
|
||||||
|
localStorage.setItem("projectId", newProjectId);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
:root {
|
|
||||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
|
||||||
line-height: 1.5;
|
|
||||||
font-weight: 400;
|
|
||||||
|
|
||||||
color-scheme: light dark;
|
|
||||||
color: rgba(255, 255, 255, 0.87);
|
|
||||||
background-color: #242424;
|
|
||||||
|
|
||||||
font-synthesis: none;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
font-weight: 500;
|
|
||||||
color: #646cff;
|
|
||||||
text-decoration: inherit;
|
|
||||||
}
|
|
||||||
a:hover {
|
|
||||||
color: #535bf2;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
display: flex;
|
|
||||||
place-items: center;
|
|
||||||
min-width: 320px;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 3.2em;
|
|
||||||
line-height: 1.1;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
padding: 0.6em 1.2em;
|
|
||||||
font-size: 1em;
|
|
||||||
font-weight: 500;
|
|
||||||
font-family: inherit;
|
|
||||||
background-color: #1a1a1a;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: border-color 0.25s;
|
|
||||||
}
|
|
||||||
button:hover {
|
|
||||||
border-color: #646cff;
|
|
||||||
}
|
|
||||||
button:focus,
|
|
||||||
button:focus-visible {
|
|
||||||
outline: 4px auto -webkit-focus-ring-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
padding: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#app {
|
|
||||||
max-width: 1280px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 2rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: light) {
|
|
||||||
:root {
|
|
||||||
color: #213547;
|
|
||||||
background-color: #ffffff;
|
|
||||||
}
|
|
||||||
a:hover {
|
|
||||||
color: #747bff;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<template>
|
||||||
|
<div class="about">
|
||||||
|
<h1>This is an about page</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
|
||||||
|
@Options({
|
||||||
|
components: {},
|
||||||
|
})
|
||||||
|
export default class AboutView extends Vue {}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
<template>
|
||||||
|
<!-- QUICK NAV -->
|
||||||
|
<nav id="QuickNav" class="fixed bottom-0 left-0 right-0 bg-slate-200">
|
||||||
|
<ul class="flex text-2xl p-2 gap-2">
|
||||||
|
<!-- Home Feed -->
|
||||||
|
<li class="basis-1/5 rounded-md text-slate-500">
|
||||||
|
<router-link :to="{ name: 'home' }" class="block text-center py-3 px-1">
|
||||||
|
<fa icon="house-chimney" class="fa-fw"></fa>
|
||||||
|
</router-link>
|
||||||
|
</li>
|
||||||
|
<!-- Search -->
|
||||||
|
<li class="basis-1/5 rounded-md text-slate-500">
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'discover' }"
|
||||||
|
class="block text-center py-3 px-1"
|
||||||
|
>
|
||||||
|
<fa icon="magnifying-glass" class="fa-fw"></fa>
|
||||||
|
</router-link>
|
||||||
|
</li>
|
||||||
|
<!-- Projects -->
|
||||||
|
<li class="basis-1/5 rounded-md text-slate-500">
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'projects' }"
|
||||||
|
class="block text-center py-3 px-1"
|
||||||
|
>
|
||||||
|
<fa icon="folder-open" class="fa-fw"></fa>
|
||||||
|
</router-link>
|
||||||
|
</li>
|
||||||
|
<!-- Commitments -->
|
||||||
|
<li class="basis-1/5 rounded-md text-slate-500">
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'commitments' }"
|
||||||
|
class="block text-center py-3 px-1"
|
||||||
|
>
|
||||||
|
<fa icon="hand" class="fa-fw"></fa>
|
||||||
|
</router-link>
|
||||||
|
</li>
|
||||||
|
<!-- Profile -->
|
||||||
|
<li class="basis-1/5 rounded-md bg-slate-400 text-white">
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'account' }"
|
||||||
|
class="block text-center py-3 px-1"
|
||||||
|
>
|
||||||
|
<fa icon="circle-user" class="fa-fw"></fa>
|
||||||
|
</router-link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- CONTENT -->
|
||||||
|
<section id="Content" class="p-6 pb-24">
|
||||||
|
<!-- Heading -->
|
||||||
|
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
|
||||||
|
Your Identity
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<!-- Friend referral requirement notice -->
|
||||||
|
<div
|
||||||
|
class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 mb-4"
|
||||||
|
>
|
||||||
|
<p class="mb-4">
|
||||||
|
<b>Important:</b> before you can create a new project or commit time to
|
||||||
|
one, you need a friend to register you.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
id="btnShowQR"
|
||||||
|
class="inline-block text-md uppercase bg-amber-600 text-white px-4 py-2 rounded-md"
|
||||||
|
>
|
||||||
|
Share Your ID
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Identity Details -->
|
||||||
|
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
|
||||||
|
<h2 class="text-xl font-semibold mb-2">Firstname Lastname</h2>
|
||||||
|
|
||||||
|
<div class="text-slate-500 text-sm font-bold">ID</div>
|
||||||
|
<div
|
||||||
|
class="text-sm text-slate-500 flex justify-between items-center mb-1"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
><code>{{ address }}</code>
|
||||||
|
<button @click="copy(address)">
|
||||||
|
<fa icon="copy" class="text-slate-400 fa-fw ml-1"></fa>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<button
|
||||||
|
class="text-xs uppercase bg-slate-500 text-white px-1.5 py-1 rounded-md"
|
||||||
|
>
|
||||||
|
<fa icon="share-nodes" class="fa-fw"></fa>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="text-xs uppercase bg-slate-500 text-white px-1.5 py-1 rounded-md ml-1"
|
||||||
|
>
|
||||||
|
<fa icon="qrcode" class="fa-fw"></fa>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-slate-500 text-sm font-bold">Public Key</div>
|
||||||
|
<div class="text-sm text-slate-500 mb-1">
|
||||||
|
<span
|
||||||
|
><code>{{ publicHex }}</code>
|
||||||
|
<button @click="copy(publicHex)">
|
||||||
|
<fa icon="copy" class="text-slate-400 fa-fw ml-1"></fa>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-slate-500 text-sm font-bold">Derivation Path</div>
|
||||||
|
<div class="text-sm text-slate-500 mb-1">
|
||||||
|
<span
|
||||||
|
><code>{{ UPORT_ROOT_DERIVATION_PATH }}</code>
|
||||||
|
<button @click="copy(UPORT_ROOT_DERIVATION_PATH)">
|
||||||
|
<fa icon="copy" class="text-slate-400 fa-fw ml-1"></fa>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'new-edit-account' }"
|
||||||
|
class="block text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-8"
|
||||||
|
>Edit Identity</router-link
|
||||||
|
>
|
||||||
|
|
||||||
|
<h3 class="text-sm uppercase font-semibold mb-3">Contact Actions</h3>
|
||||||
|
|
||||||
|
<a
|
||||||
|
href="contact-scan.html"
|
||||||
|
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-6"
|
||||||
|
>Scan New Contact</a
|
||||||
|
>
|
||||||
|
|
||||||
|
<h3 class="text-sm uppercase font-semibold mb-3">Identity Actions</h3>
|
||||||
|
|
||||||
|
<a
|
||||||
|
href=""
|
||||||
|
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
|
||||||
|
>Backup Seed</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href=""
|
||||||
|
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-6"
|
||||||
|
>Backup Other Data</a
|
||||||
|
>
|
||||||
|
|
||||||
|
<!-- QR code popup -->
|
||||||
|
<dialog id="dlgQR" class="backdrop:bg-black/75 rounded-md">
|
||||||
|
<form method="dialog">
|
||||||
|
<div class="text-slate-500 text-center">
|
||||||
|
<b>ID:</b> <code>did:peer:kl45kj41lk451kl3</code>
|
||||||
|
</div>
|
||||||
|
<img src="img/sample-qr-code.png" class="w-full mb-3" />
|
||||||
|
|
||||||
|
<button
|
||||||
|
value="cancel"
|
||||||
|
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
|
||||||
|
>
|
||||||
|
Copy to Clipboard
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
value="cancel"
|
||||||
|
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
import { useClipboard } from "@vueuse/core";
|
||||||
|
import { createIdentifier, deriveAddress, newIdentifier } from "../libs/crypto";
|
||||||
|
import { db } from "../db";
|
||||||
|
import { useAppStore } from "@/store/app";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
@Options({
|
||||||
|
components: {},
|
||||||
|
})
|
||||||
|
export default class AccountViewView extends Vue {
|
||||||
|
mnemonic = "";
|
||||||
|
address = "";
|
||||||
|
privateHex = "";
|
||||||
|
publicHex = "";
|
||||||
|
UPORT_ROOT_DERIVATION_PATH = "";
|
||||||
|
source = ref("Hello");
|
||||||
|
copy = useClipboard().copy;
|
||||||
|
|
||||||
|
// 'created' hook runs when the Vue instance is first created
|
||||||
|
async created() {
|
||||||
|
const appCondition = useAppStore().condition;
|
||||||
|
if (appCondition == "uninitialized") {
|
||||||
|
this.mnemonic = createIdentifier();
|
||||||
|
[
|
||||||
|
this.address,
|
||||||
|
this.privateHex,
|
||||||
|
this.publicHex,
|
||||||
|
this.UPORT_ROOT_DERIVATION_PATH,
|
||||||
|
] = deriveAddress(this.mnemonic);
|
||||||
|
|
||||||
|
const newId = newIdentifier(
|
||||||
|
this.address,
|
||||||
|
this.publicHex,
|
||||||
|
this.privateHex,
|
||||||
|
this.UPORT_ROOT_DERIVATION_PATH
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
await db.open();
|
||||||
|
const num_accounts = await db.accounts.count();
|
||||||
|
if (num_accounts === 0) {
|
||||||
|
await db.accounts.add({
|
||||||
|
publicKey: newId.keys[0].publicKeyHex,
|
||||||
|
mnemonic: this.mnemonic,
|
||||||
|
identity: JSON.stringify(newId),
|
||||||
|
dateCreated: new Date().getTime(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
useAppStore().setCondition("registered");
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await db.open();
|
||||||
|
const num_accounts = await db.accounts.count();
|
||||||
|
if (num_accounts === 0) {
|
||||||
|
console.log("Problem! Should have a profile!");
|
||||||
|
} else {
|
||||||
|
const accounts = await db.accounts.toArray();
|
||||||
|
const identity = JSON.parse(accounts[0].identity);
|
||||||
|
this.address = identity.did;
|
||||||
|
this.publicHex = identity.keys[0].publicKeyHex;
|
||||||
|
this.UPORT_ROOT_DERIVATION_PATH = identity.keys[0].meta.derivationPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<template>
|
||||||
|
<section id="Content" class="p-6 pb-24"></section>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<template>
|
||||||
|
<!-- CONTENT -->
|
||||||
|
<section id="Content" class="p-6 pb-24">
|
||||||
|
<!-- Breadcrumb -->
|
||||||
|
<div id="ViewBreadcrumb" class="mb-8">
|
||||||
|
<h1 class="text-lg text-center font-light relative px-7">
|
||||||
|
<!-- Cancel -->
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'account' }"
|
||||||
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
|
><fa icon="chevron-left" class="fa-fw"></fa
|
||||||
|
></router-link>
|
||||||
|
|
||||||
|
Confirm Contact
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form>
|
||||||
|
<p class="text-center text-xl mb-4 font-light">
|
||||||
|
Would you like to add <i>Firstname</i> to your network?
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Account Details -->
|
||||||
|
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
|
||||||
|
<h2 class="text-xl font-semibold mb-2">Firstname Lastname</h2>
|
||||||
|
|
||||||
|
<div class="text-slate-500 text-sm font-bold">ID</div>
|
||||||
|
<div class="text-sm text-slate-500 mb-1">
|
||||||
|
<span><code>did:peer:kl45kj41lk451kl3</code></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-8">
|
||||||
|
<input
|
||||||
|
type="submit"
|
||||||
|
class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
|
||||||
|
value="Add Contact"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
|
||||||
|
@Options({
|
||||||
|
components: {},
|
||||||
|
})
|
||||||
|
export default class ConfirmContactView extends Vue {}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
<template>
|
||||||
|
<section id="Content" class="p-6 pb-24">
|
||||||
|
<!-- Breadcrumb -->
|
||||||
|
<div id="ViewBreadcrumb" class="mb-8">
|
||||||
|
<h1 class="text-lg text-center font-light relative px-7">
|
||||||
|
<!-- Cancel -->
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'account' }"
|
||||||
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
|
><fa icon="chevron-left" class="fa-fw"></fa
|
||||||
|
></router-link>
|
||||||
|
|
||||||
|
Scan Contact
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form>
|
||||||
|
<h3 class="text-sm uppercase font-semibold mb-2">Scan a QR Code…</h3>
|
||||||
|
<div class="bg-black rounded overflow-hidden relative mb-4">
|
||||||
|
<img src="https://picsum.photos/400/400?random=1" class="w-full" />
|
||||||
|
|
||||||
|
<!-- Darken overlay -->
|
||||||
|
<!-- Top -->
|
||||||
|
<div class="absolute top-0 left-0 right-0 bg-black/50 h-1/4"></div>
|
||||||
|
<!-- Reft -->
|
||||||
|
<div class="absolute top-1/4 bottom-1/4 left-0 bg-black/50 w-1/4"></div>
|
||||||
|
<!-- Right -->
|
||||||
|
<div
|
||||||
|
class="absolute top-1/4 bottom-1/4 right-0 bg-black/50 w-1/4"
|
||||||
|
></div>
|
||||||
|
<!-- Bottom -->
|
||||||
|
<div class="absolute bottom-0 left-0 right-0 bg-black/50 h-1/4"></div>
|
||||||
|
|
||||||
|
<!-- Reticle overlay -->
|
||||||
|
<!-- Top-left -->
|
||||||
|
<div
|
||||||
|
class="absolute top-1/4 left-1/4 h-6 w-6 border-white border-t-4 border-l-4 drop-shadow"
|
||||||
|
></div>
|
||||||
|
<!-- Top-right -->
|
||||||
|
<div
|
||||||
|
class="absolute top-1/4 right-1/4 h-6 w-6 border-white border-t-4 border-r-4 drop-shadow"
|
||||||
|
></div>
|
||||||
|
<!-- Bottom-left -->
|
||||||
|
<div
|
||||||
|
class="absolute bottom-1/4 left-1/4 h-6 w-6 border-white border-b-4 border-l-4 drop-shadow"
|
||||||
|
></div>
|
||||||
|
<!-- Bottom-right -->
|
||||||
|
<div
|
||||||
|
class="absolute bottom-1/4 right-1/4 h-6 w-6 border-white border-b-4 border-r-4 drop-shadow"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 class="text-sm uppercase font-semibold mb-2">
|
||||||
|
…or Enter Contact Data
|
||||||
|
</h3>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Name (optional)"
|
||||||
|
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="ID"
|
||||||
|
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Public Key (optional)"
|
||||||
|
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="mt-8">
|
||||||
|
<input
|
||||||
|
type="submit"
|
||||||
|
class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
|
||||||
|
value="Look Up Contact"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
|
||||||
|
@Options({
|
||||||
|
components: {},
|
||||||
|
})
|
||||||
|
export default class ContactScanView extends Vue {}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
<template>
|
||||||
|
<!-- QUICK NAV -->
|
||||||
|
<nav id="QuickNav" class="fixed bottom-0 left-0 right-0 bg-slate-200">
|
||||||
|
<ul class="flex text-2xl p-2 gap-2">
|
||||||
|
<!-- Home Feed -->
|
||||||
|
<li class="basis-1/5 rounded-md text-slate-500">
|
||||||
|
<router-link :to="{ name: 'home' }" class="block text-center py-3 px-1"
|
||||||
|
><fa icon="house-chimney" class="fa-fw"></fa
|
||||||
|
></router-link>
|
||||||
|
</li>
|
||||||
|
<!-- Search -->
|
||||||
|
<li class="basis-1/5 rounded-md bg-slate-400 text-white">
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'discover' }"
|
||||||
|
class="block text-center py-3 px-1"
|
||||||
|
><fa icon="magnifying-glass" class="fa-fw"></fa
|
||||||
|
></router-link>
|
||||||
|
</li>
|
||||||
|
<!-- Projects -->
|
||||||
|
<li class="basis-1/5 rounded-md text-slate-500">
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'projects' }"
|
||||||
|
class="block text-center py-3 px-1"
|
||||||
|
><fa icon="folder-open" class="fa-fw"></fa
|
||||||
|
></router-link>
|
||||||
|
</li>
|
||||||
|
<!-- Commitments -->
|
||||||
|
<li class="basis-1/5 rounded-md text-slate-500">
|
||||||
|
<router-link :to="{ name: '' }" class="block text-center py-3 px-1"
|
||||||
|
><fa icon="hand" class="fa-fw"></fa
|
||||||
|
></router-link>
|
||||||
|
</li>
|
||||||
|
<!-- Profile -->
|
||||||
|
<li class="basis-1/5 rounded-md text-slate-500">
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'account' }"
|
||||||
|
class="block text-center py-3 px-1"
|
||||||
|
><fa icon="circle-user" class="fa-fw"></fa
|
||||||
|
></router-link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- CONTENT -->
|
||||||
|
<section id="Content" class="p-6 pb-24">
|
||||||
|
<!-- Heading -->
|
||||||
|
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
|
||||||
|
Discover
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<!-- Quick Search -->
|
||||||
|
<form id="QuickSearch" class="mb-4 flex">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search…"
|
||||||
|
class="block w-full rounded-l border border-r-0 border-slate-400 px-3 py-2"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
class="px-4 rounded-r bg-slate-200 border border-l-0 border-slate-400"
|
||||||
|
>
|
||||||
|
<fa icon="magnifying-glass" class="fa-fw"></fa>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Result Tabs -->
|
||||||
|
<div class="text-center text-slate-500 border-b border-slate-300">
|
||||||
|
<ul class="flex flex-wrap justify-center gap-4 -mb-px">
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="inline-block py-3 rounded-t-lg border-b-2 active text-blue-600 border-blue-600 font-semibold"
|
||||||
|
>
|
||||||
|
Nearby
|
||||||
|
<span
|
||||||
|
class="font-semibold text-sm bg-slate-200 px-1.5 py-0.5 rounded-md"
|
||||||
|
>20+</span
|
||||||
|
>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="inline-block py-3 rounded-t-lg border-b-2 border-transparent hover:text-slate-600 hover:border-slate-300"
|
||||||
|
>
|
||||||
|
Remote
|
||||||
|
<span
|
||||||
|
class="font-semibold text-sm bg-slate-200 px-1.5 py-0.5 rounded-md"
|
||||||
|
>13</span
|
||||||
|
>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Results List -->
|
||||||
|
<ul class="">
|
||||||
|
<li class="border-b border-slate-300">
|
||||||
|
<a href="project-view.html" class="block py-4 flex gap-4">
|
||||||
|
<div class="w-12">
|
||||||
|
<img
|
||||||
|
src="https://picsum.photos/200/200?random=1"
|
||||||
|
class="w-full rounded"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grow">
|
||||||
|
<h2 class="text-base font-semibold">Canyon cleanup</h2>
|
||||||
|
<div class="text-sm">
|
||||||
|
<fa icon="user" class="fa-fw text-slate-400"></fa> Rotary
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="border-b border-slate-300">
|
||||||
|
<a href="project-view.html" class="block py-4 flex gap-4">
|
||||||
|
<div class="w-12">
|
||||||
|
<img
|
||||||
|
src="https://picsum.photos/200/200?random=2"
|
||||||
|
class="w-full rounded"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grow">
|
||||||
|
<h2 class="text-base font-semibold">Potluck with neighbors</h2>
|
||||||
|
<div class="text-sm">
|
||||||
|
<fa icon="user" class="fa-fw text-slate-400"></fa> Andrew A.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="border-b border-slate-300">
|
||||||
|
<a href="project-view.html" class="block py-4 flex gap-4">
|
||||||
|
<div class="w-12">
|
||||||
|
<img
|
||||||
|
src="https://picsum.photos/200/200?random=3"
|
||||||
|
class="w-full rounded"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grow">
|
||||||
|
<h2 class="text-base font-semibold">Historical site</h2>
|
||||||
|
<div class="text-sm">
|
||||||
|
<fa icon="user" class="fa-fw text-slate-400 mr-1"></fa>
|
||||||
|
<em>Unknown</em>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
|
||||||
|
@Options({
|
||||||
|
components: {},
|
||||||
|
})
|
||||||
|
export default class DiscoverView extends Vue {}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<template>
|
||||||
|
<section></section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
import HelloWorld from "@/components/HelloWorld.vue"; // @ is an alias to /src
|
||||||
|
|
||||||
|
@Options({
|
||||||
|
components: {
|
||||||
|
HelloWorld,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export default class HomeView extends Vue {}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
<template>
|
||||||
|
<section id="Content" class="p-6 pb-24">
|
||||||
|
<!-- Breadcrumb -->
|
||||||
|
<div id="ViewBreadcrumb" class="mb-8">
|
||||||
|
<h1 class="text-lg text-center font-light relative px-7">
|
||||||
|
<!-- Cancel -->
|
||||||
|
<button
|
||||||
|
@click="$router.go(-1)"
|
||||||
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
|
>
|
||||||
|
<fa icon="chevron-left"></fa>
|
||||||
|
</button>
|
||||||
|
Import Existing Identity
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<!-- Import Account Form -->
|
||||||
|
<p class="text-center text-xl mb-4 font-light">
|
||||||
|
Enter your seed phrase below to import your identity on this device.
|
||||||
|
</p>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Seed Phrase"
|
||||||
|
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
|
||||||
|
v-model="mnemonic"
|
||||||
|
/>
|
||||||
|
{{ mnemonic }}
|
||||||
|
<div class="mt-8">
|
||||||
|
<button
|
||||||
|
@click="from_mnemonic()"
|
||||||
|
class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
|
||||||
|
>
|
||||||
|
Import
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
@click="onCancelClick()"
|
||||||
|
type="button"
|
||||||
|
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
import { deriveAddress, newIdentifier } from "../libs/crypto";
|
||||||
|
import { db } from "@/db";
|
||||||
|
import { useAppStore } from "@/store/app";
|
||||||
|
|
||||||
|
@Options({
|
||||||
|
components: {},
|
||||||
|
})
|
||||||
|
export default class ImportAccountView extends Vue {
|
||||||
|
mnemonic = "";
|
||||||
|
address = "";
|
||||||
|
privateHex = "";
|
||||||
|
publicHex = "";
|
||||||
|
UPORT_ROOT_DERIVATION_PATH = "";
|
||||||
|
|
||||||
|
public onCancelClick() {
|
||||||
|
this.$router.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async from_mnemonic() {
|
||||||
|
const mne: string = this.mnemonic.trim().toLowerCase();
|
||||||
|
if (this.mnemonic.trim().length > 0) {
|
||||||
|
[
|
||||||
|
this.address,
|
||||||
|
this.privateHex,
|
||||||
|
this.publicHex,
|
||||||
|
this.UPORT_ROOT_DERIVATION_PATH,
|
||||||
|
] = deriveAddress(mne);
|
||||||
|
|
||||||
|
const newId = newIdentifier(
|
||||||
|
this.address,
|
||||||
|
this.publicHex,
|
||||||
|
this.privateHex,
|
||||||
|
this.UPORT_ROOT_DERIVATION_PATH
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.open();
|
||||||
|
const num_accounts = await db.accounts.count();
|
||||||
|
if (num_accounts === 0) {
|
||||||
|
console.log("...");
|
||||||
|
await db.accounts.add({
|
||||||
|
publicKey: newId.keys[0].publicKeyHex,
|
||||||
|
mnemonic: mne,
|
||||||
|
identity: JSON.stringify(newId),
|
||||||
|
dateCreated: new Date().getTime(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
useAppStore().setCondition("registered");
|
||||||
|
this.$router.push({ name: "account" });
|
||||||
|
} catch (err) {
|
||||||
|
console.log("Error!");
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<template>
|
||||||
|
<section id="Content" class="p-6 pb-24">
|
||||||
|
<!-- Breadcrumb -->
|
||||||
|
<div id="ViewBreadcrumb" class="mb-8">
|
||||||
|
<h1 class="text-lg text-center font-light relative px-7">
|
||||||
|
<!-- Cancel -->
|
||||||
|
<button
|
||||||
|
@click="$router.go(-1)"
|
||||||
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
|
>
|
||||||
|
<fa icon="chevron-left" class="fa-fw"></fa>
|
||||||
|
</button>
|
||||||
|
[New/Edit] Identity
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<form>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="First Name"
|
||||||
|
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Last Name"
|
||||||
|
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="mt-8">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
|
||||||
|
>
|
||||||
|
Save Changes
|
||||||
|
</button>
|
||||||
|
<!-- SHOW ME instead while processing saving changes -->
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
|
||||||
|
@Options({
|
||||||
|
components: {},
|
||||||
|
})
|
||||||
|
export default class NewEditAccountView extends Vue {}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<template>
|
||||||
|
<!-- CONTENT -->
|
||||||
|
<section id="Content" class="p-6 pb-24">
|
||||||
|
<!-- Breadcrumb -->
|
||||||
|
<div id="ViewBreadcrumb" class="mb-8">
|
||||||
|
<h1 class="text-lg text-center font-light relative px-7">
|
||||||
|
<!-- Cancel -->
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'project' }"
|
||||||
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
|
><fa icon="chevron-left" class="fa-fw"></fa>
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
Make Commitment
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Project Details -->
|
||||||
|
<form>
|
||||||
|
<select
|
||||||
|
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
|
||||||
|
>
|
||||||
|
<option disabled>Choose a commitment type…</option>
|
||||||
|
<option selected>Time</option>
|
||||||
|
<option>Cryptocurrency</option>
|
||||||
|
<option>Money</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- Time amount -->
|
||||||
|
<div class="mb-4 flex items-stretch">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
placeholder="0.0"
|
||||||
|
class="block w-full rounded-l border border-slate-400 px-3 py-2"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="px-4 py-2 rounded-r bg-slate-200 border border-l-0 border-slate-400"
|
||||||
|
>hours</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Crypto amount -->
|
||||||
|
|
||||||
|
<!-- Money amount -->
|
||||||
|
|
||||||
|
<div class="mt-8">
|
||||||
|
<input
|
||||||
|
type="submit"
|
||||||
|
class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
|
||||||
|
value="Commit"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
|
||||||
|
>
|
||||||
|
Maybe Later
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
|
||||||
|
@Options({
|
||||||
|
components: {},
|
||||||
|
})
|
||||||
|
export default class NewEditCommitmentView extends Vue {}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
<template>
|
||||||
|
<!-- CONTENT -->
|
||||||
|
<section id="Content" class="p-6 pb-24">
|
||||||
|
<!-- Breadcrumb -->
|
||||||
|
<div id="ViewBreadcrumb" class="mb-8">
|
||||||
|
<h1 class="text-lg text-center font-light relative px-7">
|
||||||
|
<!-- Cancel -->
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'project' }"
|
||||||
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
|
><fa icon="chevron-left" class="fa-fw"></fa
|
||||||
|
></router-link>
|
||||||
|
[New/Edit] Project
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Project Details -->
|
||||||
|
<!-- Image - (see design model) Empty -->
|
||||||
|
|
||||||
|
<!-- Image - Populated -->
|
||||||
|
<div class="relative mb-4 rounded-md overflow-hidden">
|
||||||
|
<div class="absolute top-3 right-3 flex gap-2">
|
||||||
|
<button
|
||||||
|
class="text-md font-bold uppercase bg-blue-600 text-white px-3 py-2 rounded"
|
||||||
|
>
|
||||||
|
<fa icon="pen" class="fa-fw"></fa>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="text-md font-bold uppercase bg-red-600 text-white px-3 py-2 rounded"
|
||||||
|
>
|
||||||
|
<fa icon="trash-can" class="fa-fw"></fa>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<img src="https://picsum.photos/800/400" class="w-full" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Project Name"
|
||||||
|
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
|
||||||
|
v-model="projectName"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<textarea
|
||||||
|
placeholder="Description"
|
||||||
|
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
|
||||||
|
rows="5"
|
||||||
|
v-model="description"
|
||||||
|
></textarea>
|
||||||
|
<div class="text-xs text-slate-500 italic -mt-3 mb-4">
|
||||||
|
88/500 max. characters
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-8">
|
||||||
|
<button
|
||||||
|
class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
|
||||||
|
@click="onSaveProjectClick()"
|
||||||
|
>
|
||||||
|
Save Project
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
|
||||||
|
@click="onCancelClick()"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
import { AppString } from "@/constants/app";
|
||||||
|
import { db } from "../db";
|
||||||
|
import { accessToken, SimpleSigner } from "@/libs/crypto";
|
||||||
|
import * as didJwt from "did-jwt";
|
||||||
|
import { IIdentifier } from "@veramo/core";
|
||||||
|
import { useAppStore } from "@/store/app";
|
||||||
|
|
||||||
|
@Options({
|
||||||
|
components: {},
|
||||||
|
})
|
||||||
|
export default class NewEditProjectView extends Vue {
|
||||||
|
projectName = "";
|
||||||
|
description = "";
|
||||||
|
|
||||||
|
private async SaveProject(identity: IIdentifier) {
|
||||||
|
const address = identity.did;
|
||||||
|
// Make a claim
|
||||||
|
const vcClaim = {
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "PlanAction",
|
||||||
|
identifier: address,
|
||||||
|
name: this.projectName,
|
||||||
|
description: this.description,
|
||||||
|
};
|
||||||
|
// Make a payload for the claim
|
||||||
|
const vcPayload = {
|
||||||
|
sub: "PlanAction",
|
||||||
|
vc: {
|
||||||
|
"@context": ["https://www.w3.org/2018/credentials/v1"],
|
||||||
|
type: ["VerifiableCredential"],
|
||||||
|
credentialSubject: vcClaim,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// create a signature using private key of identity
|
||||||
|
if (
|
||||||
|
identity.keys[0].privateKeyHex !== "undefined" &&
|
||||||
|
identity.keys[0].privateKeyHex !== null
|
||||||
|
) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
const privateKeyHex: string = identity.keys[0].privateKeyHex!;
|
||||||
|
const signer = await SimpleSigner(privateKeyHex);
|
||||||
|
const alg = undefined;
|
||||||
|
// create a JWT for the request
|
||||||
|
const vcJwt: string = await didJwt.createJWT(vcPayload, {
|
||||||
|
alg: alg,
|
||||||
|
issuer: identity.did,
|
||||||
|
signer: signer,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Make the xhr request payload
|
||||||
|
|
||||||
|
const payload = JSON.stringify({ jwtEncoded: vcJwt });
|
||||||
|
const endorserApiServer = AppString.DEFAULT_ENDORSER_API_SERVER;
|
||||||
|
const url = endorserApiServer + "/api/claim";
|
||||||
|
const token = await accessToken(identity);
|
||||||
|
const headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: "Bearer " + token,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resp = await this.axios.post(url, payload, { headers });
|
||||||
|
console.log(resp.status, resp.data);
|
||||||
|
useAppStore().setProjectId(resp.data);
|
||||||
|
const route = {
|
||||||
|
name: "project",
|
||||||
|
};
|
||||||
|
console.log(route);
|
||||||
|
this.$router.push(route);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async onSaveProjectClick() {
|
||||||
|
await db.open();
|
||||||
|
const num_accounts = await db.accounts.count();
|
||||||
|
if (num_accounts === 0) {
|
||||||
|
console.log("Problem! Should have a profile!");
|
||||||
|
} else {
|
||||||
|
const accounts = await db.accounts.toArray();
|
||||||
|
const identity = JSON.parse(accounts[0].identity);
|
||||||
|
this.SaveProject(identity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public onCancelClick() {
|
||||||
|
this.$router.back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
<template>
|
||||||
|
<!-- QUICK NAV -->
|
||||||
|
<nav id="QuickNav" class="fixed bottom-0 left-0 right-0 bg-slate-200">
|
||||||
|
<ul class="flex text-2xl p-2 gap-2">
|
||||||
|
<!-- Home Feed -->
|
||||||
|
<li class="basis-1/5 rounded-md text-slate-500">
|
||||||
|
<a href="" class="block text-center py-3 px-1"
|
||||||
|
><fa icon="house-chimney" class="fa-fw"></fa
|
||||||
|
></a>
|
||||||
|
</li>
|
||||||
|
<!-- Search -->
|
||||||
|
<li class="basis-1/5 rounded-md text-slate-500">
|
||||||
|
<a href="search.html" class="block text-center py-3 px-1"
|
||||||
|
><fa icon="magnifying-glass" class="fa-fw"></fa
|
||||||
|
></a>
|
||||||
|
</li>
|
||||||
|
<!-- Projects -->
|
||||||
|
<li class="basis-1/5 rounded-md bg-slate-400 text-white">
|
||||||
|
<a href="" class="block text-center py-3 px-1"
|
||||||
|
><fa icon="folder-open" class="fa-fw"></fa
|
||||||
|
></a>
|
||||||
|
</li>
|
||||||
|
<!-- Commitments -->
|
||||||
|
<li class="basis-1/5 rounded-md text-slate-500">
|
||||||
|
<a href="" class="block text-center py-3 px-1"
|
||||||
|
><fa icon="hand" class="fa-fw"></fa
|
||||||
|
></a>
|
||||||
|
</li>
|
||||||
|
<!-- Profile -->
|
||||||
|
<li class="basis-1/5 rounded-md text-slate-500">
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'account' }"
|
||||||
|
class="block text-center py-3 px-1"
|
||||||
|
><fa icon="circle-user" class="fa-fw"></fa
|
||||||
|
></router-link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- CONTENT -->
|
||||||
|
<section id="Content" class="p-6 pb-24">
|
||||||
|
<!-- Breadcrumb -->
|
||||||
|
<div id="ViewBreadcrumb" class="mb-8">
|
||||||
|
<h1 class="text-lg text-center font-light relative px-7">
|
||||||
|
<!-- Back -->
|
||||||
|
<button
|
||||||
|
@click="$router.go(-1)"
|
||||||
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
||||||
|
>
|
||||||
|
<fa icon="chevron-left" class="fa-fw"></fa>
|
||||||
|
</button>
|
||||||
|
<!-- Context Menu -->
|
||||||
|
<a
|
||||||
|
href=""
|
||||||
|
class="text-lg text-center px-2 py-1 absolute -right-2 -top-1"
|
||||||
|
><fa icon="ellipsis-vertical" class="fa-fw"></fa
|
||||||
|
></a>
|
||||||
|
|
||||||
|
View Project
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Project Details -->
|
||||||
|
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
|
||||||
|
<!-- Image -->
|
||||||
|
<div class="-mx-4 -mt-3 mb-3">
|
||||||
|
<img src="https://picsum.photos/800/400" class="w-full" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2 class="text-xl font-semibold">Canyon cleanup</h2>
|
||||||
|
<div class="flex justify-between gap-4 text-sm mb-3">
|
||||||
|
<span><fa icon="user" class="fa-fw text-slate-400"></fa> Rotary</span>
|
||||||
|
<span
|
||||||
|
><fa icon="calendar" class="fa-fw text-slate-400"></fa> 8 days
|
||||||
|
ago</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-sm text-slate-500">
|
||||||
|
Sed ut perspiciatis unde omnis iste natus error sit voluptatem
|
||||||
|
accusantium doloremque laudantium…
|
||||||
|
<a href="" class="uppercase text-xs font-semibold text-slate-700"
|
||||||
|
>Read More</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Commit -->
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'new-edit-commitment' }"
|
||||||
|
class="block text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-8"
|
||||||
|
>Make Commitment</router-link
|
||||||
|
>
|
||||||
|
|
||||||
|
<!-- Commitments -->
|
||||||
|
<div class="bg-slate-100 px-4 py-3 rounded-md">
|
||||||
|
<h3 class="text-sm uppercase font-semibold mb-3">Commitments</h3>
|
||||||
|
|
||||||
|
<ul class="text-sm border-t border-slate-300">
|
||||||
|
<li class="flex justify-between gap-4 py-1.5 border-b border-slate-300">
|
||||||
|
<span>[Username]</span>
|
||||||
|
<span
|
||||||
|
>5 hours <fa icon="spinner" class="fa-fw text-slate-400"></fa
|
||||||
|
></span>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="flex justify-between gap-4 py-1.5 border-b border-slate-300">
|
||||||
|
<span>[Username]</span>
|
||||||
|
<span
|
||||||
|
>US$ 20.00 <fa icon="circle-check" class="fa-fw text-lime-500"></fa
|
||||||
|
></span>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="flex justify-between gap-4 py-1.5 border-b border-slate-300">
|
||||||
|
<span>[Username]</span>
|
||||||
|
<span
|
||||||
|
>0.1 BTC <fa icon="spinner" class="fa-fw text-slate-400"></fa
|
||||||
|
></span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
import { useAppStore } from "@/store/app";
|
||||||
|
|
||||||
|
@Options({
|
||||||
|
components: {},
|
||||||
|
})
|
||||||
|
export default class ProjectViewView extends Vue {
|
||||||
|
projectId = "";
|
||||||
|
created(): void {
|
||||||
|
this.projectId = useAppStore().projectId;
|
||||||
|
console.log(this.projectId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
<template>
|
||||||
|
<section id="Content" class="p-6 pb-24">
|
||||||
|
<!-- Heading -->
|
||||||
|
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
|
||||||
|
My Projects
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<!-- Quick Search -->
|
||||||
|
<form id="QuickSearch" class="mb-4 flex">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search…"
|
||||||
|
class="block w-full rounded-l border-r-0 border-slate-400"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
class="px-4 rounded-r bg-slate-200 border border-l-0 border-slate-400"
|
||||||
|
>
|
||||||
|
<i class="fa-solid fa-magnifying-glass fa-fw"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- New Project -->
|
||||||
|
<button
|
||||||
|
class="fixed right-6 bottom-24 text-center text-4xl leading-none bg-blue-600 text-white w-14 py-2.5 rounded-full"
|
||||||
|
@click="onClickNewProject()"
|
||||||
|
>
|
||||||
|
<fa icon="plus" class="fa-fw"></fa>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Results List -->
|
||||||
|
<ul class="">
|
||||||
|
<li class="border-b border-slate-300">
|
||||||
|
<a href="project-view.html" class="block py-4 flex gap-4">
|
||||||
|
<div class="flex-none w-12">
|
||||||
|
<img
|
||||||
|
src="https://picsum.photos/200/200?random=1"
|
||||||
|
class="w-full rounded"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grow overflow-hidden">
|
||||||
|
<h2 class="text-base font-semibold">Canyon cleanup</h2>
|
||||||
|
<div class="text-sm truncate">
|
||||||
|
The quick brown fox jumps over the lazy dog. The quick brown fox
|
||||||
|
jumps over the lazy dog.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="border-b border-slate-300">
|
||||||
|
<a href="project-view.html" class="block py-4 flex gap-4">
|
||||||
|
<div class="flex-none w-12">
|
||||||
|
<img
|
||||||
|
src="https://picsum.photos/200/200?random=2"
|
||||||
|
class="w-full rounded"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grow overflow-hidden">
|
||||||
|
<h2 class="text-base font-semibold">Potluck with neighbors</h2>
|
||||||
|
<div class="text-sm truncate">
|
||||||
|
The quick brown fox jumps over the lazy dog. The quick brown fox
|
||||||
|
jumps over the lazy dog.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="border-b border-slate-300">
|
||||||
|
<a href="project-view.html" class="block py-4 flex gap-4">
|
||||||
|
<div class="flex-none w-12">
|
||||||
|
<img
|
||||||
|
src="https://picsum.photos/200/200?random=3"
|
||||||
|
class="w-full rounded"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grow overflow-hidden">
|
||||||
|
<h2 class="text-base font-semibold">Historical site</h2>
|
||||||
|
<div class="text-sm truncate">
|
||||||
|
The quick brown fox jumps over the lazy dog. The quick brown fox
|
||||||
|
jumps over the lazy dog.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
|
||||||
|
@Options({
|
||||||
|
components: {},
|
||||||
|
})
|
||||||
|
export default class ProjectsView extends Vue {
|
||||||
|
onClickNewProject(): void {
|
||||||
|
const route = {
|
||||||
|
name: "new-edit-project",
|
||||||
|
};
|
||||||
|
console.log(route);
|
||||||
|
this.$router.push(route);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<section
|
||||||
|
id="Content"
|
||||||
|
class="p-6 pb-24 min-h-screen flex flex-col justify-center"
|
||||||
|
>
|
||||||
|
<!-- Heading -->
|
||||||
|
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
|
||||||
|
Start Here
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div class="mt-8">
|
||||||
|
<p class="text-center text-xl mb-4 font-light">
|
||||||
|
Do you already have an identity to import?
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
@click="onClickYes()"
|
||||||
|
class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
|
||||||
|
>
|
||||||
|
No
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
@click="onClickNo()"
|
||||||
|
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
|
||||||
|
>Yes</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
|
||||||
|
@Options({
|
||||||
|
components: {},
|
||||||
|
})
|
||||||
|
export default class StartView extends Vue {
|
||||||
|
public onClickYes() {
|
||||||
|
this.$router.push({ name: "account" });
|
||||||
|
}
|
||||||
|
|
||||||
|
public onClickNo() {
|
||||||
|
this.$router.push({ name: "import-account" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1 +0,0 @@
|
|||||||
/// <reference types="vite/client" />
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: ["./src/**/*.vue"],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
};
|
||||||
@@ -1,25 +1,41 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2020",
|
"target": "esnext",
|
||||||
"useDefineForClassFields": true,
|
"module": "esnext",
|
||||||
"module": "ESNext",
|
|
||||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
||||||
"skipLibCheck": true,
|
|
||||||
|
|
||||||
/* Bundler mode */
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"jsx": "preserve",
|
|
||||||
|
|
||||||
/* Linting */
|
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noUnusedLocals": true,
|
"jsx": "preserve",
|
||||||
"noUnusedParameters": true,
|
"moduleResolution": "node",
|
||||||
"noFallthroughCasesInSwitch": true
|
"experimentalDecorators": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"types": [
|
||||||
|
"webpack-env"
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"src/*"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
|
"lib": [
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
"esnext",
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"scripthost"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.tsx",
|
||||||
|
"src/**/*.vue",
|
||||||
|
"tests/**/*.ts",
|
||||||
|
"tests/**/*.tsx"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"composite": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowSyntheticDefaultImports": true
|
|
||||||
},
|
|
||||||
"include": ["vite.config.ts"]
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import { defineConfig } from 'vite'
|
|
||||||
import vue from '@vitejs/plugin-vue'
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
|
||||||
export default defineConfig({
|
|
||||||
plugins: [vue()],
|
|
||||||
})
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
const { defineConfig } = require("@vue/cli-service");
|
||||||
|
module.exports = defineConfig({
|
||||||
|
transpileDependencies: true,
|
||||||
|
configureWebpack: {
|
||||||
|
devtool: "source-map",
|
||||||
|
experiments: {
|
||||||
|
topLevelAwait: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||