Add web frontend

This commit is contained in:
Divyam Ahuja 2024-11-02 12:23:32 +05:30
parent fe8c807b97
commit 081c774e75
21 changed files with 1186 additions and 1 deletions

10
.gitignore vendored
View file

@ -1 +1,9 @@
.env .DS_STORE
.env
**/*_templ.go
**/*_templ.txt
**/configmap.yaml
tailwindcss

BIN
bin/web Executable file

Binary file not shown.

View file

@ -0,0 +1,33 @@
FROM golang:1.23-alpine AS builder
WORKDIR /app
RUN apk --no-cache add curl nodejs npm
RUN npm install -D tailwindcss
RUN go install github.com/a-h/templ/cmd/templ@latest
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN templ generate
RUN npx tailwindcss -i ./internal/web/assets/css/input.css -o ./internal/web/assets/css/output.css
RUN go build -o web ./cmd/web
FROM alpine:latest
WORKDIR /app
COPY --from=builder /app/web /app/web
ENV GIN_MODE=release
EXPOSE 8080
ENTRYPOINT ["/app/web"]

View file

@ -9,3 +9,4 @@ data:
DB_HOST: "psql-service" DB_HOST: "psql-service"
DB_PORT: "5432" DB_PORT: "5432"
KEYGEN_HOST: "keygen-service" KEYGEN_HOST: "keygen-service"
API_URL: "http://api-server-service"

View file

@ -0,0 +1,38 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: fastbin-web
spec:
replicas: 3
selector:
matchLabels:
app: fastbin-web
template:
metadata:
labels:
app: fastbin-web
spec:
containers:
- name: api-server
image: registry.lab.divyam.dev/fastbin-web:latest
ports:
- containerPort: 8080
env:
- name: API_URL
valueFrom:
configMapKeyRef:
name: fastbin-config
key: API_URL
---
apiVersion: v1
kind: Service
metadata:
name: fastbin-web-service
spec:
ports:
- port: 80
targetPort: 8080
selector:
app: fastbin-web
type: LoadBalancer

22
cmd/web/main.go Normal file
View file

@ -0,0 +1,22 @@
package main
import (
"fastbin/internal/pkg/env"
webserver "fastbin/internal/web"
"fmt"
"log"
"net/http"
"strconv"
)
func main() {
port, err := strconv.Atoi(env.GetEnv("WEB_INTERNAL_PORT", "8080"))
if err != nil {
log.Fatalf("error listening port: %v, err: %v", port, err)
}
server := webserver.NewServer(port)
err = server.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
panic(fmt.Sprintf("http server error: %s", err))
}
}

1
go.mod
View file

@ -3,6 +3,7 @@ module fastbin
go 1.23.2 go 1.23.2
require ( require (
github.com/a-h/templ v0.2.793
github.com/gin-gonic/gin v1.10.0 github.com/gin-gonic/gin v1.10.0
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
google.golang.org/grpc v1.67.1 google.golang.org/grpc v1.67.1

2
go.sum
View file

@ -1,3 +1,5 @@
github.com/a-h/templ v0.2.793 h1:Io+/ocnfGWYO4VHdR0zBbf39PQlnzVCVVD+wEEs6/qY=
github.com/a-h/templ v0.2.793/go.mod h1:lq48JXoUvuQrU0VThrK31yFwdRjTCnIE5bcPCM9IP1w=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=

View file

@ -0,0 +1,10 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--primary-color: 29 29 29;
--border-color: 69 69 69;
}
}

View file

@ -0,0 +1,740 @@
*, ::before, ::after {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-gradient-from-position: ;
--tw-gradient-via-position: ;
--tw-gradient-to-position: ;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
--tw-contain-size: ;
--tw-contain-layout: ;
--tw-contain-paint: ;
--tw-contain-style: ;
}
::backdrop {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-gradient-from-position: ;
--tw-gradient-via-position: ;
--tw-gradient-to-position: ;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
--tw-contain-size: ;
--tw-contain-layout: ;
--tw-contain-paint: ;
--tw-contain-style: ;
}
/*
! tailwindcss v3.4.14 | MIT License | https://tailwindcss.com
*/
/*
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
*/
*,
::before,
::after {
box-sizing: border-box;
/* 1 */
border-width: 0;
/* 2 */
border-style: solid;
/* 2 */
border-color: #e5e7eb;
/* 2 */
}
::before,
::after {
--tw-content: '';
}
/*
1. Use a consistent sensible line-height in all browsers.
2. Prevent adjustments of font size after orientation changes in iOS.
3. Use a more readable tab size.
4. Use the user's configured `sans` font-family by default.
5. Use the user's configured `sans` font-feature-settings by default.
6. Use the user's configured `sans` font-variation-settings by default.
7. Disable tap highlights on iOS
*/
html,
:host {
line-height: 1.5;
/* 1 */
-webkit-text-size-adjust: 100%;
/* 2 */
-moz-tab-size: 4;
/* 3 */
-o-tab-size: 4;
tab-size: 4;
/* 3 */
font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
/* 4 */
font-feature-settings: normal;
/* 5 */
font-variation-settings: normal;
/* 6 */
-webkit-tap-highlight-color: transparent;
/* 7 */
}
/*
1. Remove the margin in all browsers.
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
*/
body {
margin: 0;
/* 1 */
line-height: inherit;
/* 2 */
}
/*
1. Add the correct height in Firefox.
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
3. Ensure horizontal rules are visible by default.
*/
hr {
height: 0;
/* 1 */
color: inherit;
/* 2 */
border-top-width: 1px;
/* 3 */
}
/*
Add the correct text decoration in Chrome, Edge, and Safari.
*/
abbr:where([title]) {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
/*
Remove the default font size and weight for headings.
*/
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: inherit;
font-weight: inherit;
}
/*
Reset links to optimize for opt-in styling instead of opt-out.
*/
a {
color: inherit;
text-decoration: inherit;
}
/*
Add the correct font weight in Edge and Safari.
*/
b,
strong {
font-weight: bolder;
}
/*
1. Use the user's configured `mono` font-family by default.
2. Use the user's configured `mono` font-feature-settings by default.
3. Use the user's configured `mono` font-variation-settings by default.
4. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp,
pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
/* 1 */
font-feature-settings: normal;
/* 2 */
font-variation-settings: normal;
/* 3 */
font-size: 1em;
/* 4 */
}
/*
Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/*
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/*
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
3. Remove gaps between table borders by default.
*/
table {
text-indent: 0;
/* 1 */
border-color: inherit;
/* 2 */
border-collapse: collapse;
/* 3 */
}
/*
1. Change the font styles in all browsers.
2. Remove the margin in Firefox and Safari.
3. Remove default padding in all browsers.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit;
/* 1 */
font-feature-settings: inherit;
/* 1 */
font-variation-settings: inherit;
/* 1 */
font-size: 100%;
/* 1 */
font-weight: inherit;
/* 1 */
line-height: inherit;
/* 1 */
letter-spacing: inherit;
/* 1 */
color: inherit;
/* 1 */
margin: 0;
/* 2 */
padding: 0;
/* 3 */
}
/*
Remove the inheritance of text transform in Edge and Firefox.
*/
button,
select {
text-transform: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Remove default button styles.
*/
button,
input:where([type='button']),
input:where([type='reset']),
input:where([type='submit']) {
-webkit-appearance: button;
/* 1 */
background-color: transparent;
/* 2 */
background-image: none;
/* 2 */
}
/*
Use the modern Firefox focus style for all focusable elements.
*/
:-moz-focusring {
outline: auto;
}
/*
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
*/
:-moz-ui-invalid {
box-shadow: none;
}
/*
Add the correct vertical alignment in Chrome and Firefox.
*/
progress {
vertical-align: baseline;
}
/*
Correct the cursor style of increment and decrement buttons in Safari.
*/
::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
height: auto;
}
/*
1. Correct the odd appearance in Chrome and Safari.
2. Correct the outline style in Safari.
*/
[type='search'] {
-webkit-appearance: textfield;
/* 1 */
outline-offset: -2px;
/* 2 */
}
/*
Remove the inner padding in Chrome and Safari on macOS.
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button;
/* 1 */
font: inherit;
/* 2 */
}
/*
Add the correct display in Chrome and Safari.
*/
summary {
display: list-item;
}
/*
Removes the default spacing and border for appropriate elements.
*/
blockquote,
dl,
dd,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
figure,
p,
pre {
margin: 0;
}
fieldset {
margin: 0;
padding: 0;
}
legend {
padding: 0;
}
ol,
ul,
menu {
list-style: none;
margin: 0;
padding: 0;
}
/*
Reset default styling for dialogs.
*/
dialog {
padding: 0;
}
/*
Prevent resizing textareas horizontally by default.
*/
textarea {
resize: vertical;
}
/*
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
2. Set the default placeholder color to the user's configured gray 400 color.
*/
input::-moz-placeholder, textarea::-moz-placeholder {
opacity: 1;
/* 1 */
color: #9ca3af;
/* 2 */
}
input::placeholder,
textarea::placeholder {
opacity: 1;
/* 1 */
color: #9ca3af;
/* 2 */
}
/*
Set the default cursor for buttons.
*/
button,
[role="button"] {
cursor: pointer;
}
/*
Make sure disabled buttons don't get the pointer cursor.
*/
:disabled {
cursor: default;
}
/*
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
This can trigger a poorly considered lint error in some tools but is included by design.
*/
img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
display: block;
/* 1 */
vertical-align: middle;
/* 2 */
}
/*
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
*/
img,
video {
max-width: 100%;
height: auto;
}
/* Make elements with the HTML hidden attribute stay hidden by default */
[hidden]:where(:not([hidden="until-found"])) {
display: none;
}
:root {
--primary-color: 29 29 29;
--border-color: 69 69 69;
}
.pointer-events-none {
pointer-events: none;
}
.block {
display: block;
}
.inline-block {
display: inline-block;
}
.flex {
display: flex;
}
.h-\[70px\] {
height: 70px;
}
.h-dvh {
height: 100dvh;
}
.h-full {
height: 100%;
}
.w-\[70px\] {
width: 70px;
}
.w-dvw {
width: 100dvw;
}
.w-full {
width: 100%;
}
.flex-1 {
flex: 1 1 0%;
}
.cursor-pointer {
cursor: pointer;
}
.select-none {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.resize-none {
resize: none;
}
.resize {
resize: both;
}
.flex-col {
flex-direction: column;
}
.items-start {
align-items: flex-start;
}
.items-center {
align-items: center;
}
.items-stretch {
align-items: stretch;
}
.justify-center {
justify-content: center;
}
.justify-between {
justify-content: space-between;
}
.self-stretch {
align-self: stretch;
}
.border-b-2 {
border-bottom-width: 2px;
}
.border-none {
border-style: none;
}
.border-b-border {
--tw-border-opacity: 1;
border-bottom-color: rgb(var(--border-color) / var(--tw-border-opacity));
}
.bg-primary {
--tw-bg-opacity: 1;
background-color: rgb(var(--primary-color) / var(--tw-bg-opacity));
}
.bg-transparent {
background-color: transparent;
}
.px-2\.5 {
padding-left: 0.625rem;
padding-right: 0.625rem;
}
.px-\[20px\] {
padding-left: 20px;
padding-right: 20px;
}
.py-\[10px\] {
padding-top: 10px;
padding-bottom: 10px;
}
.text-center {
text-align: center;
}
.align-middle {
vertical-align: middle;
}
.text-4xl {
font-size: 2.25rem;
line-height: 2.5rem;
}
.text-8xl {
font-size: 6rem;
line-height: 1;
}
.text-xl {
font-size: 1.25rem;
line-height: 1.75rem;
}
.italic {
font-style: italic;
}
.text-white {
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
}
.outline-none {
outline: 2px solid transparent;
outline-offset: 2px;
}
.filter {
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
}
.transition {
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.hover\:bg-border\/50:hover {
background-color: rgb(var(--border-color) / 0.5);
}

1
internal/web/assets/js/htmx.min.js vendored Normal file

File diff suppressed because one or more lines are too long

15
internal/web/efs.go Normal file
View file

@ -0,0 +1,15 @@
package web
import (
"embed"
"io/fs"
)
//go:embed assets
var f embed.FS
var Files fs.FS
func init() {
Files, _ = fs.Sub(f, "assets")
}

61
internal/web/renderer.go Normal file
View file

@ -0,0 +1,61 @@
// Credits: https://github.com/a-h/templ/blob/main/examples/integration-gin/gintemplrenderer/renderer.go
package web
import (
"context"
"net/http"
"github.com/gin-gonic/gin/render"
"github.com/a-h/templ"
)
var Default = &HTMLTemplRenderer{}
type HTMLTemplRenderer struct {
FallbackHtmlRenderer render.HTMLRender
}
func (r *HTMLTemplRenderer) Instance(s string, d any) render.Render {
templData, ok := d.(templ.Component)
if !ok {
if r.FallbackHtmlRenderer != nil {
return r.FallbackHtmlRenderer.Instance(s, d)
}
}
return &Renderer{
Ctx: context.Background(),
Status: -1,
Component: templData,
}
}
func NewGinTemplRenderer(ctx context.Context, status int, component templ.Component) *Renderer {
return &Renderer{
Ctx: ctx,
Status: status,
Component: component,
}
}
type Renderer struct {
Ctx context.Context
Status int
Component templ.Component
}
func (t Renderer) Render(w http.ResponseWriter) error {
t.WriteContentType(w)
if t.Status != -1 {
w.WriteHeader(t.Status)
}
if t.Component != nil {
return t.Component.Render(t.Ctx, w)
}
return nil
}
func (t Renderer) WriteContentType(w http.ResponseWriter) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
}

110
internal/web/server.go Normal file
View file

@ -0,0 +1,110 @@
package web
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
"fastbin/internal/pkg/env"
"fastbin/internal/web/views"
)
type api_write_response struct {
Key string `json:"key"`
}
type api_read_response struct {
Error string `json:"error"`
Text string `json:"text"`
}
func NewServer(port int) *http.Server {
API_URL := env.GetEnv("API_URL", "localhost:8080")
r := gin.Default()
engineHTMLRenderer := r.HTMLRender
r.HTMLRender = &HTMLTemplRenderer{FallbackHtmlRenderer: engineHTMLRenderer}
r.StaticFS("/assets", http.FS(Files))
r.GET("/", func(ctx *gin.Context) {
ctx.HTML(http.StatusOK, "", views.Write())
})
r.POST("/", func(ctx *gin.Context) {
text := ctx.Request.FormValue("text")
postBody, _ := json.Marshal(map[string]string{
"text": text,
})
url := API_URL + "/write"
response, err := http.Post(url, "application/json", bytes.NewBuffer(postBody))
if err != nil {
ctx.Redirect(http.StatusTemporaryRedirect, "/500")
}
var api_res api_write_response
decoder := json.NewDecoder(response.Body)
err = decoder.Decode(&api_res)
if err != nil {
ctx.Redirect(http.StatusTemporaryRedirect, "/500")
}
if response.StatusCode == http.StatusOK {
ctx.Writer.Header().Add("Hx-Redirect", api_res.Key)
} else {
ctx.Redirect(http.StatusTemporaryRedirect, "/500")
}
})
r.GET("/404", func(ctx *gin.Context) {
ctx.HTML(http.StatusOK, "", views.NotFound())
})
r.GET("/500", func(ctx *gin.Context) {
ctx.HTML(http.StatusOK, "", views.ServerError())
})
r.GET("/:key", func(ctx *gin.Context) {
url := API_URL + "/read/" + ctx.Param("key")
response, err := http.Get(url)
if err != nil {
ctx.Redirect(http.StatusTemporaryRedirect, "/500")
}
var api_res api_read_response
decoder := json.NewDecoder(response.Body)
err = decoder.Decode(&api_res)
if err != nil {
ctx.Redirect(http.StatusTemporaryRedirect, "/500")
}
if response.StatusCode == http.StatusOK {
ctx.HTML(http.StatusOK, "", views.Read(api_res.Text))
} else if response.StatusCode == http.StatusNotFound {
ctx.Redirect(http.StatusTemporaryRedirect, "/404")
} else {
ctx.Redirect(http.StatusTemporaryRedirect, "/500")
}
})
server := &http.Server{
Addr: fmt.Sprintf(":%d", port),
Handler: r,
IdleTimeout: time.Minute,
ReadTimeout: 10 * time.Second,
WriteTimeout: 30 * time.Second,
}
return server
}

View file

@ -0,0 +1,17 @@
package views
templ NotFound() {
@Base("fastbin") {
@Header() {
<a href="/" class="inline-block text-center align-middle">
@Button() {
<span class="material-symbols-outlined">note_add</span>
}
</a>
}
<div class="flex flex-1 w-full text-white text-xl">
<div class="h-full px-2.5 select-none">></div>
<div class="flex w-full h-full bg-transparent resize-none text-white justify-center items-center text-8xl"><div>404: Page Not Found. </div></div>
</div>
}
}

View file

@ -0,0 +1,17 @@
package views
templ ServerError() {
@Base("fastbin") {
@Header() {
<a href="/" class="inline-block text-center align-middle">
@Button() {
<span class="material-symbols-outlined">note_add</span>
}
</a>
}
<div class="flex flex-1 w-full text-white text-xl">
<div class="h-full px-2.5 select-none">></div>
<div class="flex w-full h-full bg-transparent resize-none text-white justify-center items-center text-8xl"><div>500: Internal Server Error</div></div>
</div>
}
}

View file

@ -0,0 +1,34 @@
package views
templ Base(title string) {
<html>
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined" />
<script src="assets/js/htmx.min.js"></script>
<link rel="stylesheet" href="assets/css/output.css"/>
<title>{ title }</title>
</head>
<body>
<main class="w-dvw h-dvh bg-primary flex flex-col items-start justify-center">
{ children... }
</main>
</body>
</html>
}
templ Header() {
<div class="w-full flex justify-between items-center text-white border-b-2 border-b-border">
<div class="px-[20px] py-[10px] text-4xl italic select-none pointer-events-none">fastbin</div>
<div class="flex self-stretch items-stretch">
{ children... }
</div>
</div>
}
templ Button() {
<div class="w-[70px] h-[70px] flex justify-center align-middle items-center text-center cursor-pointer select-none hover:bg-border/50">
{ children... }
</div>
}

View file

@ -0,0 +1,17 @@
package views
templ Read(text string) {
@Base("fastbin") {
@Header() {
<a href="/" class="inline-block text-center align-middle">
@Button() {
<span class="material-symbols-outlined">note_add</span>
}
</a>
}
<div class="flex flex-1 w-full text-white text-xl">
<div class="h-full px-2.5 select-none">></div>
<textarea required readonly name="text" id="input" autofocus wrap="off" spellcheck="false" class="flex-1 w-full h-full bg-transparent resize-none outline-none border-none text-white text-xl">{ text } </textarea>
</div>
}
}

View file

@ -0,0 +1,41 @@
package views
var onceHandle = templ.NewOnceHandle()
templ Write() {
@Base("fastbin") {
<form id="input_form" hx-post="/" class="w-dvw h-dvh bg-primary flex flex-col items-start justify-center">
@Header() {
<a href="/" class="inline-block text-center align-middle">
@Button() {
<span class="material-symbols-outlined">note_add</span>
}
</a>
<a>
<button type="submit">
@Button() {
<span class="material-symbols-outlined">save</span>
}
</button>
</a>
}
<script type="text/javascript">
function keyDownHandler(e) {
if (e.key === "Tab") {
e.preventDefault()
e.currentTarget.setRangeText(
'\t',
e.currentTarget.selectionStart,
e.currentTarget.selectionStart,
'end'
)
}
}
</script>
<div class="flex flex-1 w-full text-white text-xl">
<div class="h-full px-2.5 select-none">></div>
<textarea required name="text" id="input" onkeydown="keyDownHandler(event)" autofocus wrap="off" spellcheck="false" class="flex-1 w-full h-full bg-transparent resize-none outline-none border-none text-white text-xl"></textarea>
</div>
</form>
}
}

View file

@ -1,6 +1,8 @@
# API_INTERNAL_PORT=8080 # When using without docker # API_INTERNAL_PORT=8080 # When using without docker
# KEYGEN_INTERNAL_PORT=8081 # When using without docker # KEYGEN_INTERNAL_PORT=8081 # When using without docker
API_URL=http://api-server
KEYGEN_PORT=8080 KEYGEN_PORT=8080
KEYGEN_HOST=localhost KEYGEN_HOST=localhost

15
tailwind.config.js Normal file
View file

@ -0,0 +1,15 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./internal/**/*.{go,js,templ,html}"
],
theme: {
extend: {
colors: {
"primary": "rgb(var(--primary-color) / <alpha-value>)",
"border": "rgb(var(--border-color) / <alpha-value>)"
}
},
},
plugins: [],
}