From d0be4a26546559012f93da034e5cdfbb1754a91d Mon Sep 17 00:00:00 2001 From: Stavros Date: Sat, 12 Jul 2025 00:08:24 +0300 Subject: [PATCH] feat: add version information to login screen --- frontend/bun.lock | 5 ++ frontend/package.json | 1 + .../language-selector.tsx} | 0 .../components/components/version-tooltip.tsx | 23 ++++++++ frontend/src/components/layout/layout.tsx | 4 +- frontend/src/components/ui/tooltip.tsx | 59 +++++++++++++++++++ frontend/src/schemas/app-context-schema.ts | 3 + internal/handlers/handlers.go | 4 ++ internal/server/server_test.go | 3 + internal/types/api.go | 3 + 10 files changed, 104 insertions(+), 1 deletion(-) rename frontend/src/components/{language/language.tsx => components/language-selector.tsx} (100%) create mode 100644 frontend/src/components/components/version-tooltip.tsx create mode 100644 frontend/src/components/ui/tooltip.tsx diff --git a/frontend/bun.lock b/frontend/bun.lock index a280889..a2c3516 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -9,6 +9,7 @@ "@radix-ui/react-select": "^2.2.5", "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-tooltip": "^1.2.7", "@tailwindcss/vite": "^4.1.11", "@tanstack/react-query": "^5.82.0", "axios": "^1.10.0", @@ -224,6 +225,8 @@ "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ=="], + "@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA=="], + "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], "@radix-ui/react-select": ["@radix-ui/react-select@2.2.5", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.7", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-HnMTdXEVuuyzx63ME0ut4+sEMYW6oouHWNGUZc7ddvUWIcfCva/AMoqEW/3wnEllriMWBa0RHspCYnfCWJQYmA=="], @@ -232,6 +235,8 @@ "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + "@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.7", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.7", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Ap+fNYwKTYJ9pzqW+Xe2HtMRbQ/EeWkj2qykZ6SuEV4iS/o1bZI5ssJbk4D2r8XuDuOBVz/tIx2JObtuqU+5Zw=="], + "@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="], "@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.2", "", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg=="], diff --git a/frontend/package.json b/frontend/package.json index 9c9739d..825d2e0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,6 +15,7 @@ "@radix-ui/react-select": "^2.2.5", "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-tooltip": "^1.2.7", "@tailwindcss/vite": "^4.1.11", "@tanstack/react-query": "^5.82.0", "axios": "^1.10.0", diff --git a/frontend/src/components/language/language.tsx b/frontend/src/components/components/language-selector.tsx similarity index 100% rename from frontend/src/components/language/language.tsx rename to frontend/src/components/components/language-selector.tsx diff --git a/frontend/src/components/components/version-tooltip.tsx b/frontend/src/components/components/version-tooltip.tsx new file mode 100644 index 0000000..358fcad --- /dev/null +++ b/frontend/src/components/components/version-tooltip.tsx @@ -0,0 +1,23 @@ +import { useAppContext } from "@/context/app-context"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip"; + +export const VersionTooltip = () => { + const { version, buildTimestamp, commitHash } = useAppContext(); + + return ( + + + Tinyauth {version} + + +

Version: {version}

+

Build Timestamp: {buildTimestamp}

+

Commit Hash: {commitHash}

+
+
+ ); +}; diff --git a/frontend/src/components/layout/layout.tsx b/frontend/src/components/layout/layout.tsx index 773185b..0d723a2 100644 --- a/frontend/src/components/layout/layout.tsx +++ b/frontend/src/components/layout/layout.tsx @@ -1,6 +1,7 @@ import { useAppContext } from "@/context/app-context"; -import { LanguageSelector } from "../language/language"; +import { LanguageSelector } from "../components/language-selector"; import { Outlet } from "react-router"; +import { VersionTooltip } from "../components/version-tooltip"; export const Layout = () => { const { backgroundImage } = useAppContext(); @@ -16,6 +17,7 @@ export const Layout = () => { > + ); }; diff --git a/frontend/src/components/ui/tooltip.tsx b/frontend/src/components/ui/tooltip.tsx new file mode 100644 index 0000000..eabdac3 --- /dev/null +++ b/frontend/src/components/ui/tooltip.tsx @@ -0,0 +1,59 @@ +import * as React from "react"; +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; + +import { cn } from "@/lib/utils"; + +function TooltipProvider({ + delayDuration = 0, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function Tooltip({ + ...props +}: React.ComponentProps) { + return ( + + + + ); +} + +function TooltipTrigger({ + ...props +}: React.ComponentProps) { + return ; +} + +function TooltipContent({ + className, + sideOffset = 0, + children, + ...props +}: React.ComponentProps) { + return ( + + + {children} + + + + ); +} + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; diff --git a/frontend/src/schemas/app-context-schema.ts b/frontend/src/schemas/app-context-schema.ts index 31ded49..14a81ac 100644 --- a/frontend/src/schemas/app-context-schema.ts +++ b/frontend/src/schemas/app-context-schema.ts @@ -9,6 +9,9 @@ export const appContextSchema = z.object({ forgotPasswordMessage: z.string(), oauthAutoRedirect: z.enum(["none", "github", "google", "generic"]), backgroundImage: z.string(), + version: z.string(), + buildTimestamp: z.string(), + commitHash: z.string(), }); export type AppContextSchema = z.infer; diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 495c7d8..b4efc10 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -6,6 +6,7 @@ import ( "strings" "time" "tinyauth/internal/auth" + "tinyauth/internal/constants" "tinyauth/internal/docker" "tinyauth/internal/hooks" "tinyauth/internal/providers" @@ -565,6 +566,9 @@ func (h *Handlers) AppHandler(c *gin.Context) { ForgotPasswordMessage: h.Config.ForgotPasswordMessage, BackgroundImage: h.Config.BackgroundImage, OAuthAutoRedirect: h.Config.OAuthAutoRedirect, + Version: constants.Version, + BuildTimestamp: constants.BuildTimestamp, + CommitHash: constants.CommitHash, } // Return app context diff --git a/internal/server/server_test.go b/internal/server/server_test.go index 094fe66..8fde817 100644 --- a/internal/server/server_test.go +++ b/internal/server/server_test.go @@ -218,6 +218,9 @@ func TestAppContext(t *testing.T) { BackgroundImage: "https://example.com/image.png", OAuthAutoRedirect: "none", Domain: "localhost", + Version: "development", + BuildTimestamp: "n/a", + CommitHash: "n/a", } // We should get the username back diff --git a/internal/types/api.go b/internal/types/api.go index fbf8bf7..da0cad0 100644 --- a/internal/types/api.go +++ b/internal/types/api.go @@ -54,6 +54,9 @@ type AppContext struct { ForgotPasswordMessage string `json:"forgotPasswordMessage"` BackgroundImage string `json:"backgroundImage"` OAuthAutoRedirect string `json:"oauthAutoRedirect"` + Version string `json:"version"` + BuildTimestamp string `json:"buildTimestamp"` + CommitHash string `json:"commitHash"` } // Totp request is the request for the totp endpoint