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

@ -1,4 +1,4 @@
package downloadFileHandler package downloadHandler
import ( import (
"fmt" "fmt"

View File

@ -1,37 +1,38 @@
package downloadHandler package fileHandler
import ( import (
"fmt"
"github.com/fossyy/filekeeper/app" "github.com/fossyy/filekeeper/app"
"github.com/fossyy/filekeeper/view/client/download"
"net/http"
"github.com/fossyy/filekeeper/types" "github.com/fossyy/filekeeper/types"
"github.com/fossyy/filekeeper/utils" "github.com/fossyy/filekeeper/utils"
fileView "github.com/fossyy/filekeeper/view/client/file"
"net/http"
"strconv"
) )
func GET(w http.ResponseWriter, r *http.Request) { func GET(w http.ResponseWriter, r *http.Request) {
userSession := r.Context().Value("user").(types.User) userSession := r.Context().Value("user").(types.User)
files, err := app.Server.Database.GetFiles(userSession.UserID.String()) files, err := app.Server.Database.GetFiles(userSession.UserID.String())
if err != nil { if err != nil {
fmt.Println(err.Error())
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
return return
} }
var filesData []types.FileData var filesData []types.FileData
for i := 0; i < len(files); i++ { for i := 0; i < len(files); i++ {
filesData = append(filesData, types.FileData{ filesData = append(filesData, types.FileData{
ID: files[i].ID.String(), ID: files[i].ID.String(),
Name: files[i].Name, Name: files[i].Name,
Size: utils.ConvertFileSize(files[i].Size), Size: utils.ConvertFileSize(files[i].Size),
Downloaded: files[i].Downloaded, Downloaded: strconv.FormatUint(files[i].Downloaded, 10),
}) })
} }
component := downloadView.Main("Filekeeper - Download Page", filesData) component := fileView.Main("File Dashboard", filesData, userSession)
err = component.Render(r.Context(), w) err = component.Render(r.Context(), w)
if err != nil { if err != nil {
fmt.Println(err.Error())
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
app.Server.Logger.Error(err.Error())
return return
} }
} }

View File

@ -4,7 +4,6 @@ import (
"fmt" "fmt"
"github.com/fossyy/filekeeper/app" "github.com/fossyy/filekeeper/app"
"github.com/fossyy/filekeeper/types" "github.com/fossyy/filekeeper/types"
filesView "github.com/fossyy/filekeeper/view/client/upload"
"io" "io"
"net/http" "net/http"
"os" "os"
@ -13,14 +12,6 @@ import (
"strings" "strings"
) )
func GET(w http.ResponseWriter, r *http.Request) {
component := filesView.Main("Filekeeper - Upload")
if err := component.Render(r.Context(), w); err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
}
func POST(w http.ResponseWriter, r *http.Request) { func POST(w http.ResponseWriter, r *http.Request) {
fileID := r.PathValue("id") fileID := r.PathValue("id")
if err := r.ParseMultipartForm(32 << 20); err != nil { if err := r.ParseMultipartForm(32 << 20); err != nil {

View File

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"github.com/a-h/templ"
"github.com/fossyy/filekeeper/app" "github.com/fossyy/filekeeper/app"
"github.com/fossyy/filekeeper/view/client/user/totp" "github.com/fossyy/filekeeper/view/client/user/totp"
"image/png" "image/png"
@ -41,10 +42,18 @@ func GET(w http.ResponseWriter, r *http.Request) {
return return
} }
component := userTotpSetupView.Main("Filekeeper - 2FA Setup Page", base64Str, secret, userSession, types.Message{ var component templ.Component
Code: 3, if r.Header.Get("hx-request") == "true" {
Message: "", component = userTotpSetupView.MainContent(base64Str, secret, userSession, types.Message{
}) Code: 3,
Message: "",
})
} else {
component = userTotpSetupView.Main("Filekeeper - 2FA Setup Page", base64Str, secret, userSession, types.Message{
Code: 3,
Message: "",
})
}
if err := component.Render(r.Context(), w); err != nil { if err := component.Render(r.Context(), w); err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
return return
@ -69,26 +78,42 @@ func POST(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
return return
} }
var component templ.Component
if totp.Verify(code, time.Now().Unix()) { if totp.Verify(code, time.Now().Unix()) {
if err := app.Server.Database.InitializeTotp(userSession.Email, secret); err != nil { if err := app.Server.Database.InitializeTotp(userSession.Email, secret); err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
return return
} }
app.Server.Service.DeleteUser(userSession.Email) app.Server.Service.DeleteUser(userSession.Email)
component := userTotpSetupView.Main("Filekeeper - 2FA Setup Page", base64Str, secret, userSession, types.Message{ if r.Header.Get("hx-request") == "true" {
Code: 1, component = userTotpSetupView.MainContent(base64Str, secret, userSession, types.Message{
Message: "Your TOTP setup is complete! Your account is now more secure.", Code: 1,
}) Message: "Your TOTP setup is complete! Your account is now more secure.",
})
} else {
component = userTotpSetupView.Main("Filekeeper - 2FA Setup Page", base64Str, secret, userSession, types.Message{
Code: 1,
Message: "Your TOTP setup is complete! Your account is now more secure.",
})
}
if err := component.Render(r.Context(), w); err != nil { if err := component.Render(r.Context(), w); err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
return return
} }
return return
} else { } else {
component := userTotpSetupView.Main("Filekeeper - 2FA Setup Page", base64Str, secret, userSession, types.Message{ if r.Header.Get("hx-request") == "true" {
Code: 0, component = userTotpSetupView.MainContent(base64Str, secret, userSession, types.Message{
Message: "The code you entered is incorrect. Please double-check the code and try again.", Code: 0,
}) Message: "The code you entered is incorrect. Please double-check the code and try again.",
})
} else {
component = userTotpSetupView.Main("Filekeeper - 2FA Setup Page", base64Str, secret, userSession, types.Message{
Code: 0,
Message: "The code you entered is incorrect. Please double-check the code and try again.",
})
}
if err := component.Render(r.Context(), w); err != nil { if err := component.Render(r.Context(), w); err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
return return

View File

@ -61,7 +61,6 @@ func GET(w http.ResponseWriter, r *http.Request) {
handlerWS(upgrade, userSession) handlerWS(upgrade, userSession)
} }
var component templ.Component
sessions, err := session.GetSessions(userSession.Email) sessions, err := session.GetSessions(userSession.Email)
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
@ -86,21 +85,35 @@ func GET(w http.ResponseWriter, r *http.Request) {
AllowanceUsedPercent: fmt.Sprintf("%.2f", float64(usage)/float64(allowance.AllowanceByte)*100), AllowanceUsedPercent: fmt.Sprintf("%.2f", float64(usage)/float64(allowance.AllowanceByte)*100),
} }
var component templ.Component
if err := r.URL.Query().Get("error"); err != "" { if err := r.URL.Query().Get("error"); err != "" {
message, ok := errorMessages[err] message, ok := errorMessages[err]
if !ok { if !ok {
message = "Unknown error occurred. Please contact support at bagas@fossy.my.id for assistance." message = "Unknown error occurred. Please contact support at bagas@fossy.my.id for assistance."
} }
if r.Header.Get("hx-request") == "true" {
component = userView.Main("Filekeeper - User Page", userSession, allowanceStats, sessions, types.Message{ component = userView.MainContent(userSession, allowanceStats, sessions, types.Message{
Code: 0, Code: 0,
Message: message, Message: message,
}) })
} else {
component = userView.Main("Filekeeper - User Page", userSession, allowanceStats, sessions, types.Message{
Code: 0,
Message: message,
})
}
} else { } else {
component = userView.Main("Filekeeper - User Page", userSession, allowanceStats, sessions, types.Message{ if r.Header.Get("hx-request") == "true" {
Code: 1, component = userView.MainContent(userSession, allowanceStats, sessions, types.Message{
Message: "", Code: 1,
}) Message: "",
})
} else {
component = userView.Main("Filekeeper - User Page", userSession, allowanceStats, sessions, types.Message{
Code: 1,
Message: "",
})
}
} }
err = component.Render(r.Context(), w) err = component.Render(r.Context(), w)
if err != nil { if err != nil {

View File

@ -78,55 +78,32 @@ async function handleFile(file){
function addNewUploadElement(file){ function addNewUploadElement(file){
const newDiv = document.createElement('div'); const newDiv = document.createElement('div');
newDiv.innerHTML = ` newDiv.innerHTML = `
<div class="p-6 rounded-lg shadow bg-gray-800 border-gray-700"> <div class="space-y-4">
<div class="mb-2 flex justify-between items-center"> <div class="p-4 flex justify-between items-center">
<div class="flex items-center gap-x-3"> <div class="flex items-center space-x-2">
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="100" height="100" viewBox="0 0 48 48"> <div class="relative">
<path fill="#90CAF9" d="M40 45L8 45 8 3 30 3 40 13z"></path> <svg class="w-12 h-12" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
<path fill="#E1F5FE" d="M38.5 14L29 14 29 4.5z"></path> <circle cx="18" cy="18" r="16" fill="none" class="stroke-current text-gray-200" stroke-width="2"></circle>
</svg> <circle id="progress-${ file.name }-1" cx="18" cy="18" r="16" fill="none" class="stroke-current text-blue-600" stroke-width="2" stroke-dasharray="100" stroke-dashoffset="100" transform="rotate(-90 18 18)"></circle>
<div> </svg>
<p class="text-sm font-medium text-white">${ file.name }</p> <div class="absolute inset-0 flex items-center justify-center">
<p class="text-xs text-gray-500">${ convertFileSize(file.size) }</p> <span id="progress-${ file.name }-2" class="text-xs font-medium">0%</span>
</div> </div>
</div> </div>
<div class="inline-flex items-center gap-x-2"> <div class="flex flex-col">
<a class="text-gray-500 hover:text-gray-800" href="#"> <span class="text-base font-medium truncate w-48">${ file.name }</span>
<svg class="flex-shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" <div class="flex items-center gap-x-3 whitespace-nowrap">
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round">
<rect width="4" height="16" x="6" y="4" />
<rect width="4" height="16" x="14" y="4" />
</svg>
</a>
<a class="text-gray-500 hover:text-gray-800" href="#">
<svg class="flex-shrink-0 size-4" 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">
<path d="M3 6h18" />
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" />
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
<line x1="10" x2="10" y1="11" y2="17" />
<line x1="14" x2="14" y1="11" y2="17" />
</svg>
</a>
</div>
</div>
<div class="flex items-center gap-x-3 whitespace-nowrap">
<div id="progress-${ file.name }-1" class="flex w-full h-2 rounded-full overflow-hidden bg-gray-200"
role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100">
<div id="progress-${ file.name }-2" </div>
class="flex flex-col justify-center rounded-full overflow-hidden bg-teal-500 text-xs text-white text-center whitespace-nowrap transition duration-500"> <div id="progress-${ file.name }-3" class="text-sm text-gray-500">Starting...</div>
</div> </div>
</div>
</div> <button class="text-blue-500 text-base font-medium">Batal</button>
<span id="progress-${ file.name }-3" class="text-sm text-white ">Starting...</span> </div>
</div> </div>
<div id="progress-${ file.name }-4" class="text-sm text-gray-500">Uploading 0%</div>
</div>
`; `;
document.getElementById('container').appendChild(newDiv); document.getElementById('FileUploadBoxItem').appendChild(newDiv);
document.getElementById('uploadBox').classList.remove('hidden');
} }
function convertFileSize(sizeInBytes) { function convertFileSize(sizeInBytes) {
@ -150,7 +127,6 @@ async function splitFile(file, chunkSize) {
const start = i * chunkSize; const start = i * chunkSize;
const end = Math.min(fileSize, start + chunkSize); const end = Math.min(fileSize, start + chunkSize);
const chunk = file.slice(start, end); const chunk = file.slice(start, end);
chunk.hash = "test"
fileChunks.push(chunk); fileChunks.push(chunk);
} }
@ -171,7 +147,6 @@ async function uploadChunks(name, size, chunks, chunkArray, FileID) {
let progress1 = document.getElementById(`progress-${name}-1`); let progress1 = document.getElementById(`progress-${name}-1`);
let progress2 = document.getElementById(`progress-${name}-2`); let progress2 = document.getElementById(`progress-${name}-2`);
let progress3 = document.getElementById(`progress-${name}-3`); let progress3 = document.getElementById(`progress-${name}-3`);
let progress4 = document.getElementById(`progress-${name}-4`);
let isFailed = false let isFailed = false
for (let index = 0; index < chunks.length; index++) { for (let index = 0; index < chunks.length; index++) {
const percentComplete = Math.round((index + 1) / chunks.length * 100); const percentComplete = Math.round((index + 1) / chunks.length * 100);
@ -183,12 +158,12 @@ async function uploadChunks(name, size, chunks, chunkArray, FileID) {
formData.append('index', index); formData.append('index', index);
formData.append('done', false); formData.append('done', false);
progress1.setAttribute("aria-valuenow", percentComplete); progress1.style.strokeDashoffset = 100 - percentComplete;
progress2.style.width = `${percentComplete}%`; progress2.innerText = `${percentComplete}%`;
const startTime = performance.now(); const startTime = performance.now();
try { try {
await fetch(`/upload/${FileID}`, { await fetch(`/file/${FileID}`, {
method: 'POST', method: 'POST',
body: formData body: formData
}); });
@ -203,22 +178,18 @@ async function uploadChunks(name, size, chunks, chunkArray, FileID) {
const totalTime = (endTime - startTime) / 1000; const totalTime = (endTime - startTime) / 1000;
const uploadSpeed = chunk.size / totalTime / 1024 / 1024; const uploadSpeed = chunk.size / totalTime / 1024 / 1024;
byteUploaded += chunk.size byteUploaded += chunk.size
progress3.innerText = `${uploadSpeed.toFixed(2)} MB/s`; progress3.innerText = `Uploading... ${uploadSpeed.toFixed(2)} MB/s`;
progress4.innerText = `Uploading ${percentComplete}% - ${convertFileSize(byteUploaded)} of ${ convertFileSize(size)}`;
} else { } else {
progress1.setAttribute("aria-valuenow", percentComplete); progress1.style.strokeDashoffset = 100 - percentComplete;
progress2.style.width = `${percentComplete}%`; progress2.innerText = `${percentComplete}%`;
progress3.innerText = `Fixing Missing Byte`; progress3.innerText = `Fixing Missing Byte`;
progress4.innerText = `Uploading Missing Byte ${percentComplete}% - ${convertFileSize(byteUploaded)} of ${ convertFileSize(size)}`;
byteUploaded += chunk.size byteUploaded += chunk.size
} }
} }
if (isFailed) { if (isFailed) {
progress3.innerText = `Upload Failed`; progress3.innerText = `Upload Failed`;
progress4.innerText = `There was an issue uploading the file. Please try again.`;
} else { } else {
progress3.innerText = `Done`; progress3.innerText = `Done`;
progress4.innerText = `File Uploaded 100% - ${convertFileSize(byteUploaded)} of ${ convertFileSize(size)}`;
} }
} }

View File

@ -5,8 +5,9 @@ import (
googleOauthCallbackHandler "github.com/fossyy/filekeeper/handler/auth/google/callback" googleOauthCallbackHandler "github.com/fossyy/filekeeper/handler/auth/google/callback"
googleOauthSetupHandler "github.com/fossyy/filekeeper/handler/auth/google/setup" googleOauthSetupHandler "github.com/fossyy/filekeeper/handler/auth/google/setup"
totpHandler "github.com/fossyy/filekeeper/handler/auth/totp" totpHandler "github.com/fossyy/filekeeper/handler/auth/totp"
downloadHandler "github.com/fossyy/filekeeper/handler/download" fileHandler "github.com/fossyy/filekeeper/handler/file"
downloadFileHandler "github.com/fossyy/filekeeper/handler/download/file" downloadHandler "github.com/fossyy/filekeeper/handler/file/download"
uploadHandler "github.com/fossyy/filekeeper/handler/file/upload"
forgotPasswordHandler "github.com/fossyy/filekeeper/handler/forgotPassword" forgotPasswordHandler "github.com/fossyy/filekeeper/handler/forgotPassword"
forgotPasswordVerifyHandler "github.com/fossyy/filekeeper/handler/forgotPassword/verify" forgotPasswordVerifyHandler "github.com/fossyy/filekeeper/handler/forgotPassword/verify"
indexHandler "github.com/fossyy/filekeeper/handler/index" indexHandler "github.com/fossyy/filekeeper/handler/index"
@ -14,7 +15,6 @@ import (
signinHandler "github.com/fossyy/filekeeper/handler/signin" signinHandler "github.com/fossyy/filekeeper/handler/signin"
signupHandler "github.com/fossyy/filekeeper/handler/signup" signupHandler "github.com/fossyy/filekeeper/handler/signup"
signupVerifyHandler "github.com/fossyy/filekeeper/handler/signup/verify" signupVerifyHandler "github.com/fossyy/filekeeper/handler/signup/verify"
uploadHandler "github.com/fossyy/filekeeper/handler/upload"
userHandler "github.com/fossyy/filekeeper/handler/user" userHandler "github.com/fossyy/filekeeper/handler/user"
userHandlerResetPassword "github.com/fossyy/filekeeper/handler/user/ResetPassword" userHandlerResetPassword "github.com/fossyy/filekeeper/handler/user/ResetPassword"
userSessionTerminateHandler "github.com/fossyy/filekeeper/handler/user/session/terminate" userSessionTerminateHandler "github.com/fossyy/filekeeper/handler/user/session/terminate"
@ -109,20 +109,16 @@ func SetupRoutes() *http.ServeMux {
middleware.Auth(userHandlerTotpSetup.POST, w, r) middleware.Auth(userHandlerTotpSetup.POST, w, r)
}) })
handler.HandleFunc("GET /upload", func(w http.ResponseWriter, r *http.Request) { handler.HandleFunc("GET /file", func(w http.ResponseWriter, r *http.Request) {
middleware.Auth(uploadHandler.GET, w, r) middleware.Auth(fileHandler.GET, w, r)
}) })
handler.HandleFunc("POST /upload/{id}", func(w http.ResponseWriter, r *http.Request) { handler.HandleFunc("POST /file/{id}", func(w http.ResponseWriter, r *http.Request) {
middleware.Auth(uploadHandler.POST, w, r) middleware.Auth(uploadHandler.POST, w, r)
}) })
handler.HandleFunc("GET /download", func(w http.ResponseWriter, r *http.Request) { handler.HandleFunc("GET /file/{id}", func(w http.ResponseWriter, r *http.Request) {
middleware.Auth(downloadHandler.GET, w, r) downloadHandler.GET(w, r)
})
handler.HandleFunc("GET /download/{id}", func(w http.ResponseWriter, r *http.Request) {
downloadFileHandler.GET(w, r)
}) })
handler.HandleFunc("GET /logout", func(w http.ResponseWriter, r *http.Request) { handler.HandleFunc("GET /logout", func(w http.ResponseWriter, r *http.Request) {

View File

@ -30,7 +30,7 @@ type FileData struct {
ID string ID string
Name string Name string
Size string Size string
Downloaded uint64 Downloaded string
} }
type FileWithDetail struct { type FileWithDetail struct {

View File

@ -1,76 +0,0 @@
package downloadView
import (
"github.com/fossyy/filekeeper/view/client/layout"
"github.com/fossyy/filekeeper/types"
)
templ component(title string, files []types.FileData){
@layout.BaseAuth(title){
<div class="dark min-h-screen p-4 sm:p-6 bg-gray-900 text-white">
<div class="space-y-4">
<header class="text-center">
<div class="space-y-2">
<h1 class="text-3xl font-bold tracking-tighter sm:text-4xl">Download Files</h1>
</div>
</header>
<div class="mx-auto grid w-full max-w-3xl gap-4 px-4">
for _, file := range files {
<div class="rounded-lg border bg-card text-card-foreground shadow-sm">
<div class="flex space-y-4 flex-col p-4">
<div class="space-y-1">
<h2 class="text-lg font-bold tracking-wide">{ file.Name }</h2>
<p class="text-sm leading-none"> { file.Size }</p>
</div>
<div class="space-x-2">
<button class="inline-flex items-center justify-center whitespace-nowrap 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-9 rounded-md px-3">
<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="M4 13.5V4a2 2 0 0 1 2-2h8.5L20 7.5V20a2 2 0 0 1-2 2h-5.5"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<path d="M10.42 12.61a2.1 2.1 0 1 1 2.97 2.97L7.95 21 4 22l.99-3.95 5.43-5.44Z"></path>
</svg>
<span class="sr-only">Edit</span>
</button>
<a href={ templ.SafeURL("/download/" + file.ID) } class="inline-flex items-center justify-center p-5 text-base font-medium text-gray-500 rounded-lg bg-gray-50 hover:text-gray-900 hover:bg-gray-100 dark:text-gray-400 dark:bg-gray-800 dark:hover:bg-gray-700 dark:hover:text-white">
<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="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" x2="12" y1="15" y2="3"></line>
</svg>
</a>
</div>
</div>
</div>
}
</div>
</div>
</div>
}
}
templ Main(title string, files []types.FileData){
@component(title, files)
}

View File

@ -1,124 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.663
package downloadView
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import (
"github.com/fossyy/filekeeper/types"
"github.com/fossyy/filekeeper/view/client/layout"
)
func component(title string, files []types.FileData) 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_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var2 := 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)
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"dark min-h-screen p-4 sm:p-6 bg-gray-900 text-white\"><div class=\"space-y-4\"><header class=\"text-center\"><div class=\"space-y-2\"><h1 class=\"text-3xl font-bold tracking-tighter sm:text-4xl\">Download Files</h1></div></header><div class=\"mx-auto grid w-full max-w-3xl gap-4 px-4\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, file := range files {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"rounded-lg border bg-card text-card-foreground shadow-sm\"><div class=\"flex space-y-4 flex-col p-4\"><div class=\"space-y-1\"><h2 class=\"text-lg font-bold tracking-wide\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(file.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\client\download\download.templ`, Line: 22, Col: 86}
}
_, 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("</h2><p class=\"text-sm leading-none\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(file.Size)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\client\download\download.templ`, Line: 23, Col: 75}
}
_, 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("</p></div><div class=\"space-x-2\"><button class=\"inline-flex items-center justify-center whitespace-nowrap 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-9 rounded-md px-3\"><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=\"M4 13.5V4a2 2 0 0 1 2-2h8.5L20 7.5V20a2 2 0 0 1-2 2h-5.5\"></path> <polyline points=\"14 2 14 8 20 8\"></polyline> <path d=\"M10.42 12.61a2.1 2.1 0 1 1 2.97 2.97L7.95 21 4 22l.99-3.95 5.43-5.44Z\"></path></svg> <span class=\"sr-only\">Edit</span></button> <a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 templ.SafeURL = templ.SafeURL("/download/" + file.ID)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var5)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" class=\"inline-flex items-center justify-center p-5 text-base font-medium text-gray-500 rounded-lg bg-gray-50 hover:text-gray-900 hover:bg-gray-100 dark:text-gray-400 dark:bg-gray-800 dark:hover:bg-gray-700 dark:hover:text-white\"><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=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"></path> <polyline points=\"7 10 12 15 17 10\"></polyline> <line x1=\"12\" x2=\"12\" y1=\"15\" y2=\"3\"></line></svg></a></div></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer)
}
return templ_7745c5c3_Err
})
templ_7745c5c3_Err = layout.BaseAuth(title).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), 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, files []types.FileData) 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_Var6 := templ.GetChildren(ctx)
if templ_7745c5c3_Var6 == nil {
templ_7745c5c3_Var6 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = component(title, files).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
})
}

137
view/client/file/file.templ Normal file
View File

@ -0,0 +1,137 @@
package fileView
import (
"github.com/fossyy/filekeeper/view/client/layout"
"github.com/fossyy/filekeeper/types"
)
templ component(title string, files []types.FileData, user types.User) {
@layout.BaseAuth(title) {
@layout.Navbar(user)
<main class="flex-grow h-full bg-gray-100">
<div class="bg-gray-50 min-h-screen px-4 py-8 sm:px-6 lg:px-8">
<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-7xl">
<div id="dropzone-file" class="border-2 border-dashed border-gray-300 rounded-lg p-8 mb-8 text-center hover:bg-gray-200 transition-colors duration-200 ease-in-out cursor-pointer">
<label for="file-upload" class="flex flex-col items-center justify-center pt-5 pb-6 cursor-pointer">
<svg class="w-12 h-12 mb-4 text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
</svg>
<p class="mb-2 text-lg text-gray-500 font-semibold">
Click to upload or drag and drop
</p>
</label>
<input id="file-upload" name="file-upload" type="file" class="hidden" />
</div>
<div class="overflow-x-auto bg-white shadow-md rounded-lg">
<table class="w-full text-sm text-left text-gray-500">
<thead class="text-xs text-gray-700 uppercase bg-gray-50">
<tr>
<th scope="col" class="px-6 py-3 w-[40%]">File Name</th>
<th scope="col" class="px-6 py-3 w-[20%]">File Size</th>
<th scope="col" class="px-6 py-3 w-[20%]">Downloads</th>
<th scope="col" class="px-6 py-3 w-[20%]">Action</th>
</tr>
</thead>
<tbody>
for _, file := range files {
<tr class="bg-white border-b">
<td class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap flex items-center">
<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-5 w-5 mr-2 text-green-500">
<rect width="18" height="18" x="3" y="3" rx="2" ry="2"></rect>
<circle cx="9" cy="9" r="2"></circle>
<path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"></path>
</svg>
{file.Name}
</td>
<td class="px-6 py-4">{file.Size}</td>
<td class="px-6 py-4">
<div class="flex items-center">
<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-5 w-5 mr-2 text-gray-400">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" x2="12" y1="15" y2="3"></line>
</svg>
{file.Downloaded}
</div>
</td>
<td class="px-6 py-4">
<a href={ templ.SafeURL("/file/" + file.ID) } class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded text-xs">
Download
</a>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</main>
<div id="FileUploadBox">
</div>
<script type="text/javascript">
document.addEventListener("dragover", function (event) {
event.preventDefault();
document.getElementById('dropzone-file').classList.add('bg-gray-100', 'border-blue-500');
});
['dragenter', 'dragover'].forEach(eventName => {
document.getElementById('dropzone-file').addEventListener(eventName, highlight, false);
});
['dragleave', 'drop'].forEach(eventName => {
document.getElementById('dropzone-file').addEventListener(eventName, unhighlight, false);
});
function highlight(e) {
document.getElementById('dropzone-file').classList.add('bg-gray-100', 'border-blue-500');
}
function unhighlight(e) {
document.getElementById('dropzone-file').classList.remove('bg-gray-100', 'border-blue-500');
}
document.addEventListener("drop", async function (event) {
event.preventDefault();
const file = event.dataTransfer.files[0]
await handleFile(file)
});
document.getElementById('dropzone-file').addEventListener('change', async function(event) {
event.preventDefault();
const file = event.target.files[0]
await handleFile(file)
});
</script>
}
}
templ Main(title string, files []types.FileData, user types.User) {
@component(title, files, user)
}

View File

@ -0,0 +1,141 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.663
package fileView
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import (
"github.com/fossyy/filekeeper/types"
"github.com/fossyy/filekeeper/view/client/layout"
)
func component(title string, files []types.FileData, user types.User) 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_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var2 := 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)
}
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=\"flex-grow h-full bg-gray-100\"><div class=\"bg-gray-50 min-h-screen px-4 py-8 sm:px-6 lg:px-8\"><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-7xl\"><div id=\"dropzone-file\" class=\"border-2 border-dashed border-gray-300 rounded-lg p-8 mb-8 text-center hover:bg-gray-200 transition-colors duration-200 ease-in-out cursor-pointer\"><label for=\"file-upload\" class=\"flex flex-col items-center justify-center pt-5 pb-6 cursor-pointer\"><svg class=\"w-12 h-12 mb-4 text-gray-400\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12\"></path></svg><p class=\"mb-2 text-lg text-gray-500 font-semibold\">Click to upload or drag and drop\r</p></label> <input id=\"file-upload\" name=\"file-upload\" type=\"file\" class=\"hidden\"></div><div class=\"overflow-x-auto bg-white shadow-md rounded-lg\"><table class=\"w-full text-sm text-left text-gray-500\"><thead class=\"text-xs text-gray-700 uppercase bg-gray-50\"><tr><th scope=\"col\" class=\"px-6 py-3 w-[40%]\">File Name</th><th scope=\"col\" class=\"px-6 py-3 w-[20%]\">File Size</th><th scope=\"col\" class=\"px-6 py-3 w-[20%]\">Downloads</th><th scope=\"col\" class=\"px-6 py-3 w-[20%]\">Action</th></tr></thead> <tbody>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, file := range files {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<tr class=\"bg-white border-b\"><td class=\"px-6 py-4 font-medium text-gray-900 whitespace-nowrap flex items-center\"><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-5 w-5 mr-2 text-green-500\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" ry=\"2\"></rect> <circle cx=\"9\" cy=\"9\" r=\"2\"></circle> <path d=\"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21\"></path></svg> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(file.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\client\file\file.templ`, Line: 70, Col: 48}
}
_, 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("</td><td class=\"px-6 py-4\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(file.Size)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\client\file\file.templ`, Line: 72, Col: 69}
}
_, 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("</td><td class=\"px-6 py-4\"><div class=\"flex items-center\"><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-5 w-5 mr-2 text-gray-400\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"></path> <polyline points=\"7 10 12 15 17 10\"></polyline> <line x1=\"12\" x2=\"12\" y1=\"15\" y2=\"3\"></line></svg> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(file.Downloaded)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\client\file\file.templ`, Line: 80, Col: 64}
}
_, 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></td><td class=\"px-6 py-4\"><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 templ.SafeURL = templ.SafeURL("/file/" + file.ID)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var6)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" class=\"bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded text-xs\">Download\r</a></td></tr>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</tbody></table></div></div></div></main><div id=\"FileUploadBox\"></div><script type=\"text/javascript\">\r\n document.addEventListener(\"dragover\", function (event) {\r\n event.preventDefault();\r\n document.getElementById('dropzone-file').classList.add('bg-gray-100', 'border-blue-500');\r\n });\r\n\r\n ['dragenter', 'dragover'].forEach(eventName => {\r\n document.getElementById('dropzone-file').addEventListener(eventName, highlight, false);\r\n });\r\n\r\n ['dragleave', 'drop'].forEach(eventName => {\r\n document.getElementById('dropzone-file').addEventListener(eventName, unhighlight, false);\r\n });\r\n\r\n function highlight(e) {\r\n document.getElementById('dropzone-file').classList.add('bg-gray-100', 'border-blue-500');\r\n }\r\n\r\n function unhighlight(e) {\r\n document.getElementById('dropzone-file').classList.remove('bg-gray-100', 'border-blue-500');\r\n }\r\n document.addEventListener(\"drop\", async function (event) {\r\n event.preventDefault();\r\n const file = event.dataTransfer.files[0]\r\n await handleFile(file)\r\n });\r\n\r\n document.getElementById('dropzone-file').addEventListener('change', async function(event) {\r\n event.preventDefault();\r\n const file = event.target.files[0]\r\n await handleFile(file)\r\n });\r\n </script>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer)
}
return templ_7745c5c3_Err
})
templ_7745c5c3_Err = layout.BaseAuth(title).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), 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, files []types.FileData, user types.User) 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_Var7 := templ.GetChildren(ctx)
if templ_7745c5c3_Var7 == nil {
templ_7745c5c3_Var7 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = component(title, files, user).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
})
}

View File

@ -20,12 +20,12 @@ templ content(title string, user types.User) {
<div class="rounded-md shadow"> <div class="rounded-md shadow">
if user.Authenticated { if user.Authenticated {
<a class="w-full flex items-center justify-center px-8 py-3 text-base leading-6 font-medium rounded-md text-white bg-pink-400 hover:bg-pink-500 hover:text-white focus:ring ring-offset-2 ring-pink-400 focus:outline-none transition duration-150 ease-in-out md:py-4 md:text-lg md:px-10" <a class="w-full flex items-center justify-center px-8 py-3 text-base leading-6 font-medium rounded-md text-white bg-pink-400 hover:bg-pink-500 hover:text-white focus:ring ring-offset-2 ring-pink-400 focus:outline-none transition duration-150 ease-in-out md:py-4 md:text-lg md:px-10"
href="/user" hx-get="/user" hx-swap="outerHTML" hx-push-url="true" hx-target="#content"> href="/user" hx-get="/user" hx-swap="innerHTML" hx-push-url="true" hx-target="#content">
Open Dashboard Open Dashboard
</a> </a>
} else { } else {
<a class="w-full flex items-center justify-center px-8 py-3 text-base leading-6 font-medium rounded-md text-white bg-pink-400 hover:bg-pink-500 hover:text-white focus:ring ring-offset-2 ring-pink-400 focus:outline-none transition duration-150 ease-in-out md:py-4 md:text-lg md:px-10" <a class="w-full flex items-center justify-center px-8 py-3 text-base leading-6 font-medium rounded-md text-white bg-pink-400 hover:bg-pink-500 hover:text-white focus:ring ring-offset-2 ring-pink-400 focus:outline-none transition duration-150 ease-in-out md:py-4 md:text-lg md:px-10"
href="/signup" hx-get="/signup" hx-swap="outerHTML" hx-push-url="true" hx-target="#content"> href="/signup" hx-get="/signup" hx-swap="innerHTML" hx-push-url="true" hx-target="#content">
Get started Get started
</a> </a>
} }

View File

@ -47,12 +47,12 @@ func content(title string, user types.User) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if user.Authenticated { if user.Authenticated {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a class=\"w-full flex items-center justify-center px-8 py-3 text-base leading-6 font-medium rounded-md text-white bg-pink-400 hover:bg-pink-500 hover:text-white focus:ring ring-offset-2 ring-pink-400 focus:outline-none transition duration-150 ease-in-out md:py-4 md:text-lg md:px-10\" href=\"/user\" hx-get=\"/user\" hx-swap=\"outerHTML\" hx-push-url=\"true\" hx-target=\"#content\">Open Dashboard</a>") _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a class=\"w-full flex items-center justify-center px-8 py-3 text-base leading-6 font-medium rounded-md text-white bg-pink-400 hover:bg-pink-500 hover:text-white focus:ring ring-offset-2 ring-pink-400 focus:outline-none transition duration-150 ease-in-out md:py-4 md:text-lg md:px-10\" href=\"/user\" hx-get=\"/user\" hx-swap=\"innerHTML\" hx-push-url=\"true\" hx-target=\"#content\">Open Dashboard</a>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} else { } else {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a class=\"w-full flex items-center justify-center px-8 py-3 text-base leading-6 font-medium rounded-md text-white bg-pink-400 hover:bg-pink-500 hover:text-white focus:ring ring-offset-2 ring-pink-400 focus:outline-none transition duration-150 ease-in-out md:py-4 md:text-lg md:px-10\" href=\"/signup\" hx-get=\"/signup\" hx-swap=\"outerHTML\" hx-push-url=\"true\" hx-target=\"#content\">Get started</a>") _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a class=\"w-full flex items-center justify-center px-8 py-3 text-base leading-6 font-medium rounded-md text-white bg-pink-400 hover:bg-pink-500 hover:text-white focus:ring ring-offset-2 ring-pink-400 focus:outline-none transition duration-150 ease-in-out md:py-4 md:text-lg md:px-10\" href=\"/signup\" hx-get=\"/signup\" hx-swap=\"innerHTML\" hx-push-url=\"true\" hx-target=\"#content\">Get started</a>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }

View File

@ -48,19 +48,32 @@ templ BaseAuth(title string){
{ children... } { children... }
</div> </div>
</div> </div>
<script type="text/javascript"> <div class="hidden" id="uploadBox">
function addScript() { <div class="fixed bottom-6 right-6 w-80 bg-white rounded-lg shadow-lg border border-gray-200 overflow-hidden">
if (!window.scriptAdded) { <div class="p-4 bg-gray-100 flex justify-between items-center">
const script = document.createElement('script'); <div class="flex items-center">
script.src = "/public/main.js"; <span class="font-medium text-base">Mengupload 1 item</span>
window.scriptAdded = true; <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 ml-2 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
script.onerror = onerror; <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
document.head.appendChild(script); </svg>
} </div>
} <button class="text-gray-500 hover:text-gray-700">
addScript() <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
</script> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
<div id="FileUploadBoxItem">
</div>
</div>
</div>
@MainScript()
@modalScript() @modalScript()
<script type="text/javascript">
function showFileUploadBox() {
document.getElementById('FileUploadBox').classList.remove('hidden');
}
</script>
</body> </body>
</html> </html>
} }
@ -80,6 +93,21 @@ templ modal() {
</div> </div>
} }
templ MainScript() {
<script type="text/javascript">
function addScript() {
if (!window.scriptAdded) {
const script = document.createElement('script');
script.src = "/public/main.js";
window.scriptAdded = true;
script.onerror = onerror;
document.head.appendChild(script);
}
}
addScript()
</script>
}
templ modalScript(){ templ modalScript(){
<script type="text/javascript"> <script type="text/javascript">
window.modalContainer = document.getElementById('modalContainer'); window.modalContainer = document.getElementById('modalContainer');

View File

@ -115,7 +115,11 @@ func BaseAuth(title string) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div><script type=\"text/javascript\">\n function addScript() {\n if (!window.scriptAdded) {\n const script = document.createElement('script');\n script.src = \"/public/main.js\";\n window.scriptAdded = true;\n script.onerror = onerror;\n document.head.appendChild(script);\n }\n }\n addScript()\n </script>") _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div><div class=\"hidden\" id=\"uploadBox\"><div class=\"fixed bottom-6 right-6 w-80 bg-white rounded-lg shadow-lg border border-gray-200 overflow-hidden\"><div class=\"p-4 bg-gray-100 flex justify-between items-center\"><div class=\"flex items-center\"><span class=\"font-medium text-base\">Mengupload 1 item</span> <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5 ml-2 text-gray-500\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path></svg></div><button class=\"text-gray-500 hover:text-gray-700\"><svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M6 18L18 6M6 6l12 12\"></path></svg></button></div><div id=\"FileUploadBoxItem\"></div></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = MainScript().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@ -123,7 +127,7 @@ func BaseAuth(title string) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</body></html>") _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<script type=\"text/javascript\">\n function showFileUploadBox() {\n document.getElementById('FileUploadBox').classList.remove('hidden');\n }\n </script></body></html>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@ -158,7 +162,7 @@ func modal() templ.Component {
}) })
} }
func modalScript() templ.Component { func MainScript() templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { 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) templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer { if !templ_7745c5c3_IsBuffer {
@ -171,6 +175,30 @@ func modalScript() templ.Component {
templ_7745c5c3_Var6 = templ.NopComponent templ_7745c5c3_Var6 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<script type=\"text/javascript\">\n function addScript() {\n if (!window.scriptAdded) {\n const script = document.createElement('script');\n script.src = \"/public/main.js\";\n window.scriptAdded = true;\n script.onerror = onerror;\n document.head.appendChild(script);\n }\n }\n addScript()\n </script>")
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 modalScript() 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_Var7 := templ.GetChildren(ctx)
if templ_7745c5c3_Var7 == nil {
templ_7745c5c3_Var7 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<script type=\"text/javascript\">\n window.modalContainer = document.getElementById('modalContainer');\n window.closeModalBtn = document.getElementById('closeModal');\n window.content = document.getElementById('content');\n function toggleModal() {\n window.modalContainer.classList.contains('hidden')\n ? modalContainer.classList.remove('hidden')\n : modalContainer.classList.add('hidden');\n }\n\n window.closeModalBtn.addEventListener('click', toggleModal);\n window.modalContainer.addEventListener('click', function(event) {\n if (event.target === modalContainer) {\n toggleModal();\n }\n })\n </script>") _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<script type=\"text/javascript\">\n window.modalContainer = document.getElementById('modalContainer');\n window.closeModalBtn = document.getElementById('closeModal');\n window.content = document.getElementById('content');\n function toggleModal() {\n window.modalContainer.classList.contains('hidden')\n ? modalContainer.classList.remove('hidden')\n : modalContainer.classList.add('hidden');\n }\n\n window.closeModalBtn.addEventListener('click', toggleModal);\n window.modalContainer.addEventListener('click', function(event) {\n if (event.target === modalContainer) {\n toggleModal();\n }\n })\n </script>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
@ -190,9 +218,9 @@ func Navbar(user types.User) templ.Component {
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
} }
ctx = templ.InitializeContext(ctx) ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var7 := templ.GetChildren(ctx) templ_7745c5c3_Var8 := templ.GetChildren(ctx)
if templ_7745c5c3_Var7 == nil { if templ_7745c5c3_Var8 == nil {
templ_7745c5c3_Var7 = templ.NopComponent templ_7745c5c3_Var8 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<header class=\"flex items-center justify-between border-b border-gray-200 bg-white px-6 py-4\"><div class=\"flex items-center gap-4\"><a class=\"flex items-center gap-2\" href=\"#\"><img src=\"/public/brand.svg\" width=\"48\" height=\"48\" alt=\"Filekeeper Logo\"> <span class=\"text-lg font-semibold\">Filekeeper</span></a></div><div class=\"flex space-x-4\">") _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<header class=\"flex items-center justify-between border-b border-gray-200 bg-white px-6 py-4\"><div class=\"flex items-center gap-4\"><a class=\"flex items-center gap-2\" href=\"#\"><img src=\"/public/brand.svg\" width=\"48\" height=\"48\" alt=\"Filekeeper Logo\"> <span class=\"text-lg font-semibold\">Filekeeper</span></a></div><div class=\"flex space-x-4\">")
@ -204,12 +232,12 @@ func Navbar(user types.User) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var8 string var templ_7745c5c3_Var9 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(user.Username) templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(user.Username)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\client\layout\base.templ`, Line: 149, Col: 40} return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\client\layout\base.templ`, Line: 177, Col: 40}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@ -217,12 +245,12 @@ func Navbar(user types.User) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var9 string var templ_7745c5c3_Var10 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(user.Email) templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(user.Email)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\client\layout\base.templ`, Line: 150, Col: 67} return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\client\layout\base.templ`, Line: 178, Col: 67}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@ -255,9 +283,9 @@ func Footer() templ.Component {
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
} }
ctx = templ.InitializeContext(ctx) ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var10 := templ.GetChildren(ctx) templ_7745c5c3_Var11 := templ.GetChildren(ctx)
if templ_7745c5c3_Var10 == nil { if templ_7745c5c3_Var11 == nil {
templ_7745c5c3_Var10 = templ.NopComponent templ_7745c5c3_Var11 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<footer class=\"bg-white p-6 md:p-8 w-full bottom-0 border-t border-gray-200 w-full py-8\"><div class=\"container mx-auto flex flex-col items-center justify-between gap-6 md:flex-row\"><div class=\"flex items-center gap-2\"><img src=\"/public/brand.svg\" width=\"48\" height=\"48\" alt=\"Filekeeper Logo\"> <span class=\"text-lg font-semibold\">Filekeeper</span></div><nav class=\"flex flex-wrap items-center justify-center gap-4 text-sm font-medium\"><a class=\"hover:underline\" href=\"#\">Pricing</a> <a class=\"hover:underline\" href=\"#\">About</a> <a class=\"hover:underline\" href=\"#\">Contact</a> <a class=\"hover:underline\" href=\"#\">Terms</a> <a class=\"hover:underline\" href=\"#\">Privacy</a></nav><p class=\"text-sm text-gray-500\">© 2024 Filekeeper. All rights reserved.</p></div></footer>") _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<footer class=\"bg-white p-6 md:p-8 w-full bottom-0 border-t border-gray-200 w-full py-8\"><div class=\"container mx-auto flex flex-col items-center justify-between gap-6 md:flex-row\"><div class=\"flex items-center gap-2\"><img src=\"/public/brand.svg\" width=\"48\" height=\"48\" alt=\"Filekeeper Logo\"> <span class=\"text-lg font-semibold\">Filekeeper</span></div><nav class=\"flex flex-wrap items-center justify-center gap-4 text-sm font-medium\"><a class=\"hover:underline\" href=\"#\">Pricing</a> <a class=\"hover:underline\" href=\"#\">About</a> <a class=\"hover:underline\" href=\"#\">Contact</a> <a class=\"hover:underline\" href=\"#\">Terms</a> <a class=\"hover:underline\" href=\"#\">Privacy</a></nav><p class=\"text-sm text-gray-500\">© 2024 Filekeeper. All rights reserved.</p></div></footer>")

View File

@ -1,68 +0,0 @@
package uploadView
import "github.com/fossyy/filekeeper/view/client/layout"
templ content(title string) {
@layout.BaseAuth(title){
<div class="flex items-center min-h-screen p-4 sm:p-6 bg-gray-900 text-white">
<div class="mx-auto w-full max-w-md space-y-8">
<div class="rounded-lg border bg-card text-card-foreground shadow-sm w-full max-w-md" data-v0-t="card">
<div class="flex flex-col space-y-1.5 p-4">
<div class="flex items-center justify-center w-full">
<label for="dropzone-file"
class="flex flex-col items-center justify-center w-full h-64 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 dark:hover:bg-bray-800 dark:bg-gray-700 hover:bg-gray-100 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:bg-gray-600">
<div class="flex flex-col items-center justify-center pt-5 pb-6">
<svg class="w-8 h-8 mb-4 text-gray-400" aria-hidden="true"
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 16">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2"
d="M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2" />
</svg>
<p class="mb-2 text-sm text-gray-400 font-semibold">Click to upload or drag and drop</p>
</div>
<input id="dropzone-file" type="file" class="hidden" />
</label>
</div>
<div>
<div hidden>
<div class="flex items-center gap-x-3 whitespace-nowrap">
<div id="progress-fake" class="flex w-full h-2 rounded-full overflow-hidden bg-gray-700"
role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100">
<div id="progress-fake"
class="flex flex-col justify-center rounded-full overflow-hidden bg-teal-500 text-xs text-white text-center whitespace-nowrap transition duration-500">
</div>
</div>
<div class="w-6 text-end">
<span id="progress-fake" class="text-sm text-white">Starting...</span>
</div>
</div>
</div>
<div id="container"></div>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
document.addEventListener("dragover", function (event) {
event.preventDefault();
});
document.addEventListener("drop", async function (event) {
event.preventDefault();
const file = event.dataTransfer.files[0]
await handleFile(file)
});
document.getElementById('dropzone-file').addEventListener('change', async function(event) {
event.preventDefault();
const file = event.target.files[0]
await handleFile(file)
});
</script>
}
}
templ Main(title string) {
@content(title)
}

View File

@ -1,76 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.663
package uploadView
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import "context"
import "io"
import "bytes"
import "github.com/fossyy/filekeeper/view/client/layout"
func content(title string) 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_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var2 := 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)
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"flex items-center min-h-screen p-4 sm:p-6 bg-gray-900 text-white\"><div class=\"mx-auto w-full max-w-md space-y-8\"><div class=\"rounded-lg border bg-card text-card-foreground shadow-sm w-full max-w-md\" data-v0-t=\"card\"><div class=\"flex flex-col space-y-1.5 p-4\"><div class=\"flex items-center justify-center w-full\"><label for=\"dropzone-file\" class=\"flex flex-col items-center justify-center w-full h-64 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 dark:hover:bg-bray-800 dark:bg-gray-700 hover:bg-gray-100 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:bg-gray-600\"><div class=\"flex flex-col items-center justify-center pt-5 pb-6\"><svg class=\"w-8 h-8 mb-4 text-gray-400\" aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 20 16\"><path stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2\"></path></svg><p class=\"mb-2 text-sm text-gray-400 font-semibold\">Click to upload or drag and drop</p></div><input id=\"dropzone-file\" type=\"file\" class=\"hidden\"></label></div><div><div hidden><div class=\"flex items-center gap-x-3 whitespace-nowrap\"><div id=\"progress-fake\" class=\"flex w-full h-2 rounded-full overflow-hidden bg-gray-700\" role=\"progressbar\" aria-valuenow=\"100\" aria-valuemin=\"0\" aria-valuemax=\"100\"><div id=\"progress-fake\" class=\"flex flex-col justify-center rounded-full overflow-hidden bg-teal-500 text-xs text-white text-center whitespace-nowrap transition duration-500\"></div></div><div class=\"w-6 text-end\"><span id=\"progress-fake\" class=\"text-sm text-white\">Starting...</span></div></div></div><div id=\"container\"></div></div></div></div></div></div><script type=\"text/javascript\">\n document.addEventListener(\"dragover\", function (event) {\n event.preventDefault();\n });\n\n document.addEventListener(\"drop\", async function (event) {\n event.preventDefault();\n const file = event.dataTransfer.files[0]\n await handleFile(file)\n });\n\n document.getElementById('dropzone-file').addEventListener('change', async function(event) {\n event.preventDefault();\n const file = event.target.files[0]\n await handleFile(file)\n });\n </script>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer)
}
return templ_7745c5c3_Err
})
templ_7745c5c3_Err = layout.BaseAuth(title).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), 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) 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 = content(title).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
})
}

View File

@ -7,115 +7,120 @@ import (
templ content(title string, qrcode string, code string, user types.User, msg types.Message) { templ content(title string, qrcode string, code string, user types.User, msg types.Message) {
@layout.BaseAuth(title){ @layout.BaseAuth(title){
@layout.Navbar(user) @MainContent(qrcode, code, user, msg)
<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()
} }
} }
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) { templ Main(title string, qrcode string, code string, user types.User, msg types.Message) {
@content(title, qrcode, code, user, msg) @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() templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
} }
templ_7745c5c3_Err = layout.Navbar(user).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
}
_, 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)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err 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 { 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) { 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) 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) defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
} }
ctx = templ.InitializeContext(ctx) ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var8 := templ.GetChildren(ctx) templ_7745c5c3_Var9 := templ.GetChildren(ctx)
if templ_7745c5c3_Var8 == nil { if templ_7745c5c3_Var9 == nil {
templ_7745c5c3_Var8 = templ.NopComponent templ_7745c5c3_Var9 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = content(title, qrcode, code, user, msg).Render(ctx, templ_7745c5c3_Buffer) 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) { templ content(message types.Message, title string, user types.User, allowance *types.Allowance, ListSession []*session.SessionInfo) {
@layout.BaseAuth(title){ @layout.BaseAuth(title){
@layout.Navbar(user) @MainContent(user, allowance, ListSession, message)
<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> templ MainContent(user types.User, allowance *types.Allowance, ListSession []*session.SessionInfo, message types.Message) {
<h2 class="text-2xl font-bold tracking-tight">Profile</h2> @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="mt-6 grid gap-6">
<div class="grid gap-2"> <div class="grid gap-2">
<label <label
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
for="name"> for="currentPassword">
Name Current password
</label> </label>
<input <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" 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>
<div class="grid gap-2"> <div class="grid gap-2">
<label <label
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
for="email"> for="password">
Email New password
</label> </label>
<input <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" 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>
<div class="grid gap-2"> <div class="grid gap-2">
<label <label
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
for="profile-picture"> for="confirmPassword">
Profile Picture New password confirmation
</label> </label>
<div class="flex items-center gap-4"> <input
<div 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"
class="inline-flex items-center justify-center w-10 h-10 overflow-hidden bg-gray-100 rounded-full"> type="password" id="confirmPassword" placeholder="New password confirmation" />
<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>
<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> </div>
</section> </form>
<section> </section>
<h2 class="text-2xl font-bold tracking-tight">Session Management</h2> <div class="grid gap-1">
<div class="mt-6 grid gap-6"> <div class="flex items-center justify-between">
<div class="grid gap-2"> <a
<div class="flex items-center justify-between"> href="/logout"
<label 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">
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
for="two-factor"> fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
Two-Factor Authentication stroke-linejoin="round" class="mr-2 h-4 w-4">
</label> <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
<a <polyline points="16 17 21 12 16 7"></polyline>
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" <line x1="21" x2="9" y1="12" y2="12"></line>
type="button" id="radix-:rq:" aria-haspopup="menu" </svg>
aria-expanded="false" data-state="closed" Log Out
href="/user/totp/setup" hx-get="/user/totp/setup" hx-swap="outerHTML" hx-push-url="true" hx-target="#content"> </a>
Setup </div>
</a> <p class="text-sm text-gray-500">
</div> Click to log out or terminate the current session.
</div> </p>
<div class="grid gap-2"> </div>
<div class="bg-white rounded-lg shadow-md overflow-hidden"> </div>
<div class="w-full overflow-auto">
<table class="w-full caption-bottom text-sm"> <div class="space-y-8">
<thead class="[&amp;_tr]:border-b"> <div class="rounded-lg border bg-card text-card-foreground shadow-sm" data-v0-t="card">
<tr <div class="flex flex-row justify-between items-center p-6">
class="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted"> <h3 class="whitespace-nowrap text-2xl font-semibold leading-none tracking-tight">Storage Usage</h3>
<th <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">
class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&amp;:has([role=checkbox])]:pr-0"> <line x1="22" x2="2" y1="12" y2="12"></line>
IP Address <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>
</th> <line x1="6" x2="6.01" y1="16" y2="16"></line>
<th <line x1="10" x2="10.01" y1="16" y2="16"></line>
class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&amp;:has([role=checkbox])]:pr-0"> </svg>
Browser </div>
</th> <div class="p-6 grid gap-4">
<th <div class="text-2xl font-bold">{allowance.AllowanceUsedByte} / {allowance.AllowanceByte}</div>
class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&amp;:has([role=checkbox])]:pr-0"> <div class="w-full bg-gray-300 rounded-full h-2.5">
Device <div class="bg-gray-800 h-2.5 rounded-full" id="allowanceProgress" style="width: 0%;"></div>
</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>
</div> </div>
</section> <a
<section> 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"
<h2 class="text-2xl font-bold tracking-tight">Reset Password</h2> type="button" href="/file" hx-get="/file" hx-swap="innerHTML" hx-push-url="true" hx-target="#content">
switch message.Code { Manage
case 0: </a>
<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>
</div> </div>
</div> </div>
<div class="rounded-lg border bg-card text-card-foreground shadow-sm" data-v0-t="card">
<div class="space-y-8"> <div class="flex flex-col space-y-1.5 p-6">
<div class="rounded-lg border bg-card text-card-foreground shadow-sm" data-v0-t="card"> <h3 class="whitespace-nowrap text-2xl font-semibold leading-none tracking-tight">Upgrade Storage
<div class="flex flex-row justify-between items-center p-6"> </h3>
<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> </div>
<div class="rounded-lg border bg-card text-card-foreground shadow-sm" data-v0-t="card"> <div class="p-6 grid gap-4">
<div class="flex flex-col space-y-1.5 p-6"> <div class="grid gap-2">
<h3 class="whitespace-nowrap text-2xl font-semibold leading-none tracking-tight">Upgrade Storage <h3 class="text-lg font-semibold">Pro Plan</h3>
</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>
<div class="p-6 grid gap-4"> <div class="grid gap-2">
<div class="grid gap-2"> <h3 class="text-lg font-semibold">Enterprise Plan</h3>
<h3 class="text-lg font-semibold">Pro Plan</h3> <p class="text-gray-500">1TB of storage for $49.99/month</p>
<p class="text-gray-500">50GB of storage for $9.99/month</p> <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"
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="#">
type="button" href="#"> Upgrade
Upgrade </a>
</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> </div>
</div> </div>
</div> </div>
</main> </div>
@templ.JSONScript("AllowanceUsedPercent", allowance.AllowanceUsedPercent) </main>
<script type="text/javascript"> @layout.Footer()
document.getElementById('currentPassword').addEventListener('input', function() { @templ.JSONScript("AllowanceUsedPercent", allowance.AllowanceUsedPercent)
var validationBox = document.getElementById('validationBox'); <script type="text/javascript">
if (this.value.length > 0) { document.getElementById('currentPassword').addEventListener('input', function() {
validationBox.classList.remove('hidden'); var validationBox = document.getElementById('validationBox');
} else { if (this.value.length > 0) {
validationBox.classList.add('hidden'); validationBox.classList.remove('hidden');
} } else {
}); validationBox.classList.add('hidden');
}
});
window.allowanceProgress = document.getElementById(`allowanceProgress`); window.allowanceProgress = document.getElementById(`allowanceProgress`);
window.AllowanceUsedPercent = JSON.parse(document.getElementById('AllowanceUsedPercent').textContent); window.AllowanceUsedPercent = JSON.parse(document.getElementById('AllowanceUsedPercent').textContent);
allowanceProgress.style.width = `${AllowanceUsedPercent}%`; allowanceProgress.style.width = `${AllowanceUsedPercent}%`;
</script> </script>
<script src="/public/validatePassword.js" /> @layout.MainScript()
@layout.Footer() <script src="/public/validatePassword.js" />
}
} }
templ SessionTable(ListSession []*session.SessionInfo){ 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" 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" type="button" id="radix-:rq:" aria-haspopup="menu"
aria-expanded="false" data-state="closed" 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 Terminate
</button> </button>
</td> </td>

File diff suppressed because one or more lines are too long