feat: create new file page with improved UI, combine upload and download functionality

This commit is contained in:
2024-09-15 17:24:18 +07:00
parent b54d0f16a5
commit eee047a9b0
22 changed files with 1189 additions and 1137 deletions

View File

@ -7,115 +7,120 @@ import (
templ content(title string, qrcode string, code string, user types.User, msg types.Message) {
@layout.BaseAuth(title){
@layout.Navbar(user)
<main class="container mx-auto px-4 py-12 md:px-6 md:py-16 lg:py-10">
<a
class="inline-flex items-center space-x-2 rounded-md bg-muted px-4 py-2 text-sm font-medium text-muted-foreground transition-colors hover:bg-muted/80 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
href="/user" hx-get="/user" hx-swap="outerHTML" hx-push-url="true" hx-target="#content"
rel="ugc"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="h-4 w-4"
>
<path d="m12 19-7-7 7-7"></path>
<path d="M19 12H5"></path>
</svg>
<span>Back</span>
</a>
<div class="mx-auto max-w-md px-4 py-12 sm:px-6 lg:px-8">
<div class="space-y-6 text-center">
<div class="flex items-center">
<h1 class="text-3xl font-bold">Set up Two-Factor Authentication</h1>
</div>
<p class="text-muted-foreground">Secure your account with time-based one-time passwords (TOTP).</p>
<div class="mt-4 text-left text-muted-foreground">
<p>Here's how to set up the Google Authenticator app:</p>
<ol class="list-decimal pl-6">
<li>Download the Google Authenticator app on your mobile device.</li>
<li>Open the app and tap "Begin Setup".</li>
<li>Select "Scan a barcode" and point your camera at the QR code below.</li>
<li>The app will automatically add your account and display a 6-digit code.</li>
<li>Enter this code on the website to complete the setup.</li>
</ol>
</div>
</div>
<div class="rounded-lg border rounded-lg bg-muted p-6bg-card text-card-foreground shadow-sm mt-5" data-v0-t="card">
<div class="p-6 space-y-6">
switch msg.Code {
case 0:
<div class="flex items-center p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50" role="alert">
<svg class="flex-shrink-0 inline w-4 h-4 me-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z"/>
</svg>
<span class="sr-only">Info</span>
<div>
<span class="font-medium">Error!</span> {msg.Message}
</div>
</div>
case 1:
<div class="flex items-center p-4 mb-4 text-sm text-green-800 rounded-lg bg-green-50" role="alert">
<svg class="flex-shrink-0 inline w-4 h-4 me-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z"/>
</svg>
<span class="sr-only">Info</span>
<div>
<span class="font-medium">Success!</span> {msg.Message}
</div>
</div>
}
<div class="flex items-center justify-center">
<img
src={"data:image/png;base64," + qrcode}
width="200"
height="200"
alt="QR Code"
class="rounded-lg"
style="aspect-ratio: 200 / 200; object-fit: cover;"
/>
</div>
<div class="mt-6 space-y-2">
<p class="font-medium">Backup Code:</p>
<div class="rounded-md bg-background px-4 py-2 text-sm font-mono text-muted-foreground">----|----</div>
<p class="font-medium">TOTP Secret:</p>
<div class="rounded-md bg-background px-4 py-2 text-sm font-mono text-muted-foreground">
{code}
</div>
</div>
<form method="post" action="/user/totp/setup">
<div class="grid gap-2">
<label
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
for="totp">
Totp Code
</label>
<input id="secret" name="secret" value={code} type='hidden' />
<input
class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
id="totp" name="totp" placeholder="Code from authenticator app" />
<div class="flex items-center p-6">
<button class="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2 w-full" type="submit">
Enable TOTP
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</main>
@layout.Footer()
@MainContent(qrcode, code, user, msg)
}
}
templ MainContent(qrcode string, code string, user types.User, msg types.Message) {
@layout.Navbar(user)
<main class="container mx-auto px-4 py-12 md:px-6 md:py-16 lg:py-10">
<a
class="inline-flex items-center space-x-2 rounded-md bg-muted px-4 py-2 text-sm font-medium text-muted-foreground transition-colors hover:bg-muted/80 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
href="/user" hx-get="/user" hx-swap="innerHTML" hx-push-url="true" hx-target="#content"
rel="ugc"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="h-4 w-4"
>
<path d="m12 19-7-7 7-7"></path>
<path d="M19 12H5"></path>
</svg>
<span>Back</span>
</a>
<div class="mx-auto max-w-md px-4 py-12 sm:px-6 lg:px-8">
<div class="space-y-6 text-center">
<div class="flex items-center">
<h1 class="text-3xl font-bold">Set up Two-Factor Authentication</h1>
</div>
<p class="text-muted-foreground">Secure your account with time-based one-time passwords (TOTP).</p>
<div class="mt-4 text-left text-muted-foreground">
<p>Here's how to set up the Google Authenticator app:</p>
<ol class="list-decimal pl-6">
<li>Download the Google Authenticator app on your mobile device.</li>
<li>Open the app and tap "Begin Setup".</li>
<li>Select "Scan a barcode" and point your camera at the QR code below.</li>
<li>The app will automatically add your account and display a 6-digit code.</li>
<li>Enter this code on the website to complete the setup.</li>
</ol>
</div>
</div>
<div class="rounded-lg border rounded-lg bg-muted p-6bg-card text-card-foreground shadow-sm mt-5" data-v0-t="card">
<div class="p-6 space-y-6">
switch msg.Code {
case 0:
<div class="flex items-center p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50" role="alert">
<svg class="flex-shrink-0 inline w-4 h-4 me-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z"/>
</svg>
<span class="sr-only">Info</span>
<div>
<span class="font-medium">Error!</span> {msg.Message}
</div>
</div>
case 1:
<div class="flex items-center p-4 mb-4 text-sm text-green-800 rounded-lg bg-green-50" role="alert">
<svg class="flex-shrink-0 inline w-4 h-4 me-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z"/>
</svg>
<span class="sr-only">Info</span>
<div>
<span class="font-medium">Success!</span> {msg.Message}
</div>
</div>
}
<div class="flex items-center justify-center">
<img
src={"data:image/png;base64," + qrcode}
width="200"
height="200"
alt="QR Code"
class="rounded-lg"
style="aspect-ratio: 200 / 200; object-fit: cover;"
/>
</div>
<div class="mt-6 space-y-2">
<p class="font-medium">Backup Code:</p>
<div class="rounded-md bg-background px-4 py-2 text-sm font-mono text-muted-foreground">----|----</div>
<p class="font-medium">TOTP Secret:</p>
<div class="rounded-md bg-background px-4 py-2 text-sm font-mono text-muted-foreground">
{code}
</div>
</div>
<form method="post" action="/user/totp/setup">
<div class="grid gap-2">
<label
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
for="totp">
Totp Code
</label>
<input id="secret" name="secret" value={code} type='hidden' />
<input
class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
id="totp" name="totp" placeholder="Code from authenticator app" />
<div class="flex items-center p-6">
<button class="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2 w-full" type="submit">
Enable TOTP
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</main>
@layout.Footer()
@layout.MainScript()
}
templ Main(title string, qrcode string, code string, user types.User, msg types.Message) {
@content(title, qrcode, code, user, msg)
}

View File

@ -34,96 +34,7 @@ func content(title string, qrcode string, code string, user types.User, msg type
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
templ_7745c5c3_Err = layout.Navbar(user).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <main class=\"container mx-auto px-4 py-12 md:px-6 md:py-16 lg:py-10\"><a class=\"inline-flex items-center space-x-2 rounded-md bg-muted px-4 py-2 text-sm font-medium text-muted-foreground transition-colors hover:bg-muted/80 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2\" href=\"/user\" hx-get=\"/user\" hx-swap=\"outerHTML\" hx-push-url=\"true\" hx-target=\"#content\" rel=\"ugc\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"h-4 w-4\"><path d=\"m12 19-7-7 7-7\"></path> <path d=\"M19 12H5\"></path></svg> <span>Back</span></a><div class=\"mx-auto max-w-md px-4 py-12 sm:px-6 lg:px-8\"><div class=\"space-y-6 text-center\"><div class=\"flex items-center\"><h1 class=\"text-3xl font-bold\">Set up Two-Factor Authentication</h1></div><p class=\"text-muted-foreground\">Secure your account with time-based one-time passwords (TOTP).</p><div class=\"mt-4 text-left text-muted-foreground\"><p>Here's how to set up the Google Authenticator app:</p><ol class=\"list-decimal pl-6\"><li>Download the Google Authenticator app on your mobile device.</li><li>Open the app and tap \"Begin Setup\".</li><li>Select \"Scan a barcode\" and point your camera at the QR code below.</li><li>The app will automatically add your account and display a 6-digit code.</li><li>Enter this code on the website to complete the setup.</li></ol></div></div><div class=\"rounded-lg border rounded-lg bg-muted p-6bg-card text-card-foreground shadow-sm mt-5\" data-v0-t=\"card\"><div class=\"p-6 space-y-6\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
switch msg.Code {
case 0:
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"flex items-center p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50\" role=\"alert\"><svg class=\"flex-shrink-0 inline w-4 h-4 me-3\" aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 20 20\"><path d=\"M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z\"></path></svg> <span class=\"sr-only\">Info</span><div><span class=\"font-medium\">Error!</span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(msg.Message)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\client\user\totp\setup.templ`, Line: 61, Col: 80}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 1:
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"flex items-center p-4 mb-4 text-sm text-green-800 rounded-lg bg-green-50\" role=\"alert\"><svg class=\"flex-shrink-0 inline w-4 h-4 me-3\" aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 20 20\"><path d=\"M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z\"></path></svg> <span class=\"sr-only\">Info</span><div><span class=\"font-medium\">Success!</span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(msg.Message)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\client\user\totp\setup.templ`, Line: 71, Col: 82}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"flex items-center justify-center\"><img src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs("data:image/png;base64," + qrcode)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\client\user\totp\setup.templ`, Line: 77, Col: 60}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" width=\"200\" height=\"200\" alt=\"QR Code\" class=\"rounded-lg\" style=\"aspect-ratio: 200 / 200; object-fit: cover;\"></div><div class=\"mt-6 space-y-2\"><p class=\"font-medium\">Backup Code:</p><div class=\"rounded-md bg-background px-4 py-2 text-sm font-mono text-muted-foreground\">----|----</div><p class=\"font-medium\">TOTP Secret:</p><div class=\"rounded-md bg-background px-4 py-2 text-sm font-mono text-muted-foreground\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(code)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\client\user\totp\setup.templ`, Line: 90, Col: 27}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div><form method=\"post\" action=\"/user/totp/setup\"><div class=\"grid gap-2\"><label class=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\" for=\"totp\">Totp Code</label> <input id=\"secret\" name=\"secret\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(code)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\client\user\totp\setup.templ`, Line: 100, Col: 68}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" type=\"hidden\"> <input class=\"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50\" id=\"totp\" name=\"totp\" placeholder=\"Code from authenticator app\"><div class=\"flex items-center p-6\"><button class=\"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2 w-full\" type=\"submit\">Enable TOTP</button></div></div></form></div></div></div></main>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = layout.Footer().Render(ctx, templ_7745c5c3_Buffer)
templ_7745c5c3_Err = MainContent(qrcode, code, user, msg).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -143,6 +54,123 @@ func content(title string, qrcode string, code string, user types.User, msg type
})
}
func MainContent(qrcode string, code string, user types.User, msg types.Message) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
if templ_7745c5c3_Var3 == nil {
templ_7745c5c3_Var3 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = layout.Navbar(user).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<main class=\"container mx-auto px-4 py-12 md:px-6 md:py-16 lg:py-10\"><a class=\"inline-flex items-center space-x-2 rounded-md bg-muted px-4 py-2 text-sm font-medium text-muted-foreground transition-colors hover:bg-muted/80 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2\" href=\"/user\" hx-get=\"/user\" hx-swap=\"innerHTML\" hx-push-url=\"true\" hx-target=\"#content\" rel=\"ugc\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"h-4 w-4\"><path d=\"m12 19-7-7 7-7\"></path> <path d=\"M19 12H5\"></path></svg> <span>Back</span></a><div class=\"mx-auto max-w-md px-4 py-12 sm:px-6 lg:px-8\"><div class=\"space-y-6 text-center\"><div class=\"flex items-center\"><h1 class=\"text-3xl font-bold\">Set up Two-Factor Authentication</h1></div><p class=\"text-muted-foreground\">Secure your account with time-based one-time passwords (TOTP).</p><div class=\"mt-4 text-left text-muted-foreground\"><p>Here's how to set up the Google Authenticator app:</p><ol class=\"list-decimal pl-6\"><li>Download the Google Authenticator app on your mobile device.</li><li>Open the app and tap \"Begin Setup\".</li><li>Select \"Scan a barcode\" and point your camera at the QR code below.</li><li>The app will automatically add your account and display a 6-digit code.</li><li>Enter this code on the website to complete the setup.</li></ol></div></div><div class=\"rounded-lg border rounded-lg bg-muted p-6bg-card text-card-foreground shadow-sm mt-5\" data-v0-t=\"card\"><div class=\"p-6 space-y-6\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
switch msg.Code {
case 0:
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"flex items-center p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50\" role=\"alert\"><svg class=\"flex-shrink-0 inline w-4 h-4 me-3\" aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 20 20\"><path d=\"M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z\"></path></svg> <span class=\"sr-only\">Info</span><div><span class=\"font-medium\">Error!</span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(msg.Message)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\client\user\totp\setup.templ`, Line: 66, Col: 76}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 1:
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"flex items-center p-4 mb-4 text-sm text-green-800 rounded-lg bg-green-50\" role=\"alert\"><svg class=\"flex-shrink-0 inline w-4 h-4 me-3\" aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 20 20\"><path d=\"M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z\"></path></svg> <span class=\"sr-only\">Info</span><div><span class=\"font-medium\">Success!</span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(msg.Message)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\client\user\totp\setup.templ`, Line: 76, Col: 78}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"flex items-center justify-center\"><img src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs("data:image/png;base64," + qrcode)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\client\user\totp\setup.templ`, Line: 82, Col: 56}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" width=\"200\" height=\"200\" alt=\"QR Code\" class=\"rounded-lg\" style=\"aspect-ratio: 200 / 200; object-fit: cover;\"></div><div class=\"mt-6 space-y-2\"><p class=\"font-medium\">Backup Code:</p><div class=\"rounded-md bg-background px-4 py-2 text-sm font-mono text-muted-foreground\">----|----</div><p class=\"font-medium\">TOTP Secret:</p><div class=\"rounded-md bg-background px-4 py-2 text-sm font-mono text-muted-foreground\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(code)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\client\user\totp\setup.templ`, Line: 95, Col: 23}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div><form method=\"post\" action=\"/user/totp/setup\"><div class=\"grid gap-2\"><label class=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\" for=\"totp\">Totp Code</label> <input id=\"secret\" name=\"secret\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(code)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\client\user\totp\setup.templ`, Line: 105, Col: 64}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" type=\"hidden\"> <input class=\"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50\" id=\"totp\" name=\"totp\" placeholder=\"Code from authenticator app\"><div class=\"flex items-center p-6\"><button class=\"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2 w-full\" type=\"submit\">Enable TOTP</button></div></div></form></div></div></div></main>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = layout.Footer().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = layout.MainScript().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}
func Main(title string, qrcode string, code string, user types.User, msg types.Message) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
@ -151,9 +179,9 @@ func Main(title string, qrcode string, code string, user types.User, msg types.M
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var8 := templ.GetChildren(ctx)
if templ_7745c5c3_Var8 == nil {
templ_7745c5c3_Var8 = templ.NopComponent
templ_7745c5c3_Var9 := templ.GetChildren(ctx)
if templ_7745c5c3_Var9 == nil {
templ_7745c5c3_Var9 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = content(title, qrcode, code, user, msg).Render(ctx, templ_7745c5c3_Buffer)

View File

@ -8,302 +8,302 @@ import (
templ content(message types.Message, title string, user types.User, allowance *types.Allowance, ListSession []*session.SessionInfo) {
@layout.BaseAuth(title){
@layout.Navbar(user)
<main class="container mx-auto px-4 py-12 md:px-6 md:py-16 lg:py-10">
<div class="grid gap-10 lg:grid-cols-[1fr_300px]">
<div class="space-y-8">
<section>
<h2 class="text-2xl font-bold tracking-tight">Profile</h2>
@MainContent(user, allowance, ListSession, message)
}
}
templ MainContent(user types.User, allowance *types.Allowance, ListSession []*session.SessionInfo, message types.Message) {
@layout.Navbar(user)
<main class="container mx-auto px-4 py-12 md:px-6 md:py-16 lg:py-10">
<div class="grid gap-10 lg:grid-cols-[1fr_300px]">
<div class="space-y-8">
<section>
<h2 class="text-2xl font-bold tracking-tight">Profile</h2>
<div class="mt-6 grid gap-6">
<div class="grid gap-2">
<label
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
for="name">
Name
</label>
<input
class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
id="name" placeholder="Enter your name" value={user.Username} disabled />
</div>
<div class="grid gap-2">
<label
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
for="email">
Email
</label>
<input
class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
type="email" id="email" placeholder="Enter your email" value={user.Email} disabled />
</div>
<div class="grid gap-2">
<label
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
for="profile-picture">
Profile Picture
</label>
<div class="flex items-center gap-4">
<div
class="inline-flex items-center justify-center w-10 h-10 overflow-hidden bg-gray-100 rounded-full">
<span class="font-medium text-gray-600">JL</span>
</div>
<button
class="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 px-4 py-2">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="mr-2 h-4 w-4">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="17 8 12 3 7 8"></polyline>
<line x1="12" x2="12" y1="3" y2="15"></line>
</svg>
Upload
</button>
</div>
</div>
</div>
</section>
<section>
<h2 class="text-2xl font-bold tracking-tight">Session Management</h2>
<div class="mt-6 grid gap-6">
<div class="grid gap-2">
<div class="flex items-center justify-between">
<label
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
for="two-factor">
Two-Factor Authentication
</label>
<a
class="hover:bg-gray-200 inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 px-4 py-2"
type="button" id="radix-:rq:" aria-haspopup="menu"
aria-expanded="false" data-state="closed"
href="/user/totp/setup" hx-get="/user/totp/setup" hx-swap="innerHTML" hx-push-url="true" hx-target="#content">
Setup
</a>
</div>
</div>
<div class="grid gap-2">
<div class="bg-white rounded-lg shadow-md overflow-hidden">
<div class="w-full overflow-auto">
<table class="w-full caption-bottom text-sm">
<thead class="[&amp;_tr]:border-b">
<tr
class="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
<th
class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&amp;:has([role=checkbox])]:pr-0">
IP Address
</th>
<th
class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&amp;:has([role=checkbox])]:pr-0">
Browser
</th>
<th
class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&amp;:has([role=checkbox])]:pr-0">
Device
</th>
<th
class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&amp;:has([role=checkbox])]:pr-0">
Actions
</th>
</tr>
</thead>
<tbody class="[&amp;_tr:last-child]:border-0" id="session-tables">
for _, ses := range ListSession {
<tr
class="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
<td class="p-4 align-middle [&amp;:has([role=checkbox])]:pr-0">{ses.IP}
</td>
<td class="p-4 align-middle [&amp;:has([role=checkbox])]:pr-0">{ses.Browser + ses.Version}
</td>
<td class="p-4 align-middle [&amp;:has([role=checkbox])]:pr-0">{ses.OS + ses.OSVersion}
</td>
<td class="p-4 align-middle [&amp;:has([role=checkbox])]:pr-0">
<button
class="hover:bg-gray-200 inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 px-4 py-2"
type="button" id="radix-:rq:" aria-haspopup="menu"
aria-expanded="false" data-state="closed"
hx-delete={"/user/session/terminate/"+ses.SessionID} hx-target="#session-tables" hx-swap="innerHTML">
Terminate
</button>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</div>
</section>
<section>
<h2 class="text-2xl font-bold tracking-tight">Reset Password</h2>
switch message.Code {
case 0:
<div class="p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 text-center" role="alert">
{message.Message}
</div>
}
<form method="POST" action="/user/reset-password">
<div class="mt-6 grid gap-6">
<div class="grid gap-2">
<label
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
for="name">
Name
for="currentPassword">
Current password
</label>
<input
class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
id="name" placeholder="Enter your name" value={user.Username} disabled />
type="password" id="currentPassword" name="currentPassword" placeholder="Current password" />
</div>
<div class="grid gap-2">
<label
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
for="email">
Email
for="password">
New password
</label>
<input
class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
type="email" id="email" placeholder="Enter your email" value={user.Email} disabled />
type="password" id="password" name="password" placeholder="New password" />
</div>
<div class="grid gap-2">
<label
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
for="profile-picture">
Profile Picture
for="confirmPassword">
New password confirmation
</label>
<div class="flex items-center gap-4">
<div
class="inline-flex items-center justify-center w-10 h-10 overflow-hidden bg-gray-100 rounded-full">
<span class="font-medium text-gray-600">JL</span>
</div>
<button
class="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 px-4 py-2">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="mr-2 h-4 w-4">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="17 8 12 3 7 8"></polyline>
<line x1="12" x2="12" y1="3" y2="15"></line>
</svg>
Upload
</button>
</div>
<input
class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
type="password" id="confirmPassword" placeholder="New password confirmation" />
</div>
<div id="validationBox" class="justify-start mt-3 ml-4 p-1 hidden">
<ul>
<li class="flex items-center py-1">
<div id="matchSvgContainer" class="rounded-full p-1 fill-current bg-red-200 text-green-700">
<svg id="matchSvgIcon" class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path id="matchGoodPath" style="display: none;" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
<path id="matchBadPath" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</div>
<span id="matchStatusText" class="font-medium text-sm ml-3 text-red-700"> New Passwords do not match</span>
</li>
<li class="flex items-center py-1">
<div id="uppercaseSvgContainer" class="rounded-full p-1 fill-current bg-red-200 text-green-700">
<svg id="uppercaseSvgIcon" class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path id="uppercaseGoodPath" style="display: none;" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
<path id="uppercaseBadPath" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</div>
<span id="uppercaseStatusText" class="font-medium text-sm ml-3 text-red-700"> New Password must contain at least one uppercase letter</span>
</li>
<li class="flex items-center py-1">
<div id="lengthSvgContainer" class="rounded-full p-1 fill-current bg-red-200 text-green-700">
<svg id="lengthSvgIcon" class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path id="lengthGoodPath" style="display: none;" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
<path id="lengthBadPath" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</div>
<span id="lengthStatusText" class="font-medium text-sm ml-3 text-red-700"> New Password length must be at least 8 characters</span>
</li>
</ul>
</div>
<button type="submit" id="submit" class="focus:outline-none disabled:bg-red-100 text-white bg-red-500 hover:bg-red-700 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2" disabled>Update password</button>
</div>
</section>
<section>
<h2 class="text-2xl font-bold tracking-tight">Session Management</h2>
<div class="mt-6 grid gap-6">
<div class="grid gap-2">
<div class="flex items-center justify-between">
<label
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
for="two-factor">
Two-Factor Authentication
</label>
<a
class="hover:bg-gray-200 inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 px-4 py-2"
type="button" id="radix-:rq:" aria-haspopup="menu"
aria-expanded="false" data-state="closed"
href="/user/totp/setup" hx-get="/user/totp/setup" hx-swap="outerHTML" hx-push-url="true" hx-target="#content">
Setup
</a>
</div>
</div>
<div class="grid gap-2">
<div class="bg-white rounded-lg shadow-md overflow-hidden">
<div class="w-full overflow-auto">
<table class="w-full caption-bottom text-sm">
<thead class="[&amp;_tr]:border-b">
<tr
class="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
<th
class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&amp;:has([role=checkbox])]:pr-0">
IP Address
</th>
<th
class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&amp;:has([role=checkbox])]:pr-0">
Browser
</th>
<th
class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&amp;:has([role=checkbox])]:pr-0">
Device
</th>
<th
class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&amp;:has([role=checkbox])]:pr-0">
Actions
</th>
</tr>
</thead>
<tbody class="[&amp;_tr:last-child]:border-0" id="session-tables">
for _, ses := range ListSession {
<tr
class="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
<td class="p-4 align-middle [&amp;:has([role=checkbox])]:pr-0">{ses.IP}
</td>
<td class="p-4 align-middle [&amp;:has([role=checkbox])]:pr-0">{ses.Browser + ses.Version}
</td>
<td class="p-4 align-middle [&amp;:has([role=checkbox])]:pr-0">{ses.OS + ses.OSVersion}
</td>
<td class="p-4 align-middle [&amp;:has([role=checkbox])]:pr-0">
<button
class="hover:bg-gray-200 inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 px-4 py-2"
type="button" id="radix-:rq:" aria-haspopup="menu"
aria-expanded="false" data-state="closed"
hx-delete={"/user/session/terminate/"+ses.SessionID} hx-target="#session-tables" hx-swap="outerHTML">
Terminate
</button>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</form>
</section>
<div class="grid gap-1">
<div class="flex items-center justify-between">
<a
href="/logout"
class="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 px-4 py-2">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="mr-2 h-4 w-4">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
<polyline points="16 17 21 12 16 7"></polyline>
<line x1="21" x2="9" y1="12" y2="12"></line>
</svg>
Log Out
</a>
</div>
<p class="text-sm text-gray-500">
Click to log out or terminate the current session.
</p>
</div>
</div>
<div class="space-y-8">
<div class="rounded-lg border bg-card text-card-foreground shadow-sm" data-v0-t="card">
<div class="flex flex-row justify-between items-center p-6">
<h3 class="whitespace-nowrap text-2xl font-semibold leading-none tracking-tight">Storage Usage</h3>
<svg class="w-4 h-4 text-muted-foreground" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="22" x2="2" y1="12" y2="12"></line>
<path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"></path>
<line x1="6" x2="6.01" y1="16" y2="16"></line>
<line x1="10" x2="10.01" y1="16" y2="16"></line>
</svg>
</div>
<div class="p-6 grid gap-4">
<div class="text-2xl font-bold">{allowance.AllowanceUsedByte} / {allowance.AllowanceByte}</div>
<div class="w-full bg-gray-300 rounded-full h-2.5">
<div class="bg-gray-800 h-2.5 rounded-full" id="allowanceProgress" style="width: 0%;"></div>
</div>
</section>
<section>
<h2 class="text-2xl font-bold tracking-tight">Reset Password</h2>
switch message.Code {
case 0:
<div class="p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 text-center" role="alert">
{message.Message}
</div>
}
<form method="POST" action="/user/reset-password">
<div class="mt-6 grid gap-6">
<div class="grid gap-2">
<label
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
for="currentPassword">
Current password
</label>
<input
class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
type="password" id="currentPassword" name="currentPassword" placeholder="Current password" />
</div>
<div class="grid gap-2">
<label
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
for="password">
New password
</label>
<input
class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
type="password" id="password" name="password" placeholder="New password" />
</div>
<div class="grid gap-2">
<label
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
for="confirmPassword">
New password confirmation
</label>
<input
class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
type="password" id="confirmPassword" placeholder="New password confirmation" />
</div>
<div id="validationBox" class="justify-start mt-3 ml-4 p-1 hidden">
<ul>
<li class="flex items-center py-1">
<div id="matchSvgContainer" class="rounded-full p-1 fill-current bg-red-200 text-green-700">
<svg id="matchSvgIcon" class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path id="matchGoodPath" style="display: none;" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
<path id="matchBadPath" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</div>
<span id="matchStatusText" class="font-medium text-sm ml-3 text-red-700"> New Passwords do not match</span>
</li>
<li class="flex items-center py-1">
<div id="uppercaseSvgContainer" class="rounded-full p-1 fill-current bg-red-200 text-green-700">
<svg id="uppercaseSvgIcon" class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path id="uppercaseGoodPath" style="display: none;" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
<path id="uppercaseBadPath" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</div>
<span id="uppercaseStatusText" class="font-medium text-sm ml-3 text-red-700"> New Password must contain at least one uppercase letter</span>
</li>
<li class="flex items-center py-1">
<div id="lengthSvgContainer" class="rounded-full p-1 fill-current bg-red-200 text-green-700">
<svg id="lengthSvgIcon" class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path id="lengthGoodPath" style="display: none;" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
<path id="lengthBadPath" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</div>
<span id="lengthStatusText" class="font-medium text-sm ml-3 text-red-700"> New Password length must be at least 8 characters</span>
</li>
</ul>
</div>
<button type="submit" id="submit" class="focus:outline-none disabled:bg-red-100 text-white bg-red-500 hover:bg-red-700 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2" disabled>Update password</button>
</div>
</form>
</section>
<div class="grid gap-1">
<div class="flex items-center justify-between">
<a
href="/logout"
class="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 px-4 py-2">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="mr-2 h-4 w-4">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
<polyline points="16 17 21 12 16 7"></polyline>
<line x1="21" x2="9" y1="12" y2="12"></line>
</svg>
Log Out
</a>
</div>
<p class="text-sm text-gray-500">
Click to log out or terminate the current session.
</p>
<a
class="hover:bg-gray-200 inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 px-4 py-2"
type="button" href="/file" hx-get="/file" hx-swap="innerHTML" hx-push-url="true" hx-target="#content">
Manage
</a>
</div>
</div>
<div class="space-y-8">
<div class="rounded-lg border bg-card text-card-foreground shadow-sm" data-v0-t="card">
<div class="flex flex-row justify-between items-center p-6">
<h3 class="whitespace-nowrap text-2xl font-semibold leading-none tracking-tight">Storage Usage</h3>
<svg class="w-4 h-4 text-muted-foreground" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="22" x2="2" y1="12" y2="12"></line>
<path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"></path>
<line x1="6" x2="6.01" y1="16" y2="16"></line>
<line x1="10" x2="10.01" y1="16" y2="16"></line>
</svg>
</div>
<div class="p-6 grid gap-4">
<div class="text-2xl font-bold">{allowance.AllowanceUsedByte} / {allowance.AllowanceByte}</div>
<div class="w-full bg-gray-300 rounded-full h-2.5">
<div class="bg-gray-800 h-2.5 rounded-full" id="allowanceProgress" style="width: 0%;"></div>
</div>
<a
class="hover:bg-gray-200 inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 px-4 py-2"
type="button" href="/upload" hx-get="/upload" hx-swap="outerHTML" hx-push-url="true" hx-target="#content">
Upload
</a>
<a
class="hover:bg-gray-200 inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 px-4 py-2"
type="button" href="/download" hx-get="/download" hx-swap="outerHTML" hx-push-url="true" hx-target="#content">
Download
</a>
</div>
<div class="rounded-lg border bg-card text-card-foreground shadow-sm" data-v0-t="card">
<div class="flex flex-col space-y-1.5 p-6">
<h3 class="whitespace-nowrap text-2xl font-semibold leading-none tracking-tight">Upgrade Storage
</h3>
</div>
<div class="rounded-lg border bg-card text-card-foreground shadow-sm" data-v0-t="card">
<div class="flex flex-col space-y-1.5 p-6">
<h3 class="whitespace-nowrap text-2xl font-semibold leading-none tracking-tight">Upgrade Storage
</h3>
<div class="p-6 grid gap-4">
<div class="grid gap-2">
<h3 class="text-lg font-semibold">Pro Plan</h3>
<p class="text-gray-500">50GB of storage for $9.99/month</p>
<a
class="hover:bg-gray-200 inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 px-4 py-2"
type="button" href="#">
Upgrade
</a>
</div>
<div class="p-6 grid gap-4">
<div class="grid gap-2">
<h3 class="text-lg font-semibold">Pro Plan</h3>
<p class="text-gray-500">50GB of storage for $9.99/month</p>
<a
class="hover:bg-gray-200 inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 px-4 py-2"
type="button" href="#">
Upgrade
</a>
</div>
<div class="grid gap-2">
<h3 class="text-lg font-semibold">Enterprise Plan</h3>
<p class="text-gray-500">1TB of storage for $49.99/month</p>
<a
class="hover:bg-gray-200 inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 px-4 py-2"
type="button" href="#">
Upgrade
</a>
</div>
<div class="grid gap-2">
<h3 class="text-lg font-semibold">Enterprise Plan</h3>
<p class="text-gray-500">1TB of storage for $49.99/month</p>
<a
class="hover:bg-gray-200 inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 px-4 py-2"
type="button" href="#">
Upgrade
</a>
</div>
</div>
</div>
</div>
</main>
@templ.JSONScript("AllowanceUsedPercent", allowance.AllowanceUsedPercent)
<script type="text/javascript">
document.getElementById('currentPassword').addEventListener('input', function() {
var validationBox = document.getElementById('validationBox');
if (this.value.length > 0) {
validationBox.classList.remove('hidden');
} else {
validationBox.classList.add('hidden');
}
});
</div>
</main>
@layout.Footer()
@templ.JSONScript("AllowanceUsedPercent", allowance.AllowanceUsedPercent)
<script type="text/javascript">
document.getElementById('currentPassword').addEventListener('input', function() {
var validationBox = document.getElementById('validationBox');
if (this.value.length > 0) {
validationBox.classList.remove('hidden');
} else {
validationBox.classList.add('hidden');
}
});
window.allowanceProgress = document.getElementById(`allowanceProgress`);
window.AllowanceUsedPercent = JSON.parse(document.getElementById('AllowanceUsedPercent').textContent);
allowanceProgress.style.width = `${AllowanceUsedPercent}%`;
</script>
<script src="/public/validatePassword.js" />
@layout.Footer()
}
window.allowanceProgress = document.getElementById(`allowanceProgress`);
window.AllowanceUsedPercent = JSON.parse(document.getElementById('AllowanceUsedPercent').textContent);
allowanceProgress.style.width = `${AllowanceUsedPercent}%`;
</script>
@layout.MainScript()
<script src="/public/validatePassword.js" />
}
templ SessionTable(ListSession []*session.SessionInfo){
@ -322,7 +322,7 @@ templ SessionTable(ListSession []*session.SessionInfo){
class="hover:bg-gray-200 inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 px-4 py-2"
type="button" id="radix-:rq:" aria-haspopup="menu"
aria-expanded="false" data-state="closed"
hx-delete={"/user/session/terminate/"+ses.SessionID} hx-target="#session-tables" hx-swap="outerHTML">
hx-delete={"/user/session/terminate/"+ses.SessionID} hx-target="#session-tables" hx-swap="innerHTML">
Terminate
</button>
</td>

File diff suppressed because one or more lines are too long