@welshare/react 0.0.1-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +7 -0
- package/README.md +124 -0
- package/dist/esm/components/connect-button.d.ts +6 -0
- package/dist/esm/components/connect-button.d.ts.map +1 -0
- package/dist/esm/components/connect-button.js +58 -0
- package/dist/esm/hooks/use-welshare.d.ts +10 -0
- package/dist/esm/hooks/use-welshare.d.ts.map +1 -0
- package/dist/esm/hooks/use-welshare.js +111 -0
- package/dist/esm/index.d.ts +7 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +10 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/types.d.ts +32 -0
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +1 -0
- package/dist/node_modules/@welshare/react/.gitignore +6 -0
- package/dist/node_modules/@welshare/react/.npmignore +34 -0
- package/dist/node_modules/@welshare/react/.tshy/build.json +8 -0
- package/dist/node_modules/@welshare/react/.tshy/esm.json +16 -0
- package/dist/node_modules/@welshare/react/LICENSE +7 -0
- package/dist/node_modules/@welshare/react/README.md +124 -0
- package/dist/node_modules/@welshare/react/dist/esm/components/connect-button.d.ts +6 -0
- package/dist/node_modules/@welshare/react/dist/esm/components/connect-button.d.ts.map +1 -0
- package/dist/node_modules/@welshare/react/dist/esm/components/connect-button.js +58 -0
- package/dist/node_modules/@welshare/react/dist/esm/hooks/use-welshare.d.ts +10 -0
- package/dist/node_modules/@welshare/react/dist/esm/hooks/use-welshare.d.ts.map +1 -0
- package/dist/node_modules/@welshare/react/dist/esm/hooks/use-welshare.js +111 -0
- package/dist/node_modules/@welshare/react/dist/esm/index.d.ts +7 -0
- package/dist/node_modules/@welshare/react/dist/esm/index.d.ts.map +1 -0
- package/dist/node_modules/@welshare/react/dist/esm/index.js +10 -0
- package/dist/node_modules/@welshare/react/dist/esm/package.json +3 -0
- package/dist/node_modules/@welshare/react/dist/esm/types.d.ts +32 -0
- package/dist/node_modules/@welshare/react/dist/esm/types.d.ts.map +1 -0
- package/dist/node_modules/@welshare/react/dist/esm/types.js +1 -0
- package/dist/node_modules/@welshare/react/eslint.config.mjs +4 -0
- package/dist/node_modules/@welshare/react/package.json +88 -0
- package/dist/node_modules/@welshare/react/src/components/connect-button.tsx +99 -0
- package/dist/node_modules/@welshare/react/src/hooks/use-welshare.ts +148 -0
- package/dist/node_modules/@welshare/react/src/index.ts +13 -0
- package/dist/node_modules/@welshare/react/src/types.ts +37 -0
- package/dist/node_modules/@welshare/react/tsconfig.json +21 -0
- package/package.json +87 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
export const ConnectWelshareButton = (props) => {
|
|
3
|
+
// Self-contained styles that won't conflict with external applications
|
|
4
|
+
const buttonStyles = {
|
|
5
|
+
// Reset any inherited styles
|
|
6
|
+
all: 'unset',
|
|
7
|
+
boxSizing: 'border-box',
|
|
8
|
+
// Layout
|
|
9
|
+
display: 'inline-flex',
|
|
10
|
+
alignItems: 'center',
|
|
11
|
+
justifyContent: 'center',
|
|
12
|
+
width: '100%',
|
|
13
|
+
minHeight: '44px',
|
|
14
|
+
padding: '12px 24px',
|
|
15
|
+
// Typography
|
|
16
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
17
|
+
fontSize: '16px',
|
|
18
|
+
fontWeight: '600',
|
|
19
|
+
lineHeight: '1.5',
|
|
20
|
+
textAlign: 'center',
|
|
21
|
+
textDecoration: 'none',
|
|
22
|
+
whiteSpace: 'nowrap',
|
|
23
|
+
// Visual styling
|
|
24
|
+
backgroundColor: '#0198ff',
|
|
25
|
+
background: 'linear-gradient(135deg, #0198ff 0%, #16ffef 100%)',
|
|
26
|
+
color: '#ffffff',
|
|
27
|
+
border: 'none',
|
|
28
|
+
borderRadius: '8px',
|
|
29
|
+
cursor: 'pointer',
|
|
30
|
+
transition: 'all 0.2s ease-in-out',
|
|
31
|
+
// Accessibility
|
|
32
|
+
userSelect: 'none',
|
|
33
|
+
WebkitUserSelect: 'none',
|
|
34
|
+
MozUserSelect: 'none',
|
|
35
|
+
// Focus states
|
|
36
|
+
outline: 'none',
|
|
37
|
+
};
|
|
38
|
+
// Handle hover and focus states since inline styles don't support pseudo-selectors
|
|
39
|
+
const handleMouseEnter = (e) => {
|
|
40
|
+
e.currentTarget.style.transform = 'translateY(-1px)';
|
|
41
|
+
e.currentTarget.style.boxShadow = '0 4px 12px rgba(1, 152, 255, 0.3)';
|
|
42
|
+
};
|
|
43
|
+
const handleMouseLeave = (e) => {
|
|
44
|
+
e.currentTarget.style.transform = 'translateY(0)';
|
|
45
|
+
e.currentTarget.style.boxShadow = '0 2px 6px rgba(1, 152, 255, 0.2)';
|
|
46
|
+
};
|
|
47
|
+
const handleMouseDown = (e) => {
|
|
48
|
+
e.currentTarget.style.transform = 'translateY(0)';
|
|
49
|
+
e.currentTarget.style.boxShadow = '0 2px 6px rgba(1, 152, 255, 0.2)';
|
|
50
|
+
};
|
|
51
|
+
const handleFocus = (e) => {
|
|
52
|
+
e.currentTarget.style.boxShadow = '0 0 0 3px rgba(1, 152, 255, 0.3)';
|
|
53
|
+
};
|
|
54
|
+
const handleBlur = (e) => {
|
|
55
|
+
e.currentTarget.style.boxShadow = '0 2px 6px rgba(1, 152, 255, 0.2)';
|
|
56
|
+
};
|
|
57
|
+
return (_jsx("button", { onClick: props.openWallet, style: buttonStyles, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onMouseDown: handleMouseDown, onFocus: handleFocus, onBlur: handleBlur, type: "button", children: props.children || "Connect Welshare Profile" }));
|
|
58
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { SubmissionPayload, SubmissionSchemaId, WelshareConnectionOptions } from "@/types.js";
|
|
2
|
+
export declare const useWelshare: (props: WelshareConnectionOptions) => {
|
|
3
|
+
isConnected: boolean;
|
|
4
|
+
storageKey: string | undefined;
|
|
5
|
+
openWallet: () => void;
|
|
6
|
+
isDialogOpen: boolean;
|
|
7
|
+
submitData: <T>(schemaId: SubmissionSchemaId, submission: SubmissionPayload<T>) => void;
|
|
8
|
+
isSubmitting: boolean;
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=use-welshare.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-welshare.d.ts","sourceRoot":"","sources":["../../../src/hooks/use-welshare.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,iBAAiB,EACjB,kBAAkB,EAClB,yBAAyB,EAC1B,MAAM,YAAY,CAAC;AAGpB,eAAO,MAAM,WAAW,UAAW,yBAAyB;;;;;iBAkFtC,CAAC,YACT,kBAAkB,cAChB,iBAAiB,CAAC,CAAC,CAAC;;CAuDnC,CAAC"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
export const useWelshare = (props) => {
|
|
3
|
+
const [storageKey, setStorageKey] = useState();
|
|
4
|
+
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
|
5
|
+
const [dialogWindow, setDialogWindow] = useState(null);
|
|
6
|
+
const [messageIdCounter, setMessageIdCounter] = useState(0);
|
|
7
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
8
|
+
const options = {
|
|
9
|
+
environment: "development",
|
|
10
|
+
apiBaseUrl: "https://wallet.welshare.health",
|
|
11
|
+
...props,
|
|
12
|
+
};
|
|
13
|
+
const WELSHARE_WALLET_URL = `${options.apiBaseUrl}/wallet-external`;
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const handleMessage = (event) => {
|
|
16
|
+
// Verify origin for security
|
|
17
|
+
if (event.origin !== new URL(WELSHARE_WALLET_URL).origin) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const message = event.data;
|
|
21
|
+
let errorMessage = "";
|
|
22
|
+
switch (message.type) {
|
|
23
|
+
case "ERROR":
|
|
24
|
+
errorMessage = message.payload.error || "An unknown error occurred";
|
|
25
|
+
console.error("Welshare Wallet sent an error:", errorMessage);
|
|
26
|
+
setIsSubmitting(false);
|
|
27
|
+
options.callbacks.onError?.(errorMessage);
|
|
28
|
+
break;
|
|
29
|
+
case "DIALOG_READY":
|
|
30
|
+
setIsDialogOpen(true);
|
|
31
|
+
console.debug("Welshare Wallet Dialog is ready");
|
|
32
|
+
break;
|
|
33
|
+
case "SESSION_READY":
|
|
34
|
+
if (!message.payload.sessionPublicKey) {
|
|
35
|
+
errorMessage = "No session public key found";
|
|
36
|
+
console.error("Welshare Wallet sent a session without a key:", message.payload);
|
|
37
|
+
setStorageKey(undefined);
|
|
38
|
+
options.callbacks.onError?.(errorMessage);
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
console.debug("Welshare Profile Session ready", message.payload);
|
|
42
|
+
setStorageKey(message.payload.sessionPublicKey);
|
|
43
|
+
options.callbacks.onSessionReady?.(message.payload.sessionPublicKey);
|
|
44
|
+
break;
|
|
45
|
+
case "DIALOG_CLOSING":
|
|
46
|
+
console.debug("Welshare Wallet Dialog is closing");
|
|
47
|
+
setStorageKey(undefined);
|
|
48
|
+
setIsDialogOpen(false);
|
|
49
|
+
options.callbacks.onDialogClosing?.();
|
|
50
|
+
break;
|
|
51
|
+
case "DATA_UPLOADED":
|
|
52
|
+
console.debug("data uploaded", message.payload);
|
|
53
|
+
setIsSubmitting(false);
|
|
54
|
+
options.callbacks.onUploaded?.(message.payload);
|
|
55
|
+
break;
|
|
56
|
+
default:
|
|
57
|
+
console.log("Received unexpected message from Welshare Wallet:", message);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
window.addEventListener("message", handleMessage);
|
|
61
|
+
return () => {
|
|
62
|
+
window.removeEventListener("message", handleMessage);
|
|
63
|
+
};
|
|
64
|
+
}, [WELSHARE_WALLET_URL, options.callbacks]);
|
|
65
|
+
/**
|
|
66
|
+
* @param schemaType a welshare schema type uid
|
|
67
|
+
* @param submission your submission that validates against the schema type
|
|
68
|
+
*/
|
|
69
|
+
const submitData = (schemaId, submission) => {
|
|
70
|
+
if (!dialogWindow) {
|
|
71
|
+
throw new Error("Dialog window not open");
|
|
72
|
+
}
|
|
73
|
+
const submissionPayload = {
|
|
74
|
+
applicationId: options.applicationId,
|
|
75
|
+
schemaId,
|
|
76
|
+
timestamp: new Date(),
|
|
77
|
+
submission,
|
|
78
|
+
};
|
|
79
|
+
//todo: before submitting data, consider verifying it locally here;
|
|
80
|
+
// 1. request the actual schema using a welshare api
|
|
81
|
+
// 2. create a json schema validator
|
|
82
|
+
// 3. run payload against that validator
|
|
83
|
+
// 4. submit
|
|
84
|
+
const message = {
|
|
85
|
+
type: "SUBMIT_DATA",
|
|
86
|
+
payload: submissionPayload,
|
|
87
|
+
id: String(messageIdCounter),
|
|
88
|
+
};
|
|
89
|
+
dialogWindow.postMessage(message, WELSHARE_WALLET_URL);
|
|
90
|
+
setIsSubmitting(true);
|
|
91
|
+
setMessageIdCounter((prev) => prev + 1);
|
|
92
|
+
};
|
|
93
|
+
const openWallet = () => {
|
|
94
|
+
const width = 800;
|
|
95
|
+
const height = 600;
|
|
96
|
+
const left = window.screenX + (window.outerWidth - width) / 2;
|
|
97
|
+
const top = window.screenY + (window.outerHeight - height) / 2;
|
|
98
|
+
const newWindow = window.open(WELSHARE_WALLET_URL, "Welshare Wallet", `width=${width},height=${height},left=${left},top=${top}`);
|
|
99
|
+
if (newWindow) {
|
|
100
|
+
setDialogWindow(newWindow);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
return {
|
|
104
|
+
isConnected: !!storageKey,
|
|
105
|
+
storageKey,
|
|
106
|
+
openWallet,
|
|
107
|
+
isDialogOpen,
|
|
108
|
+
submitData,
|
|
109
|
+
isSubmitting,
|
|
110
|
+
};
|
|
111
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAEvE,OAAO,EACL,WAAW,EACZ,MAAM,yBAAyB,CAAC;AAGjC,eAAO,MAAM,OAAO;;;CAGnB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//todo: this is not tree shaken and leaks nillion dependencies transitively. Consider pulling it up
|
|
2
|
+
//import { QuestionnaireResponseSchema, ReflexSubmissionSchema } from "@welshare/sdk";
|
|
3
|
+
export { ConnectWelshareButton } from "./components/connect-button.js";
|
|
4
|
+
// ---- hooks ----
|
|
5
|
+
export { useWelshare } from "./hooks/use-welshare.js";
|
|
6
|
+
//todo: import them from the SDK or a dedicated SDK constants export
|
|
7
|
+
export const Schemas = {
|
|
8
|
+
QuestionnaireResponse: "b14b538f-7de3-4767-ad77-464d755d78bd", //QuestionnaireResponseSchema.schemaUid,
|
|
9
|
+
ReflexSubmission: "f5cf2d8a-1f78-4f21-b4bd-082e983b830c" //ReflexSubmissionSchema.schemaUid,
|
|
10
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface DialogMessage {
|
|
2
|
+
type: string;
|
|
3
|
+
payload?: any;
|
|
4
|
+
id?: string;
|
|
5
|
+
}
|
|
6
|
+
export type WelshareEnvironment = "development" | "production";
|
|
7
|
+
/**
|
|
8
|
+
* a welshare schema type uid
|
|
9
|
+
*/
|
|
10
|
+
export type SubmissionSchemaId = string;
|
|
11
|
+
export interface SubmissionPayload<T> {
|
|
12
|
+
applicationId: string;
|
|
13
|
+
timestamp: Date;
|
|
14
|
+
schemaId: SubmissionSchemaId;
|
|
15
|
+
submission: T;
|
|
16
|
+
}
|
|
17
|
+
export interface DataSubmissionDialogMessage extends DialogMessage {
|
|
18
|
+
payload: SubmissionPayload<unknown>;
|
|
19
|
+
}
|
|
20
|
+
export interface WelshareConnectionOptions {
|
|
21
|
+
applicationId: string;
|
|
22
|
+
apiBaseUrl?: string;
|
|
23
|
+
environment?: WelshareEnvironment;
|
|
24
|
+
callbacks: {
|
|
25
|
+
onUploaded?: (payload: SubmissionPayload<unknown>) => void;
|
|
26
|
+
onError?: (error: string) => void;
|
|
27
|
+
onSessionReady?: (sessionPubKey: string) => void;
|
|
28
|
+
onDialogClosing?: () => void;
|
|
29
|
+
onDialogReady?: () => void;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,GAAG,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,MAAM,mBAAmB,GAAG,aAAa,GAAG,YAAY,CAAC;AAE/D;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAA;AAEvC,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAClC,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,IAAI,CAAC;IAChB,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,UAAU,EAAE,CAAC,CAAC;CACf;AAED,MAAM,WAAW,2BAA4B,SAAQ,aAAa;IAChE,OAAO,EAAE,iBAAiB,CAAC,OAAO,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,yBAAyB;IACxC,aAAa,EAAE,MAAM,CAAC;IAEtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,SAAS,EAAE;QACT,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,iBAAiB,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;QAC3D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;QAClC,cAAc,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,IAAI,CAAC;QACjD,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;QAC7B,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;KAC5B,CAAC;CACH"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@welshare/react",
|
|
3
|
+
"version": "0.0.1-alpha.1",
|
|
4
|
+
"description": "React library for integrating with Welshare's sovereign data sharing platform",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"react",
|
|
7
|
+
"welshare",
|
|
8
|
+
"data-sharing",
|
|
9
|
+
"health",
|
|
10
|
+
"questionnaire",
|
|
11
|
+
"fhir"
|
|
12
|
+
],
|
|
13
|
+
"author": "Welshare UG (haftungsbeschränkt)",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/welshare/surveys-monorepo.git",
|
|
18
|
+
"directory": "packages/welshare-react"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/welshare/surveys-monorepo/issues"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://welshare.health",
|
|
24
|
+
"type": "module",
|
|
25
|
+
"scripts": {
|
|
26
|
+
"lint": "eslint . --max-warnings 10",
|
|
27
|
+
"build": "tshy",
|
|
28
|
+
"build:clean": "rm -rf ./dist",
|
|
29
|
+
"test": "vitest",
|
|
30
|
+
"test:run": "vitest run",
|
|
31
|
+
"test:coverage": "vitest run --coverage",
|
|
32
|
+
"prepublishOnly": "npm run build:clean && npm run build"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"react": "^19",
|
|
36
|
+
"react-dom": "^19"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@turbo/gen": "^2.4.2",
|
|
40
|
+
"@types/node": "^22",
|
|
41
|
+
"@types/react": "^19",
|
|
42
|
+
"@workspace/eslint-config": "workspace:*",
|
|
43
|
+
"@workspace/typescript-config": "workspace:*",
|
|
44
|
+
"tshy": "^3.0.2",
|
|
45
|
+
"typescript": "^5.7.3",
|
|
46
|
+
"vitest": "^3.2.4",
|
|
47
|
+
"@vitest/ui": "^3.2.4",
|
|
48
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
49
|
+
"jsdom": "^26.1.0"
|
|
50
|
+
},
|
|
51
|
+
"tshy": {
|
|
52
|
+
"dialects": [
|
|
53
|
+
"esm"
|
|
54
|
+
],
|
|
55
|
+
"exclude": [
|
|
56
|
+
"src/**/__tests__/**"
|
|
57
|
+
],
|
|
58
|
+
"exports": {
|
|
59
|
+
"./package.json": "./package.json",
|
|
60
|
+
"./types": "./src/types.ts",
|
|
61
|
+
".": "./src/index.ts"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"exports": {
|
|
65
|
+
"./package.json": "./package.json",
|
|
66
|
+
"./types": {
|
|
67
|
+
"import": {
|
|
68
|
+
"types": "./dist/esm/types.d.ts",
|
|
69
|
+
"default": "./dist/esm/types.js"
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
".": {
|
|
73
|
+
"import": {
|
|
74
|
+
"types": "./dist/esm/index.d.ts",
|
|
75
|
+
"default": "./dist/esm/index.js"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"module": "./dist/esm/index.js",
|
|
80
|
+
"files": [
|
|
81
|
+
"dist/**/*",
|
|
82
|
+
"README.md",
|
|
83
|
+
"LICENSE"
|
|
84
|
+
],
|
|
85
|
+
"engines": {
|
|
86
|
+
"node": ">=20.0.0"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export const ConnectWelshareButton = (props: {
|
|
4
|
+
openWallet: () => void;
|
|
5
|
+
children?: React.ReactNode;
|
|
6
|
+
}) => {
|
|
7
|
+
// Self-contained styles that won't conflict with external applications
|
|
8
|
+
const buttonStyles: React.CSSProperties = {
|
|
9
|
+
// Reset any inherited styles
|
|
10
|
+
all: 'unset',
|
|
11
|
+
boxSizing: 'border-box',
|
|
12
|
+
|
|
13
|
+
// Layout
|
|
14
|
+
display: 'inline-flex',
|
|
15
|
+
alignItems: 'center',
|
|
16
|
+
justifyContent: 'center',
|
|
17
|
+
width: '100%',
|
|
18
|
+
minHeight: '44px',
|
|
19
|
+
padding: '12px 24px',
|
|
20
|
+
|
|
21
|
+
// Typography
|
|
22
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
23
|
+
fontSize: '16px',
|
|
24
|
+
fontWeight: '600',
|
|
25
|
+
lineHeight: '1.5',
|
|
26
|
+
textAlign: 'center',
|
|
27
|
+
textDecoration: 'none',
|
|
28
|
+
whiteSpace: 'nowrap',
|
|
29
|
+
|
|
30
|
+
// Visual styling
|
|
31
|
+
backgroundColor: '#0198ff',
|
|
32
|
+
background: 'linear-gradient(135deg, #0198ff 0%, #16ffef 100%)',
|
|
33
|
+
color: '#ffffff',
|
|
34
|
+
border: 'none',
|
|
35
|
+
borderRadius: '8px',
|
|
36
|
+
cursor: 'pointer',
|
|
37
|
+
transition: 'all 0.2s ease-in-out',
|
|
38
|
+
|
|
39
|
+
// Accessibility
|
|
40
|
+
userSelect: 'none',
|
|
41
|
+
WebkitUserSelect: 'none',
|
|
42
|
+
MozUserSelect: 'none',
|
|
43
|
+
|
|
44
|
+
// Focus states
|
|
45
|
+
outline: 'none',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Handle hover and focus states since inline styles don't support pseudo-selectors
|
|
49
|
+
const handleMouseEnter = (e: React.MouseEvent<HTMLButtonElement>) => {
|
|
50
|
+
e.currentTarget.style.transform = 'translateY(-1px)';
|
|
51
|
+
e.currentTarget.style.boxShadow = '0 4px 12px rgba(1, 152, 255, 0.3)';
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const handleMouseLeave = (e: React.MouseEvent<HTMLButtonElement>) => {
|
|
55
|
+
e.currentTarget.style.transform = 'translateY(0)';
|
|
56
|
+
e.currentTarget.style.boxShadow = '0 2px 6px rgba(1, 152, 255, 0.2)';
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const handleMouseDown = (e: React.MouseEvent<HTMLButtonElement>) => {
|
|
60
|
+
e.currentTarget.style.transform = 'translateY(0)';
|
|
61
|
+
e.currentTarget.style.boxShadow = '0 2px 6px rgba(1, 152, 255, 0.2)';
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const handleFocus = (e: React.FocusEvent<HTMLButtonElement>) => {
|
|
65
|
+
e.currentTarget.style.boxShadow = '0 0 0 3px rgba(1, 152, 255, 0.3)';
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const handleBlur = (e: React.FocusEvent<HTMLButtonElement>) => {
|
|
69
|
+
e.currentTarget.style.boxShadow = '0 2px 6px rgba(1, 152, 255, 0.2)';
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<button
|
|
74
|
+
onClick={props.openWallet}
|
|
75
|
+
style={buttonStyles}
|
|
76
|
+
onMouseEnter={handleMouseEnter}
|
|
77
|
+
onMouseLeave={handleMouseLeave}
|
|
78
|
+
onMouseDown={handleMouseDown}
|
|
79
|
+
onFocus={handleFocus}
|
|
80
|
+
onBlur={handleBlur}
|
|
81
|
+
type="button"
|
|
82
|
+
>
|
|
83
|
+
{props.children || "Connect Welshare Profile"}
|
|
84
|
+
</button>
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/* this one was used in the old reflex app
|
|
89
|
+
*
|
|
90
|
+
* <div className="p-[3px] bg-gradient-to-br from-[#0198ff]/80 to-[#16ffef]/80 rounded-lg">
|
|
91
|
+
<button
|
|
92
|
+
onClick={openDialog}
|
|
93
|
+
className="bg-black text-white px-6 py-3 rounded-lg w-full"
|
|
94
|
+
>
|
|
95
|
+
Connect to Welshare
|
|
96
|
+
</button>
|
|
97
|
+
</div>
|
|
98
|
+
*
|
|
99
|
+
*/
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DialogMessage,
|
|
3
|
+
SubmissionPayload,
|
|
4
|
+
SubmissionSchemaId,
|
|
5
|
+
WelshareConnectionOptions,
|
|
6
|
+
} from "@/types.js";
|
|
7
|
+
import { useEffect, useState } from "react";
|
|
8
|
+
|
|
9
|
+
export const useWelshare = (props: WelshareConnectionOptions) => {
|
|
10
|
+
const [storageKey, setStorageKey] = useState<string>();
|
|
11
|
+
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
|
12
|
+
const [dialogWindow, setDialogWindow] = useState<Window | null>(null);
|
|
13
|
+
const [messageIdCounter, setMessageIdCounter] = useState(0);
|
|
14
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
15
|
+
|
|
16
|
+
const options: WelshareConnectionOptions = {
|
|
17
|
+
environment: "development",
|
|
18
|
+
apiBaseUrl: "https://wallet.welshare.health",
|
|
19
|
+
...props,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const WELSHARE_WALLET_URL = `${options.apiBaseUrl}/wallet-external`;
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
const handleMessage = (event: MessageEvent<DialogMessage>) => {
|
|
26
|
+
// Verify origin for security
|
|
27
|
+
if (event.origin !== new URL(WELSHARE_WALLET_URL).origin) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const message = event.data;
|
|
32
|
+
let errorMessage = "";
|
|
33
|
+
switch (message.type) {
|
|
34
|
+
case "ERROR":
|
|
35
|
+
errorMessage = message.payload.error || "An unknown error occurred";
|
|
36
|
+
console.error("Welshare Wallet sent an error:", errorMessage);
|
|
37
|
+
setIsSubmitting(false);
|
|
38
|
+
options.callbacks.onError?.(errorMessage);
|
|
39
|
+
break;
|
|
40
|
+
case "DIALOG_READY":
|
|
41
|
+
setIsDialogOpen(true);
|
|
42
|
+
console.debug("Welshare Wallet Dialog is ready");
|
|
43
|
+
break;
|
|
44
|
+
case "SESSION_READY":
|
|
45
|
+
if (!message.payload.sessionPublicKey) {
|
|
46
|
+
errorMessage = "No session public key found";
|
|
47
|
+
console.error(
|
|
48
|
+
"Welshare Wallet sent a session without a key:",
|
|
49
|
+
message.payload
|
|
50
|
+
);
|
|
51
|
+
setStorageKey(undefined);
|
|
52
|
+
options.callbacks.onError?.(errorMessage);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
console.debug("Welshare Profile Session ready", message.payload);
|
|
56
|
+
setStorageKey(message.payload.sessionPublicKey);
|
|
57
|
+
options.callbacks.onSessionReady?.(message.payload.sessionPublicKey);
|
|
58
|
+
break;
|
|
59
|
+
|
|
60
|
+
case "DIALOG_CLOSING":
|
|
61
|
+
console.debug("Welshare Wallet Dialog is closing");
|
|
62
|
+
setStorageKey(undefined);
|
|
63
|
+
setIsDialogOpen(false);
|
|
64
|
+
options.callbacks.onDialogClosing?.();
|
|
65
|
+
break;
|
|
66
|
+
|
|
67
|
+
case "DATA_UPLOADED":
|
|
68
|
+
console.debug("data uploaded", message.payload);
|
|
69
|
+
setIsSubmitting(false);
|
|
70
|
+
options.callbacks.onUploaded?.(message.payload);
|
|
71
|
+
break;
|
|
72
|
+
default:
|
|
73
|
+
console.log(
|
|
74
|
+
"Received unexpected message from Welshare Wallet:",
|
|
75
|
+
message
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
window.addEventListener("message", handleMessage);
|
|
81
|
+
|
|
82
|
+
return () => {
|
|
83
|
+
window.removeEventListener("message", handleMessage);
|
|
84
|
+
};
|
|
85
|
+
}, [WELSHARE_WALLET_URL, options.callbacks]);
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @param schemaType a welshare schema type uid
|
|
89
|
+
* @param submission your submission that validates against the schema type
|
|
90
|
+
*/
|
|
91
|
+
const submitData = <T>(
|
|
92
|
+
schemaId: SubmissionSchemaId,
|
|
93
|
+
submission: SubmissionPayload<T>
|
|
94
|
+
) => {
|
|
95
|
+
if (!dialogWindow) {
|
|
96
|
+
throw new Error("Dialog window not open");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const submissionPayload = {
|
|
100
|
+
applicationId: options.applicationId,
|
|
101
|
+
schemaId,
|
|
102
|
+
timestamp: new Date(),
|
|
103
|
+
submission,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
//todo: before submitting data, consider verifying it locally here;
|
|
107
|
+
// 1. request the actual schema using a welshare api
|
|
108
|
+
// 2. create a json schema validator
|
|
109
|
+
// 3. run payload against that validator
|
|
110
|
+
// 4. submit
|
|
111
|
+
|
|
112
|
+
const message: DialogMessage = {
|
|
113
|
+
type: "SUBMIT_DATA",
|
|
114
|
+
payload: submissionPayload,
|
|
115
|
+
id: String(messageIdCounter),
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
dialogWindow.postMessage(message, WELSHARE_WALLET_URL);
|
|
119
|
+
setIsSubmitting(true);
|
|
120
|
+
setMessageIdCounter((prev) => prev + 1);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const openWallet = () => {
|
|
124
|
+
const width = 800;
|
|
125
|
+
const height = 600;
|
|
126
|
+
const left = window.screenX + (window.outerWidth - width) / 2;
|
|
127
|
+
const top = window.screenY + (window.outerHeight - height) / 2;
|
|
128
|
+
|
|
129
|
+
const newWindow = window.open(
|
|
130
|
+
WELSHARE_WALLET_URL,
|
|
131
|
+
"Welshare Wallet",
|
|
132
|
+
`width=${width},height=${height},left=${left},top=${top}`
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
if (newWindow) {
|
|
136
|
+
setDialogWindow(newWindow);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
isConnected: !!storageKey,
|
|
142
|
+
storageKey,
|
|
143
|
+
openWallet,
|
|
144
|
+
isDialogOpen,
|
|
145
|
+
submitData,
|
|
146
|
+
isSubmitting,
|
|
147
|
+
};
|
|
148
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//todo: this is not tree shaken and leaks nillion dependencies transitively. Consider pulling it up
|
|
2
|
+
//import { QuestionnaireResponseSchema, ReflexSubmissionSchema } from "@welshare/sdk";
|
|
3
|
+
export { ConnectWelshareButton } from "./components/connect-button.js";
|
|
4
|
+
// ---- hooks ----
|
|
5
|
+
export {
|
|
6
|
+
useWelshare
|
|
7
|
+
} from "./hooks/use-welshare.js";
|
|
8
|
+
|
|
9
|
+
//todo: import them from the SDK or a dedicated SDK constants export
|
|
10
|
+
export const Schemas = {
|
|
11
|
+
QuestionnaireResponse: "b14b538f-7de3-4767-ad77-464d755d78bd", //QuestionnaireResponseSchema.schemaUid,
|
|
12
|
+
ReflexSubmission: "f5cf2d8a-1f78-4f21-b4bd-082e983b830c" //ReflexSubmissionSchema.schemaUid,
|
|
13
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface DialogMessage {
|
|
2
|
+
type: string;
|
|
3
|
+
payload?: any;
|
|
4
|
+
id?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export type WelshareEnvironment = "development" | "production";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* a welshare schema type uid
|
|
11
|
+
*/
|
|
12
|
+
export type SubmissionSchemaId = string
|
|
13
|
+
|
|
14
|
+
export interface SubmissionPayload<T> {
|
|
15
|
+
applicationId: string;
|
|
16
|
+
timestamp: Date;
|
|
17
|
+
schemaId: SubmissionSchemaId;
|
|
18
|
+
submission: T;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface DataSubmissionDialogMessage extends DialogMessage {
|
|
22
|
+
payload: SubmissionPayload<unknown>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface WelshareConnectionOptions {
|
|
26
|
+
applicationId: string;
|
|
27
|
+
//todo: must go into build config, not supposed to be used by users
|
|
28
|
+
apiBaseUrl?: string;
|
|
29
|
+
environment?: WelshareEnvironment;
|
|
30
|
+
callbacks: {
|
|
31
|
+
onUploaded?: (payload: SubmissionPayload<unknown>) => void;
|
|
32
|
+
onError?: (error: string) => void;
|
|
33
|
+
onSessionReady?: (sessionPubKey: string) => void;
|
|
34
|
+
onDialogClosing?: () => void;
|
|
35
|
+
onDialogReady?: () => void;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "@workspace/typescript-config/react-library.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"baseUrl": ".",
|
|
5
|
+
"paths": {
|
|
6
|
+
"@/*": ["./src/*"],
|
|
7
|
+
"@/types/*": ["./types/*"],
|
|
8
|
+
},
|
|
9
|
+
"plugins": [
|
|
10
|
+
],
|
|
11
|
+
"allowJs": true,
|
|
12
|
+
"jsx": "react-jsx"
|
|
13
|
+
},
|
|
14
|
+
"include": [
|
|
15
|
+
"next-env.d.ts",
|
|
16
|
+
"next.config.ts",
|
|
17
|
+
"**/*.ts",
|
|
18
|
+
"**/*.tsx",
|
|
19
|
+
],
|
|
20
|
+
"exclude": ["node_modules"]
|
|
21
|
+
}
|