stage 0 database schema ready

This commit is contained in:
portakal 2026-05-25 20:07:14 +03:00
commit 7720522a32
18 changed files with 5577 additions and 4 deletions

View file

@ -1 +1,206 @@
package main
import (
"fmt"
"os"
"strings"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
// ============================================================================
// STYLING DEFINITIONS (Lipgloss)
// ============================================================================
var (
focusedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("205")).Bold(true)
blurredStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("240"))
cursorStyle = focusedStyle.Copy()
noStyle = lipgloss.NewStyle()
// A nice box around the login screen
dialogBoxStyle = lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("87")).
Padding(1, 4).
MarginTop(1).
MarginLeft(2)
)
// ============================================================================
// 1. THE MODEL
// ============================================================================
type loginModel struct {
focusIndex int
inputs []textinput.Model
cursorMode textinput.CursorMode
// Flags to return back to the main application
isSubmitted bool
isCanceled bool
errorMessage string
}
func initialModell() loginModel {
m := loginModel{
inputs: make([]textinput.Model, 2),
}
var t textinput.Model
for i := range m.inputs {
t = textinput.New()
t.Cursor.Style = cursorStyle
t.CharLimit = 32
switch i {
case 0:
t.Placeholder = "Username"
t.Focus()
t.PromptStyle = focusedStyle
t.TextStyle = focusedStyle
case 1:
t.Placeholder = "Password"
t.EchoMode = textinput.EchoPassword // Masks the password with *
t.EchoCharacter = '•'
}
m.inputs[i] = t
}
return m
}
// ============================================================================
// 2. THE INIT FUNCTION (Runs once on startup)
// ============================================================================
func (m loginModel) Init() tea.Cmd {
return textinput.Blink
}
// ============================================================================
// 3. THE UPDATE FUNCTION (Handles keypresses)
// ============================================================================
func (m loginModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "esc":
m.isCanceled = true
return m, tea.Quit
// Handle switching between username and password fields
case "tab", "shift+tab", "enter", "up", "down":
s := msg.String()
// If they press Enter on the password field, submit the form
if s == "enter" && m.focusIndex == len(m.inputs)-1 {
m.isSubmitted = true
return m, tea.Quit
}
// Cycle through inputs
if s == "up" || s == "shift+tab" {
m.focusIndex--
} else {
m.focusIndex++
}
if m.focusIndex > len(m.inputs)-1 {
m.focusIndex = 0
} else if m.focusIndex < 0 {
m.focusIndex = len(m.inputs) - 1
}
// Apply focus states
cmds := make([]tea.Cmd, len(m.inputs))
for i := 0; i <= len(m.inputs)-1; i++ {
if i == m.focusIndex {
cmds[i] = m.inputs[i].Focus()
m.inputs[i].PromptStyle = focusedStyle
m.inputs[i].TextStyle = focusedStyle
continue
}
m.inputs[i].Blur()
m.inputs[i].PromptStyle = noStyle
m.inputs[i].TextStyle = noStyle
}
return m, tea.Batch(cmds...)
}
}
// Handle character inputs and blinking
cmd := m.updateInputs(msg)
return m, cmd
}
func (m *loginModel) updateInputs(msg tea.Msg) tea.Cmd {
cmds := make([]tea.Cmd, len(m.inputs))
for i := range m.inputs {
m.inputs[i], cmds[i] = m.inputs[i].Update(msg)
}
return tea.Batch(cmds...)
}
// ============================================================================
// 4. THE VIEW FUNCTION (Renders the UI)
// ============================================================================
func (m loginModel) View() string {
// Build the UI string
var b strings.Builder
b.WriteString(lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("78")).Render("✈️ SKYRAMA TERMINAL LOGIN\n\n"))
for i := range m.inputs {
b.WriteString(m.inputs[i].View())
if i < len(m.inputs)-1 {
b.WriteRune('\n')
}
}
button := &lipgloss.Style{}
if m.focusIndex == len(m.inputs) {
button = &focusedStyle
} else {
button = &blurredStyle
}
b.WriteString("\n")
if m.errorMessage != "" {
b.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("9")).Render("⚠️ " + m.errorMessage + "\n"))
} else {
b.WriteString("\n")
}
b.WriteString(button.Render("[ Submit ]"))
b.WriteString(blurredStyle.Render("\n\n(esc to quit)"))
// Wrap the whole thing in our dialog box
return dialogBoxStyle.Render(b.String()) + "\n"
}
// ============================================================================
// THE WRAPPER FUNCTION (Call this from your app)
// ============================================================================
// RunLoginScreen starts the TUI and returns the username, password, and a boolean
// indicating if the user completed the form (true) or pressed escape (false).
func RunLoginScreen() (username string, password string, submitted bool) {
p := tea.NewProgram(initialModell())
// Run the Bubble Tea program synchronously
finalModel, err := p.Run()
if err != nil {
fmt.Printf("Error running login screen: %v", err)
os.Exit(1)
}
// Cast the returned tea.Model back into our specific loginModel
if m, ok := finalModel.(loginModel); ok {
if m.isSubmitted {
return m.inputs[0].Value(), m.inputs[1].Value(), true
}
}
return "", "", false
}