Compare commits

..
Author SHA1 Message Date
Matthew Aaron Raymer 1c0881fe14 Subject: workflow for saving of project
* Changed from passing parameters since this isn't supported anymore by Vue
* Added new AppStore values for a projectId which are accessible in the ProjectView View
2023-01-10 16:22:07 +08:00
trentlarson 2a7c858662 refactor: use our own SimpleSigner since library version is deprecated 2023-01-09 19:08:41 -07:00
Matthew Aaron Raymer 41d8df2238 Added Project New Button 2023-01-09 16:05:45 +08:00
Matthew Aaron Raymer 71546ea605 Added new projects view 2023-01-09 15:57:58 +08:00
Matthew Aaron Raymer 487997b87c Adding a less complex router 2023-01-09 15:10:05 +08:00
Matthew Aaron Raymer ba85663048 Linted 2023-01-07 13:19:04 +08:00
trentlarson 9d566fa977 fix: use SimpleSigner directly from did-jwt 2023-01-06 19:25:53 -07:00
Matthew Aaron Raymer 3440f28121 Added some ideas for doing actions from the console ... Work in Progress 2023-01-06 17:56:37 +08:00
Matthew Aaron Raymer 150b35c4c7 Update with our own SimpleSigner 2023-01-06 17:30:41 +08:00
Matthew Aaron Raymer 39c931cde9 A touch of documenting process 2023-01-04 17:15:11 +08:00
Matthew Aaron Raymer f858a4d29a Linted 2023-01-04 17:09:11 +08:00
Matthew Aaron Raymer f021fcdb1c New Project 2023-01-04 17:05:08 +08:00
Matthew Aaron Raymer 6325bcbe35 Work in progress 2023-01-03 17:28:23 +08:00
Matthew Aaron Raymer 0ee35e4946 Added variation of accessToken method carried over from endorser-mobile 2023-01-03 12:35:41 +08:00
Matthew Aaron Raymer f5a2d71ed3 Added some elementary Vue constructs 2023-01-02 17:55:43 +08:00
anomalist c43fdcbb7f Merge pull request 'chore: add setup file for asdf-vm.com' (#3) from setup-asdf into master
Reviewed-on: trent_larson/kick-starter-for-time-pwa#3
2023-01-02 01:53:16 -05:00
trentlarson 3034d66a5d docs: insert coding project task list into this repo 2023-01-01 12:48:42 -07:00
trentlarson ba14fd4242 chore: add setup file for asdf-vm.com 2023-01-01 12:43:34 -07:00
anomalist 236c1c2836 Merge pull request 'remove code for lowercase checks (that were for old uPort)' (#1) from remove-unused-lowercases into master
Reviewed-on: trent_larson/kick-starter-for-time-pwa#1
2022-12-31 21:03:38 -05:00
trentlarson d2cea34242 fix: remove code for lowercase checks (that were for old uPort) 2022-12-29 16:13:51 -07:00
Matthew Aaron Raymer 3687e5e282 Subject line: Added v-model for input box on seed input.
Also completed recovery from seed.  Should be ready to move to Make commitment
2022-12-22 15:24:17 +08:00
Matthew Aaron Raymer 65381e103c Flesh out a bit more of the import and also refactor the router 2022-12-21 18:02:19 +08:00
Matthew Aaron Raymer 07f763e167 Fixing import function 2022-12-21 16:08:40 +08:00
Matthew Aaron Raymer c9919987ca Added clipboard copy actions.
We need to add designs for feedback on the copy action
2022-12-19 17:51:14 +08:00
Matthew Aaron Raymer 9f94aad88a Subject line: Adding routing for post-registration.
Had to add a bogus home page (left over boilerplate home) to fill the role of an initial load state.
The app will look at the app Pinia state and if it is 'registered' and then send to the discovery page.
2022-12-19 15:58:17 +08:00
Matthew Aaron Raymer b4557c3596 Adding markers to keep track of registration state 2022-12-19 14:19:33 +08:00
Matthew Aaron Raymer 05e969fbc4 Some small cleanup. Tried Plugin and have abandoned. 2022-12-17 18:28:53 +08:00
Matthew Aaron Raymer 4a407b43ae Fix encrypt-decrypt issue. 2022-12-17 13:32:00 +08:00
Matthew Aaron Raymer c6d0473fab Refactored again. Fields update on Account page 2022-12-16 18:46:01 +08:00
Matthew Aaron Raymer 607230b51c Cleaned up old Dexie class. Added mnemonic and dateCreated columns. 2022-12-16 13:34:01 +08:00
Matthew Aaron Raymer c9d5ab82fd Adds an initial account as long as there are no available accounts. 2022-12-15 17:52:42 +08:00
Matthew Aaron Raymer 3ac8f911ac DB being created. Still getting an exception. 2022-12-15 16:45:38 +08:00
Matthew Aaron Raymer 9232afb5af In transition ... experimenting 2022-12-15 16:36:23 +08:00
Matthew Aaron Raymer 2c57bbf4ee Added a source map to aid in debugging 2022-12-14 17:34:53 +08:00
Matthew Aaron Raymer c239906a96 New structure 2022-12-14 17:06:16 +08:00
Matthew Aaron Raymer 0fa0936c59 Clean up a bit 2022-12-13 18:00:40 +08:00
Matthew Aaron Raymer aad6b8273b Stil fixing database writes 2022-12-13 17:47:04 +08:00
Matthew Aaron Raymer 571fd241aa Added Dixie class and cryptography. Need to figure out generating password, storing, and boot sequence. 2022-12-12 18:35:20 +08:00
Matthew Aaron Raymer e0a3f92211 Add Dixie and make some dependency updates 2022-12-12 14:28:58 +08:00
Matthew Aaron Raymer b58c0ce820 First iteration of account creation. More refactoring to come 2022-12-09 18:23:53 +08:00
Matthew Aaron Raymer cbad2e7308 Initial foray into the enrolling. Expect refactoring. 2022-12-08 17:36:26 +08:00
Matthew Aaron Raymer 84af5287de Fix to new routing rules 2022-12-08 15:37:07 +08:00
Matthew Aaron Raymer 39f2d73007 Adding new routing logic ... broken for the moment 2022-12-07 17:59:37 +08:00
Matthew Aaron Raymer ed23317b0f Adding an appStore Pinia datastore 2022-12-05 17:50:28 +08:00
Matthew Aaron Raymer 3c843b2f16 Add a back-button action 2022-12-05 16:54:40 +08:00
Matthew Aaron Raymer 617de58a92 Change to anchors with click handlers so we can run code before transitioning to next pages 2022-12-05 16:52:28 +08:00
Matthew Aaron Raymer 290a13fbf2 Fixing up some dependencies 2022-12-05 16:10:29 +08:00
Matthew Aaron Raymer 5c14275a75 Added Pinia account state tracking. Also modified router to go to Start if no account is defined. 2022-12-02 18:54:47 +08:00
Matthew Aaron Raymer 3c388da22d Added Pinia in addition to Vuex for state-management. Will migrate to Pinia eventually 2022-12-01 17:43:43 +08:00
Matthew Aaron Raymer ba143dfccd Cleanup of some workflow actions and quick nav 2022-12-01 16:16:58 +08:00
Matthew Aaron Raymer 037fb09d82 A few small fixes and adding some possible dependencies we'll need later 2022-11-30 16:36:21 +08:00
Matthew Aaron Raymer 3357ee08eb Updates to quick navigation 2022-11-30 15:40:10 +08:00
Matthew Aaron Raymer 65d4efb936 Updates to add back buttons and known router paths 2022-11-30 14:39:24 +08:00
Matthew Aaron Raymer f28e2123b1 Resynching master branch 2022-11-30 13:20:45 +08:00
Matthew Aaron Raymer 301b96ef3a Merge branch 'tmp' 2022-11-27 14:18:18 +08:00
Matthew Aaron Raymer 6a0ddb15e5 Fix config 2022-11-27 14:16:57 +08:00
Matthew Aaron Raymer 7cb2821e76 FontAwesome added 2022-11-27 13:44:12 +08:00
Matthew Aaron Raymer 7d707e47f4 Clean up vestigal boilerplat 2022-11-27 13:30:21 +08:00
Matthew Aaron Raymer a75a745b8b Fix up css 2022-11-27 13:15:25 +08:00
Matthew Aaron Raymer 813a874d12 Added first path to start view 2022-11-27 12:17:54 +08:00
Matthew Aaron Raymer c1acee63bf Linted views 2022-11-27 12:11:07 +08:00
Matthew Aaron Raymer acbf1b062b TypeScript classes added 2022-11-27 12:09:44 +08:00
Matthew Aaron Raymer 303f22b60d Raw templates for all current designs 2022-11-27 11:58:01 +08:00
Matthew Aaron Raymer ff826a81f3 Add ImportAccountView 2022-11-26 17:02:50 +08:00
Matthew Aaron Raymer 099dbbdad8 Added NewEditAccountView and gitignore 2022-11-26 16:36:37 +08:00
Matthew Aaron Raymer ea668a87d4 Adding Tailwind and start application views 2022-11-26 12:36:39 +08:00
Matthew Aaron Raymer 2243458a67 Updates to dependencies 2022-11-25 16:28:00 +08:00
Matthew Aaron Raymer 4952e17fa4 init 2022-11-16 14:48:22 +08:00
70 changed files with 29227 additions and 229 deletions
+4
View File
@@ -0,0 +1,4 @@
> 1%
last 2 versions
not dead
not ie 11
+20
View File
@@ -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",
},
};
+11 -12
View File
@@ -1,22 +1,21 @@
# Logs
logs
*.log
.DS_Store
node_modules
/dist
*~
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
.vscode
*.suo
*.ntvs*
*.njsproj
+1
View File
@@ -0,0 +1 @@
nodejs 16.18.0
-3
View File
@@ -1,3 +0,0 @@
{
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
}
+124 -11
View File
@@ -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
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.
## Other
```
// 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, [])
}
```
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"],
};
-13
View File
@@ -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>
+45
View File
@@ -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.
+26703
View File
File diff suppressed because it is too large Load Diff
+64 -11
View File
@@ -1,20 +1,73 @@
{
"name": "timesafari",
"name": "kickstart-for-time-pwa",
"version": "0.1.0",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc && vite build",
"preview": "vite preview"
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"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": {
"@vitejs/plugin-vue": "^4.5.2",
"typescript": "^5.2.2",
"vite": "^5.0.8",
"vue-tsc": "^1.8.25"
"@types/ramda": "^0.28.20",
"@typescript-eslint/eslint-plugin": "^5.44.0",
"@typescript-eslint/parser": "^5.44.0",
"@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"
}
}
+6
View File
@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
+6
View File
@@ -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
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 799 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

+3
View File
@@ -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

+17
View File
@@ -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>
+2
View File
@@ -0,0 +1,2 @@
User-agent: *
Disallow:
-1
View File
@@ -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

+4 -27
View File
@@ -1,30 +1,7 @@
<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<div>
<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" />
<router-view />
</template>
<style scoped>
.logo {
height: 6em;
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>
<style></style>
<script lang="ts"></script>
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

+11
View File
@@ -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
View File
@@ -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

+141 -29
View File
@@ -1,38 +1,150 @@
<script setup lang="ts">
import { ref } from 'vue'
defineProps<{ msg: string }>()
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<div class="card">
<button type="button" @click="count++">count is {{ count }}</button>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
Edit
<code>components/HelloWorld.vue</code> to test HMR
For a guide and recipes on how to configure / customize this project,<br />
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener"
>vue-cli documentation</a
>.
</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>
<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>
<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>
.read-the-docs {
color: #888;
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
+9
View File
@@ -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",
}
+32
View File
@@ -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);
+18
View File
@@ -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",
};
+150
View File
@@ -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");
}
+100
View File
@@ -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 });
*/
+151
View File
@@ -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 })
+58 -4
View File
@@ -1,5 +1,59 @@
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import { createPinia } from "pinia";
import { createApp } from "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");
+34
View File
@@ -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);
},
});
}
+120
View File
@@ -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;
+6
View File
@@ -0,0 +1,6 @@
/* eslint-disable */
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
+22
View File
@@ -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");
},
},
});
+35
View File
@@ -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);
},
},
});
-79
View File
@@ -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;
}
}
+14
View File
@@ -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>
+241
View File
@@ -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>
+3
View File
@@ -0,0 +1,3 @@
<template>
<section id="Content" class="p-6 pb-24"></section>
</template>
+57
View File
@@ -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>
+96
View File
@@ -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>
+162
View File
@@ -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>
+15
View File
@@ -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>
+103
View File
@@ -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>
+54
View File
@@ -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>
+70
View File
@@ -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>
+165
View File
@@ -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>
+141
View File
@@ -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>
+107
View File
@@ -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>
+45
View File
@@ -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
View File
@@ -1 +0,0 @@
/// <reference types="vite/client" />
+8
View File
@@ -0,0 +1,8 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.vue"],
theme: {
extend: {},
},
plugins: [],
};
+36 -20
View File
@@ -1,25 +1,41 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
/* Linting */
"target": "esnext",
"module": "esnext",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
"jsx": "preserve",
"moduleResolution": "node",
"experimentalDecorators": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"useDefineForClassFields": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
"webpack-env"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}
-10
View File
@@ -1,10 +0,0 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}
-7
View File
@@ -1,7 +0,0 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
})
+10
View File
@@ -0,0 +1,10 @@
const { defineConfig } = require("@vue/cli-service");
module.exports = defineConfig({
transpileDependencies: true,
configureWebpack: {
devtool: "source-map",
experiments: {
topLevelAwait: true,
},
},
});