Hypercode/alex/hypercodePublic

Code

  1. hypercode
  2. views
  3. pages
  4. device_auth.go
device_auth.go161 lines
package pages

import (
	"net/http"

	"github.com/hypercodehq/hypercode/database/models"
	"github.com/hypercodehq/libhtml"
	"github.com/hypercodehq/libhtml/attr"
	"github.com/hypercodehq/hypercode/views/components/layouts"
	"github.com/hypercodehq/hypercode/views/components/ui"
)

type DeviceAuthData struct {
	User    *models.User
	Code    string
	Success bool
	Error   string
}

func DeviceAuth(r *http.Request, data *DeviceAuthData) html.Node {
	if data == nil {
		data = &DeviceAuthData{}
	}

	var content html.Node

	if data.Success {
		// Show success message
		content = html.Div(
			attr.Class("max-w-md mx-auto text-center"),
			html.Div(
				attr.Class("mb-6 flex justify-center"),
				html.Div(
					attr.Class("rounded-full bg-emerald-100 p-4"),
					ui.SVGIcon(ui.IconCheck, "size-12 text-emerald-600"),
				),
			),
			html.H2(
				attr.Class("text-2xl font-semibold mb-3"),
				html.Text("Device Connected!"),
			),
			html.P(
				attr.Class("text-muted-foreground mb-6 text-pretty"),
				html.Text("Your device has been successfully authenticated. You can now close this window and return to your terminal."),
			),
		)
	} else if data.User != nil {
		// Show confirmation form
		content = html.Div(
			attr.Class("max-w-md mx-auto"),
			html.Div(
				attr.Class("text-center mb-8"),
				html.H2(
					attr.Class("text-2xl font-semibold mb-3"),
					html.Text("Authenticate Hypercode CLI"),
				),
				html.P(
					attr.Class("text-muted-foreground"),
					html.Text("You are signing in as "),
					html.Span(
						attr.Class("font-semibold text-foreground"),
						html.Text("@"+data.User.Username),
					),
				),
			),

			html.If(data.Error != "", html.Div(
				attr.Class("mb-6 p-4 rounded-lg bg-red-50 border border-red-200 text-red-800 text-sm"),
				html.Text(data.Error),
			)),

			html.Form(
				attr.Method("POST"),
				attr.Action("/auth/device/confirm"),
				attr.Class("space-y-6"),

				html.Div(
					attr.Class("space-y-2"),
					html.Label(
						attr.For("code"),
						attr.Class("label text-center block"),
						html.Text("Enter the code shown in your terminal:"),
					),
					html.Input(
						attr.Type("text"),
						attr.Id("code"),
						attr.Name("code"),
						attr.Value(data.Code),
						attr.Placeholder("XXXX-XXXX"),
						attr.Required(),
						attr.Class("input w-full text-center text-2xl font-mono tracking-wider uppercase"),
						attr.Attribute{Key: "maxlength", Value: "9"}, // 8 chars + 1 hyphen
						attr.Attribute{Key: "pattern", Value: "[A-Z0-9]{4}-[A-Z0-9]{4}"},
						attr.Attribute{Key: "autocomplete", Value: "off"},
					),
					html.P(
						attr.Class("text-xs text-muted-foreground text-center"),
						html.Text("The code should look like: ABCD-1234"),
					),
				),

				ui.Button(ui.ButtonProps{
					Variant: ui.ButtonPrimary,
					Type:    "submit",
					Class:   "flex-1 w-full",
				}, html.Text("Confirm")),
			),

			// JavaScript to format code input
			html.Script(
				html.Text(`
					const codeInput = document.getElementById('code');
					if (codeInput) {
						codeInput.addEventListener('input', (e) => {
							let value = e.target.value.toUpperCase().replace(/[^A-Z0-9]/g, '');
							if (value.length > 4) {
								value = value.slice(0, 4) + '-' + value.slice(4, 8);
							}
							e.target.value = value;
						});
						codeInput.focus();
					}
				`),
			),
		)
	} else {
		// Show sign-in prompt
		content = html.Div(
			attr.Class("max-w-md mx-auto text-center"),
			html.Div(
				attr.Class("mb-6"),
				ui.SVGIcon(ui.IconLock, "size-12 text-muted-foreground mx-auto"),
			),
			html.H2(
				attr.Class("text-2xl font-semibold mb-3"),
				html.Text("Sign In Required"),
			),
			html.P(
				attr.Class("text-muted-foreground mb-6"),
				html.Text("You need to be signed in to authenticate your device."),
			),
			ui.Button(ui.ButtonProps{
				Variant: ui.ButtonPrimary,
			},
				html.Element("a",
					attr.Href("/auth/sign-in"),
					html.Text("Sign In"),
				),
			),
		)
	}

	return layouts.Main(r,
		"Device Authentication",
		html.Main(
			attr.Class("min-h-[calc(100vh-61px)] flex items-center justify-center px-4 py-8"),
			content,
		),
	)
}