diff options
Diffstat (limited to 'src/client')
| -rw-r--r-- | src/client/App.tsx | 101 | ||||
| -rw-r--r-- | src/client/index.css | 301 | ||||
| -rw-r--r-- | src/client/utils.ts | 33 | 
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  | 
