@ -7,9 +7,11 @@ COPY /view /src/view
|
||||
|
||||
RUN npm install -g tailwindcss
|
||||
RUN npm install -g javascript-obfuscator
|
||||
RUN npx tailwindcss -i ./public/input.css -o ./public/output.css
|
||||
RUN npm install -g clean-css-cli
|
||||
RUN npx tailwindcss -i ./public/input.css -o ./tmp/output.css
|
||||
RUN javascript-obfuscator ./public/upload.js --compact true --self-defending true --output ./public/upload_obfuscated.js
|
||||
RUN javascript-obfuscator ./public/validatePassword.js --compact true --self-defending true --output ./public/validatePassword_obfuscated.js
|
||||
RUN cleancss -o ./public/output.css ./tmp/output.css
|
||||
|
||||
FROM golang:1.22.2-alpine3.19 AS go_builder
|
||||
|
||||
@ -19,7 +21,7 @@ COPY --from=node_builder /src/public /src/public
|
||||
COPY --from=node_builder /src/public/upload_obfuscated.js /src/public/upload.js
|
||||
COPY --from=node_builder /src/public/validatePassword_obfuscated.js /src/public/validatePassword.js
|
||||
|
||||
RUN apk update && apk upgrade && apk add --no-cache ca-certificates
|
||||
RUN apk update && apk upgrade && apk add --no-cache ca-certificates tzdata
|
||||
RUN update-ca-certificates
|
||||
RUN go install github.com/a-h/templ/cmd/templ@$(go list -m -f '{{ .Version }}' github.com/a-h/templ)
|
||||
RUN templ generate
|
||||
@ -30,9 +32,12 @@ FROM scratch
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
COPY --from=go_builder /usr/share/zoneinfo /usr/share/zoneinfo
|
||||
COPY --from=go_builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
COPY --from=go_builder /src/schema.sql /src
|
||||
COPY --from=go_builder /src/public /src/public
|
||||
COPY --from=go_builder /src/tmp/main /src
|
||||
|
||||
ENV TZ Asia/Jakarta
|
||||
|
||||
ENTRYPOINT ["./main"]
|
||||
|
@ -1,6 +1,7 @@
|
||||
package indexHandler
|
||||
|
||||
import (
|
||||
"github.com/fossyy/filekeeper/session"
|
||||
"net/http"
|
||||
|
||||
"github.com/fossyy/filekeeper/logger"
|
||||
@ -14,7 +15,8 @@ func init() {
|
||||
}
|
||||
|
||||
func GET(w http.ResponseWriter, r *http.Request) {
|
||||
component := indexView.Main("main page")
|
||||
_, userSession, _ := session.GetSession(r)
|
||||
component := indexView.Main("main page", userSession)
|
||||
err := component.Render(r.Context(), w)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
|
@ -17,7 +17,7 @@ func init() {
|
||||
|
||||
func GET(w http.ResponseWriter, r *http.Request) {
|
||||
userSession := r.Context().Value("user").(types.User)
|
||||
component := userView.Main("User Page", userSession.Email, userSession.Username, session.UserSessionInfoList[userSession.Email])
|
||||
component := userView.Main("User Page", userSession.Email, userSession.Username, session.GetSessions(userSession.Email))
|
||||
err := component.Render(r.Context(), w)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
|
@ -59,10 +59,13 @@ func Handler(next http.Handler) http.Handler {
|
||||
}
|
||||
|
||||
func Auth(next http.HandlerFunc, w http.ResponseWriter, r *http.Request) {
|
||||
status, user := session.GetSession(r)
|
||||
status, user, sessionID := session.GetSession(r)
|
||||
|
||||
switch status {
|
||||
case session.Authorized:
|
||||
userSession := session.GetSessionInfo(user.Email, sessionID)
|
||||
userSession.UpdateAccessTime()
|
||||
|
||||
ctx := context.WithValue(r.Context(), "user", user)
|
||||
req := r.WithContext(ctx)
|
||||
r.Context().Value("user")
|
||||
@ -94,7 +97,7 @@ func Auth(next http.HandlerFunc, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func Guest(next http.HandlerFunc, w http.ResponseWriter, r *http.Request) {
|
||||
status, _ := session.GetSession(r)
|
||||
status, _, _ := session.GetSession(r)
|
||||
|
||||
switch status {
|
||||
case session.Authorized:
|
||||
|
1
public/brand.svg
Normal file
1
public/brand.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 11 KiB |
@ -40,10 +40,8 @@ const (
|
||||
InvalidSession UserStatus = "invalid_session"
|
||||
)
|
||||
|
||||
type SessionInfoList map[string][]*SessionInfo
|
||||
|
||||
var GlobalSessionStore = SessionStore{Sessions: make(map[string]*Session)}
|
||||
var UserSessionInfoList = make(SessionInfoList)
|
||||
var UserSessionInfoList = make(map[string]map[string]*SessionInfo)
|
||||
|
||||
type SessionNotFoundError struct{}
|
||||
|
||||
@ -95,22 +93,22 @@ func (s *Session) Destroy(w http.ResponseWriter) {
|
||||
}
|
||||
|
||||
func AddSessionInfo(email string, sessionInfo *SessionInfo) {
|
||||
UserSessionInfoList[email] = append(UserSessionInfoList[email], sessionInfo)
|
||||
if _, ok := UserSessionInfoList[email]; !ok {
|
||||
UserSessionInfoList[email] = make(map[string]*SessionInfo)
|
||||
}
|
||||
|
||||
UserSessionInfoList[email][sessionInfo.SessionID] = sessionInfo
|
||||
}
|
||||
|
||||
func RemoveSessionInfo(email string, id string) {
|
||||
sessionInfos := UserSessionInfoList[email]
|
||||
var updatedSessionInfos []*SessionInfo
|
||||
for _, sessionInfo := range sessionInfos {
|
||||
if sessionInfo.SessionID != id {
|
||||
updatedSessionInfos = append(updatedSessionInfos, sessionInfo)
|
||||
}
|
||||
}
|
||||
if len(updatedSessionInfos) > 0 {
|
||||
UserSessionInfoList[email] = updatedSessionInfos
|
||||
return
|
||||
}
|
||||
if userSessions, ok := UserSessionInfoList[email]; ok {
|
||||
if _, ok := userSessions[id]; ok {
|
||||
delete(userSessions, id)
|
||||
if len(userSessions) == 0 {
|
||||
delete(UserSessionInfoList, email)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func RemoveAllSessions(email string) {
|
||||
@ -122,8 +120,8 @@ func RemoveAllSessions(email string) {
|
||||
}
|
||||
|
||||
func GetSessionInfo(email string, id string) *SessionInfo {
|
||||
for _, sessionInfo := range UserSessionInfoList[email] {
|
||||
if sessionInfo.SessionID == id {
|
||||
if userSession, ok := UserSessionInfoList[email]; ok {
|
||||
if sessionInfo, ok := userSession[id]; ok {
|
||||
return sessionInfo
|
||||
}
|
||||
}
|
||||
@ -136,26 +134,37 @@ func (sessionInfo *SessionInfo) UpdateAccessTime() {
|
||||
sessionInfo.AccessAt = formattedTime
|
||||
}
|
||||
|
||||
func GetSession(r *http.Request) (UserStatus, types.User) {
|
||||
func GetSession(r *http.Request) (UserStatus, types.User, string) {
|
||||
cookie, err := r.Cookie("Session")
|
||||
if err != nil {
|
||||
return Unauthorized, types.User{}
|
||||
return Unauthorized, types.User{}, ""
|
||||
}
|
||||
|
||||
storeSession, err := GlobalSessionStore.Get(cookie.Value)
|
||||
if err != nil {
|
||||
if errors.Is(err, &SessionNotFoundError{}) {
|
||||
return InvalidSession, types.User{}
|
||||
return InvalidSession, types.User{}, ""
|
||||
}
|
||||
return Unauthorized, types.User{}
|
||||
return Unauthorized, types.User{}, ""
|
||||
}
|
||||
|
||||
val := storeSession.Values["user"]
|
||||
var userSession = types.User{}
|
||||
userSession, ok := val.(types.User)
|
||||
if !ok {
|
||||
return Unauthorized, types.User{}
|
||||
return Unauthorized, types.User{}, ""
|
||||
}
|
||||
|
||||
return Authorized, userSession
|
||||
return Authorized, userSession, cookie.Value
|
||||
}
|
||||
|
||||
func GetSessions(email string) []*SessionInfo {
|
||||
if sessions, ok := UserSessionInfoList[email]; ok {
|
||||
result := make([]*SessionInfo, 0, len(sessions))
|
||||
for _, sessionInfo := range sessions {
|
||||
result = append(result, sessionInfo)
|
||||
}
|
||||
return result
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -26,10 +26,6 @@ type Env struct {
|
||||
var env *Env
|
||||
var log *logger.AggregatedLogger
|
||||
|
||||
const (
|
||||
csrfTokenLength = 32 // Length of the CSRF token in bytes
|
||||
)
|
||||
|
||||
func init() {
|
||||
env = &Env{value: map[string]string{}}
|
||||
}
|
||||
|
@ -11,8 +11,8 @@ templ form(err types.Message, title string) {
|
||||
<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 email below to login to your account</p>
|
||||
<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">
|
||||
|
@ -1,26 +1,72 @@
|
||||
package indexView
|
||||
|
||||
import (
|
||||
"github.com/fossyy/filekeeper/types"
|
||||
"github.com/fossyy/filekeeper/view/layout"
|
||||
)
|
||||
|
||||
templ content(title string) {
|
||||
templ content(title string, user types.User) {
|
||||
@layout.Base(title){
|
||||
<div class="bg-white">
|
||||
<nav class="container mx-auto px-6 py-4 flex justify-between items-center">
|
||||
<a href="#" class="text-blue-500 text-xl font-semibold">
|
||||
C.
|
||||
<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="#">
|
||||
<image src="public/brand.svg" width="48" height="48"/>
|
||||
<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">
|
||||
Sign up
|
||||
</a>
|
||||
<a href="/signin" class="text-gray-600 hover:text-gray-800">
|
||||
Sign in
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</nav>
|
||||
<header class="container mx-auto px-6 py-16 text-center">
|
||||
</header>
|
||||
<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
|
||||
@ -29,75 +75,128 @@ templ content(title string) {
|
||||
<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">
|
||||
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">
|
||||
Get started
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-center items-center space-x-4 mt-8">
|
||||
<div class="grid gap-4 md:gap-8 lg:gap-4 items-start sm:grid-cols-2">
|
||||
<div class="flex items-center space-x-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="w-6 h-6 flex-shrink-0 text-gray-500">
|
||||
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
|
||||
<polyline points="22 4 12 14.01 9 11.01"></polyline>
|
||||
|
||||
<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 class="text-sm font-medium leading-none md:text-base">
|
||||
Secure encryption to protect your data
|
||||
</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="flex items-center space-x-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="w-6 h-6 flex-shrink-0 text-gray-500">
|
||||
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
|
||||
<polyline points="22 4 12 14.01 9 11.01"></polyline>
|
||||
<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 class="text-sm font-medium leading-none md:text-base">
|
||||
Easy file sharing with customizable permissions
|
||||
</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="flex items-center space-x-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="w-6 h-6 flex-shrink-0 text-gray-500">
|
||||
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
|
||||
<polyline points="22 4 12 14.01 9 11.01"></polyline>
|
||||
<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 class="text-sm font-medium leading-none md:text-base">
|
||||
Unlimited storage capacity for your files
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-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="w-6 h-6 flex-shrink-0 text-gray-500">
|
||||
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
|
||||
<polyline points="22 4 12 14.01 9 11.01"></polyline>
|
||||
</svg>
|
||||
<div class="text-sm font-medium leading-none md:text-base">
|
||||
Seamless collaboration with built-in productivity tools
|
||||
<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>
|
||||
</header>
|
||||
</div>
|
||||
<footer class="w-full border-t">
|
||||
<div class="container grid-inset py-12 gap-4 text-center md:gap-8 md:py-16">
|
||||
<div class="flex items-center justify-center gap-2 text-xs tracking-wide md:gap-4">
|
||||
<p class="text-gray-500 dark:text-gray-400">© 2023 Acme, Inc. All rights reserved.</p>
|
||||
<p class="text-gray-500 dark:text-gray-400">Terms of Service</p>
|
||||
<p class="text-gray-500 dark:text-gray-400">Privacy Policy</p>
|
||||
|
||||
<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">
|
||||
<image src="public/brand.svg" width="48" height="48"/>
|
||||
<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 Main(title string) {
|
||||
@content(title)
|
||||
templ Main(title string, user types.User) {
|
||||
@content(title, user)
|
||||
}
|
@ -12,7 +12,7 @@ templ form(err types.Message, title string) {
|
||||
<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 email below to login to your account</p>
|
||||
<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">
|
||||
|
@ -218,16 +218,12 @@ templ content(email string, username string, title string, ListSession []*sessio
|
||||
</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" id="radix-:rq:" aria-haspopup="menu"
|
||||
aria-expanded="false" data-state="closed"
|
||||
href="/upload">
|
||||
type="button" href="/upload">
|
||||
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" id="radix-:rq:" aria-haspopup="menu"
|
||||
aria-expanded="false" data-state="closed"
|
||||
href="/download">
|
||||
type="button" href="/download">
|
||||
Download
|
||||
</a>
|
||||
</div>
|
||||
@ -241,18 +237,20 @@ templ content(email string, username string, title string, ListSession []*sessio
|
||||
<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>
|
||||
<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 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2">
|
||||
<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
|
||||
</button>
|
||||
</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>
|
||||
<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 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2">
|
||||
<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
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user