feat(views): add templated login page
This commit is contained in:
512
views/login.templ
Normal file
512
views/login.templ
Normal file
@@ -0,0 +1,512 @@
|
||||
package views
|
||||
|
||||
import "nebula/layouts"
|
||||
|
||||
type LoginState struct {
|
||||
Step string
|
||||
Method string
|
||||
Error string
|
||||
}
|
||||
|
||||
templ LoginPage(state LoginState) {
|
||||
@layouts.CenteredCard("Sign In - Sonr") {
|
||||
<div id="step-content" class="step-content">
|
||||
@LoginStepContent(state)
|
||||
</div>
|
||||
<div id="htmx-indicator" class="htmx-indicator">
|
||||
<wa-spinner></wa-spinner>
|
||||
</div>
|
||||
<footer slot="footer">
|
||||
@LoginFooter()
|
||||
</footer>
|
||||
}
|
||||
}
|
||||
|
||||
templ LoginStepContent(state LoginState) {
|
||||
switch state.Step {
|
||||
case "qr":
|
||||
@LoginQRStep()
|
||||
case "recovery":
|
||||
@LoginRecoveryStep()
|
||||
case "success":
|
||||
@LoginSuccessStep()
|
||||
default:
|
||||
@LoginStep1()
|
||||
}
|
||||
}
|
||||
|
||||
templ LoginStepWithOOB(state LoginState) {
|
||||
@LoginStepContent(state)
|
||||
}
|
||||
|
||||
templ LoginStep1() {
|
||||
@loginStyles()
|
||||
<div class="wa-stack wa-gap-l">
|
||||
<div class="wa-stack wa-gap-2xs">
|
||||
<h2 class="wa-heading-l">Welcome Back</h2>
|
||||
<p class="wa-caption-m" style="color: var(--wa-color-neutral-600);">How would you like to sign in?</p>
|
||||
</div>
|
||||
<wa-input
|
||||
name="username"
|
||||
type="text"
|
||||
label="Username"
|
||||
placeholder="Enter your username"
|
||||
id="login-username"
|
||||
autocomplete="username webauthn"
|
||||
>
|
||||
<wa-icon slot="end" variant="regular" name="user"></wa-icon>
|
||||
</wa-input>
|
||||
<div class="wa-stack wa-gap-s" id="auth-methods">
|
||||
@AuthMethodCard("passkey", "fingerprint", "Face or Fingerprint", "Use your face, fingerprint, or device PIN", false)
|
||||
@AuthMethodCard("security-key", "key", "Hardware Key", "Use a physical security key", false)
|
||||
@AuthMethodCard("qr-code", "qrcode", "Use Another Device", "Scan a QR code with your phone", false)
|
||||
</div>
|
||||
<wa-divider></wa-divider>
|
||||
<div class="wa-cluster wa-justify-content-between wa-gap-s">
|
||||
<wa-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
hx-get="/login/step/recovery"
|
||||
hx-target="#step-content"
|
||||
hx-swap="innerHTML transition:true"
|
||||
hx-indicator="#htmx-indicator"
|
||||
>
|
||||
Can't sign in?
|
||||
</wa-button>
|
||||
<wa-button variant="brand" id="btn-signin" disabled>
|
||||
Sign In
|
||||
<wa-icon slot="end" variant="regular" name="arrow-right"></wa-icon>
|
||||
</wa-button>
|
||||
</div>
|
||||
</div>
|
||||
@loginStep1Scripts()
|
||||
}
|
||||
|
||||
templ AuthMethodCard(method string, icon string, title string, description string, disabled bool) {
|
||||
<div
|
||||
class={ "auth-method-card", templ.KV("disabled", disabled) }
|
||||
data-method={ method }
|
||||
id={ "method-" + method }
|
||||
>
|
||||
<wa-icon family="duotone" name={ icon }></wa-icon>
|
||||
<div class="auth-method-info">
|
||||
<h4 class="wa-label-m">{ title }</h4>
|
||||
<p class="wa-caption-s">{ description }</p>
|
||||
</div>
|
||||
if method != "qr-code" {
|
||||
<wa-spinner style="--size: 1.25rem; display: none;" id={ method + "-spinner" }></wa-spinner>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
templ LoginQRStep() {
|
||||
@loginStyles()
|
||||
<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</p>
|
||||
</div>
|
||||
<wa-qr-code
|
||||
value="https://sonr.id/auth?session=xyz789abc123"
|
||||
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/a/xyz789abc123"
|
||||
disabled
|
||||
style="width: 100%;"
|
||||
>
|
||||
<wa-copy-button slot="end" value="sonr.id/a/xyz789abc123"></wa-copy-button>
|
||||
</wa-input>
|
||||
<wa-divider></wa-divider>
|
||||
<div
|
||||
class="wa-stack wa-gap-s wa-align-items-center"
|
||||
style="width: 100%;"
|
||||
id="qr-status"
|
||||
hx-get="/login/qr-status"
|
||||
hx-trigger="every 2s"
|
||||
hx-target="this"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
<wa-spinner></wa-spinner>
|
||||
<p class="wa-caption-s" style="color: var(--wa-color-neutral-500);">Waiting for your phone...</p>
|
||||
</div>
|
||||
<wa-button
|
||||
appearance="outlined"
|
||||
variant="neutral"
|
||||
style="width: 100%;"
|
||||
hx-get="/login/step/1"
|
||||
hx-target="#step-content"
|
||||
hx-swap="innerHTML transition:true"
|
||||
hx-indicator="#htmx-indicator"
|
||||
>
|
||||
Back
|
||||
</wa-button>
|
||||
</div>
|
||||
}
|
||||
|
||||
templ LoginRecoveryStep() {
|
||||
@loginStyles()
|
||||
<div class="wa-stack wa-gap-l">
|
||||
<div class="wa-stack wa-gap-xs">
|
||||
<h2 class="wa-heading-l">Recover Your Account</h2>
|
||||
</div>
|
||||
<wa-radio-group
|
||||
label="How would you like to recover?"
|
||||
orientation="horizontal"
|
||||
name="recovery-method"
|
||||
value="recovery-key"
|
||||
id="recovery-method-group"
|
||||
>
|
||||
<wa-radio appearance="button" value="recovery-key">Backup Code</wa-radio>
|
||||
<wa-radio appearance="button" value="qr-code">Phone</wa-radio>
|
||||
</wa-radio-group>
|
||||
<div id="recovery-key-form">
|
||||
<div class="wa-stack wa-gap-m">
|
||||
<p class="wa-caption-s" style="color: var(--wa-color-neutral-600);">
|
||||
Enter the backup code you saved when you created your account.
|
||||
</p>
|
||||
<wa-input
|
||||
name="recovery-key"
|
||||
type="text"
|
||||
label="Backup Code"
|
||||
placeholder="rk_sec_..."
|
||||
id="recovery-key-input"
|
||||
>
|
||||
<wa-icon slot="end" variant="regular" name="key"></wa-icon>
|
||||
</wa-input>
|
||||
</div>
|
||||
</div>
|
||||
<div id="recovery-qr-form" style="display: none;">
|
||||
<div class="wa-stack wa-gap-m wa-align-items-center">
|
||||
<p class="wa-caption-s" style="color: var(--wa-color-neutral-600);">
|
||||
Scan this code with your phone, then enter the code shown.
|
||||
</p>
|
||||
<wa-qr-code
|
||||
value="https://sonr.id/recover?token=rec123xyz"
|
||||
label="Scan with your phone"
|
||||
></wa-qr-code>
|
||||
<wa-input
|
||||
type="text"
|
||||
placeholder="000000"
|
||||
maxlength="6"
|
||||
id="recovery-code"
|
||||
name="recovery-code"
|
||||
class="verification-code-input"
|
||||
></wa-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wa-cluster wa-justify-content-end wa-gap-s">
|
||||
<wa-button
|
||||
appearance="outlined"
|
||||
variant="neutral"
|
||||
hx-get="/login/step/1"
|
||||
hx-target="#step-content"
|
||||
hx-swap="innerHTML transition:true"
|
||||
hx-indicator="#htmx-indicator"
|
||||
>
|
||||
Back
|
||||
</wa-button>
|
||||
<wa-button variant="brand" id="btn-recover">
|
||||
Recover
|
||||
</wa-button>
|
||||
</div>
|
||||
</div>
|
||||
@recoveryScripts()
|
||||
}
|
||||
|
||||
templ LoginSuccessStep() {
|
||||
@loginStyles()
|
||||
<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 In</h2>
|
||||
<p class="wa-caption-m" style="color: var(--wa-color-neutral-600);">
|
||||
Signed in successfully.
|
||||
</p>
|
||||
</div>
|
||||
<wa-button variant="brand" 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>
|
||||
}
|
||||
|
||||
templ QRStatusWaiting() {
|
||||
<wa-spinner></wa-spinner>
|
||||
<p class="wa-caption-s" style="color: var(--wa-color-neutral-500);">Waiting for your phone...</p>
|
||||
}
|
||||
|
||||
templ QRStatusSuccess() {
|
||||
<wa-icon name="circle-check" style="font-size: 2rem; color: var(--wa-color-success);"></wa-icon>
|
||||
<p class="wa-caption-s" style="color: var(--wa-color-success);">Phone connected!</p>
|
||||
<script>
|
||||
setTimeout(() => {
|
||||
htmx.ajax('GET', '/login/step/success', {target: '#step-content', swap: 'innerHTML transition:true'});
|
||||
}, 500);
|
||||
</script>
|
||||
}
|
||||
|
||||
templ LoginFooter() {
|
||||
<div class="wa-cluster wa-justify-content-center wa-gap-m">
|
||||
<span class="wa-caption-s" style="color: var(--wa-color-neutral-500);">Don't have an account?</span>
|
||||
<wa-button appearance="plain" size="small" onclick="window.location.href='/register'">
|
||||
Create Account
|
||||
</wa-button>
|
||||
</div>
|
||||
}
|
||||
|
||||
templ loginStyles() {
|
||||
<style>
|
||||
.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 {
|
||||
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;
|
||||
}
|
||||
.auth-method-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--wa-space-m);
|
||||
padding: var(--wa-space-m);
|
||||
border-radius: var(--wa-radius-m);
|
||||
background: var(--wa-color-surface-alt);
|
||||
cursor: pointer;
|
||||
border: 2px solid transparent;
|
||||
transition: border-color 0.2s, background 0.2s;
|
||||
}
|
||||
.auth-method-card:hover:not(.disabled) {
|
||||
background: var(--wa-color-surface-alt-hover, var(--wa-color-neutral-100));
|
||||
}
|
||||
.auth-method-card.selected {
|
||||
border-color: var(--wa-color-primary);
|
||||
}
|
||||
.auth-method-card.disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.auth-method-card wa-icon {
|
||||
font-size: 2rem;
|
||||
color: var(--wa-color-primary);
|
||||
}
|
||||
.auth-method-info {
|
||||
flex: 1;
|
||||
}
|
||||
.auth-method-info h4 {
|
||||
margin: 0 0 var(--wa-space-2xs) 0;
|
||||
}
|
||||
.auth-method-info p {
|
||||
margin: 0;
|
||||
color: var(--wa-color-neutral-600);
|
||||
}
|
||||
.success-icon {
|
||||
font-size: 4rem;
|
||||
color: var(--wa-color-success);
|
||||
}
|
||||
.verification-code-input {
|
||||
text-align: center;
|
||||
font-size: 1.5rem;
|
||||
letter-spacing: 0.3em;
|
||||
max-width: 180px;
|
||||
}
|
||||
@media (max-width: 400px) {
|
||||
.auth-method-card {
|
||||
padding: var(--wa-space-s);
|
||||
gap: var(--wa-space-s);
|
||||
}
|
||||
.auth-method-card wa-icon {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
.verification-code-input {
|
||||
font-size: 1.25rem;
|
||||
max-width: 160px;
|
||||
}
|
||||
.success-icon {
|
||||
font-size: 3rem;
|
||||
}
|
||||
}
|
||||
@media (max-height: 600px) {
|
||||
.auth-method-card {
|
||||
padding: var(--wa-space-s);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
}
|
||||
|
||||
templ loginStep1Scripts() {
|
||||
<script>
|
||||
(function() {
|
||||
let selectedMethod = null;
|
||||
const btnSignin = document.getElementById('btn-signin');
|
||||
|
||||
async function checkCapabilities() {
|
||||
if (!window.PublicKeyCredential) {
|
||||
document.getElementById('method-passkey')?.classList.add('disabled');
|
||||
document.getElementById('method-security-key')?.classList.add('disabled');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const hasPlatform = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
|
||||
if (!hasPlatform) {
|
||||
document.getElementById('method-passkey')?.classList.add('disabled');
|
||||
}
|
||||
} catch (e) {
|
||||
document.getElementById('method-passkey')?.classList.add('disabled');
|
||||
}
|
||||
|
||||
try {
|
||||
if (PublicKeyCredential.isConditionalMediationAvailable) {
|
||||
const hasConditional = await PublicKeyCredential.isConditionalMediationAvailable();
|
||||
if (hasConditional) {
|
||||
startConditionalUI();
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
async function startConditionalUI() {
|
||||
try {
|
||||
const challenge = new Uint8Array(32);
|
||||
crypto.getRandomValues(challenge);
|
||||
|
||||
const credential = await navigator.credentials.get({
|
||||
publicKey: {
|
||||
challenge: challenge,
|
||||
rpId: window.location.hostname,
|
||||
userVerification: "preferred",
|
||||
timeout: 300000
|
||||
},
|
||||
mediation: "conditional"
|
||||
});
|
||||
|
||||
if (credential) {
|
||||
console.log("Conditional UI auth successful:", credential);
|
||||
htmx.ajax('GET', '/login/step/success', {target: '#step-content', swap: 'innerHTML transition:true'});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Conditional UI not used:", error.message);
|
||||
}
|
||||
}
|
||||
|
||||
function selectMethod(method) {
|
||||
const card = document.getElementById('method-' + method);
|
||||
if (!card || card.classList.contains('disabled')) return;
|
||||
|
||||
document.querySelectorAll('.auth-method-card').forEach(c => c.classList.remove('selected'));
|
||||
card.classList.add('selected');
|
||||
selectedMethod = method;
|
||||
btnSignin?.removeAttribute('disabled');
|
||||
}
|
||||
|
||||
async function signIn() {
|
||||
if (!selectedMethod) return;
|
||||
|
||||
if (selectedMethod === 'qr-code') {
|
||||
htmx.ajax('GET', '/login/step/qr', {target: '#step-content', swap: 'innerHTML transition:true'});
|
||||
return;
|
||||
}
|
||||
|
||||
const spinner = document.getElementById(selectedMethod + '-spinner');
|
||||
if (spinner) spinner.style.display = 'block';
|
||||
|
||||
try {
|
||||
const challenge = new Uint8Array(32);
|
||||
crypto.getRandomValues(challenge);
|
||||
|
||||
const getOptions = {
|
||||
publicKey: {
|
||||
challenge: challenge,
|
||||
rpId: window.location.hostname,
|
||||
userVerification: selectedMethod === 'passkey' ? "required" : "preferred",
|
||||
timeout: 60000
|
||||
}
|
||||
};
|
||||
|
||||
if (selectedMethod === 'security-key') {
|
||||
getOptions.publicKey.allowCredentials = [];
|
||||
}
|
||||
|
||||
const credential = await navigator.credentials.get(getOptions);
|
||||
console.log("Auth successful:", credential);
|
||||
htmx.ajax('GET', '/login/step/success', {target: '#step-content', swap: 'innerHTML transition:true'});
|
||||
} catch (error) {
|
||||
console.error("Auth failed:", error);
|
||||
alert("Authentication failed: " + error.message);
|
||||
} finally {
|
||||
if (spinner) spinner.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
document.querySelectorAll('.auth-method-card').forEach(card => {
|
||||
card.addEventListener('click', () => selectMethod(card.dataset.method));
|
||||
});
|
||||
|
||||
btnSignin?.addEventListener('click', signIn);
|
||||
checkCapabilities();
|
||||
})();
|
||||
</script>
|
||||
}
|
||||
|
||||
templ recoveryScripts() {
|
||||
<script>
|
||||
(function() {
|
||||
const methodGroup = document.getElementById('recovery-method-group');
|
||||
const keyForm = document.getElementById('recovery-key-form');
|
||||
const qrForm = document.getElementById('recovery-qr-form');
|
||||
const btnRecover = document.getElementById('btn-recover');
|
||||
|
||||
methodGroup?.addEventListener('wa-change', (e) => {
|
||||
if (e.target.value === 'recovery-key') {
|
||||
keyForm.style.display = 'block';
|
||||
qrForm.style.display = 'none';
|
||||
} else {
|
||||
keyForm.style.display = 'none';
|
||||
qrForm.style.display = 'block';
|
||||
}
|
||||
});
|
||||
|
||||
btnRecover?.addEventListener('click', () => {
|
||||
const method = methodGroup?.value;
|
||||
if (method === 'recovery-key') {
|
||||
const key = document.getElementById('recovery-key-input')?.value;
|
||||
if (key?.startsWith('rk_')) {
|
||||
console.log("Recovery key validated");
|
||||
htmx.ajax('GET', '/login/step/success', {target: '#step-content', swap: 'innerHTML transition:true'});
|
||||
} else {
|
||||
alert("Please enter a valid recovery key (starts with rk_)");
|
||||
}
|
||||
} else {
|
||||
const code = document.getElementById('recovery-code')?.value;
|
||||
if (code?.length === 6) {
|
||||
console.log("Recovery code verified");
|
||||
htmx.ajax('GET', '/login/step/success', {target: '#step-content', swap: 'innerHTML transition:true'});
|
||||
} else {
|
||||
alert("Please enter a valid 6-digit code");
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
}
|
||||
623
views/login_templ.go
Normal file
623
views/login_templ.go
Normal file
@@ -0,0 +1,623 @@
|
||||
// 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/layouts"
|
||||
|
||||
type LoginState struct {
|
||||
Step string
|
||||
Method string
|
||||
Error string
|
||||
}
|
||||
|
||||
func LoginPage(state LoginState) 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 id=\"step-content\" class=\"step-content\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = LoginStepContent(state).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=\"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 = LoginFooter().Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</footer>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
templ_7745c5c3_Err = layouts.CenteredCard("Sign In - Sonr").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func LoginStepContent(state LoginState) 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 "qr":
|
||||
templ_7745c5c3_Err = LoginQRStep().Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
case "recovery":
|
||||
templ_7745c5c3_Err = LoginRecoveryStep().Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
case "success":
|
||||
templ_7745c5c3_Err = LoginSuccessStep().Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
default:
|
||||
templ_7745c5c3_Err = LoginStep1().Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func LoginStepWithOOB(state LoginState) 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 = LoginStepContent(state).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func LoginStep1() 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 = loginStyles().Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"wa-stack wa-gap-l\"><div class=\"wa-stack wa-gap-2xs\"><h2 class=\"wa-heading-l\">Welcome Back</h2><p class=\"wa-caption-m\" style=\"color: var(--wa-color-neutral-600);\">How would you like to sign in?</p></div><wa-input name=\"username\" type=\"text\" label=\"Username\" placeholder=\"Enter your username\" id=\"login-username\" autocomplete=\"username webauthn\"><wa-icon slot=\"end\" variant=\"regular\" name=\"user\"></wa-icon></wa-input><div class=\"wa-stack wa-gap-s\" id=\"auth-methods\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = AuthMethodCard("passkey", "fingerprint", "Face or Fingerprint", "Use your face, fingerprint, or device PIN", false).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = AuthMethodCard("security-key", "key", "Hardware Key", "Use a physical security key", false).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = AuthMethodCard("qr-code", "qrcode", "Use Another Device", "Scan a QR code with your phone", false).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</div><wa-divider></wa-divider><div class=\"wa-cluster wa-justify-content-between wa-gap-s\"><wa-button appearance=\"plain\" size=\"small\" hx-get=\"/login/step/recovery\" hx-target=\"#step-content\" hx-swap=\"innerHTML transition:true\" hx-indicator=\"#htmx-indicator\">Can't sign in?</wa-button> <wa-button variant=\"brand\" id=\"btn-signin\" disabled>Sign In <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 = loginStep1Scripts().Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func AuthMethodCard(method string, icon string, title string, description string, disabled 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{"auth-method-card", templ.KV("disabled", disabled)}
|
||||
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, 6, "<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/login.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, 7, "\" data-method=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(method)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/login.templ`, Line: 88, Col: 22}
|
||||
}
|
||||
_, 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, 8, "\" id=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var10 string
|
||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs("method-" + method)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/login.templ`, Line: 89, Col: 25}
|
||||
}
|
||||
_, 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, 9, "\"><wa-icon family=\"duotone\" name=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var11 string
|
||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(icon)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/login.templ`, Line: 91, Col: 39}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\"></wa-icon><div class=\"auth-method-info\"><h4 class=\"wa-label-m\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var12 string
|
||||
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(title)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/login.templ`, Line: 93, Col: 33}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</h4><p class=\"wa-caption-s\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var13 string
|
||||
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(description)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/login.templ`, Line: 94, 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, 12, "</p></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if method != "qr-code" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<wa-spinner style=\"--size: 1.25rem; display: none;\" id=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var14 string
|
||||
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(method + "-spinner")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/login.templ`, Line: 97, Col: 79}
|
||||
}
|
||||
_, 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, 14, "\"></wa-spinner>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func LoginQRStep() 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_Var15 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var15 == nil {
|
||||
templ_7745c5c3_Var15 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = loginStyles().Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<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</p></div><wa-qr-code value=\"https://sonr.id/auth?session=xyz789abc123\" 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/a/xyz789abc123\" disabled style=\"width: 100%;\"><wa-copy-button slot=\"end\" value=\"sonr.id/a/xyz789abc123\"></wa-copy-button></wa-input> <wa-divider></wa-divider><div class=\"wa-stack wa-gap-s wa-align-items-center\" style=\"width: 100%;\" id=\"qr-status\" hx-get=\"/login/qr-status\" hx-trigger=\"every 2s\" hx-target=\"this\" hx-swap=\"innerHTML\"><wa-spinner></wa-spinner><p class=\"wa-caption-s\" style=\"color: var(--wa-color-neutral-500);\">Waiting for your phone...</p></div><wa-button appearance=\"outlined\" variant=\"neutral\" style=\"width: 100%;\" hx-get=\"/login/step/1\" hx-target=\"#step-content\" hx-swap=\"innerHTML transition:true\" hx-indicator=\"#htmx-indicator\">Back</wa-button></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func LoginRecoveryStep() 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 = loginStyles().Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<div class=\"wa-stack wa-gap-l\"><div class=\"wa-stack wa-gap-xs\"><h2 class=\"wa-heading-l\">Recover Your Account</h2></div><wa-radio-group label=\"How would you like to recover?\" orientation=\"horizontal\" name=\"recovery-method\" value=\"recovery-key\" id=\"recovery-method-group\"><wa-radio appearance=\"button\" value=\"recovery-key\">Backup Code</wa-radio> <wa-radio appearance=\"button\" value=\"qr-code\">Phone</wa-radio></wa-radio-group><div id=\"recovery-key-form\"><div class=\"wa-stack wa-gap-m\"><p class=\"wa-caption-s\" style=\"color: var(--wa-color-neutral-600);\">Enter the backup code you saved when you created your account.</p><wa-input name=\"recovery-key\" type=\"text\" label=\"Backup Code\" placeholder=\"rk_sec_...\" id=\"recovery-key-input\"><wa-icon slot=\"end\" variant=\"regular\" name=\"key\"></wa-icon></wa-input></div></div><div id=\"recovery-qr-form\" style=\"display: none;\"><div class=\"wa-stack wa-gap-m wa-align-items-center\"><p class=\"wa-caption-s\" style=\"color: var(--wa-color-neutral-600);\">Scan this code with your phone, then enter the code shown.</p><wa-qr-code value=\"https://sonr.id/recover?token=rec123xyz\" label=\"Scan with your phone\"></wa-qr-code> <wa-input type=\"text\" placeholder=\"000000\" maxlength=\"6\" id=\"recovery-code\" name=\"recovery-code\" class=\"verification-code-input\"></wa-input></div></div><div class=\"wa-cluster wa-justify-content-end wa-gap-s\"><wa-button appearance=\"outlined\" variant=\"neutral\" hx-get=\"/login/step/1\" hx-target=\"#step-content\" hx-swap=\"innerHTML transition:true\" hx-indicator=\"#htmx-indicator\">Back</wa-button> <wa-button variant=\"brand\" id=\"btn-recover\">Recover</wa-button></div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = recoveryScripts().Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func LoginSuccessStep() 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 = loginStyles().Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<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 In</h2><p class=\"wa-caption-m\" style=\"color: var(--wa-color-neutral-600);\">Signed in successfully.</p></div><wa-button variant=\"brand\" 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
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func QRStatusWaiting() 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 = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<wa-spinner></wa-spinner><p class=\"wa-caption-s\" style=\"color: var(--wa-color-neutral-500);\">Waiting for your phone...</p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func QRStatusSuccess() 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 = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<wa-icon name=\"circle-check\" style=\"font-size: 2rem; color: var(--wa-color-success);\"></wa-icon><p class=\"wa-caption-s\" style=\"color: var(--wa-color-success);\">Phone connected!</p><script>\n\t\tsetTimeout(() => {\n\t\t\thtmx.ajax('GET', '/login/step/success', {target: '#step-content', swap: 'innerHTML transition:true'});\n\t\t}, 500);\n\t</script>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func LoginFooter() 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, 21, "<div class=\"wa-cluster wa-justify-content-center wa-gap-m\"><span class=\"wa-caption-s\" style=\"color: var(--wa-color-neutral-500);\">Don't have an account?</span> <wa-button appearance=\"plain\" size=\"small\" onclick=\"window.location.href='/register'\">Create Account</wa-button></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func loginStyles() 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, 22, "<style>\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 {\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.auth-method-card {\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tgap: var(--wa-space-m);\n\t\t\tpadding: 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\tcursor: pointer;\n\t\t\tborder: 2px solid transparent;\n\t\t\ttransition: border-color 0.2s, background 0.2s;\n\t\t}\n\t\t.auth-method-card:hover:not(.disabled) {\n\t\t\tbackground: var(--wa-color-surface-alt-hover, var(--wa-color-neutral-100));\n\t\t}\n\t\t.auth-method-card.selected {\n\t\t\tborder-color: var(--wa-color-primary);\n\t\t}\n\t\t.auth-method-card.disabled {\n\t\t\topacity: 0.5;\n\t\t\tcursor: not-allowed;\n\t\t}\n\t\t.auth-method-card wa-icon {\n\t\t\tfont-size: 2rem;\n\t\t\tcolor: var(--wa-color-primary);\n\t\t}\n\t\t.auth-method-info {\n\t\t\tflex: 1;\n\t\t}\n\t\t.auth-method-info h4 {\n\t\t\tmargin: 0 0 var(--wa-space-2xs) 0;\n\t\t}\n\t\t.auth-method-info p {\n\t\t\tmargin: 0;\n\t\t\tcolor: var(--wa-color-neutral-600);\n\t\t}\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 {\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@media (max-width: 400px) {\n\t\t\t.auth-method-card {\n\t\t\t\tpadding: var(--wa-space-s);\n\t\t\t\tgap: var(--wa-space-s);\n\t\t\t}\n\t\t\t.auth-method-card wa-icon {\n\t\t\t\tfont-size: 1.5rem;\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.auth-method-card {\n\t\t\t\tpadding: 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
|
||||
})
|
||||
}
|
||||
|
||||
func loginStep1Scripts() 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, 23, "<script>\n\t\t(function() {\n\t\t\tlet selectedMethod = null;\n\t\t\tconst btnSignin = document.getElementById('btn-signin');\n\t\t\t\n\t\t\tasync function checkCapabilities() {\n\t\t\t\tif (!window.PublicKeyCredential) {\n\t\t\t\t\tdocument.getElementById('method-passkey')?.classList.add('disabled');\n\t\t\t\t\tdocument.getElementById('method-security-key')?.classList.add('disabled');\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\ttry {\n\t\t\t\t\tconst hasPlatform = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();\n\t\t\t\t\tif (!hasPlatform) {\n\t\t\t\t\t\tdocument.getElementById('method-passkey')?.classList.add('disabled');\n\t\t\t\t\t}\n\t\t\t\t} catch (e) {\n\t\t\t\t\tdocument.getElementById('method-passkey')?.classList.add('disabled');\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\ttry {\n\t\t\t\t\tif (PublicKeyCredential.isConditionalMediationAvailable) {\n\t\t\t\t\t\tconst hasConditional = await PublicKeyCredential.isConditionalMediationAvailable();\n\t\t\t\t\t\tif (hasConditional) {\n\t\t\t\t\t\t\tstartConditionalUI();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch (e) {}\n\t\t\t}\n\t\t\t\n\t\t\tasync function startConditionalUI() {\n\t\t\t\ttry {\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 credential = await navigator.credentials.get({\n\t\t\t\t\t\tpublicKey: {\n\t\t\t\t\t\t\tchallenge: challenge,\n\t\t\t\t\t\t\trpId: window.location.hostname,\n\t\t\t\t\t\t\tuserVerification: \"preferred\",\n\t\t\t\t\t\t\ttimeout: 300000\n\t\t\t\t\t\t},\n\t\t\t\t\t\tmediation: \"conditional\"\n\t\t\t\t\t});\n\t\t\t\t\t\n\t\t\t\t\tif (credential) {\n\t\t\t\t\t\tconsole.log(\"Conditional UI auth successful:\", credential);\n\t\t\t\t\t\thtmx.ajax('GET', '/login/step/success', {target: '#step-content', swap: 'innerHTML transition:true'});\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.log(\"Conditional UI not used:\", error.message);\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\tfunction selectMethod(method) {\n\t\t\t\tconst card = document.getElementById('method-' + method);\n\t\t\t\tif (!card || card.classList.contains('disabled')) return;\n\t\t\t\t\n\t\t\t\tdocument.querySelectorAll('.auth-method-card').forEach(c => c.classList.remove('selected'));\n\t\t\t\tcard.classList.add('selected');\n\t\t\t\tselectedMethod = method;\n\t\t\t\tbtnSignin?.removeAttribute('disabled');\n\t\t\t}\n\t\t\t\n\t\t\tasync function signIn() {\n\t\t\t\tif (!selectedMethod) return;\n\t\t\t\t\n\t\t\t\tif (selectedMethod === 'qr-code') {\n\t\t\t\t\thtmx.ajax('GET', '/login/step/qr', {target: '#step-content', swap: 'innerHTML transition:true'});\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tconst spinner = document.getElementById(selectedMethod + '-spinner');\n\t\t\t\tif (spinner) spinner.style.display = 'block';\n\t\t\t\t\n\t\t\t\ttry {\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 getOptions = {\n\t\t\t\t\t\tpublicKey: {\n\t\t\t\t\t\t\tchallenge: challenge,\n\t\t\t\t\t\t\trpId: window.location.hostname,\n\t\t\t\t\t\t\tuserVerification: selectedMethod === 'passkey' ? \"required\" : \"preferred\",\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\tif (selectedMethod === 'security-key') {\n\t\t\t\t\t\tgetOptions.publicKey.allowCredentials = [];\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tconst credential = await navigator.credentials.get(getOptions);\n\t\t\t\t\tconsole.log(\"Auth successful:\", credential);\n\t\t\t\t\thtmx.ajax('GET', '/login/step/success', {target: '#step-content', swap: 'innerHTML transition:true'});\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error(\"Auth failed:\", error);\n\t\t\t\t\talert(\"Authentication failed: \" + error.message);\n\t\t\t\t} finally {\n\t\t\t\t\tif (spinner) spinner.style.display = 'none';\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\tdocument.querySelectorAll('.auth-method-card').forEach(card => {\n\t\t\t\tcard.addEventListener('click', () => selectMethod(card.dataset.method));\n\t\t\t});\n\t\t\t\n\t\t\tbtnSignin?.addEventListener('click', signIn);\n\t\t\tcheckCapabilities();\n\t\t})();\n\t</script>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func recoveryScripts() 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, 24, "<script>\n\t\t(function() {\n\t\t\tconst methodGroup = document.getElementById('recovery-method-group');\n\t\t\tconst keyForm = document.getElementById('recovery-key-form');\n\t\t\tconst qrForm = document.getElementById('recovery-qr-form');\n\t\t\tconst btnRecover = document.getElementById('btn-recover');\n\t\t\t\n\t\t\tmethodGroup?.addEventListener('wa-change', (e) => {\n\t\t\t\tif (e.target.value === 'recovery-key') {\n\t\t\t\t\tkeyForm.style.display = 'block';\n\t\t\t\t\tqrForm.style.display = 'none';\n\t\t\t\t} else {\n\t\t\t\t\tkeyForm.style.display = 'none';\n\t\t\t\t\tqrForm.style.display = 'block';\n\t\t\t\t}\n\t\t\t});\n\t\t\t\n\t\t\tbtnRecover?.addEventListener('click', () => {\n\t\t\t\tconst method = methodGroup?.value;\n\t\t\t\tif (method === 'recovery-key') {\n\t\t\t\t\tconst key = document.getElementById('recovery-key-input')?.value;\n\t\t\t\t\tif (key?.startsWith('rk_')) {\n\t\t\t\t\t\tconsole.log(\"Recovery key validated\");\n\t\t\t\t\t\thtmx.ajax('GET', '/login/step/success', {target: '#step-content', swap: 'innerHTML transition:true'});\n\t\t\t\t\t} else {\n\t\t\t\t\t\talert(\"Please enter a valid recovery key (starts with rk_)\");\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tconst code = document.getElementById('recovery-code')?.value;\n\t\t\t\t\tif (code?.length === 6) {\n\t\t\t\t\t\tconsole.log(\"Recovery code verified\");\n\t\t\t\t\t\thtmx.ajax('GET', '/login/step/success', {target: '#step-content', swap: 'innerHTML transition:true'});\n\t\t\t\t\t} else {\n\t\t\t\t\t\talert(\"Please enter a valid 6-digit code\");\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
|
||||
Reference in New Issue
Block a user