init(nebula): add go templating files and gitignore

This commit is contained in:
2026-01-05 13:34:45 -05:00
parent 9018ca2e6a
commit 901b1116be
3 changed files with 1493 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
nebula

692
views/register.templ Normal file
View File

@@ -0,0 +1,692 @@
package views
import (
"nebula/components"
"nebula/layouts"
)
var registerSteps = []string{"Detect", "Register", "Complete"}
func boolStr(b bool) string {
if b {
return "true"
}
return "false"
}
// DeviceCapabilities holds the WebAuthn capability detection results
type DeviceCapabilities struct {
Platform bool // Biometrics (Face ID, Touch ID, Windows Hello)
CrossPlatform bool // Security keys (YubiKey, etc.)
Conditional bool // Passkey autofill support
}
// RegisterState holds the current registration state
type RegisterState struct {
Step int
Method string // "passkey", "security-key", or "qr-code"
Username string
Error string
}
// RegisterPage renders the full registration page with the specified step
templ RegisterPage(state RegisterState) {
@layouts.CenteredCard("Register - Sonr") {
<div slot="header" id="stepper-container" hx-swap-oob:inherited="true">
@components.OnboardingStepper(state.Step, registerSteps)
</div>
<div id="step-content" class="step-content">
@RegisterStepContent(state)
</div>
<div id="htmx-indicator" class="htmx-indicator">
<wa-spinner></wa-spinner>
</div>
<footer slot="footer">
@RegisterFooter()
</footer>
}
}
// RegisterStepContent renders the content for a specific step (used for HTMX partials)
templ RegisterStepContent(state RegisterState) {
switch state.Step {
case 1:
@RegisterStep1()
case 2:
switch state.Method {
case "passkey":
@RegisterStep2Passkey()
case "security-key":
@RegisterStep2SecurityKey()
case "qr-code":
@RegisterStep2QRCode()
default:
@RegisterStep2Passkey()
}
case 3:
@RegisterStep3()
}
}
// RegisterStepWithStepper renders step content with OOB stepper update for HTMX 4
templ RegisterStepWithStepper(state RegisterState) {
@RegisterStepContent(state)
<div id="stepper-container" hx-swap-oob="innerHTML">
@components.OnboardingStepper(state.Step, registerSteps)
</div>
}
// RegisterStep1 - Device Detection and Method Selection
templ RegisterStep1() {
@registerStyles()
<div class="wa-stack wa-gap-l">
<div class="wa-stack wa-gap-xs" style="text-align: center;">
<h2 class="wa-heading-l">Create Your Account</h2>
<p class="wa-caption-m" style="color: var(--wa-color-neutral-600);">
Setting up secure sign-in...
</p>
</div>
<div
id="capabilities"
class="capability-list"
hx-get="/register/capabilities"
hx-trigger="load delay:500ms"
hx-swap="innerHTML"
>
@CapabilityItem("cap-platform", "fingerprint", "Face or Fingerprint", false, true)
@CapabilityItem("cap-cross-platform", "key", "Hardware Security Key", false, true)
@CapabilityItem("cap-conditional", "bolt", "Quick Sign-in", false, true)
</div>
<wa-divider></wa-divider>
<wa-radio-group
label="How do you want to sign in?"
orientation="vertical"
name="auth-method"
id="auth-method-group"
>
<wa-radio appearance="button" value="passkey" id="radio-passkey" disabled>
Use Face or Fingerprint (Recommended)
</wa-radio>
<wa-radio appearance="button" value="security-key" id="radio-security-key" disabled>
Use a Hardware Key
</wa-radio>
<wa-radio appearance="button" value="qr-code" id="radio-qr">
Use Another Device
</wa-radio>
</wa-radio-group>
<div class="wa-cluster wa-justify-content-end wa-gap-s">
<wa-button
id="btn-continue"
variant="brand"
disabled
hx-get="/register/step/2"
hx-target="#step-content"
hx-swap="innerHTML transition:true"
hx-indicator="#htmx-indicator"
hx-include="#auth-method-group"
>
Continue
<wa-icon slot="end" variant="regular" name="arrow-right"></wa-icon>
</wa-button>
</div>
</div>
@registerStep1Scripts()
}
// CapabilityItem renders a single capability detection item
templ CapabilityItem(id string, icon string, label string, supported bool, loading bool) {
<div
class={ "capability-item", templ.KV("supported", supported && !loading), templ.KV("unsupported", !supported && !loading) }
id={ id }
>
if loading {
<wa-spinner style="--size: 1.5rem;"></wa-spinner>
} else if supported {
<wa-icon name="circle-check" style="color: var(--wa-color-success); font-size: 1.5rem;"></wa-icon>
} else {
<wa-icon name="circle-xmark" style="color: var(--wa-color-danger); font-size: 1.5rem;"></wa-icon>
}
<span>{ label }</span>
</div>
}
templ CapabilitiesResult(caps DeviceCapabilities) {
@CapabilityItem("cap-platform", "fingerprint", "Face or Fingerprint", caps.Platform, false)
@CapabilityItem("cap-cross-platform", "key", "Hardware Security Key", caps.CrossPlatform, false)
@CapabilityItem("cap-conditional", "bolt", "Quick Sign-in", caps.Conditional, false)
@capabilitiesScript(caps)
}
templ capabilitiesScript(caps DeviceCapabilities) {
<div
id="caps-data"
data-platform={ boolStr(caps.Platform) }
data-cross-platform={ boolStr(caps.CrossPlatform) }
data-conditional={ boolStr(caps.Conditional) }
style="display:none;"
></div>
<script>
(function() {
const capsData = document.getElementById('caps-data');
const hasPlatform = capsData?.dataset.platform === 'true';
const hasCrossPlatform = capsData?.dataset.crossPlatform === 'true';
const radioPasskey = document.getElementById('radio-passkey');
const radioSecurityKey = document.getElementById('radio-security-key');
const btnContinue = document.getElementById('btn-continue');
const methodGroup = document.getElementById('auth-method-group');
if (radioPasskey && hasPlatform) {
radioPasskey.removeAttribute('disabled');
}
if (radioSecurityKey && hasCrossPlatform) {
radioSecurityKey.removeAttribute('disabled');
}
if (methodGroup) {
if (hasPlatform) {
methodGroup.value = 'passkey';
} else if (hasCrossPlatform) {
methodGroup.value = 'security-key';
} else {
methodGroup.value = 'qr-code';
}
if (btnContinue) {
btnContinue.removeAttribute('disabled');
}
}
})();
</script>
}
// RegisterStep2Passkey - Passkey Registration Form
templ RegisterStep2Passkey() {
@registerStyles()
<div class="wa-stack wa-gap-l">
<div class="wa-stack wa-gap-xs" style="text-align: center;">
<h2 class="wa-heading-l">Set Up Face or Fingerprint</h2>
<p class="wa-caption-m" style="color: var(--wa-color-neutral-600);">
Sign in securely using your face or fingerprint.
</p>
</div>
<form id="passkey-form" class="wa-stack wa-gap-m">
<wa-input
name="username"
type="text"
label="Username"
placeholder="Choose a username"
required
>
<wa-icon slot="end" variant="regular" name="user"></wa-icon>
</wa-input>
<wa-input
name="display-name"
type="text"
label="Display Name"
placeholder="Your name (optional)"
>
<wa-icon slot="end" variant="regular" name="id-card"></wa-icon>
</wa-input>
</form>
<wa-alert variant="info" open>
<wa-icon slot="icon" name="fingerprint"></wa-icon>
<strong>Next: Verify your identity</strong>
<br/>
Your device will ask for your face, fingerprint, or PIN.
</wa-alert>
<div class="wa-cluster wa-justify-content-end wa-gap-s">
<wa-button
appearance="outlined"
variant="neutral"
hx-get="/register/step/1"
hx-target="#step-content"
hx-swap="innerHTML transition:true"
hx-indicator="#htmx-indicator"
>
Back
</wa-button>
<wa-button
variant="brand"
id="btn-register-passkey"
>
Continue
<wa-icon slot="end" variant="regular" name="fingerprint"></wa-icon>
</wa-button>
</div>
</div>
@passkeyRegistrationScript()
}
// RegisterStep2SecurityKey - Security Key Registration Form
templ RegisterStep2SecurityKey() {
@registerStyles()
<div class="wa-stack wa-gap-l">
<div class="wa-stack wa-gap-xs" style="text-align: center;">
<h2 class="wa-heading-l">Set Up Hardware Key</h2>
<p class="wa-caption-m" style="color: var(--wa-color-neutral-600);">
Use a physical security key like YubiKey to sign in.
</p>
</div>
<form id="security-key-form" class="wa-stack wa-gap-m">
<wa-input
name="username"
type="text"
label="Username"
placeholder="Choose a username"
required
>
<wa-icon slot="end" variant="regular" name="user"></wa-icon>
</wa-input>
</form>
<wa-alert variant="info" open>
<wa-icon slot="icon" name="key"></wa-icon>
<strong>Plug in your security key</strong>
<br/>
After clicking Continue, tap the button on your key when it blinks.
</wa-alert>
<div class="wa-cluster wa-justify-content-end wa-gap-s">
<wa-button
appearance="outlined"
variant="neutral"
hx-get="/register/step/1"
hx-target="#step-content"
hx-swap="innerHTML transition:true"
hx-indicator="#htmx-indicator"
>
Back
</wa-button>
<wa-button
variant="brand"
id="btn-register-security-key"
>
Continue
<wa-icon slot="end" variant="regular" name="key"></wa-icon>
</wa-button>
</div>
</div>
@securityKeyRegistrationScript()
}
// RegisterStep2QRCode - QR Code Fallback Registration
templ RegisterStep2QRCode() {
@registerStyles()
<div class="wa-stack wa-gap-l wa-align-items-center">
<div class="wa-stack wa-gap-xs" style="text-align: center;">
<h2 class="wa-heading-l">Use Your Phone</h2>
<p class="wa-caption-m" style="color: var(--wa-color-neutral-600);">
Scan this code with your phone camera to continue setup
</p>
</div>
<wa-qr-code
value="https://sonr.id/register?token=abc123xyz789"
label="Scan with your phone camera"
></wa-qr-code>
<p class="wa-caption-s" style="color: var(--wa-color-neutral-500);">Or copy this link to your phone</p>
<wa-input
value="sonr.id/r/abc123xyz789"
disabled
style="width: 100%;"
>
<wa-copy-button slot="end" value="sonr.id/r/abc123xyz789"></wa-copy-button>
</wa-input>
<wa-divider></wa-divider>
<div class="wa-stack wa-gap-s wa-align-items-center" style="width: 100%;">
<h3 class="wa-heading-s">Enter Code from Phone</h3>
<p class="wa-caption-s" style="color: var(--wa-color-neutral-500);">
After scanning, enter the 6-digit code shown on your phone.
</p>
<wa-input
type="text"
placeholder="000000"
maxlength="6"
id="verification-code"
name="code"
class="verification-code-input"
></wa-input>
</div>
<div class="wa-cluster wa-justify-content-center wa-gap-s" style="width: 100%;">
<wa-button
appearance="outlined"
variant="neutral"
hx-get="/register/step/1"
hx-target="#step-content"
hx-swap="innerHTML transition:true"
hx-indicator="#htmx-indicator"
>
Back
</wa-button>
<wa-button
variant="brand"
id="btn-verify-code"
hx-post="/register/verify-code"
hx-target="#step-content"
hx-swap="innerHTML transition:true"
hx-indicator="#htmx-indicator"
hx-include="#verification-code"
>
Verify
</wa-button>
</div>
</div>
}
// RegisterStep3 - Success with Recovery Keys
templ RegisterStep3() {
@registerStyles()
<div class="wa-stack wa-gap-l wa-align-items-center">
<wa-icon name="circle-check" family="duotone" class="success-icon"></wa-icon>
<div class="wa-stack wa-gap-xs" style="text-align: center;">
<h2 class="wa-heading-l">You're All Set</h2>
<p class="wa-caption-m" style="color: var(--wa-color-neutral-600);">
Your account is ready. Save your backup codes before continuing.
</p>
</div>
<wa-alert variant="warning" open>
<wa-icon slot="icon" name="shield-exclamation"></wa-icon>
<strong>Important: Save these codes now</strong>
<br/>
If you lose your device, these codes are the only way to recover your account.
</wa-alert>
<div class="wa-stack wa-gap-m" style="width: 100%;">
<wa-input label="Account ID" value="pk_live_a1b2c3d4e5f6g7h8i9j0" disabled>
<wa-copy-button slot="end" value="pk_live_a1b2c3d4e5f6g7h8i9j0"></wa-copy-button>
</wa-input>
<wa-input label="Backup Code" value="rk_sec_z9y8x7w6v5u4t3s2r1q0" disabled>
<wa-copy-button slot="end" value="rk_sec_z9y8x7w6v5u4t3s2r1q0"></wa-copy-button>
</wa-input>
</div>
<wa-checkbox id="confirm-saved" required>
I saved these codes somewhere safe
</wa-checkbox>
<wa-button
variant="brand"
id="btn-finish"
disabled
style="width: 100%;"
onclick="window.location.href='/dashboard'"
>
Go to Dashboard
<wa-icon slot="end" variant="regular" name="arrow-right"></wa-icon>
</wa-button>
</div>
@step3Scripts()
}
// RegisterFooter renders the footer with login link
templ RegisterFooter() {
<div class="wa-cluster wa-justify-content-center wa-gap-m">
<span class="wa-caption-s" style="color: var(--wa-color-neutral-500);">Already have an account?</span>
<wa-button appearance="plain" size="small" onclick="window.location.href='/login'">
Sign In
</wa-button>
</div>
}
// registerStyles contains the CSS specific to the register page
templ registerStyles() {
<style>
/* HTMX 4 indicator and transition styles */
.htmx-indicator {
display: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 100;
}
.htmx-request .htmx-indicator {
display: flex;
}
.htmx-request.step-content {
opacity: 0.5;
pointer-events: none;
transition: opacity 0.2s ease;
}
/* View transition support for HTMX 4 */
@view-transition {
navigation: auto;
}
::view-transition-old(step-content),
::view-transition-new(step-content) {
animation-duration: 0.25s;
}
.step-content {
view-transition-name: step-content;
position: relative;
}
/* Device capability badges */
.capability-list {
display: flex;
flex-direction: column;
gap: var(--wa-space-s);
}
.capability-item {
display: flex;
align-items: center;
gap: var(--wa-space-s);
padding: var(--wa-space-s) var(--wa-space-m);
border-radius: var(--wa-radius-m);
background: var(--wa-color-surface-alt);
border-left: 3px solid transparent;
transition: all 0.3s ease;
}
.capability-item.supported {
border-left-color: var(--wa-color-success);
}
.capability-item.unsupported {
border-left-color: var(--wa-color-danger);
opacity: 0.6;
}
/* Success icon */
.success-icon {
font-size: 4rem;
color: var(--wa-color-success);
}
/* Verification code input styling */
.verification-code-input {
text-align: center;
font-size: 1.5rem;
letter-spacing: 0.3em;
max-width: 180px;
}
/* Radio group styling */
wa-radio-group[orientation="vertical"] {
display: flex;
flex-direction: column;
gap: var(--wa-space-s);
}
wa-radio[appearance="button"] {
cursor: pointer;
}
wa-radio[appearance="button"][disabled] {
cursor: not-allowed;
opacity: 0.5;
}
/* Viewport constraints for popup/webview */
@media (max-width: 400px) {
.capability-item {
padding: var(--wa-space-xs) var(--wa-space-s);
font-size: var(--wa-font-size-s);
}
.verification-code-input {
font-size: 1.25rem;
max-width: 160px;
}
.success-icon {
font-size: 3rem;
}
}
@media (max-height: 600px) {
.capability-item {
padding: var(--wa-space-xs) var(--wa-space-s);
}
}
</style>
}
// registerStep1Scripts handles method selection and continue button enabling
templ registerStep1Scripts() {
<script>
(function() {
const methodGroup = document.getElementById('auth-method-group');
const btnContinue = document.getElementById('btn-continue');
if (methodGroup && btnContinue) {
// Listen for method selection changes
methodGroup.addEventListener('wa-change', (e) => {
const selectedMethod = e.target.value;
if (selectedMethod) {
btnContinue.removeAttribute('disabled');
// Update the hx-get URL with selected method
btnContinue.setAttribute('hx-get', '/register/step/2?method=' + selectedMethod);
htmx.process(btnContinue);
}
});
}
})();
</script>
}
// passkeyRegistrationScript handles WebAuthn passkey registration
templ passkeyRegistrationScript() {
<script>
(function() {
const btn = document.getElementById('btn-register-passkey');
if (!btn) return;
btn.addEventListener('click', async () => {
try {
const username = document.querySelector('[name="username"]')?.value;
const displayName = document.querySelector('[name="display-name"]')?.value || username;
if (!username) {
alert('Please enter a username');
return;
}
// Get challenge from server (in production)
const challenge = new Uint8Array(32);
crypto.getRandomValues(challenge);
const createOptions = {
publicKey: {
challenge: challenge,
rp: {
name: "Sonr",
id: window.location.hostname
},
user: {
id: new Uint8Array(16),
name: username,
displayName: displayName
},
pubKeyCredParams: [
{ type: "public-key", alg: -7 }, // ES256
{ type: "public-key", alg: -257 } // RS256
],
authenticatorSelection: {
authenticatorAttachment: "platform",
userVerification: "required",
residentKey: "required"
},
timeout: 60000
}
};
const credential = await navigator.credentials.create(createOptions);
console.log("Passkey created:", credential);
// Navigate to success step
htmx.ajax('GET', '/register/step/3', {
target: '#step-content',
swap: 'innerHTML transition:true'
});
} catch (error) {
console.error("Passkey registration failed:", error);
alert("Registration failed: " + error.message);
}
});
})();
</script>
}
// securityKeyRegistrationScript handles WebAuthn security key registration
templ securityKeyRegistrationScript() {
<script>
(function() {
const btn = document.getElementById('btn-register-security-key');
if (!btn) return;
btn.addEventListener('click', async () => {
try {
const username = document.querySelector('[name="username"]')?.value;
if (!username) {
alert('Please enter a username');
return;
}
const challenge = new Uint8Array(32);
crypto.getRandomValues(challenge);
const createOptions = {
publicKey: {
challenge: challenge,
rp: {
name: "Sonr",
id: window.location.hostname
},
user: {
id: new Uint8Array(16),
name: username,
displayName: username
},
pubKeyCredParams: [
{ type: "public-key", alg: -7 },
{ type: "public-key", alg: -257 }
],
authenticatorSelection: {
authenticatorAttachment: "cross-platform",
userVerification: "preferred"
},
timeout: 60000
}
};
const credential = await navigator.credentials.create(createOptions);
console.log("Security key registered:", credential);
// Navigate to success step
htmx.ajax('GET', '/register/step/3', {
target: '#step-content',
swap: 'innerHTML transition:true'
});
} catch (error) {
console.error("Security key registration failed:", error);
alert("Registration failed: " + error.message);
}
});
})();
</script>
}
// step3Scripts handles the confirmation checkbox and finish button
templ step3Scripts() {
<script>
(function() {
const confirmCheckbox = document.getElementById('confirm-saved');
const finishBtn = document.getElementById('btn-finish');
if (confirmCheckbox && finishBtn) {
confirmCheckbox.addEventListener('wa-change', (e) => {
if (e.target.checked) {
finishBtn.removeAttribute('disabled');
} else {
finishBtn.setAttribute('disabled', '');
}
});
}
})();
</script>
}

800
views/register_templ.go Normal file
View File

@@ -0,0 +1,800 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.977
package views
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
"nebula/components"
"nebula/layouts"
)
var registerSteps = []string{"Detect", "Register", "Complete"}
func boolStr(b bool) string {
if b {
return "true"
}
return "false"
}
// DeviceCapabilities holds the WebAuthn capability detection results
type DeviceCapabilities struct {
Platform bool // Biometrics (Face ID, Touch ID, Windows Hello)
CrossPlatform bool // Security keys (YubiKey, etc.)
Conditional bool // Passkey autofill support
}
// RegisterState holds the current registration state
type RegisterState struct {
Step int
Method string // "passkey", "security-key", or "qr-code"
Username string
Error string
}
// RegisterPage renders the full registration page with the specified step
func RegisterPage(state RegisterState) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div slot=\"header\" id=\"stepper-container\" hx-swap-oob:inherited=\"true\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.OnboardingStepper(state.Step, registerSteps).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</div><div id=\"step-content\" class=\"step-content\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = RegisterStepContent(state).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</div><div id=\"htmx-indicator\" class=\"htmx-indicator\"><wa-spinner></wa-spinner></div><footer slot=\"footer\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = RegisterFooter().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</footer>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = layouts.CenteredCard("Register - Sonr").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// RegisterStepContent renders the content for a specific step (used for HTMX partials)
func RegisterStepContent(state RegisterState) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
if templ_7745c5c3_Var3 == nil {
templ_7745c5c3_Var3 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
switch state.Step {
case 1:
templ_7745c5c3_Err = RegisterStep1().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 2:
switch state.Method {
case "passkey":
templ_7745c5c3_Err = RegisterStep2Passkey().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case "security-key":
templ_7745c5c3_Err = RegisterStep2SecurityKey().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case "qr-code":
templ_7745c5c3_Err = RegisterStep2QRCode().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
default:
templ_7745c5c3_Err = RegisterStep2Passkey().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
case 3:
templ_7745c5c3_Err = RegisterStep3().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
}
// RegisterStepWithStepper renders step content with OOB stepper update for HTMX 4
func RegisterStepWithStepper(state RegisterState) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var4 := templ.GetChildren(ctx)
if templ_7745c5c3_Var4 == nil {
templ_7745c5c3_Var4 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = RegisterStepContent(state).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<div id=\"stepper-container\" hx-swap-oob=\"innerHTML\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.OnboardingStepper(state.Step, registerSteps).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// RegisterStep1 - Device Detection and Method Selection
func RegisterStep1() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var5 := templ.GetChildren(ctx)
if templ_7745c5c3_Var5 == nil {
templ_7745c5c3_Var5 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = registerStyles().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<div class=\"wa-stack wa-gap-l\"><div class=\"wa-stack wa-gap-xs\" style=\"text-align: center;\"><h2 class=\"wa-heading-l\">Create Your Account</h2><p class=\"wa-caption-m\" style=\"color: var(--wa-color-neutral-600);\">Setting up secure sign-in...</p></div><div id=\"capabilities\" class=\"capability-list\" hx-get=\"/register/capabilities\" hx-trigger=\"load delay:500ms\" hx-swap=\"innerHTML\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = CapabilityItem("cap-platform", "fingerprint", "Face or Fingerprint", false, true).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = CapabilityItem("cap-cross-platform", "key", "Hardware Security Key", false, true).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = CapabilityItem("cap-conditional", "bolt", "Quick Sign-in", false, true).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</div><wa-divider></wa-divider> <wa-radio-group label=\"How do you want to sign in?\" orientation=\"vertical\" name=\"auth-method\" id=\"auth-method-group\"><wa-radio appearance=\"button\" value=\"passkey\" id=\"radio-passkey\" disabled>Use Face or Fingerprint (Recommended)</wa-radio> <wa-radio appearance=\"button\" value=\"security-key\" id=\"radio-security-key\" disabled>Use a Hardware Key</wa-radio> <wa-radio appearance=\"button\" value=\"qr-code\" id=\"radio-qr\">Use Another Device</wa-radio></wa-radio-group><div class=\"wa-cluster wa-justify-content-end wa-gap-s\"><wa-button id=\"btn-continue\" variant=\"brand\" disabled hx-get=\"/register/step/2\" hx-target=\"#step-content\" hx-swap=\"innerHTML transition:true\" hx-indicator=\"#htmx-indicator\" hx-include=\"#auth-method-group\">Continue <wa-icon slot=\"end\" variant=\"regular\" name=\"arrow-right\"></wa-icon></wa-button></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = registerStep1Scripts().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// CapabilityItem renders a single capability detection item
func CapabilityItem(id string, icon string, label string, supported bool, loading bool) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var6 := templ.GetChildren(ctx)
if templ_7745c5c3_Var6 == nil {
templ_7745c5c3_Var6 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
var templ_7745c5c3_Var7 = []any{"capability-item", templ.KV("supported", supported && !loading), templ.KV("unsupported", !supported && !loading)}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var7...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var7).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/register.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\" id=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(id)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/register.templ`, Line: 140, Col: 9}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if loading {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<wa-spinner style=\"--size: 1.5rem;\"></wa-spinner> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else if supported {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<wa-icon name=\"circle-check\" style=\"color: var(--wa-color-success); font-size: 1.5rem;\"></wa-icon> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<wa-icon name=\"circle-xmark\" style=\"color: var(--wa-color-danger); font-size: 1.5rem;\"></wa-icon> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/register.templ`, Line: 149, Col: 15}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</span></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func CapabilitiesResult(caps DeviceCapabilities) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var11 := templ.GetChildren(ctx)
if templ_7745c5c3_Var11 == nil {
templ_7745c5c3_Var11 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = CapabilityItem("cap-platform", "fingerprint", "Face or Fingerprint", caps.Platform, false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = CapabilityItem("cap-cross-platform", "key", "Hardware Security Key", caps.CrossPlatform, false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = CapabilityItem("cap-conditional", "bolt", "Quick Sign-in", caps.Conditional, false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = capabilitiesScript(caps).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func capabilitiesScript(caps DeviceCapabilities) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var12 := templ.GetChildren(ctx)
if templ_7745c5c3_Var12 == nil {
templ_7745c5c3_Var12 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<div id=\"caps-data\" data-platform=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(boolStr(caps.Platform))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/register.templ`, Line: 163, Col: 40}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "\" data-cross-platform=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(boolStr(caps.CrossPlatform))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/register.templ`, Line: 164, Col: 51}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "\" data-conditional=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(boolStr(caps.Conditional))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/register.templ`, Line: 165, Col: 46}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\" style=\"display:none;\"></div><script>\n\t\t(function() {\n\t\t\tconst capsData = document.getElementById('caps-data');\n\t\t\tconst hasPlatform = capsData?.dataset.platform === 'true';\n\t\t\tconst hasCrossPlatform = capsData?.dataset.crossPlatform === 'true';\n\t\t\t\n\t\t\tconst radioPasskey = document.getElementById('radio-passkey');\n\t\t\tconst radioSecurityKey = document.getElementById('radio-security-key');\n\t\t\tconst btnContinue = document.getElementById('btn-continue');\n\t\t\tconst methodGroup = document.getElementById('auth-method-group');\n\t\t\t\n\t\t\tif (radioPasskey && hasPlatform) {\n\t\t\t\tradioPasskey.removeAttribute('disabled');\n\t\t\t}\n\t\t\tif (radioSecurityKey && hasCrossPlatform) {\n\t\t\t\tradioSecurityKey.removeAttribute('disabled');\n\t\t\t}\n\t\t\t\n\t\t\tif (methodGroup) {\n\t\t\t\tif (hasPlatform) {\n\t\t\t\t\tmethodGroup.value = 'passkey';\n\t\t\t\t} else if (hasCrossPlatform) {\n\t\t\t\t\tmethodGroup.value = 'security-key';\n\t\t\t\t} else {\n\t\t\t\t\tmethodGroup.value = 'qr-code';\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tif (btnContinue) {\n\t\t\t\t\tbtnContinue.removeAttribute('disabled');\n\t\t\t\t}\n\t\t\t}\n\t\t})();\n\t</script>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// RegisterStep2Passkey - Passkey Registration Form
func RegisterStep2Passkey() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var16 := templ.GetChildren(ctx)
if templ_7745c5c3_Var16 == nil {
templ_7745c5c3_Var16 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = registerStyles().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<div class=\"wa-stack wa-gap-l\"><div class=\"wa-stack wa-gap-xs\" style=\"text-align: center;\"><h2 class=\"wa-heading-l\">Set Up Face or Fingerprint</h2><p class=\"wa-caption-m\" style=\"color: var(--wa-color-neutral-600);\">Sign in securely using your face or fingerprint.</p></div><form id=\"passkey-form\" class=\"wa-stack wa-gap-m\"><wa-input name=\"username\" type=\"text\" label=\"Username\" placeholder=\"Choose a username\" required><wa-icon slot=\"end\" variant=\"regular\" name=\"user\"></wa-icon></wa-input> <wa-input name=\"display-name\" type=\"text\" label=\"Display Name\" placeholder=\"Your name (optional)\"><wa-icon slot=\"end\" variant=\"regular\" name=\"id-card\"></wa-icon></wa-input></form><wa-alert variant=\"info\" open><wa-icon slot=\"icon\" name=\"fingerprint\"></wa-icon> <strong>Next: Verify your identity</strong><br>Your device will ask for your face, fingerprint, or PIN.</wa-alert><div class=\"wa-cluster wa-justify-content-end wa-gap-s\"><wa-button appearance=\"outlined\" variant=\"neutral\" hx-get=\"/register/step/1\" hx-target=\"#step-content\" hx-swap=\"innerHTML transition:true\" hx-indicator=\"#htmx-indicator\">Back</wa-button> <wa-button variant=\"brand\" id=\"btn-register-passkey\">Continue <wa-icon slot=\"end\" variant=\"regular\" name=\"fingerprint\"></wa-icon></wa-button></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = passkeyRegistrationScript().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// RegisterStep2SecurityKey - Security Key Registration Form
func RegisterStep2SecurityKey() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var17 := templ.GetChildren(ctx)
if templ_7745c5c3_Var17 == nil {
templ_7745c5c3_Var17 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = registerStyles().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<div class=\"wa-stack wa-gap-l\"><div class=\"wa-stack wa-gap-xs\" style=\"text-align: center;\"><h2 class=\"wa-heading-l\">Set Up Hardware Key</h2><p class=\"wa-caption-m\" style=\"color: var(--wa-color-neutral-600);\">Use a physical security key like YubiKey to sign in.</p></div><form id=\"security-key-form\" class=\"wa-stack wa-gap-m\"><wa-input name=\"username\" type=\"text\" label=\"Username\" placeholder=\"Choose a username\" required><wa-icon slot=\"end\" variant=\"regular\" name=\"user\"></wa-icon></wa-input></form><wa-alert variant=\"info\" open><wa-icon slot=\"icon\" name=\"key\"></wa-icon> <strong>Plug in your security key</strong><br>After clicking Continue, tap the button on your key when it blinks.</wa-alert><div class=\"wa-cluster wa-justify-content-end wa-gap-s\"><wa-button appearance=\"outlined\" variant=\"neutral\" hx-get=\"/register/step/1\" hx-target=\"#step-content\" hx-swap=\"innerHTML transition:true\" hx-indicator=\"#htmx-indicator\">Back</wa-button> <wa-button variant=\"brand\" id=\"btn-register-security-key\">Continue <wa-icon slot=\"end\" variant=\"regular\" name=\"key\"></wa-icon></wa-button></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = securityKeyRegistrationScript().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// RegisterStep2QRCode - QR Code Fallback Registration
func RegisterStep2QRCode() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var18 := templ.GetChildren(ctx)
if templ_7745c5c3_Var18 == nil {
templ_7745c5c3_Var18 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = registerStyles().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<div class=\"wa-stack wa-gap-l wa-align-items-center\"><div class=\"wa-stack wa-gap-xs\" style=\"text-align: center;\"><h2 class=\"wa-heading-l\">Use Your Phone</h2><p class=\"wa-caption-m\" style=\"color: var(--wa-color-neutral-600);\">Scan this code with your phone camera to continue setup</p></div><wa-qr-code value=\"https://sonr.id/register?token=abc123xyz789\" label=\"Scan with your phone camera\"></wa-qr-code><p class=\"wa-caption-s\" style=\"color: var(--wa-color-neutral-500);\">Or copy this link to your phone</p><wa-input value=\"sonr.id/r/abc123xyz789\" disabled style=\"width: 100%;\"><wa-copy-button slot=\"end\" value=\"sonr.id/r/abc123xyz789\"></wa-copy-button></wa-input> <wa-divider></wa-divider><div class=\"wa-stack wa-gap-s wa-align-items-center\" style=\"width: 100%;\"><h3 class=\"wa-heading-s\">Enter Code from Phone</h3><p class=\"wa-caption-s\" style=\"color: var(--wa-color-neutral-500);\">After scanning, enter the 6-digit code shown on your phone.</p><wa-input type=\"text\" placeholder=\"000000\" maxlength=\"6\" id=\"verification-code\" name=\"code\" class=\"verification-code-input\"></wa-input></div><div class=\"wa-cluster wa-justify-content-center wa-gap-s\" style=\"width: 100%;\"><wa-button appearance=\"outlined\" variant=\"neutral\" hx-get=\"/register/step/1\" hx-target=\"#step-content\" hx-swap=\"innerHTML transition:true\" hx-indicator=\"#htmx-indicator\">Back</wa-button> <wa-button variant=\"brand\" id=\"btn-verify-code\" hx-post=\"/register/verify-code\" hx-target=\"#step-content\" hx-swap=\"innerHTML transition:true\" hx-indicator=\"#htmx-indicator\" hx-include=\"#verification-code\">Verify</wa-button></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// RegisterStep3 - Success with Recovery Keys
func RegisterStep3() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var19 := templ.GetChildren(ctx)
if templ_7745c5c3_Var19 == nil {
templ_7745c5c3_Var19 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = registerStyles().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "<div class=\"wa-stack wa-gap-l wa-align-items-center\"><wa-icon name=\"circle-check\" family=\"duotone\" class=\"success-icon\"></wa-icon><div class=\"wa-stack wa-gap-xs\" style=\"text-align: center;\"><h2 class=\"wa-heading-l\">You're All Set</h2><p class=\"wa-caption-m\" style=\"color: var(--wa-color-neutral-600);\">Your account is ready. Save your backup codes before continuing.</p></div><wa-alert variant=\"warning\" open><wa-icon slot=\"icon\" name=\"shield-exclamation\"></wa-icon> <strong>Important: Save these codes now</strong><br>If you lose your device, these codes are the only way to recover your account.</wa-alert><div class=\"wa-stack wa-gap-m\" style=\"width: 100%;\"><wa-input label=\"Account ID\" value=\"pk_live_a1b2c3d4e5f6g7h8i9j0\" disabled><wa-copy-button slot=\"end\" value=\"pk_live_a1b2c3d4e5f6g7h8i9j0\"></wa-copy-button></wa-input> <wa-input label=\"Backup Code\" value=\"rk_sec_z9y8x7w6v5u4t3s2r1q0\" disabled><wa-copy-button slot=\"end\" value=\"rk_sec_z9y8x7w6v5u4t3s2r1q0\"></wa-copy-button></wa-input></div><wa-checkbox id=\"confirm-saved\" required>I saved these codes somewhere safe</wa-checkbox> <wa-button variant=\"brand\" id=\"btn-finish\" disabled style=\"width: 100%;\" onclick=\"window.location.href='/dashboard'\">Go to Dashboard <wa-icon slot=\"end\" variant=\"regular\" name=\"arrow-right\"></wa-icon></wa-button></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = step3Scripts().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// RegisterFooter renders the footer with login link
func RegisterFooter() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var20 := templ.GetChildren(ctx)
if templ_7745c5c3_Var20 == nil {
templ_7745c5c3_Var20 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<div class=\"wa-cluster wa-justify-content-center wa-gap-m\"><span class=\"wa-caption-s\" style=\"color: var(--wa-color-neutral-500);\">Already have an account?</span> <wa-button appearance=\"plain\" size=\"small\" onclick=\"window.location.href='/login'\">Sign In</wa-button></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// registerStyles contains the CSS specific to the register page
func registerStyles() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var21 := templ.GetChildren(ctx)
if templ_7745c5c3_Var21 == nil {
templ_7745c5c3_Var21 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<style>\n\t\t/* HTMX 4 indicator and transition styles */\n\t\t.htmx-indicator {\n\t\t\tdisplay: none;\n\t\t\tposition: absolute;\n\t\t\ttop: 50%;\n\t\t\tleft: 50%;\n\t\t\ttransform: translate(-50%, -50%);\n\t\t\tz-index: 100;\n\t\t}\n\t\t.htmx-request .htmx-indicator {\n\t\t\tdisplay: flex;\n\t\t}\n\t\t.htmx-request.step-content {\n\t\t\topacity: 0.5;\n\t\t\tpointer-events: none;\n\t\t\ttransition: opacity 0.2s ease;\n\t\t}\n\t\t/* View transition support for HTMX 4 */\n\t\t@view-transition {\n\t\t\tnavigation: auto;\n\t\t}\n\t\t::view-transition-old(step-content),\n\t\t::view-transition-new(step-content) {\n\t\t\tanimation-duration: 0.25s;\n\t\t}\n\t\t.step-content {\n\t\t\tview-transition-name: step-content;\n\t\t\tposition: relative;\n\t\t}\n\t\t/* Device capability badges */\n\t\t.capability-list {\n\t\t\tdisplay: flex;\n\t\t\tflex-direction: column;\n\t\t\tgap: var(--wa-space-s);\n\t\t}\n\t\t.capability-item {\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tgap: var(--wa-space-s);\n\t\t\tpadding: var(--wa-space-s) var(--wa-space-m);\n\t\t\tborder-radius: var(--wa-radius-m);\n\t\t\tbackground: var(--wa-color-surface-alt);\n\t\t\tborder-left: 3px solid transparent;\n\t\t\ttransition: all 0.3s ease;\n\t\t}\n\t\t.capability-item.supported {\n\t\t\tborder-left-color: var(--wa-color-success);\n\t\t}\n\t\t.capability-item.unsupported {\n\t\t\tborder-left-color: var(--wa-color-danger);\n\t\t\topacity: 0.6;\n\t\t}\n\t\t/* Success icon */\n\t\t.success-icon {\n\t\t\tfont-size: 4rem;\n\t\t\tcolor: var(--wa-color-success);\n\t\t}\n\t\t/* Verification code input styling */\n\t\t.verification-code-input {\n\t\t\ttext-align: center;\n\t\t\tfont-size: 1.5rem;\n\t\t\tletter-spacing: 0.3em;\n\t\t\tmax-width: 180px;\n\t\t}\n\t\t/* Radio group styling */\n\t\twa-radio-group[orientation=\"vertical\"] {\n\t\t\tdisplay: flex;\n\t\t\tflex-direction: column;\n\t\t\tgap: var(--wa-space-s);\n\t\t}\n\t\twa-radio[appearance=\"button\"] {\n\t\t\tcursor: pointer;\n\t\t}\n\t\twa-radio[appearance=\"button\"][disabled] {\n\t\t\tcursor: not-allowed;\n\t\t\topacity: 0.5;\n\t\t}\n\t\t/* Viewport constraints for popup/webview */\n\t\t@media (max-width: 400px) {\n\t\t\t.capability-item {\n\t\t\t\tpadding: var(--wa-space-xs) var(--wa-space-s);\n\t\t\t\tfont-size: var(--wa-font-size-s);\n\t\t\t}\n\t\t\t.verification-code-input {\n\t\t\t\tfont-size: 1.25rem;\n\t\t\t\tmax-width: 160px;\n\t\t\t}\n\t\t\t.success-icon {\n\t\t\t\tfont-size: 3rem;\n\t\t\t}\n\t\t}\n\t\t@media (max-height: 600px) {\n\t\t\t.capability-item {\n\t\t\t\tpadding: var(--wa-space-xs) var(--wa-space-s);\n\t\t\t}\n\t\t}\n\t</style>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// registerStep1Scripts handles method selection and continue button enabling
func registerStep1Scripts() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var22 := templ.GetChildren(ctx)
if templ_7745c5c3_Var22 == nil {
templ_7745c5c3_Var22 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "<script>\n\t\t(function() {\n\t\t\tconst methodGroup = document.getElementById('auth-method-group');\n\t\t\tconst btnContinue = document.getElementById('btn-continue');\n\t\t\t\n\t\t\tif (methodGroup && btnContinue) {\n\t\t\t\t// Listen for method selection changes\n\t\t\t\tmethodGroup.addEventListener('wa-change', (e) => {\n\t\t\t\t\tconst selectedMethod = e.target.value;\n\t\t\t\t\tif (selectedMethod) {\n\t\t\t\t\t\tbtnContinue.removeAttribute('disabled');\n\t\t\t\t\t\t// Update the hx-get URL with selected method\n\t\t\t\t\t\tbtnContinue.setAttribute('hx-get', '/register/step/2?method=' + selectedMethod);\n\t\t\t\t\t\thtmx.process(btnContinue);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t})();\n\t</script>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// passkeyRegistrationScript handles WebAuthn passkey registration
func passkeyRegistrationScript() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var23 := templ.GetChildren(ctx)
if templ_7745c5c3_Var23 == nil {
templ_7745c5c3_Var23 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<script>\n\t\t(function() {\n\t\t\tconst btn = document.getElementById('btn-register-passkey');\n\t\t\tif (!btn) return;\n\t\t\t\n\t\t\tbtn.addEventListener('click', async () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst username = document.querySelector('[name=\"username\"]')?.value;\n\t\t\t\t\tconst displayName = document.querySelector('[name=\"display-name\"]')?.value || username;\n\t\t\t\t\t\n\t\t\t\t\tif (!username) {\n\t\t\t\t\t\talert('Please enter a username');\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t// Get challenge from server (in production)\n\t\t\t\t\tconst challenge = new Uint8Array(32);\n\t\t\t\t\tcrypto.getRandomValues(challenge);\n\t\t\t\t\t\n\t\t\t\t\tconst createOptions = {\n\t\t\t\t\t\tpublicKey: {\n\t\t\t\t\t\t\tchallenge: challenge,\n\t\t\t\t\t\t\trp: {\n\t\t\t\t\t\t\t\tname: \"Sonr\",\n\t\t\t\t\t\t\t\tid: window.location.hostname\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tuser: {\n\t\t\t\t\t\t\t\tid: new Uint8Array(16),\n\t\t\t\t\t\t\t\tname: username,\n\t\t\t\t\t\t\t\tdisplayName: displayName\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tpubKeyCredParams: [\n\t\t\t\t\t\t\t\t{ type: \"public-key\", alg: -7 }, // ES256\n\t\t\t\t\t\t\t\t{ type: \"public-key\", alg: -257 } // RS256\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tauthenticatorSelection: {\n\t\t\t\t\t\t\t\tauthenticatorAttachment: \"platform\",\n\t\t\t\t\t\t\t\tuserVerification: \"required\",\n\t\t\t\t\t\t\t\tresidentKey: \"required\"\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\ttimeout: 60000\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t\t\n\t\t\t\t\tconst credential = await navigator.credentials.create(createOptions);\n\t\t\t\t\tconsole.log(\"Passkey created:\", credential);\n\t\t\t\t\t\n\t\t\t\t\t// Navigate to success step\n\t\t\t\t\thtmx.ajax('GET', '/register/step/3', {\n\t\t\t\t\t\ttarget: '#step-content',\n\t\t\t\t\t\tswap: 'innerHTML transition:true'\n\t\t\t\t\t});\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error(\"Passkey registration failed:\", error);\n\t\t\t\t\talert(\"Registration failed: \" + error.message);\n\t\t\t\t}\n\t\t\t});\n\t\t})();\n\t</script>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// securityKeyRegistrationScript handles WebAuthn security key registration
func securityKeyRegistrationScript() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var24 := templ.GetChildren(ctx)
if templ_7745c5c3_Var24 == nil {
templ_7745c5c3_Var24 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<script>\n\t\t(function() {\n\t\t\tconst btn = document.getElementById('btn-register-security-key');\n\t\t\tif (!btn) return;\n\t\t\t\n\t\t\tbtn.addEventListener('click', async () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst username = document.querySelector('[name=\"username\"]')?.value;\n\t\t\t\t\t\n\t\t\t\t\tif (!username) {\n\t\t\t\t\t\talert('Please enter a username');\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tconst challenge = new Uint8Array(32);\n\t\t\t\t\tcrypto.getRandomValues(challenge);\n\t\t\t\t\t\n\t\t\t\t\tconst createOptions = {\n\t\t\t\t\t\tpublicKey: {\n\t\t\t\t\t\t\tchallenge: challenge,\n\t\t\t\t\t\t\trp: {\n\t\t\t\t\t\t\t\tname: \"Sonr\",\n\t\t\t\t\t\t\t\tid: window.location.hostname\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tuser: {\n\t\t\t\t\t\t\t\tid: new Uint8Array(16),\n\t\t\t\t\t\t\t\tname: username,\n\t\t\t\t\t\t\t\tdisplayName: username\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tpubKeyCredParams: [\n\t\t\t\t\t\t\t\t{ type: \"public-key\", alg: -7 },\n\t\t\t\t\t\t\t\t{ type: \"public-key\", alg: -257 }\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tauthenticatorSelection: {\n\t\t\t\t\t\t\t\tauthenticatorAttachment: \"cross-platform\",\n\t\t\t\t\t\t\t\tuserVerification: \"preferred\"\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\ttimeout: 60000\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t\t\n\t\t\t\t\tconst credential = await navigator.credentials.create(createOptions);\n\t\t\t\t\tconsole.log(\"Security key registered:\", credential);\n\t\t\t\t\t\n\t\t\t\t\t// Navigate to success step\n\t\t\t\t\thtmx.ajax('GET', '/register/step/3', {\n\t\t\t\t\t\ttarget: '#step-content',\n\t\t\t\t\t\tswap: 'innerHTML transition:true'\n\t\t\t\t\t});\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error(\"Security key registration failed:\", error);\n\t\t\t\t\talert(\"Registration failed: \" + error.message);\n\t\t\t\t}\n\t\t\t});\n\t\t})();\n\t</script>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// step3Scripts handles the confirmation checkbox and finish button
func step3Scripts() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var25 := templ.GetChildren(ctx)
if templ_7745c5c3_Var25 == nil {
templ_7745c5c3_Var25 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<script>\n\t\t(function() {\n\t\t\tconst confirmCheckbox = document.getElementById('confirm-saved');\n\t\t\tconst finishBtn = document.getElementById('btn-finish');\n\t\t\t\n\t\t\tif (confirmCheckbox && finishBtn) {\n\t\t\t\tconfirmCheckbox.addEventListener('wa-change', (e) => {\n\t\t\t\t\tif (e.target.checked) {\n\t\t\t\t\t\tfinishBtn.removeAttribute('disabled');\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfinishBtn.setAttribute('disabled', '');\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t})();\n\t</script>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate