refactor: move client page view file to client folder and reorganize file structure
This commit is contained in:
81
view/client/auth/auth.templ
Normal file
81
view/client/auth/auth.templ
Normal file
@ -0,0 +1,81 @@
|
||||
package authView
|
||||
|
||||
import (
|
||||
"github.com/fossyy/filekeeper/types"
|
||||
"github.com/fossyy/filekeeper/view/client/layout"
|
||||
)
|
||||
|
||||
templ form(err types.Message, title string) {
|
||||
@layout.Base(title){
|
||||
<div class="dark flex items-center min-h-screen p-4 sm:p-6 bg-gray-900">
|
||||
<div class="mx-auto w-full max-w-md space-y-8">
|
||||
<header class="text-center">
|
||||
<div class="space-y-2">
|
||||
<h1 class="text-3xl font-bold text-white">Set Up Your Account</h1>
|
||||
<p class="text-gray-500 dark:text-gray-400">Enter your information to create a new account</p>
|
||||
switch err.Code {
|
||||
case 0:
|
||||
<div class="p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400" role="alert">
|
||||
{err.Message}
|
||||
</div>
|
||||
case 1:
|
||||
<div class="p-4 mb-4 text-sm text-green-800 rounded-lg bg-green-50 dark:bg-gray-800 dark:text-green-400" role="alert">
|
||||
{err.Message}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</header>
|
||||
<form class="space-y-4" method="post" action="">
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-white" for="username">Username</label>
|
||||
<input type="text" 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 dark:bg-gray-800 dark:text-white" id="username" name="username" required="" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-white" for="password">Password</label>
|
||||
<input type="password" 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 dark:bg-gray-800 dark:text-white" id="password" name="password" required />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-white" for="confirmPassword">Confirm Password</label>
|
||||
<input type="password" 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 dark:bg-gray-800 dark:text-white" id="confirmPassword" required />
|
||||
</div>
|
||||
<div class="flex justify-start mt-3 ml-4 p-1">
|
||||
<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"> Passwords do not match</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"> Password length must be at least 8 characters</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<button class="bg-slate-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 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2 w-full" type="submit" id="submit" name="submit" disabled>
|
||||
Sign up
|
||||
</button>
|
||||
</form>
|
||||
<div class="text-center text-sm text-white">
|
||||
Already have an account?
|
||||
<a class="underline" href="/signin" rel="ugc" hx-get="/signin" hx-swap="outerHTML" hx-push-url="true" hx-target="#content">
|
||||
Sign in
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/public/validatePassword.js" />
|
||||
}
|
||||
}
|
||||
|
||||
templ GoogleSetup(title string, err types.Message) {
|
||||
@form(err, title)
|
||||
}
|
76
view/client/download/download.templ
Normal file
76
view/client/download/download.templ
Normal file
@ -0,0 +1,76 @@
|
||||
package downloadView
|
||||
|
||||
import (
|
||||
"github.com/fossyy/filekeeper/view/client/layout"
|
||||
"github.com/fossyy/filekeeper/types"
|
||||
)
|
||||
|
||||
templ component(title string, files []types.FileData){
|
||||
@layout.Base(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)
|
||||
}
|
111
view/client/email/email.templ
Normal file
111
view/client/email/email.templ
Normal file
@ -0,0 +1,111 @@
|
||||
package emailView
|
||||
|
||||
templ RegistrationEmail(name string, link string) {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Email Verification</title>
|
||||
<style>
|
||||
body, html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline-block;
|
||||
padding: 10px 20px;
|
||||
background-color: black;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Email Verification</h1>
|
||||
<p>Dear {name},</p>
|
||||
<p>Please verify your email address by clicking the button below:</p>
|
||||
<a href={ templ.SafeURL(link)} class="button">Verify Email</a>
|
||||
<p>Or copy and paste this URL into a new tab of your browser: <a href={ templ.SafeURL(link)}><br/>{link}</a></p>
|
||||
<p>If you did not request this verification, please disregard this email.</p>
|
||||
<p>Thank you, <br/> The Filekeeper Team</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
|
||||
templ ForgotPassword(name string, link string) {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Email Verification</title>
|
||||
<style>
|
||||
body, html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline-block;
|
||||
padding: 10px 20px;
|
||||
background-color: black;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Password Change Request</h1>
|
||||
<p>Dear {name},</p>
|
||||
<p>Please verify your password change request by clicking the button below:</p>
|
||||
<a href={ templ.SafeURL(link)} class="button">Verify Password Change</a>
|
||||
<p>Or copy and paste this URL into a new tab of your browser: <a href={ templ.SafeURL(link)}><br/>{link}</a></p>
|
||||
<p>If you did not request this password change, please disregard this email.</p>
|
||||
<p>Thank you, <br/> The Filekeeper Team</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
}
|
59
view/client/error/error.templ
Normal file
59
view/client/error/error.templ
Normal file
@ -0,0 +1,59 @@
|
||||
package errorView
|
||||
|
||||
import "github.com/fossyy/filekeeper/view/client/layout"
|
||||
|
||||
templ NotFound(title string){
|
||||
@layout.Base(title){
|
||||
<div class="flex flex-col items-center justify-center w-full min-h-[calc(100vh-1rem)] py-10 text-center gap-4 md:gap-8">
|
||||
<div class="space-y-2">
|
||||
<h1 class="text-4xl font-bold tracking-tighter sm:text-5xl">404 Not Found</h1>
|
||||
<p class="max-w-[600px] text-gray-500 md:text-xl/relaxed lg:text-base/relaxed xl:text-xl/relaxed">
|
||||
The page you are looking for does not exist. It might have been moved or deleted.
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
class="inline-flex h-10 items-center rounded-md border border-gray-200 border-gray-200 bg-white px-8 text-sm font-medium shadow-sm gap-2 transition-colors hover:bg-gray-100 hover:text-gray-900 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-gray-950"
|
||||
href="/" hx-get="/" hx-swap="outerHTML" hx-push-url="true" hx-target="#content"
|
||||
>
|
||||
<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="w-4 h-4"
|
||||
>
|
||||
<path d="m9 18 6-6-6-6"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
templ InternalServerError(title string){
|
||||
@layout.Base(title){
|
||||
<main class="container mx-auto px-4 md:px-6">
|
||||
<div class="flex h-screen w-full flex-col items-center justify-center bg-white">
|
||||
<image class="w-32 md:w-64 lg:w-128" src="/public/InternalServerErrorIcon.svg" alt="Cute Icon" />
|
||||
<div class="mx-auto max-w-md space-y-4 text-center">
|
||||
<h1 class="text-4xl font-bold tracking-tight text-gray-900">Oops! Something went wrong.</h1>
|
||||
<p class="text-gray-500">
|
||||
We're sorry, but an internal server error has occurred. Please try again later.
|
||||
</p>
|
||||
<div class="grid gap-2">
|
||||
<a
|
||||
class="inline-flex h-10 items-center justify-center rounded-md bg-gray-900 px-6 text-sm font-medium text-gray-50 shadow transition-colors hover:bg-gray-900/90 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-gray-950 disabled:pointer-events-none disabled:opacity-50"
|
||||
href="/" hx-get="/" hx-swap="outerHTML" hx-push-url="true" hx-target="#content"
|
||||
>
|
||||
Go back to homepage
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
}
|
||||
}
|
160
view/client/forgotPassword/forgotPassword.templ
Normal file
160
view/client/forgotPassword/forgotPassword.templ
Normal file
@ -0,0 +1,160 @@
|
||||
package forgotPasswordView
|
||||
|
||||
import (
|
||||
"github.com/fossyy/filekeeper/types"
|
||||
"github.com/fossyy/filekeeper/view/client/layout"
|
||||
)
|
||||
|
||||
templ content(title string, err types.Message) {
|
||||
@layout.Base(title){
|
||||
<div class="dark flex items-center min-h-screen p-4 sm:p-6 bg-gray-900">
|
||||
<div class="mx-auto w-full max-w-md space-y-8">
|
||||
<header class="text-center">
|
||||
<div class="space-y-2">
|
||||
<h1 class="text-3xl font-bold text-white">Forgot password</h1>
|
||||
<p class="text-gray-500 dark:text-gray-400">Enter your email below to reset your password</p>
|
||||
switch err.Code {
|
||||
case 0:
|
||||
<div class="p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400" role="alert">
|
||||
{err.Message}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</header>
|
||||
<form class="space-y-4" method="post" action="">
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-white" for="email">Email</label>
|
||||
<input type="email" 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 dark:bg-gray-800 dark:text-white" id="email" name="email" placeholder="m@example.com" required="" />
|
||||
</div>
|
||||
<button class="bg-slate-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 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2 w-full" type="submit">
|
||||
Submit
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
templ Main(title string, err types.Message) {
|
||||
@content(title, err)
|
||||
}
|
||||
|
||||
templ NewPasswordForm(title string, err types.Message) {
|
||||
@layout.Base(title){
|
||||
<div class="dark flex items-center min-h-screen p-4 sm:p-6 bg-gray-900">
|
||||
<div class="mx-auto w-full max-w-md space-y-8">
|
||||
<header class="text-center">
|
||||
<div class="space-y-2">
|
||||
<h1 class="text-3xl font-bold text-white">Forgot password</h1>
|
||||
<p class="text-gray-500 dark:text-gray-400">Enter your email below to reset your password</p>
|
||||
switch err.Code {
|
||||
case 0:
|
||||
<div class="p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400" role="alert">
|
||||
{err.Message}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</header>
|
||||
<form class="space-y-4" method="post" action="">
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-white" for="password">Password</label>
|
||||
<input type="password" 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 dark:bg-gray-800 dark:text-white" id="password" name="password" required />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-white" for="confirmPassword">Confirm Password</label>
|
||||
<input type="password" 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 dark:bg-gray-800 dark:text-white" id="confirmPassword" required />
|
||||
</div>
|
||||
<div class="flex justify-start mt-3 ml-4 p-1">
|
||||
<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"> Passwords do not match</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"> Password length must be at least 8 characters</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<button class="bg-slate-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 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2 w-full" type="submit" id="submit" name="submit" disabled>
|
||||
Submit
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/public/validatePassword.js" />
|
||||
}
|
||||
}
|
||||
|
||||
templ EmailSend(title string) {
|
||||
@layout.Base(title){
|
||||
<style>h1, h2, h3, h4, h5, h6 { font-family: 'Arimo', sans-serif; --font-sans: 'Arimo'; }</style>
|
||||
<style>body { font-family: 'Libre Franklin', sans-serif; --font-sans: 'Libre Franklin'; }</style>
|
||||
<div class="flex flex-col items-center justify-center min-h-[80vh] gap-6">
|
||||
<div class="flex items-center justify-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-16 w-16 text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
<rect width="20" height="16" x="2" y="4" rx="2"></rect>
|
||||
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="space-y-2 text-center">
|
||||
<h1 class="text-3xl font-bold">Email Verification Sent</h1>
|
||||
<p class="text-gray-500 dark:text-gray-400">
|
||||
We've sent a verification email to your inbox. Please check your email and follow the instructions to change your password.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
templ ChangeSuccess(title string) {
|
||||
@layout.Base(title){
|
||||
<style>h1, h2, h3, h4, h5, h6 { font-family: 'Arimo', sans-serif; --font-sans: 'Arimo'; }</style>
|
||||
<style>body { font-family: 'Libre Franklin', sans-serif; --font-sans: 'Libre Franklin'; }</style>
|
||||
<div class="flex flex-col items-center justify-center min-h-[80vh] gap-6">
|
||||
<div class="bg-green-500 text-white rounded-full p-4">
|
||||
<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-8 w-8"
|
||||
>
|
||||
<polyline points="20 6 9 17 4 12"></polyline>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="space-y-2 text-center">
|
||||
<h1 class="text-3xl font-bold">Password Changed Successfully</h1>
|
||||
<p class="text-gray-500 dark:text-gray-400">
|
||||
Your password has been successfully updated. Feel free to continue enjoying our platform.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
120
view/client/index/index.templ
Normal file
120
view/client/index/index.templ
Normal file
@ -0,0 +1,120 @@
|
||||
package indexView
|
||||
|
||||
import (
|
||||
"github.com/fossyy/filekeeper/types"
|
||||
"github.com/fossyy/filekeeper/view/client/layout"
|
||||
)
|
||||
|
||||
templ content(title string, user types.User) {
|
||||
@layout.Base(title){
|
||||
<div class="bg-white">
|
||||
@layout.Navbar(user)
|
||||
<main class="container mx-auto px-6 py-16 text-center">
|
||||
<h1 class="text-5xl font-bold text-gray-900 mb-2">Your files, always within reach</h1>
|
||||
<p class="text-gray-700 text-lg mb-8">
|
||||
Store, access, and share your files from anywhere. We offer secure and reliable file storage, so you can
|
||||
focus on what matters most.
|
||||
</p>
|
||||
<div class="flex justify-center items-center space-x-4">
|
||||
<div class="sm:flex sm:justify-center lg:justify-start">
|
||||
<div class="rounded-md shadow">
|
||||
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"
|
||||
href="/user" hx-get="/user" hx-swap="outerHTML" hx-push-url="true" hx-target="#content">
|
||||
Open Dashboard
|
||||
</a>
|
||||
} 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"
|
||||
href="/signup" hx-get="/signup" hx-swap="outerHTML" hx-push-url="true" hx-target="#content">
|
||||
Get started
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="w-full py-12 md:py-24 lg:py-32">
|
||||
<div class="container px-4 md:px-6">
|
||||
<div class="grid gap-8 sm:grid-cols-2 md:grid-cols-3">
|
||||
<div class="grid gap-1 items-center">
|
||||
<div class="flex justify-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-8 w-8 text-gray-900"
|
||||
>
|
||||
<path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-lg font-bold">Unlimited Storage</h3>
|
||||
<p class="text-gray-500">
|
||||
Store as many files as you need with our generous storage limits.
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid gap-1 items-center">
|
||||
<div class="flex justify-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-8 w-8 text-gray-900"
|
||||
>
|
||||
<rect width="18" height="11" x="3" y="11" rx="2" ry="2"></rect>
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-lg font-bold">Secure Encryption</h3>
|
||||
<p class="text-gray-500">
|
||||
Your files are encrypted with the latest security protocols to keep them safe.
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid gap-1 items-center">
|
||||
<div class="flex justify-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-8 w-8 text-gray-900"
|
||||
>
|
||||
<path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"></path>
|
||||
<polyline points="16 6 12 2 8 6"></polyline>
|
||||
<line x1="12" x2="12" y1="2" y2="15"></line>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-lg font-bold">Easy Sharing</h3>
|
||||
<p class="text-gray-500">
|
||||
Quickly share files with friends, family, or colleagues with shareable links.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@layout.Footer()
|
||||
}
|
||||
}
|
||||
|
||||
templ Main(title string, user types.User) {
|
||||
@content(title, user)
|
||||
}
|
114
view/client/layout/base.templ
Normal file
114
view/client/layout/base.templ
Normal file
@ -0,0 +1,114 @@
|
||||
package layout
|
||||
|
||||
import "github.com/fossyy/filekeeper/types"
|
||||
|
||||
templ Base(title string){
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="Secure and reliable file hosting service. Upload, organize, and share your documents, images, videos, and more. Sign up now to keep your files always within reach." />
|
||||
<meta name="keywords" content="file hosting, file sharing, cloud storage, data storage, secure file hosting, filekeeper, drive, mega" />
|
||||
<meta name="author" content="Filekeeper" />
|
||||
<link href="/public/output.css" rel="stylesheet"/>
|
||||
<title>{ title }</title>
|
||||
<script src="https://unpkg.com/htmx.org@1.9.12"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="content">
|
||||
{ children... }
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
|
||||
templ Navbar(user types.User) {
|
||||
<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">
|
||||
if user.Authenticated {
|
||||
<div class="flex items-center gap-4">
|
||||
<div
|
||||
class="relative inline-flex items-center justify-center w-10 h-10 overflow-hidden bg-gray-100 rounded-full">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"
|
||||
width="256" height="256" viewBox="0 0 256 256" xml:space="preserve">
|
||||
<defs>
|
||||
</defs>
|
||||
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;"
|
||||
transform="translate(1.4065934065934016 1.4065934065934016) scale(2.81 2.81)">
|
||||
<circle cx="45" cy="45" r="44"
|
||||
style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(178,178,178); fill-rule: nonzero; opacity: 1;"
|
||||
transform=" matrix(1 0 0 1 0 0) " />
|
||||
<circle cx="44.997" cy="39.727000000000004" r="19.817"
|
||||
style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(109,109,109); fill-rule: nonzero; opacity: 1;"
|
||||
transform=" matrix(1 0 0 1 0 0) " />
|
||||
<path
|
||||
d="M 11.266 73.25 C 19.337 63.622 31.454 57.5 45 57.5 c 13.546 0 25.663 6.122 33.734 15.75 l 0 0 C 70.663 82.878 58.547 89 45 89 C 31.454 89 19.337 82.878 11.266 73.25 L 11.266 73.25 z"
|
||||
style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(109,109,109); fill-rule: nonzero; opacity: 1;"
|
||||
transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
|
||||
<path
|
||||
d="M 45 90 C 20.187 90 0 69.813 0 45 C 0 20.187 20.187 0 45 0 c 24.813 0 45 20.187 45 45 C 90 69.813 69.813 90 45 90 z M 45 2 C 21.29 2 2 21.29 2 45 c 0 23.71 19.29 43 43 43 c 23.71 0 43 -19.29 43 -43 C 88 21.29 68.71 2 45 2 z"
|
||||
style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(43,43,43); fill-rule: nonzero; opacity: 1;"
|
||||
transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
|
||||
<path
|
||||
d="M 78.734 73.25 c -6.576 -7.844 -15.837 -13.358 -26.368 -15.133 c 7.294 -2.925 12.451 -10.048 12.451 -18.387 c 0 -10.945 -8.873 -19.817 -19.817 -19.817 S 25.183 28.785 25.183 39.73 c 0 8.339 5.157 15.462 12.451 18.387 c -10.531 1.775 -19.793 7.29 -26.368 15.133 v 0 C 19.337 82.878 31.454 89 45 89 C 58.547 89 70.663 82.878 78.734 73.25 L 78.734 73.25 z"
|
||||
style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(109,109,109); fill-rule: nonzero; opacity: 1;"
|
||||
transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
|
||||
<path
|
||||
d="M 45 90 c -13.344 0 -25.919 -5.871 -34.5 -16.107 L 9.961 73.25 l 0.539 -0.643 c 6.239 -7.441 14.692 -12.654 24.046 -14.883 c -6.379 -3.687 -10.363 -10.467 -10.363 -17.995 c 0 -11.479 9.339 -20.817 20.817 -20.817 s 20.817 9.339 20.817 20.817 c 0 7.528 -3.983 14.309 -10.362 17.995 c 9.354 2.229 17.808 7.441 24.046 14.883 l 0.538 0.643 l -0.538 0.643 C 70.919 84.129 58.344 90 45 90 z M 12.581 73.25 C 20.764 82.635 32.531 88 45 88 c 12.47 0 24.236 -5.365 32.419 -14.75 C 70.887 65.761 61.964 60.748 52.2 59.104 l -3.506 -0.591 l 3.3 -1.323 c 7.183 -2.882 11.823 -9.734 11.823 -17.46 c 0 -10.376 -8.441 -18.817 -18.817 -18.817 s -18.817 8.441 -18.817 18.817 c 0 7.726 4.641 14.578 11.823 17.46 l 3.3 1.323 L 37.8 59.104 C 28.037 60.748 19.114 65.76 12.581 73.25 z"
|
||||
style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(43,43,43); fill-rule: nonzero; opacity: 1;"
|
||||
transform=" matrix(1 0 0 1 0 0) " stroke-linecap="round" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="font-medium hidden sm:block">
|
||||
<div>{ user.Username }</div>
|
||||
<div class="text-sm text-gray-500">{ user.Email }</div>
|
||||
</div>
|
||||
</div>
|
||||
} else {
|
||||
<a href="/signup" class="text-gray-600 hover:text-gray-800" hx-get="/signup" hx-swap="outerHTML" hx-push-url="true" hx-target="#content">
|
||||
Sign up
|
||||
</a>
|
||||
<a href="/signin" class="text-gray-600 hover:text-gray-800" hx-get="/signin" hx-swap="outerHTML" hx-push-url="true" hx-target="#content">
|
||||
Sign in
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</header>
|
||||
}
|
||||
|
||||
templ Footer() {
|
||||
<footer class="bg-white p-6 md:p-8 w-full relative 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>
|
||||
}
|
66
view/client/signin/signin.templ
Normal file
66
view/client/signin/signin.templ
Normal file
@ -0,0 +1,66 @@
|
||||
package signinView
|
||||
|
||||
import (
|
||||
"github.com/fossyy/filekeeper/types"
|
||||
"github.com/fossyy/filekeeper/view/client/layout"
|
||||
)
|
||||
|
||||
templ content(err types.Message, title string) {
|
||||
@layout.Base(title){
|
||||
<div class="dark flex items-center min-h-screen p-4 sm:p-6 bg-gray-900">
|
||||
<div class="mx-auto w-full max-w-md space-y-8">
|
||||
<header class="text-center">
|
||||
<div class="space-y-2">
|
||||
<h1 class="text-3xl font-bold text-white">Sign In</h1>
|
||||
<p class="text-gray-500 dark:text-gray-400">Enter your email or username below to login to your account</p>
|
||||
switch err.Code {
|
||||
case 0:
|
||||
<div class="p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400" role="alert">
|
||||
{err.Message}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</header>
|
||||
<form class="space-y-4" method="post" action="">
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-white" for="email">Email</label>
|
||||
<input type="email" 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 dark:bg-gray-800 dark:text-white" id="email" name="email" placeholder="m@example.com" required="" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center">
|
||||
<label class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-white" for="password">Password</label>
|
||||
<a class="ml-auto inline-block text-sm underline text-gray-300 dark:text-gray-400" href="/forgot-password" rel="ugc" hx-get="/forgot-password" hx-swap="outerHTML" hx-push-url="true" hx-target="#content">
|
||||
Forgot your password?
|
||||
</a>
|
||||
</div>
|
||||
<input type="password" 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 dark:bg-gray-800 dark:text-white" id="password" name="password" required="" />
|
||||
</div>
|
||||
<button class="bg-slate-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 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2 w-full" type="submit">
|
||||
Login
|
||||
</button>
|
||||
</form>
|
||||
<div
|
||||
class="my-4 flex items-center before:mt-0.5 before:flex-1 before:border-t before:border-neutral-300 after:mt-0.5 after:flex-1 after:border-t after:border-neutral-300">
|
||||
<p
|
||||
class="mx-4 mb-0 text-white text-center font-semibold">
|
||||
OR
|
||||
</p>
|
||||
</div>
|
||||
<a href="/auth/google" 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 h-10 px-4 py-2 w-full">
|
||||
<svg class="h-6 w-6 mr-2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="800px" height="800px" viewBox="-0.5 0 48 48" version="1.1"> <title>Google-color</title> <desc>Created with Sketch.</desc> <defs> </defs> <g id="Icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="Color-" transform="translate(-401.000000, -860.000000)"> <g id="Google" transform="translate(401.000000, 860.000000)"> <path d="M9.82727273,24 C9.82727273,22.4757333 10.0804318,21.0144 10.5322727,19.6437333 L2.62345455,13.6042667 C1.08206818,16.7338667 0.213636364,20.2602667 0.213636364,24 C0.213636364,27.7365333 1.081,31.2608 2.62025,34.3882667 L10.5247955,28.3370667 C10.0772273,26.9728 9.82727273,25.5168 9.82727273,24" id="Fill-1" fill="#FBBC05"> </path> <path d="M23.7136364,10.1333333 C27.025,10.1333333 30.0159091,11.3066667 32.3659091,13.2266667 L39.2022727,6.4 C35.0363636,2.77333333 29.6954545,0.533333333 23.7136364,0.533333333 C14.4268636,0.533333333 6.44540909,5.84426667 2.62345455,13.6042667 L10.5322727,19.6437333 C12.3545909,14.112 17.5491591,10.1333333 23.7136364,10.1333333" id="Fill-2" fill="#EB4335"> </path> <path d="M23.7136364,37.8666667 C17.5491591,37.8666667 12.3545909,33.888 10.5322727,28.3562667 L2.62345455,34.3946667 C6.44540909,42.1557333 14.4268636,47.4666667 23.7136364,47.4666667 C29.4455,47.4666667 34.9177955,45.4314667 39.0249545,41.6181333 L31.5177727,35.8144 C29.3995682,37.1488 26.7323182,37.8666667 23.7136364,37.8666667" id="Fill-3" fill="#34A853"> </path> <path d="M46.1454545,24 C46.1454545,22.6133333 45.9318182,21.12 45.6113636,19.7333333 L23.7136364,19.7333333 L23.7136364,28.8 L36.3181818,28.8 C35.6879545,31.8912 33.9724545,34.2677333 31.5177727,35.8144 L39.0249545,41.6181333 C43.3393409,37.6138667 46.1454545,31.6490667 46.1454545,24" id="Fill-4" fill="#4285F4"> </path> </g> </g> </g> </svg>
|
||||
<span>Continue with Google</span>
|
||||
</a>
|
||||
<div class="text-center text-sm text-white">
|
||||
Don't have an account?
|
||||
<a class="underline" href="/signup" rel="ugc" hx-get="/signup" hx-swap="outerHTML" hx-push-url="true" hx-target="#content">
|
||||
Sign up
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
templ Main(title string, err types.Message) {
|
||||
@content(err, title)
|
||||
}
|
150
view/client/signup/signup.templ
Normal file
150
view/client/signup/signup.templ
Normal file
@ -0,0 +1,150 @@
|
||||
package signup
|
||||
|
||||
import (
|
||||
"github.com/fossyy/filekeeper/types"
|
||||
"github.com/fossyy/filekeeper/view/client/layout"
|
||||
)
|
||||
|
||||
templ form(err types.Message, title string) {
|
||||
@layout.Base(title){
|
||||
<div class="dark flex items-center min-h-screen p-4 sm:p-6 bg-gray-900">
|
||||
<div class="mx-auto w-full max-w-md space-y-8">
|
||||
<header class="text-center">
|
||||
<div class="space-y-2">
|
||||
<h1 class="text-3xl font-bold text-white">Sign Up</h1>
|
||||
<p class="text-gray-500 dark:text-gray-400">Enter your information to create an account</p>
|
||||
switch err.Code {
|
||||
case 0:
|
||||
<div class="p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400" role="alert">
|
||||
{err.Message}
|
||||
</div>
|
||||
case 1:
|
||||
<div class="p-4 mb-4 text-sm text-green-800 rounded-lg bg-green-50 dark:bg-gray-800 dark:text-green-400" role="alert">
|
||||
{err.Message}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</header>
|
||||
<form class="space-y-4" method="post" action="">
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-white" for="email">Email</label>
|
||||
<input type="email" 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 dark:bg-gray-800 dark:text-white" id="email" name="email" placeholder="m@example.com" required="" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center">
|
||||
<label class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-white" for="password">Username</label>
|
||||
</div>
|
||||
<input type="text" 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 dark:bg-gray-800 dark:text-white" id="username" name="username" required="" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-white" for="password">Password</label>
|
||||
<input type="password" 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 dark:bg-gray-800 dark:text-white" id="password" name="password" required />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-white" for="confirmPassword">Confirm Password</label>
|
||||
<input type="password" 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 dark:bg-gray-800 dark:text-white" id="confirmPassword" required />
|
||||
</div>
|
||||
<div class="flex justify-start mt-3 ml-4 p-1">
|
||||
<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"> Passwords do not match</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"> Password length must be at least 8 characters</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<button class="bg-slate-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 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2 w-full" type="submit" id="submit" name="submit" disabled>
|
||||
Sign up
|
||||
</button>
|
||||
</form>
|
||||
<div class="text-center text-sm text-white">
|
||||
Already have an account?
|
||||
<a class="underline" href="/signin" rel="ugc" hx-get="/signin" hx-swap="outerHTML" hx-push-url="true" hx-target="#content">
|
||||
Sign in
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/public/validatePassword.js" />
|
||||
}
|
||||
}
|
||||
|
||||
templ Main(title string, err types.Message) {
|
||||
@form(err, title)
|
||||
}
|
||||
|
||||
templ EmailSend(title string) {
|
||||
@layout.Base(title){
|
||||
<style>h1, h2, h3, h4, h5, h6 { font-family: 'Arimo', sans-serif; --font-sans: 'Arimo'; }</style>
|
||||
<style>body { font-family: 'Libre Franklin', sans-serif; --font-sans: 'Libre Franklin'; }</style>
|
||||
<div class="flex flex-col items-center justify-center min-h-[80vh] gap-6">
|
||||
<div class="flex items-center justify-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-16 w-16 text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
<rect width="20" height="16" x="2" y="4" rx="2"></rect>
|
||||
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="space-y-2 text-center">
|
||||
<h1 class="text-3xl font-bold">Email Verification Sent</h1>
|
||||
<p class="text-gray-500 dark:text-gray-400">
|
||||
We've sent a verification email to your inbox. Please check your email and follow the instructions to verify your account.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
templ VerifySuccess(title string) {
|
||||
@layout.Base(title){
|
||||
<style>h1, h2, h3, h4, h5, h6 { font-family: 'Arimo', sans-serif; --font-sans: 'Arimo'; }</style>
|
||||
<style>body { font-family: 'Libre Franklin', sans-serif; --font-sans: 'Libre Franklin'; }</style>
|
||||
<div class="flex flex-col items-center justify-center min-h-[80vh] gap-6">
|
||||
<div class="bg-green-500 text-white rounded-full p-4">
|
||||
<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-8 w-8"
|
||||
>
|
||||
<polyline points="20 6 9 17 4 12"></polyline>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="space-y-2 text-center">
|
||||
<h1 class="text-3xl font-bold">Account Verified</h1>
|
||||
<p class="text-gray-500 dark:text-gray-400">
|
||||
Your account has been successfully verified. You can now access all the features of our platform.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
67
view/client/totp/totp.templ
Normal file
67
view/client/totp/totp.templ
Normal file
@ -0,0 +1,67 @@
|
||||
package totpView
|
||||
|
||||
import (
|
||||
"github.com/fossyy/filekeeper/view/client/layout"
|
||||
"github.com/fossyy/filekeeper/types"
|
||||
)
|
||||
|
||||
templ content(title string, msg types.Message) {
|
||||
@layout.Base(title){
|
||||
<main class="container mx-auto px-4 py-12 md:px-6 md:py-16 lg:py-10">
|
||||
<div class="flex min-h-screen items-center justify-center bg-background px-4 py-12 sm:px-6 lg:px-8">
|
||||
<div class="w-full max-w-md space-y-8">
|
||||
<div>
|
||||
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>
|
||||
}
|
||||
<h2 class="mt-6 text-center text-3xl font-bold tracking-tight text-foreground">Verify Your Identity</h2>
|
||||
<p class="mt-2 text-center text-sm text-muted-foreground">
|
||||
Please enter the 6-digit code generated by your authentication app to complete the login process.
|
||||
</p>
|
||||
</div>
|
||||
<form class="space-y-6" method="POST">
|
||||
<div>
|
||||
<label for="code" class="block text-sm font-medium text-muted-foreground">
|
||||
Verification Code
|
||||
</label>
|
||||
<div class="mt-1">
|
||||
<input
|
||||
class="h-10 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 block w-full appearance-none rounded-md border border-input bg-background px-3 py-2 placeholder-muted-foreground shadow-sm focus:border-primary focus:outline-none focus:ring-primary sm:text-sm"
|
||||
id="code"
|
||||
autocomplete="one-time-code"
|
||||
required=""
|
||||
placeholder="123456"
|
||||
pattern="[0-9]{6}"
|
||||
maxlength="6"
|
||||
type="text"
|
||||
name="code"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
class="items-center whitespace-nowrap 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 h-10 flex w-full justify-center rounded-md bg-black py-2 px-4 text-sm font-medium text-primary-foreground shadow-sm text-white hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
|
||||
type="submit"
|
||||
>
|
||||
Verify Code
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
}
|
||||
}
|
||||
|
||||
templ Main(title string, msg types.Message) {
|
||||
@content(title, msg)
|
||||
}
|
52
view/client/upload/upload.templ
Normal file
52
view/client/upload/upload.templ
Normal file
@ -0,0 +1,52 @@
|
||||
package uploadView
|
||||
|
||||
import "github.com/fossyy/filekeeper/view/client/layout"
|
||||
|
||||
templ content(title string) {
|
||||
@layout.Base(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 src="/public/upload.js" />
|
||||
}
|
||||
}
|
||||
|
||||
templ Main(title string) {
|
||||
@content(title)
|
||||
}
|
121
view/client/user/totp/setup.templ
Normal file
121
view/client/user/totp/setup.templ
Normal file
@ -0,0 +1,121 @@
|
||||
package userTotpSetupView
|
||||
|
||||
import (
|
||||
"github.com/fossyy/filekeeper/view/client/layout"
|
||||
"github.com/fossyy/filekeeper/types"
|
||||
)
|
||||
|
||||
templ content(title string, qrcode string, code string, user types.User, msg types.Message) {
|
||||
@layout.Base(title){
|
||||
@layout.Navbar(user)
|
||||
<main class="container mx-auto px-4 py-12 md:px-6 md:py-16 lg:py-10">
|
||||
<a
|
||||
class="inline-flex items-center space-x-2 rounded-md bg-muted px-4 py-2 text-sm font-medium text-muted-foreground transition-colors hover:bg-muted/80 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
||||
href="/user" hx-get="/user" hx-swap="outerHTML" hx-push-url="true" hx-target="#content"
|
||||
rel="ugc"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="h-4 w-4"
|
||||
>
|
||||
<path d="m12 19-7-7 7-7"></path>
|
||||
<path d="M19 12H5"></path>
|
||||
</svg>
|
||||
<span>Back</span>
|
||||
</a>
|
||||
<div class="mx-auto max-w-md px-4 py-12 sm:px-6 lg:px-8">
|
||||
<div class="space-y-6 text-center">
|
||||
<div class="flex items-center">
|
||||
<h1 class="text-3xl font-bold">Set up Two-Factor Authentication</h1>
|
||||
</div>
|
||||
<p class="text-muted-foreground">Secure your account with time-based one-time passwords (TOTP).</p>
|
||||
<div class="mt-4 text-left text-muted-foreground">
|
||||
<p>Here's how to set up the Google Authenticator app:</p>
|
||||
<ol class="list-decimal pl-6">
|
||||
<li>Download the Google Authenticator app on your mobile device.</li>
|
||||
<li>Open the app and tap "Begin Setup".</li>
|
||||
<li>Select "Scan a barcode" and point your camera at the QR code below.</li>
|
||||
<li>The app will automatically add your account and display a 6-digit code.</li>
|
||||
<li>Enter this code on the website to complete the setup.</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-lg border rounded-lg bg-muted p-6bg-card text-card-foreground shadow-sm mt-5" data-v0-t="card">
|
||||
<div class="p-6 space-y-6">
|
||||
switch msg.Code {
|
||||
case 0:
|
||||
<div class="flex items-center p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50" role="alert">
|
||||
<svg class="flex-shrink-0 inline w-4 h-4 me-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z"/>
|
||||
</svg>
|
||||
<span class="sr-only">Info</span>
|
||||
<div>
|
||||
<span class="font-medium">Error!</span> {msg.Message}
|
||||
</div>
|
||||
</div>
|
||||
case 1:
|
||||
<div class="flex items-center p-4 mb-4 text-sm text-green-800 rounded-lg bg-green-50" role="alert">
|
||||
<svg class="flex-shrink-0 inline w-4 h-4 me-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z"/>
|
||||
</svg>
|
||||
<span class="sr-only">Info</span>
|
||||
<div>
|
||||
<span class="font-medium">Success!</span> {msg.Message}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="flex items-center justify-center">
|
||||
<img
|
||||
src={"data:image/png;base64," + qrcode}
|
||||
width="200"
|
||||
height="200"
|
||||
alt="QR Code"
|
||||
class="rounded-lg"
|
||||
style="aspect-ratio: 200 / 200; object-fit: cover;"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-6 space-y-2">
|
||||
<p class="font-medium">Backup Code:</p>
|
||||
<div class="rounded-md bg-background px-4 py-2 text-sm font-mono text-muted-foreground">----|----</div>
|
||||
<p class="font-medium">TOTP Secret:</p>
|
||||
<div class="rounded-md bg-background px-4 py-2 text-sm font-mono text-muted-foreground">
|
||||
{code}
|
||||
</div>
|
||||
</div>
|
||||
<form method="post" action="/user/totp/setup">
|
||||
<div class="grid gap-2">
|
||||
<label
|
||||
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
for="totp">
|
||||
Totp Code
|
||||
</label>
|
||||
<input id="secret" name="secret" value={code} type='hidden' />
|
||||
<input
|
||||
class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
id="totp" name="totp" placeholder="Code from authenticator app" />
|
||||
<div class="flex items-center p-6">
|
||||
<button class="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2 w-full" type="submit">
|
||||
Enable TOTP
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
@layout.Footer()
|
||||
}
|
||||
}
|
||||
|
||||
templ Main(title string, qrcode string, code string, user types.User, msg types.Message) {
|
||||
@content(title, qrcode, code, user, msg)
|
||||
}
|
335
view/client/user/user.templ
Normal file
335
view/client/user/user.templ
Normal file
@ -0,0 +1,335 @@
|
||||
package userView
|
||||
|
||||
import (
|
||||
"github.com/fossyy/filekeeper/types"
|
||||
"github.com/fossyy/filekeeper/view/client/layout"
|
||||
"github.com/fossyy/filekeeper/session"
|
||||
)
|
||||
|
||||
templ content(message types.Message, title string, user types.User, ListSession []*session.SessionInfo) {
|
||||
@layout.Base(title){
|
||||
@layout.Navbar(user)
|
||||
<main class="container mx-auto px-4 py-12 md:px-6 md:py-16 lg:py-10">
|
||||
<div class="grid gap-10 lg:grid-cols-[1fr_300px]">
|
||||
<div class="space-y-8">
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold tracking-tight">Profile</h2>
|
||||
<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="relative 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="outerHTML" hx-push-url="true" hx-target="#content">
|
||||
Setup
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<div class="bg-white rounded-lg shadow-md overflow-hidden">
|
||||
<div class="relative w-full overflow-auto">
|
||||
<table class="w-full caption-bottom text-sm">
|
||||
<thead class="[&_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 [&:has([role=checkbox])]:pr-0">
|
||||
IP Address
|
||||
</th>
|
||||
<th
|
||||
class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0">
|
||||
Browser
|
||||
</th>
|
||||
<th
|
||||
class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0">
|
||||
Device
|
||||
</th>
|
||||
<th
|
||||
class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0">
|
||||
Last Activity
|
||||
</th>
|
||||
<th
|
||||
class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0">
|
||||
Actions
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="[&_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 [&:has([role=checkbox])]:pr-0">{ses.IP}
|
||||
</td>
|
||||
<td class="p-4 align-middle [&:has([role=checkbox])]:pr-0">{ses.Browser + ses.Version}
|
||||
</td>
|
||||
<td class="p-4 align-middle [&:has([role=checkbox])]:pr-0">{ses.OS + ses.OSVersion}
|
||||
</td>
|
||||
<td class="p-4 align-middle [&:has([role=checkbox])]:pr-0">{ses.AccessAt}
|
||||
</td>
|
||||
<td class="p-4 align-middle [&: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>
|
||||
</section>
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold tracking-tight">Reset Password</h2>
|
||||
switch message.Code {
|
||||
case 0:
|
||||
<div class="p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 text-center" role="alert">
|
||||
{message.Message}
|
||||
</div>
|
||||
}
|
||||
<form method="POST" action="/user/reset-password">
|
||||
<div class="mt-6 grid gap-6">
|
||||
<div class="grid gap-2">
|
||||
<label
|
||||
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
for="currentPassword">
|
||||
Current password
|
||||
</label>
|
||||
<input
|
||||
class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
type="password" id="currentPassword" name="currentPassword" placeholder="Current password" />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<label
|
||||
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
for="password">
|
||||
New password
|
||||
</label>
|
||||
<input
|
||||
class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
type="password" id="password" name="password" placeholder="New password" />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<label
|
||||
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
for="confirmPassword">
|
||||
New password confirmation
|
||||
</label>
|
||||
<input
|
||||
class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
type="password" id="confirmPassword" placeholder="New password confirmation" />
|
||||
</div>
|
||||
<div id="validationBox" class="justify-start mt-3 ml-4 p-1 hidden">
|
||||
<ul>
|
||||
<li class="flex items-center py-1">
|
||||
<div id="matchSvgContainer" class="rounded-full p-1 fill-current bg-red-200 text-green-700">
|
||||
<svg id="matchSvgIcon" class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path id="matchGoodPath" style="display: none;" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
<path id="matchBadPath" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<span id="matchStatusText" class="font-medium text-sm ml-3 text-red-700"> New Passwords do not match</span>
|
||||
</li>
|
||||
<li class="flex items-center py-1">
|
||||
<div id="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 class="space-y-8">
|
||||
<div class="rounded-lg border bg-card text-card-foreground shadow-sm" data-v0-t="card">
|
||||
<div class="flex flex-col space-y-1.5 p-6">
|
||||
<h3 class="whitespace-nowrap text-2xl font-semibold leading-none tracking-tight">Storage Usage
|
||||
</h3>
|
||||
</div>
|
||||
<div class="p-6 grid gap-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<span>Used</span>
|
||||
<span>42.0GB</span>
|
||||
</div>
|
||||
<div class="w-full bg-gray-300 rounded-full h-2.5">
|
||||
<div class="bg-gray-800 h-2.5 rounded-full" style="width: 45%"></div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>Available</span>
|
||||
<span>6.9GB</span>
|
||||
</div>
|
||||
<div class="w-full bg-gray-300 rounded-full h-2.5">
|
||||
<div class="bg-gray-800 h-2.5 rounded-full" style="width: 100%"></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 class="rounded-lg border bg-card text-card-foreground shadow-sm" data-v0-t="card">
|
||||
<div class="flex flex-col space-y-1.5 p-6">
|
||||
<h3 class="whitespace-nowrap text-2xl font-semibold leading-none tracking-tight">Upgrade Storage
|
||||
</h3>
|
||||
</div>
|
||||
<div class="p-6 grid gap-4">
|
||||
<div class="grid gap-2">
|
||||
<h3 class="text-lg font-semibold">Pro Plan</h3>
|
||||
<p class="text-gray-500">50GB of storage for $9.99/month</p>
|
||||
<a
|
||||
class="hover:bg-gray-200 inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 px-4 py-2"
|
||||
type="button" href="#">
|
||||
Upgrade
|
||||
</a>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<h3 class="text-lg font-semibold">Enterprise Plan</h3>
|
||||
<p class="text-gray-500">1TB of storage for $49.99/month</p>
|
||||
<a
|
||||
class="hover:bg-gray-200 inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 px-4 py-2"
|
||||
type="button" href="#">
|
||||
Upgrade
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<script type="text/javascript">
|
||||
document.getElementById('currentPassword').addEventListener('input', function() {
|
||||
var validationBox = document.getElementById('validationBox');
|
||||
if (this.value.length > 0) {
|
||||
validationBox.classList.remove('hidden');
|
||||
} else {
|
||||
validationBox.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script src="/public/validatePassword.js" />
|
||||
@layout.Footer()
|
||||
}
|
||||
}
|
||||
|
||||
templ SessionTable(ListSession []*session.SessionInfo){
|
||||
<tbody class="[&_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 [&:has([role=checkbox])]:pr-0">{ses.IP}
|
||||
</td>
|
||||
<td class="p-4 align-middle [&:has([role=checkbox])]:pr-0">{ses.Browser + ses.Version}
|
||||
</td>
|
||||
<td class="p-4 align-middle [&:has([role=checkbox])]:pr-0">{ses.OS + ses.OSVersion}
|
||||
</td>
|
||||
<td class="p-4 align-middle [&:has([role=checkbox])]:pr-0">{ses.AccessAt}
|
||||
</td>
|
||||
<td class="p-4 align-middle [&: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>
|
||||
}
|
||||
|
||||
templ Main(title string, user types.User, ListSession []*session.SessionInfo, message types.Message) {
|
||||
@content(message, title, user, ListSession)
|
||||
}
|
Reference in New Issue
Block a user