Added a basic homepage

This commit is contained in:
CamZawacki 2026-05-20 16:09:54 +01:00
parent d40640a648
commit 12fd3c04ca
113 changed files with 414 additions and 506 deletions

View file

@ -0,0 +1,61 @@
package pages
import (
"github.com/labstack/echo/v4"
"github.com/camzawacki/personal-site/internal/ui"
"github.com/camzawacki/personal-site/internal/ui/cache"
. "github.com/camzawacki/personal-site/internal/ui/components"
"github.com/camzawacki/personal-site/internal/ui/layouts"
. "maragu.dev/gomponents"
. "maragu.dev/gomponents/html"
)
func About(ctx echo.Context) error {
r := ui.NewRequest(ctx)
r.Title = "About"
r.Metatags.Description = "Learn a little about what's included in Pagoda."
// The tabs are static, so we can render and cache them.
tabs := cache.SetIfNotExists("pages.about.Tabs", func() Node {
return Group{
H2(Text("Frontend")),
P(Text("The following incredible projects make developing advanced, modern frontends possible and simple without having to write a single line of JS or CSS. You can go extremely far without leaving the comfort of Go with server-side rendered HTML.")),
Tabs(
[]Tab{
{
Title: "HTMX",
Body: "Completes HTML as a hypertext by providing attributes to AJAXify anything and much more. Visit <a href=\"https://htmx.org/\">htmx.org</a> to learn more.",
},
{
Title: "Alpine.js",
Body: "Drop-in, Vue-like functionality written directly in your markup. Visit <a href=\"https://alpinejs.dev/\">alpinejs.dev</a> to learn more.",
},
{
Title: "DaisyUI",
Body: "DaisyUI is the Tailwind CSS plugin you will love! It provides useful component class names to help you write less code and build faster. No JavaScript requirements. Visit <a href=\"https://daisyui.com/\">daisyui.com</a> to learn more.",
},
},
),
H2(Text("Backend")),
P(Text("The following incredible projects provide the foundation of the Go backend. See the repository for a complete list of included projects.")),
Tabs(
[]Tab{
{
Title: "Echo",
Body: "High performance, extensible, minimalist Go web framework. Visit <a href=\"https://echo.labstack.com/\">echo.labstack.com</a> to learn more.",
},
{
Title: "Ent",
Body: "Simple, yet powerful ORM for modeling and querying data. Visit <a href=\"https://entgo.io/\">entgo.io</a> to learn more.",
},
{
Title: "Gomponents",
Body: "HTML components written in pure Go. They render to HTML 5, and make it easy for you to build reusable components. Visit <a href=\"https://gomponents.com/\">gomponents.com</a> to learn more.",
},
},
),
}
})
return r.Render(layouts.Primary, tabs)
}

View file

@ -0,0 +1,115 @@
package pages
import (
"fmt"
"net/url"
"github.com/labstack/echo/v4"
"github.com/camzawacki/personal-site/ent/admin"
"github.com/camzawacki/personal-site/internal/routenames"
"github.com/camzawacki/personal-site/internal/ui"
. "github.com/camzawacki/personal-site/internal/ui/components"
"github.com/camzawacki/personal-site/internal/ui/forms"
"github.com/camzawacki/personal-site/internal/ui/layouts"
. "maragu.dev/gomponents"
. "maragu.dev/gomponents/html"
)
func AdminEntityDelete(ctx echo.Context, entityType admin.EntityType) error {
r := ui.NewRequest(ctx)
r.Title = fmt.Sprintf("Delete %s", entityType.GetName())
return r.Render(
layouts.Primary,
forms.AdminEntityDelete(r, entityType),
)
}
func AdminEntityInput(ctx echo.Context, entityType admin.EntityType, values url.Values) error {
r := ui.NewRequest(ctx)
if values == nil {
r.Title = fmt.Sprintf("Add %s", entityType.GetName())
} else {
r.Title = fmt.Sprintf("Edit %s", entityType.GetName())
}
return r.Render(
layouts.Primary,
forms.AdminEntity(r, entityType, values),
)
}
func AdminEntityList(
ctx echo.Context,
entityType admin.EntityType,
entityList *admin.EntityList,
) error {
r := ui.NewRequest(ctx)
r.Title = entityType.GetName()
genHeader := func() Node {
g := make(Group, 0, len(entityList.Columns)+2)
g = append(g, Th(Text("ID")))
for _, h := range entityList.Columns {
g = append(g, Th(Text(h)))
}
g = append(g, Th())
return g
}
genRow := func(row admin.EntityValues) Node {
g := make(Group, 0, len(row.Values)+3)
g = append(g, Th(Text(fmt.Sprint(row.ID))))
for _, h := range row.Values {
g = append(g, Td(Text(h)))
}
g = append(g,
Td(
ButtonLink(
ColorInfo,
r.Path(routenames.AdminEntityEdit(entityType.GetName()), row.ID),
"Edit",
),
Span(Class("mr-2")),
ButtonLink(
ColorError,
r.Path(routenames.AdminEntityDelete(entityType.GetName()), row.ID),
"Delete",
),
),
)
return g
}
genRows := func() Node {
g := make(Group, 0, len(entityList.Entities))
for _, row := range entityList.Entities {
g = append(g, Tr(genRow(row)))
}
return g
}
return r.Render(layouts.Primary, Group{
Div(
Class("form-control mb-2"),
ButtonLink(
ColorAccent,
r.Path(routenames.AdminEntityAdd(entityType.GetName())),
fmt.Sprintf("Add %s", entityType.GetName()),
),
),
Table(
Class("table table-zebra mb-2"),
THead(
Tr(genHeader()),
),
TBody(genRows()),
),
Pager(
entityList.Page,
r.Path(routenames.AdminEntityAdd(entityType.GetName())),
entityList.HasNextPage,
"",
),
})
}

46
internal/ui/pages/auth.go Normal file
View file

@ -0,0 +1,46 @@
package pages
import (
"github.com/labstack/echo/v4"
"github.com/camzawacki/personal-site/internal/ui"
"github.com/camzawacki/personal-site/internal/ui/forms"
"github.com/camzawacki/personal-site/internal/ui/layouts"
. "maragu.dev/gomponents"
. "maragu.dev/gomponents/html"
)
func Login(ctx echo.Context, form *forms.Login) error {
r := ui.NewRequest(ctx)
r.Title = "Login"
return r.Render(layouts.Auth, form.Render(r))
}
func Register(ctx echo.Context, form *forms.Register) error {
r := ui.NewRequest(ctx)
r.Title = "Register"
return r.Render(layouts.Auth, form.Render(r))
}
func ForgotPassword(ctx echo.Context, form *forms.ForgotPassword) error {
r := ui.NewRequest(ctx)
r.Title = "Forgot password"
g := Group{
Div(
Class("content"),
P(Text("Enter your email address and we'll email you a link that allows you to reset your password.")),
),
form.Render(r),
}
return r.Render(layouts.Auth, g)
}
func ResetPassword(ctx echo.Context, form *forms.ResetPassword) error {
r := ui.NewRequest(ctx)
r.Title = "Reset your password"
return r.Render(layouts.Auth, form.Render(r))
}

View file

@ -0,0 +1,15 @@
package pages
import (
"github.com/labstack/echo/v4"
"github.com/camzawacki/personal-site/internal/ui"
"github.com/camzawacki/personal-site/internal/ui/forms"
"github.com/camzawacki/personal-site/internal/ui/layouts"
)
func UpdateCache(ctx echo.Context, form *forms.Cache) error {
r := ui.NewRequest(ctx)
r.Title = "Set a cache entry"
return r.Render(layouts.Primary, form.Render(r))
}

View file

@ -0,0 +1,46 @@
package pages
import (
"github.com/labstack/echo/v4"
"github.com/camzawacki/personal-site/internal/ui"
. "github.com/camzawacki/personal-site/internal/ui/components"
"github.com/camzawacki/personal-site/internal/ui/forms"
"github.com/camzawacki/personal-site/internal/ui/layouts"
. "maragu.dev/gomponents"
. "maragu.dev/gomponents/html"
)
func ContactUs(ctx echo.Context, form *forms.Contact) error {
r := ui.NewRequest(ctx)
r.Title = "Contact us"
r.Metatags.Description = "Get in touch with us."
g := Group{
Iff(r.Htmx.Target != "contact", func() Node {
return Card(CardParams{
Title: "Card component",
Body: Group{
Span(Text("This is an example of a form with inline, server-side validation and HTMX-powered AJAX submissions without writing a single line of JavaScript.")),
Span(Text("Only the form below will update async upon submission.")),
},
Color: ColorWarning,
Size: SizeMedium,
})
}),
Iff(form.IsDone(), func() Node {
return Card(CardParams{
Title: "Thank you!",
Body: Group{
Span(Text("No email was actually sent but this entire operation was handled server-side and degrades without JavaScript enabled.")),
},
Color: ColorSuccess,
Size: SizeLarge,
})
}),
Iff(!form.IsDone(), func() Node {
return form.Render(r)
}),
}
return r.Render(layouts.Primary, g)
}

View file

@ -0,0 +1,38 @@
package pages
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/camzawacki/personal-site/internal/routenames"
"github.com/camzawacki/personal-site/internal/ui"
"github.com/camzawacki/personal-site/internal/ui/layouts"
. "maragu.dev/gomponents"
. "maragu.dev/gomponents/html"
)
func Error(ctx echo.Context, code int) error {
r := ui.NewRequest(ctx)
r.Title = http.StatusText(code)
var body Node
switch code {
case http.StatusInternalServerError:
body = Text("Please try again.")
case http.StatusForbidden, http.StatusUnauthorized:
body = Text("You are not authorized to view the requested page.")
case http.StatusNotFound:
body = Group{
Text("Click "),
A(
Href(r.Path(routenames.Home)),
Text("here"),
),
Text(" to go return home."),
}
default:
body = Text("Something went wrong.")
}
return r.Render(layouts.Primary, P(body))
}

53
internal/ui/pages/file.go Normal file
View file

@ -0,0 +1,53 @@
package pages
import (
"github.com/labstack/echo/v4"
"github.com/camzawacki/personal-site/internal/ui"
. "github.com/camzawacki/personal-site/internal/ui/components"
"github.com/camzawacki/personal-site/internal/ui/forms"
"github.com/camzawacki/personal-site/internal/ui/layouts"
"github.com/camzawacki/personal-site/internal/ui/models"
. "maragu.dev/gomponents"
. "maragu.dev/gomponents/html"
)
func UploadFile(ctx echo.Context, files []*models.File) error {
r := ui.NewRequest(ctx)
r.Title = "Upload a file"
fileList := make(Group, len(files))
for i, file := range files {
fileList[i] = file.Render()
}
n := Group{
P(Text("This is a very basic example of how to handle file uploads. Files uploaded will be saved to the directory specified in your configuration.")),
Divider(""),
forms.File{}.Render(r),
Divider(""),
H3(
Class("title"),
Text("Uploaded files"),
),
Card(CardParams{
Body: Group{Text("Below are all files in the configured upload directory.")},
Color: ColorWarning,
Size: SizeMedium,
}),
Table(
Class("table"),
THead(
Tr(
Th(Text("Filename")),
Th(Text("Size")),
Th(Text("Modified on")),
),
),
TBody(
fileList,
),
),
}
return r.Render(layouts.Primary, n)
}

63
internal/ui/pages/home.go Normal file
View file

@ -0,0 +1,63 @@
package pages
import (
"github.com/labstack/echo/v4"
// "github.com/camzawacki/personal-site/internal/routenames"
"github.com/camzawacki/personal-site/internal/ui"
// . "github.com/camzawacki/personal-site/internal/ui/components"
// "github.com/camzawacki/personal-site/internal/ui/icons"
"github.com/camzawacki/personal-site/internal/ui/layouts"
"github.com/camzawacki/personal-site/internal/ui/models"
. "maragu.dev/gomponents"
. "maragu.dev/gomponents/html"
)
func Home(ctx echo.Context, posts *models.Posts) error {
r := ui.NewRequest(ctx)
r.Metatags.Description = "This is my homepage."
r.Metatags.Keywords = []string{"Software", "Coding", "Projects", "Homepage"}
img := Div(
Class("w-full h-full flex justify-center"),
Div(
Class("bg-blue-100 size-92 object-contain overflow-hidden rounded-4xl"),
Img(
Src(ui.StaticFile("me2.webp")),
),
),
)
// tabs := cache.SetIfNotExists("pages.about.Tabs", func() Node {
banner := Div(
Class("w-full py-4 bg-red-100 text-center text-lg"),
Text("This website is currently under construction. For an older version, see "),
A(
Class("underline"),
Href("https://camzawacki.com"),
Text("camzawacki.com"),
),
)
education := Div(
Class("prose-xl"),
H2(Text("Education")),
Ul(Class("list-disc pl-3"),
Li(Text("PhD Electrical Engineering")),
Li(Text("MS Robotics")),
Li(Text("BS Mechanical Engineering & Computer Science")),
),
)
content := Div(
Class("flex flex-col p-5 mx-10 gap-2"),
img,
Div(Class("w-full divider")),
banner,
Div(
Class("mx-auto w-160"),
education,
),
)
return r.Render(layouts.Primary, content)
}

View file

@ -0,0 +1,20 @@
package pages
import (
"github.com/labstack/echo/v4"
"github.com/camzawacki/personal-site/internal/ui"
"github.com/camzawacki/personal-site/internal/ui/layouts"
"github.com/camzawacki/personal-site/internal/ui/models"
. "maragu.dev/gomponents"
)
func SearchResults(ctx echo.Context, results []*models.SearchResult) error {
r := ui.NewRequest(ctx)
g := make(Group, len(results))
for i, result := range results {
g[i] = result.Render()
}
return r.Render(layouts.Primary, g)
}

41
internal/ui/pages/task.go Normal file
View file

@ -0,0 +1,41 @@
package pages
import (
"github.com/labstack/echo/v4"
"github.com/camzawacki/personal-site/internal/ui"
. "github.com/camzawacki/personal-site/internal/ui/components"
"github.com/camzawacki/personal-site/internal/ui/forms"
"github.com/camzawacki/personal-site/internal/ui/layouts"
. "maragu.dev/gomponents"
. "maragu.dev/gomponents/html"
)
func AddTask(ctx echo.Context, form *forms.Task) error {
r := ui.NewRequest(ctx)
r.Title = "Create a task"
r.Metatags.Description = "Test creating a task to see how it works."
g := Group{
Iff(r.Htmx.Target != "task", func() Node {
return Group{
P(Raw("Submitting this form will create an <i>ExampleTask</i> in the task queue. After the specified delay, the message will be logged by the queue processor.")),
P(Raw("See <i>pkg/tasks</i> and the README for more information.")),
}
}),
form.Render(r),
Iff(r.Htmx.Target != "task", func() Node {
var text string
if r.IsAdmin {
text = "View all queued tasks by clicking on the Tasks link in the sidebar."
} else {
text = "Log in as an admin in order to access the task and queue monitoring UI."
}
return Group{
Div(Class("mt-5")),
Alert(ColorWarning, text),
}
}),
}
return r.Render(layouts.Primary, g)
}