summaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
Diffstat (limited to 'src/client')
-rw-r--r--src/client/App.tsx101
-rw-r--r--src/client/index.css301
-rw-r--r--src/client/utils.ts33
3 files changed, 344 insertions, 91 deletions
diff --git a/src/client/App.tsx b/src/client/App.tsx
index 00280cd..b3f0308 100644
--- a/src/client/App.tsx
+++ b/src/client/App.tsx
@@ -99,7 +99,7 @@ function App() {
setQrCodeDataUrl(null); // Clear previous QR code if any
setSessionId(null);
setResultData(null);
- console.log('Requesting new DigiID session...');
+ console.log('Requesting new Digi-ID session...');
try {
const response = await fetch('/api/digiid/start');
@@ -113,9 +113,9 @@ function App() {
setQrCodeDataUrl(data.qrCodeDataUrl);
setUiState('waiting'); // Move to waiting state
} catch (err) {
- console.error('Error starting DigiID session:', err);
+ console.error('Error starting Digi-ID session:', err);
const message = err instanceof Error ? err.message : 'An unknown error occurred';
- setError(`Failed to initiate DigiID: ${message}`);
+ setError(`Failed to initiate Digi-ID: ${message}`);
setUiState('initial'); // Stay in initial state on error
} finally {
setIsLoading(false);
@@ -135,57 +135,70 @@ function App() {
return (
<div className="app-container">
- <h1>DigiID-TS Demo</h1>
+ <h1>Digi-ID TypeScript Integration Demo</h1>
- {/* --- Initial State --- */}
+ {/* --- Views based on uiState --- */}
{uiState === 'initial' && (
<div className="initial-view">
- <h2>Welcome</h2>
- <p>Click the button below to generate a DigiID login QR code.</p>
- {/* TODO: Add Icon here */}
- {/* <img src="/assets/YOUR_ICON_FILENAME.png" alt="DigiID Icon" width="100" /> */}
- <button onClick={handleStart} disabled={isLoading}>
- {isLoading ? 'Generating QR...' : 'Start DigiID Login'}
+ <button onClick={handleStart} disabled={isLoading} className="signin-button">
+ <img src="/assets/digiid-logo.png" alt="Digi-ID Logo" width="24" height="24" />
+ <span>{isLoading ? 'Generating QR...' : 'Sign in with Digi-ID'}</span>
</button>
</div>
)}
- {/* --- Waiting State --- */}
- {uiState === 'waiting' && qrCodeDataUrl && (
- <div className="waiting-view">
- <h2>Scan the QR Code</h2>
- <p>Scan the QR code below using your DigiID compatible mobile wallet.</p>
- <img src={qrCodeDataUrl} alt="DigiID QR Code" width="250" />
- <p>Waiting for authentication...</p>
- {/* Optional: Add a cancel button here */}
- <button onClick={handleReset}>Cancel</button>
+ {uiState !== 'initial' && (
+ <div className="view-container">
+ {uiState === 'waiting' && qrCodeDataUrl && (
+ <div className="waiting-view view-box">
+ <h2>Scan the QR Code</h2>
+ <p>Scan the QR code below using your Digi-ID compatible mobile wallet.</p>
+ <img src={qrCodeDataUrl} alt="Digi-ID QR Code" width="250" />
+ <p>Waiting for authentication...</p>
+ <button onClick={handleReset}>Cancel</button>
+ </div>
+ )}
+
+ {uiState === 'success' && resultData?.address && (
+ <div className="success-view view-box">
+ <svg xmlns="http://www.w3.org/2000/svg" className="checkmark-icon" viewBox="0 0 52 52">
+ <circle className="checkmark__circle" cx="26" cy="26" r="25" fill="none"/>
+ <path className="checkmark__check" fill="none" d="M14.1 27.2l7.1 7.2 16.7-16.8"/>
+ </svg>
+ <h2>Authentication Successful!</h2>
+ <p>Verified Address:</p>
+ <p className="address">{resultData.address}</p>
+ <p>Address Format: {getDigiByteAddressType(resultData.address)}</p>
+ <button onClick={handleReset}>Start Over</button>
+ </div>
+ )}
+
+ {uiState === 'failed' && (
+ <div className="failed-view view-box">
+ <h2>Authentication Failed</h2>
+ <p className="error-message">
+ Reason: {resultData?.error || error || 'An unknown error occurred.'}
+ </p>
+ <button onClick={handleReset}>Try Again</button>
+ </div>
+ )}
</div>
)}
- {/* --- Success State --- */}
- {uiState === 'success' && resultData?.address && (
- <div className="success-view">
- <h2>Authentication Successful!</h2>
- <p>Verified Address:</p>
- <p className="address">{resultData.address}</p>
- <p>Address Type: {getDigiByteAddressType(resultData.address)}</p>
- <button onClick={handleReset}>Start Over</button>
- </div>
- )}
-
- {/* --- Failed State --- */}
- {uiState === 'failed' && (
- <div className="failed-view">
- <h2>Authentication Failed</h2>
- <p className="error-message">
- Reason: {resultData?.error || error || 'An unknown error occurred.'}
- </p>
- <button onClick={handleReset}>Try Again</button>
- </div>
- )}
-
- {/* General error display (e.g., for initial start error) */}
- {error && uiState === 'initial' && <p className="error-message">Error: {error}</p>}
+ {/* --- Description section (Always visible below the view container) --- */}
+ <div className="description">
+ <p className="code-link">
+ This application demonstrates integrating Digi-ID authentication using the <a href="https://github.com/pawelzelawski/digiid-ts" target="_blank" rel="noopener noreferrer">digiid-ts</a> library.
+ </p>
+ <p>
+ Upon successful verification, the system can identify the following DigiByte address formats:
+ </p>
+ <ul>
+ <li>Legacy Addresses (P2PKH) - starting with 'D'</li>
+ <li>Pay-to-Script-Hash Addresses (P2SH) - commonly starting with 'S'</li>
+ <li>Segregated Witness (SegWit) Addresses - starting with 'dgb1'</li>
+ </ul>
+ </div>
</div>
);
}
diff --git a/src/client/index.css b/src/client/index.css
index 2349c9b..6cf1cfb 100644
--- a/src/client/index.css
+++ b/src/client/index.css
@@ -2,9 +2,24 @@
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;
+
+ /* DigiID Inspired Colors (Light Theme Defaults) */
+ --digi-blue: #003366;
+ --digi-accent: #007bff;
+ --digi-text-light: #f0f0f0;
+ --digi-text-dark: #222222;
+ --digi-bg-dark: var(--digi-blue);
+ --digi-bg-light: #ffffff;
+ --digi-border-dark: #0059b3;
+ --digi-border-light: #cccccc;
+ --digi-address-bg-light: #e8f0fe;
+ --digi-error-light: #d9534f;
+ --digi-code-bg-light: #e8f0fe;
+
+ /* Default to Light Theme */
+ color-scheme: light;
+ color: var(--digi-text-dark);
+ background-color: var(--digi-bg-light);
font-synthesis: none;
text-rendering: optimizeLegibility;
@@ -14,8 +29,6 @@
body {
margin: 0;
- display: flex;
- place-items: center;
min-width: 320px;
min-height: 100vh;
}
@@ -23,7 +36,7 @@ body {
#root {
max-width: 1280px;
margin: 0 auto;
- padding: 2rem;
+ padding: 0.5rem 2rem;
text-align: center;
}
@@ -31,85 +44,301 @@ body {
display: flex;
flex-direction: column;
align-items: center;
- gap: 1.5rem;
+ gap: 1rem;
+ padding: 20px 0 0;
+ margin-top: 0;
}
-h1 {
- font-size: 2.5em;
- line-height: 1.1;
+.app-container h1 {
+ margin: 0;
margin-bottom: 1rem;
+ color: var(--digi-blue);
+ font-size: 1.8em;
+}
+
+/* Style the container that holds the changing views */
+.view-container {
+ width: 100%; /* Keep width if needed for centering the box */
+ display: flex; /* Keep display flex for centering */
+ justify-content: center; /* Center the box itself */
+}
+
+h1 {
+ font-size: 1.5em;
+ line-height: 1.2;
+ margin: 0;
+ color: var(--digi-blue);
}
button {
border-radius: 8px;
border: 1px solid transparent;
- padding: 0.6em 1.2em;
+ padding: 0.5em 1em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
- background-color: #1a1a1a;
+ background-color: var(--digi-accent);
+ color: var(--digi-text-light);
cursor: pointer;
- transition: border-color 0.25s;
+ transition: border-color 0.25s, background-color 0.25s;
+ margin-top: 0.5rem;
}
button:hover {
- border-color: #646cff;
+ background-color: var(--digi-blue);
+ border-color: var(--digi-blue);
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
button:disabled {
- background-color: #333;
+ background-color: #555; /* Keep disabled distinct */
cursor: not-allowed;
opacity: 0.6;
}
-.initial-view, .waiting-view, .success-view, .failed-view {
+/* Box styling for views that need it */
+.waiting-view, .success-view, .failed-view {
padding: 1.5rem;
- border: 1px solid #555;
+ border: 1px solid var(--digi-border-light);
+ background-color: rgba(0, 0, 0, 0.02);
border-radius: 8px;
display: flex;
flex-direction: column;
align-items: center;
+ justify-content: center;
gap: 1rem;
- min-width: 300px;
+ width: 390px;
+ height: 560px;
+ box-sizing: border-box;
+}
+
+.waiting-view {
+ padding: 1rem;
+ gap: 0.5rem;
+}
+
+.waiting-view h2 {
+ margin-bottom: 0.1rem;
+}
+
+.waiting-view p {
+ margin: 0.1rem 0;
}
.waiting-view img {
- background-color: white; /* Ensure QR code background is white */
- padding: 10px;
- border-radius: 4px;
+ margin: 0.5rem auto;
+}
+
+.waiting-view button {
+ margin-top: 0.5rem;
}
.address {
font-family: monospace;
- background-color: #333;
+ background-color: var(--digi-address-bg-light);
+ color: var(--digi-text-dark);
padding: 0.5em;
border-radius: 4px;
word-break: break-all;
}
.error-message {
- color: #ff6b6b;
+ color: var(--digi-error-light);
font-weight: bold;
}
+code, .code-link a {
+ background-color: var(--digi-code-bg-light);
+ color: var(--digi-text-dark);
+ padding: 0.2em 0.4em;
+ border-radius: 3px;
+ font-family: monospace;
+ text-decoration: none; /* Remove underline from link part */
+}
+
+.code-link a:hover {
+ text-decoration: underline;
+}
-@media (prefers-color-scheme: light) {
- :root {
- color: #213547;
- background-color: #ffffff;
- }
- button {
- background-color: #f9f9f9;
+.description {
+ font-size: 0.9em;
+ line-height: 1.4;
+ margin: 0;
+ text-align: center;
+ max-width: 100%;
+}
+
+.description ul {
+ margin-top: 0.5em;
+ padding-left: 20px;
+}
+
+.description li {
+ margin-bottom: 0.3em;
+ text-align: left;
+}
+
+/* Base styling ONLY for view transitions */
+.view-box {
+ opacity: 1;
+ transform: scale(1);
+ transition: opacity 0.5s ease-in-out, transform 0.5s ease-in-out;
+}
+
+/* Animation for success */
+.success-view {
+ /* Add specific styles if needed, e.g., border color */
+ border-color: #28a745; /* Green border for success */
+}
+
+/* Checkmark Icon Styles & Animation */
+.checkmark-icon {
+ width: 80px;
+ height: 80px;
+ border-radius: 50%;
+ display: block;
+ stroke-width: 3;
+ stroke: #28a745; /* Green checkmark */
+ stroke-miterlimit: 10;
+ margin: 10px auto;
+ box-shadow: inset 0px 0px 0px #ffffff; /* Start transparent */
+ animation: scale .3s ease-in-out .9s both, fill .4s ease-in-out .9s both;
+}
+
+.checkmark__circle {
+ stroke-dasharray: 166;
+ stroke-dashoffset: 166;
+ stroke-width: 2;
+ stroke-miterlimit: 10;
+ stroke: #7ac142;
+ fill: none;
+ animation: stroke 0.6s cubic-bezier(0.65, 0, 0.45, 1) forwards;
+}
+
+.checkmark__check {
+ transform-origin: 50% 50%;
+ stroke-dasharray: 48;
+ stroke-dashoffset: 48;
+ animation: stroke 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.8s forwards;
+}
+
+@keyframes stroke {
+ 100% {
+ stroke-dashoffset: 0;
}
- .app-container {
- /* Add light mode adjustments if needed */
+}
+
+@keyframes scale {
+ 0%, 100% {
+ transform: none;
}
- .initial-view, .waiting-view, .success-view, .failed-view {
- border-color: #ccc;
+ 50% {
+ transform: scale3d(1.1, 1.1, 1);
}
- .address {
- background-color: #eee;
+}
+
+@keyframes fill {
+ 100% {
+ box-shadow: inset 0px 0px 0px 40px #7ac142; /* Fill effect */
}
+}
+
+/* Adjust QR code size to fit better in the box */
+.qr-code {
+ width: 240px;
+ height: 240px;
+ margin: 0 auto;
+}
+
+/* Ensure success animation fits */
+.success-animation {
+ width: 100px;
+ height: 100px;
+ margin: 0 auto;
+}
+
+/* Ensure waiting spinner fits */
+.waiting-spinner {
+ width: 40px;
+ height: 40px;
+ margin: 0 auto;
+}
+
+/* Ensure failed X mark fits */
+.failed-x {
+ width: 60px;
+ height: 60px;
+ margin: 0 auto;
+}
+
+/* Remove dark mode specific overrides */
+/* @media (prefers-color-scheme: dark) { ... } */
+
+/* Initial view without box */
+.initial-view {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ min-height: 120px;
+}
+
+.initial-view h2 {
+ margin: 0;
+ color: var(--digi-blue);
+ font-size: 1.5em;
+}
+
+.initial-view .description {
+ margin-top: 0.1rem;
+ margin-bottom: 0.5rem;
+}
+
+.initial-view .qr-code {
+ margin: 0.5rem auto;
+}
+
+.initial-view .waiting-text {
+ margin: 0.25rem 0;
+}
+
+.initial-view button {
+ margin-top: 0.25rem;
+}
+
+.signin-button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+ padding: 0.6rem 1.2rem;
+ background-color: white;
+ color: #1a237e;
+ border: 1px solid var(--digi-border-light);
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
+ transition: all 0.2s ease;
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+}
+
+.signin-button:hover {
+ background-color: #f8f9fa;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.signin-button:disabled {
+ opacity: 0.7;
+ cursor: not-allowed;
+}
+
+.signin-button img {
+ margin: 0;
+ width: 20px;
+ height: 20px;
+}
+
+.signin-button span {
+ font-weight: 600;
+ font-size: 0.95rem;
+ letter-spacing: 0.3px;
} \ No newline at end of file
diff --git a/src/client/utils.ts b/src/client/utils.ts
index cd9c8ba..6a95f7b 100644
--- a/src/client/utils.ts
+++ b/src/client/utils.ts
@@ -1,20 +1,31 @@
-// Simple utility to determine DigiByte address type based on prefix
+// Utility to determine DigiByte address type based on prefix
-export type DigiByteAddressType = 'DigiByte (DGB)' | 'DigiAsset (DGA)' | 'Unknown';
+export type DigiByteAddressFormat = 'Legacy (P2PKH)' | 'Script (P2SH)' | 'SegWit (Bech32)' | 'Unknown';
-export function getDigiByteAddressType(address: string | undefined | null): DigiByteAddressType {
+/**
+ * Determines the format of a DigiByte address based on its prefix.
+ * Note: P2SH addresses on DigiByte often start with 'S', but complex scripts
+ * might result in different prefixes. This function covers common cases.
+ * Legacy starts with 'D'. SegWit starts with 'dgb1'.
+ *
+ * @param address The DigiByte address string.
+ * @returns The determined address format.
+ */
+export function getDigiByteAddressType(address: string | undefined | null): DigiByteAddressFormat {
if (!address) {
return 'Unknown';
}
if (address.startsWith('dgb1')) {
- return 'DigiByte (DGB)';
+ return 'SegWit (Bech32)';
}
- // Add other prefixes if DigiAssets use a distinct one, e.g., 'dga1'
- // For now, assume non-DGB is DigiAsset, but this might need refinement
- // depending on actual DigiAsset address formats.
- else {
- // Assuming DigiAssets might start differently or be the fallback
- // This is a placeholder assumption.
- return 'DigiAsset (DGA)'; // Placeholder - ADJUST BASED ON ACTUAL DGA PREFIX
+ if (address.startsWith('S')) { // Common prefix for P2SH on DGB
+ return 'Script (P2SH)';
}
+ if (address.startsWith('D')) {
+ return 'Legacy (P2PKH)';
+ }
+
+ // If it doesn't match known prefixes, return Unknown
+ // Could potentially add DigiAsset checks here if they have distinct prefixes
+ return 'Unknown';
} \ No newline at end of file