diff --git a/backend/appsystem/version.go b/backend/appsystem/version.go
index e8d139f..c7658e2 100644
--- a/backend/appsystem/version.go
+++ b/backend/appsystem/version.go
@@ -5,7 +5,7 @@ import (
)
// Version is the application version
-const Version = "0.2.2"
+const Version = "0.2.4"
var (
// GitCommit will be set by build flags or detected at runtime
diff --git a/backend/character/handlers.go b/backend/character/handlers.go
index 81f6c0d..549a49f 100644
--- a/backend/character/handlers.go
+++ b/backend/character/handlers.go
@@ -191,6 +191,12 @@ func ToFeChar(object *models.Char) *models.FeChar {
feC := &models.FeChar{
Char: *object,
}
+ for idx, fertigkeit := range object.Fertigkeiten {
+ fertigkeit.Bonus = GetSkillBonus(&object.Eigenschaften, &fertigkeit)
+ if fertigkeit.Bonus > 0 {
+ object.Fertigkeiten[idx].Bonus = fertigkeit.Bonus
+ }
+ }
skills, innateSkills, categories := splitSkills(object.Fertigkeiten)
feC.Fertigkeiten = skills
feC.InnateSkills = innateSkills
@@ -199,6 +205,32 @@ func ToFeChar(object *models.Char) *models.FeChar {
return feC
}
+func GetSkillBonus(eigenschaften *[]models.Eigenschaft, skill *models.SkFertigkeit) int {
+ bonus := 0
+ gsmsk := skill.GetSkillByName()
+ if gsmsk.Bonuseigenschaft != "check" {
+ for _, eigenschaft := range *eigenschaften {
+ if eigenschaft.Name == gsmsk.Bonuseigenschaft {
+ if eigenschaft.Value < 6 {
+ bonus = -2
+ break
+ } else if eigenschaft.Value < 21 {
+ bonus = -1
+ break
+ } else if eigenschaft.Value > 81 && eigenschaft.Value < 96 {
+ bonus = 1
+ break
+ } else if eigenschaft.Value >= 96 {
+ bonus = 2
+ break
+ }
+ }
+ }
+ }
+ skill.Bonus = bonus
+ return bonus
+}
+
func splitSkills(object []models.SkFertigkeit) ([]models.SkFertigkeit, []models.SkFertigkeit, map[string][]models.SkFertigkeit) {
var normSkills []models.SkFertigkeit
var innateSkills []models.SkFertigkeit
diff --git a/backend/character/handlers_test.go b/backend/character/handlers_test.go
index 35412f7..2406bd2 100644
--- a/backend/character/handlers_test.go
+++ b/backend/character/handlers_test.go
@@ -1198,3 +1198,15 @@ func TestSearchBeliefs(t *testing.T) {
})
}
}
+
+func TestToFeChar(t *testing.T) {
+ // Setup test database
+ database.SetupTestDB(true)
+ defer database.ResetTestDB()
+ char := &models.Char{}
+ char.FirstID("18")
+ feChar := ToFeChar(char)
+ assert.Equal(t, "18", feChar.ID)
+ assert.Equal(t, 2, feChar.Fertigkeiten[6].Bonus)
+
+}
diff --git a/backend/character/routes.go b/backend/character/routes.go
index 9604c8f..9bf05f9 100644
--- a/backend/character/routes.go
+++ b/backend/character/routes.go
@@ -33,20 +33,24 @@ func RegisterRoutes(r *gin.RouterGroup) {
// im Frontend wir nur noch der neue Endpunkt benutzt
//charGrp.POST("/lerncost", GetLernCost) // alter Hauptendpunkt für alle Kostenberechnungen (verwendet lerningCostsData)
charGrp.POST("/lerncost-new", GetLernCostNewSystem) // neuer Hauptendpunkt für alle Kostenberechnungen (verwendet neue Datenbank)
+ charGrp.POST("/lerncost", GetLernCostNewSystem) // neuer Hauptendpunkt für alle Kostenberechnungen (verwendet neue Datenbank)
charGrp.POST("/improve-skill-new", ImproveSkill) // Fertigkeit verbessern
+ charGrp.POST("/improve-skill", ImproveSkill) // Fertigkeit verbessern
// Lernen und Verbessern (mit automatischem Audit-Log)
charGrp.POST("/:id/learn-skill-new", LearnSkill) // Fertigkeit lernen (neues System)
- //charGrp.POST("/:id/learn-skill", LearnSkillOld) // Fertigkeit lernen (altes System)
+ charGrp.POST("/:id/learn-skill", LearnSkill) // Fertigkeit lernen (altes System)
charGrp.POST("/:id/learn-spell-new", LearnSpell) // Zauber lernen (neues System)
- //charGrp.POST("/:id/learn-spell", LearnSpellOld) // Zauber lernen (altes System)
+ charGrp.POST("/:id/learn-spell", LearnSpell) // Zauber lernen (altes System)
// Fertigkeiten-Information
//charGrp.GET("/:id/available-skills", GetAvailableSkillsOld) // Verfügbare Fertigkeiten mit Kosten (bereits gelernte ausgeschlossen)
charGrp.POST("/available-skills-new", GetAvailableSkillsNewSystem) // Verfügbare Fertigkeiten mit Kosten (bereits gelernte ausgeschlossen)
+ charGrp.POST("/available-skills", GetAvailableSkillsNewSystem) // Verfügbare Fertigkeiten mit Kosten (bereits gelernte ausgeschlossen)
charGrp.POST("/available-skills-creation", GetAvailableSkillsForCreation) // Verfügbare Fertigkeiten mit Lernkosten für Charaktererstellung
charGrp.POST("/available-spells-creation", GetAvailableSpellsForCreation) // Verfügbare Zauber mit Lernkosten für Charaktererstellung
charGrp.POST("/available-spells-new", GetAvailableSpellsNewSystem) // Verfügbare Zauber mit Kosten (bereits gelernte ausgeschlossen)
+ charGrp.POST("/available-spells", GetAvailableSpellsNewSystem) // Verfügbare Zauber mit Kosten (bereits gelernte ausgeschlossen)
charGrp.GET("/spell-details", GetSpellDetails) // Detaillierte Informationen zu einem bestimmten Zauber
// Belohnungsarten für verschiedene Lernszenarien
diff --git a/backend/config/config.go b/backend/config/config.go
index 1e44d30..0441d61 100644
--- a/backend/config/config.go
+++ b/backend/config/config.go
@@ -32,6 +32,13 @@ type Config struct {
// PDF Templates
TemplatesDir string // Directory where PDF templates are stored
ExportTempDir string // Directory for temporary PDF exports
+
+ // Mail Configuration
+ MailHost string // SMTP server host
+ MailPort int // SMTP server port
+ MailUsername string // SMTP username
+ MailPassword string // SMTP password
+ MailFrom string // Default sender email address
}
// Cfg ist die globale Konfigurationsvariable
@@ -56,6 +63,11 @@ func defaultConfig() *Config {
FrontendURL: "http://localhost:5173", // Default frontend URL for development
TemplatesDir: "./templates", // Default templates directory
ExportTempDir: "./xporttemp", // Default export temp directory
+ MailHost: "", // No default, must be configured
+ MailPort: 465, // Default SMTP SSL port
+ MailUsername: "", // No default, must be configured
+ MailPassword: "", // No default, must be configured
+ MailFrom: "", // No default, must be configured
}
}
@@ -136,6 +148,28 @@ func LoadConfig() *Config {
config.ExportTempDir = exportTempDir
}
+ // Mail Configuration
+ if mailHost := os.Getenv("MAIL_HOST"); mailHost != "" {
+ config.MailHost = mailHost
+ }
+ if mailPort := os.Getenv("MAIL_PORT"); mailPort != "" {
+ if port, err := strconv.Atoi(mailPort); err == nil {
+ config.MailPort = port
+ }
+ }
+ if mailUsername := os.Getenv("MAIL_USERNAME"); mailUsername != "" {
+ config.MailUsername = mailUsername
+ }
+ if mailPassword := os.Getenv("MAIL_PASSWORD"); mailPassword != "" {
+ config.MailPassword = mailPassword
+ }
+ if mailFrom := os.Getenv("MAIL_FROM"); mailFrom != "" {
+ config.MailFrom = mailFrom
+ } else if config.MailUsername != "" {
+ // Fallback: Verwende Username als From-Adresse
+ config.MailFrom = config.MailUsername
+ }
+
fmt.Printf("DEBUG LoadConfig - Finale Config: Environment='%s', DevTesting='%s', DatabaseType='%s'\n Complete: %v\n",
config.Environment, config.DevTesting, config.DatabaseType, config)
diff --git a/backend/mail/smtp.go b/backend/mail/smtp.go
new file mode 100644
index 0000000..7f39524
--- /dev/null
+++ b/backend/mail/smtp.go
@@ -0,0 +1,163 @@
+package mail
+
+import (
+ "crypto/tls"
+ "fmt"
+ "net/smtp"
+
+ "bamort/config"
+ "bamort/logger"
+)
+
+// Client represents an SMTP mail client
+type Client struct {
+ host string
+ port int
+ username string
+ password string
+ from string
+}
+
+// Message represents an email message
+type Message struct {
+ To string
+ Subject string
+ Body string
+}
+
+// NewClient creates a new SMTP mail client from config
+func NewClient() *Client {
+ cfg := config.Cfg
+ return &Client{
+ host: cfg.MailHost,
+ port: cfg.MailPort,
+ username: cfg.MailUsername,
+ password: cfg.MailPassword,
+ from: cfg.MailFrom,
+ }
+}
+
+// Send sends an email message via SMTP
+func (c *Client) Send(msg Message) error {
+ if c.host == "" {
+ logger.Warn("SMTP Host nicht konfiguriert - E-Mail-Versand übersprungen")
+ return fmt.Errorf("SMTP host not configured")
+ }
+
+ logger.Debug("Sende E-Mail an %s via SMTP %s:%d", msg.To, c.host, c.port)
+
+ // Prepare email headers and body
+ headers := make(map[string]string)
+ headers["From"] = c.from
+ headers["To"] = msg.To
+ headers["Subject"] = msg.Subject
+ headers["MIME-Version"] = "1.0"
+ headers["Content-Type"] = "text/html; charset=\"UTF-8\""
+
+ // Build message
+ message := ""
+ for key, value := range headers {
+ message += fmt.Sprintf("%s: %s\r\n", key, value)
+ }
+ message += "\r\n" + msg.Body
+
+ // Connect to SMTP server
+ serverAddr := fmt.Sprintf("%s:%d", c.host, c.port)
+
+ // Create TLS config
+ tlsConfig := &tls.Config{
+ ServerName: c.host,
+ InsecureSkipVerify: false,
+ }
+
+ var err error
+ var client *smtp.Client
+
+ // Port 465 requires direct TLS connection, port 587 uses STARTTLS
+ if c.port == 465 {
+ // Direct TLS connection (implicit SSL)
+ conn, err := tls.Dial("tcp", serverAddr, tlsConfig)
+ if err != nil {
+ logger.Error("Fehler beim Verbinden mit SMTP-Server (TLS): %s", err.Error())
+ return fmt.Errorf("failed to connect to SMTP server: %w", err)
+ }
+ defer conn.Close()
+
+ client, err = smtp.NewClient(conn, c.host)
+ if err != nil {
+ logger.Error("Fehler beim Erstellen des SMTP-Clients: %s", err.Error())
+ return fmt.Errorf("failed to create SMTP client: %w", err)
+ }
+ } else {
+ // Port 587 or other - use STARTTLS
+ client, err = smtp.Dial(serverAddr)
+ if err != nil {
+ logger.Error("Fehler beim Verbinden mit SMTP-Server: %s", err.Error())
+ return fmt.Errorf("failed to dial SMTP server: %w", err)
+ }
+ defer client.Close()
+
+ // Start TLS if available
+ if ok, _ := client.Extension("STARTTLS"); ok {
+ if err = client.StartTLS(tlsConfig); err != nil {
+ logger.Error("Fehler beim STARTTLS: %s", err.Error())
+ return fmt.Errorf("failed to start TLS: %w", err)
+ }
+ }
+ }
+
+ // Authenticate
+ if c.username != "" && c.password != "" {
+ auth := smtp.PlainAuth("", c.username, c.password, c.host)
+ if err = client.Auth(auth); err != nil {
+ logger.Error("Fehler bei der SMTP-Authentifizierung: %s", err.Error())
+ return fmt.Errorf("failed to authenticate: %w", err)
+ }
+ }
+
+ // Set sender
+ if err = client.Mail(c.from); err != nil {
+ logger.Error("Fehler beim Setzen des Absenders: %s", err.Error())
+ return fmt.Errorf("failed to set sender: %w", err)
+ }
+
+ // Set recipient
+ if err = client.Rcpt(msg.To); err != nil {
+ logger.Error("Fehler beim Setzen des Empfängers: %s", err.Error())
+ return fmt.Errorf("failed to set recipient: %w", err)
+ }
+
+ // Send message body
+ writer, err := client.Data()
+ if err != nil {
+ logger.Error("Fehler beim Öffnen des Data-Writers: %s", err.Error())
+ return fmt.Errorf("failed to open data writer: %w", err)
+ }
+
+ _, err = writer.Write([]byte(message))
+ if err != nil {
+ logger.Error("Fehler beim Schreiben der Nachricht: %s", err.Error())
+ writer.Close()
+ return fmt.Errorf("failed to write message: %w", err)
+ }
+
+ err = writer.Close()
+ if err != nil {
+ logger.Error("Fehler beim Schließen des Data-Writers: %s", err.Error())
+ return fmt.Errorf("failed to close data writer: %w", err)
+ }
+
+ // Quit
+ if err = client.Quit(); err != nil {
+ logger.Error("Fehler beim Beenden der SMTP-Verbindung: %s", err.Error())
+ return fmt.Errorf("failed to quit SMTP connection: %w", err)
+ }
+
+ logger.Info("E-Mail erfolgreich an %s versendet", msg.To)
+ return nil
+}
+
+// IsConfigured returns true if the mail client is properly configured
+func (c *Client) IsConfigured() bool {
+ return c.host != "" && c.port > 0 && c.from != ""
+}
diff --git a/backend/mail/smtp_test.go b/backend/mail/smtp_test.go
new file mode 100644
index 0000000..0e54e17
--- /dev/null
+++ b/backend/mail/smtp_test.go
@@ -0,0 +1,140 @@
+package mail
+
+import (
+ "os"
+ "testing"
+
+ "bamort/config"
+)
+
+// setupTestEnvironment setzt die Test-Umgebung auf
+func setupTestEnvironment(t *testing.T) {
+ original := os.Getenv("ENVIRONMENT")
+ os.Setenv("ENVIRONMENT", "test")
+ t.Cleanup(func() {
+ if original != "" {
+ os.Setenv("ENVIRONMENT", original)
+ } else {
+ os.Unsetenv("ENVIRONMENT")
+ }
+ })
+ // Reload config with test environment
+ config.Cfg = config.LoadConfig()
+}
+
+func TestNewClient(t *testing.T) {
+ setupTestEnvironment(t)
+
+ // Set test mail config
+ os.Setenv("MAIL_HOST", "smtp.example.com")
+ os.Setenv("MAIL_PORT", "465")
+ os.Setenv("MAIL_USERNAME", "test@example.com")
+ os.Setenv("MAIL_PASSWORD", "testpass")
+ os.Setenv("MAIL_FROM", "sender@example.com")
+
+ // Reload config
+ config.Cfg = config.LoadConfig()
+
+ client := NewClient()
+
+ if client.host != "smtp.example.com" {
+ t.Errorf("Expected host 'smtp.example.com', got '%s'", client.host)
+ }
+ if client.port != 465 {
+ t.Errorf("Expected port 465, got %d", client.port)
+ }
+ if client.username != "test@example.com" {
+ t.Errorf("Expected username 'test@example.com', got '%s'", client.username)
+ }
+ if client.from != "sender@example.com" {
+ t.Errorf("Expected from 'sender@example.com', got '%s'", client.from)
+ }
+}
+
+func TestIsConfigured(t *testing.T) {
+ tests := []struct {
+ name string
+ client *Client
+ expected bool
+ }{
+ {
+ name: "Fully configured",
+ client: &Client{
+ host: "smtp.example.com",
+ port: 465,
+ from: "test@example.com",
+ },
+ expected: true,
+ },
+ {
+ name: "Missing host",
+ client: &Client{
+ host: "",
+ port: 465,
+ from: "test@example.com",
+ },
+ expected: false,
+ },
+ {
+ name: "Missing port",
+ client: &Client{
+ host: "smtp.example.com",
+ port: 0,
+ from: "test@example.com",
+ },
+ expected: false,
+ },
+ {
+ name: "Missing from",
+ client: &Client{
+ host: "smtp.example.com",
+ port: 465,
+ from: "",
+ },
+ expected: false,
+ },
+ {
+ name: "Empty client",
+ client: &Client{
+ host: "",
+ port: 0,
+ from: "",
+ },
+ expected: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := tt.client.IsConfigured()
+ if result != tt.expected {
+ t.Errorf("Expected IsConfigured() to return %v, got %v", tt.expected, result)
+ }
+ })
+ }
+}
+
+func TestSend_NotConfigured(t *testing.T) {
+ setupTestEnvironment(t)
+
+ // Create client without config
+ client := &Client{
+ host: "",
+ port: 0,
+ from: "",
+ }
+
+ msg := Message{
+ To: "recipient@example.com",
+ Subject: "Test",
+ Body: "Test body",
+ }
+
+ err := client.Send(msg)
+ if err == nil {
+ t.Error("Expected error when sending with unconfigured client, got nil")
+ }
+ if err.Error() != "SMTP host not configured" {
+ t.Errorf("Expected error 'SMTP host not configured', got '%s'", err.Error())
+ }
+}
diff --git a/backend/user/handlers.go b/backend/user/handlers.go
index 2903cab..7ab9ea0 100644
--- a/backend/user/handlers.go
+++ b/backend/user/handlers.go
@@ -7,6 +7,7 @@ package user
import (
"bamort/logger"
+ "bamort/mail"
"crypto/md5"
"crypto/rand"
"encoding/hex"
@@ -239,8 +240,7 @@ func generateResetHash() (string, error) {
return hex.EncodeToString(bytes), nil
}
-// sendResetEmail simuliert das Senden einer E-Mail (hier nur Logging)
-// In einer echten Implementierung würde hier ein E-Mail-Service verwendet
+// sendResetEmail sends a password reset email via SMTP
func sendResetEmail(email, username, resetHash, frontendURL string) error {
// Verwende die mitgegebene Frontend-URL oder fallback auf Standard
baseURL := frontendURL
@@ -250,25 +250,81 @@ func sendResetEmail(email, username, resetHash, frontendURL string) error {
resetLink := fmt.Sprintf("%s/reset-password?token=%s", baseURL, resetHash)
- logger.Info("=== PASSWORD RESET EMAIL ===")
- logger.Info("An: %s", email)
- logger.Info("Betreff: Passwort zurücksetzen für %s", username)
- logger.Info("Nachricht:")
- logger.Info("Hallo %s,", username)
- logger.Info("")
- logger.Info("Sie haben eine Passwort-Zurücksetzung angefordert.")
- logger.Info("Klicken Sie auf den folgenden Link, um Ihr Passwort zurückzusetzen:")
- logger.Info("")
- logger.Info("%s", resetLink)
- logger.Info("")
- logger.Info("Dieser Link ist 14 Tage gültig.")
- logger.Info("Falls Sie diese Anfrage nicht gestellt haben, ignorieren Sie diese E-Mail.")
- logger.Info("")
- logger.Info("=== END EMAIL ===")
+ // Create mail client
+ mailClient := mail.NewClient()
- // TODO: Hier echte E-Mail-Integration hinzufügen
- // z.B. SendGrid, SMTP, etc.
+ // If mail is not configured, fallback to logging
+ if !mailClient.IsConfigured() {
+ logger.Warn("SMTP nicht konfiguriert - E-Mail wird nur geloggt")
+ logger.Info("=== PASSWORD RESET EMAIL ===")
+ logger.Info("An: %s", email)
+ logger.Info("Betreff: Passwort zurücksetzen für %s", username)
+ logger.Info("Nachricht:")
+ logger.Info("Hallo %s,", username)
+ logger.Info("")
+ logger.Info("Sie haben eine Passwort-Zurücksetzung angefordert.")
+ logger.Info("Klicken Sie auf den folgenden Link, um Ihr Passwort zurückzusetzen:")
+ logger.Info("")
+ logger.Info("%s", resetLink)
+ logger.Info("")
+ logger.Info("Dieser Link ist 14 Tage gültig.")
+ logger.Info("Falls Sie diese Anfrage nicht gestellt haben, ignorieren Sie diese E-Mail.")
+ logger.Info("")
+ logger.Info("=== END EMAIL ===")
+ return nil
+ }
+ // Build HTML email body
+ htmlBody := fmt.Sprintf(`
+
+
+
+
+
+
+
+
+
+
Hallo %s,
+
Sie haben eine Passwort-Zurücksetzung für Ihren Bamort-Account angefordert.
+
Klicken Sie auf den folgenden Button, um Ihr Passwort zurückzusetzen:
+
+ Passwort zurücksetzen
+
+
Oder kopieren Sie diesen Link in Ihren Browser:
+
%s
+
Dieser Link ist 14 Tage gültig.
+
Falls Sie diese Anfrage nicht gestellt haben, können Sie diese E-Mail ignorieren. Ihr Passwort bleibt unverändert.
+
+
+
+
+`, username, resetLink, resetLink)
+
+ // Send email
+ msg := mail.Message{
+ To: email,
+ Subject: "Passwort zurücksetzen - Bamort",
+ Body: htmlBody,
+ }
+
+ if err := mailClient.Send(msg); err != nil {
+ logger.Error("Fehler beim Versenden der Reset-E-Mail an %s: %s", email, err.Error())
+ return err
+ }
+
+ logger.Info("Password-Reset-E-Mail erfolgreich an %s versendet", email)
return nil
}
diff --git a/docker/.env.dev b/docker/.env.dev
index 9b170fc..bdc62ad 100644
--- a/docker/.env.dev
+++ b/docker/.env.dev
@@ -18,4 +18,10 @@ API_URL=http://192.168.0.48:8180
API_PORT=8180
BASE_URL=http://localhost:5173
TEMPLATES_DIR=./templates
-EXPORT_TEMP_DIR=./export_temp
\ No newline at end of file
+EXPORT_TEMP_DIR=./export_temp
+
+# Mail Configuration (for development)
+MAIL_HOST=mail.wuenscheonline.de
+MAIL_PORT=465
+MAIL_USERNAME=bamort@trokan.de
+MAIL_PASSWORD=XXXmhqbv.DW+XXX
diff --git a/docker/.env.prd b/docker/.env.prd
index be6d581..e29dc5c 100644
--- a/docker/.env.prd
+++ b/docker/.env.prd
@@ -18,3 +18,9 @@ API_PORT=8180
BASE_URL=https://bamort.trokan.de
TEMPLATES_DIR=./templates
EXPORT_TEMP_DIR=./export_temp
+
+# Mail Configuration (for production)
+MAIL_HOST=mail.wuenscheonline.de
+MAIL_PORT=465
+MAIL_USERNAME=bamort@trokan.de
+MAIL_PASSWORD=XXXXmhqbv.DW+XXX
diff --git a/frontend/VERSION.md b/frontend/VERSION.md
index 78c7a87..9ee6a4b 100644
--- a/frontend/VERSION.md
+++ b/frontend/VERSION.md
@@ -1,6 +1,6 @@
# Frontend Version Management
-## Current Version: 0.2.2
+## Current Version: 0.2.3
The frontend version is managed independently from the backend.
diff --git a/frontend/package.json b/frontend/package.json
index 1018dc2..2c766d5 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,6 +1,6 @@
{
"name": "bamort-frontend",
- "version": "0.2.2",
+ "version": "0.2.3",
"private": true,
"license": "SEE LICENSE IN LICENSE",
"type": "module",
diff --git a/frontend/src/assets/main.css b/frontend/src/assets/main.css
index bc7c8a1..d95dd5d 100644
--- a/frontend/src/assets/main.css
+++ b/frontend/src/assets/main.css
@@ -1674,7 +1674,7 @@ a:focus {
display: flex;
align-items: center;
justify-content: center;
- z-index: 1000;
+ z-index: 1100;
}
/* Modal content container */
@@ -1727,11 +1727,13 @@ a:focus {
/* Modal actions (alternative to footer) */
.modal-actions {
display: flex;
- justify-content: flex-end;
+ justify-content: space-between;
+ align-items: center;
gap: 10px;
- margin-top: 20px;
- padding-top: 15px;
- border-top: 1px solid #eee;
+ padding: 20px 24px;
+ border-top: 1px solid #dee2e6;
+ background: #f8f9fa;
+ flex-shrink: 0;
}
/* Close button */
@@ -1778,6 +1780,8 @@ a:focus {
position: absolute;
top: 0;
left: 0;
+ display: flex;
+ flex-direction: column;
}
/* ========================================
@@ -4403,3 +4407,23 @@ a:focus {
max-height: 150px;
}
}
+.help-icon {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 18px;
+ height: 18px;
+ margin-left: 6px;
+ border: 1px solid #999;
+ border-radius: 50%;
+ font-size: 12px;
+ line-height: 1;
+ /*cursor: help;*/
+ background: #f5f5f5;
+ color: #555;
+}
+
+.help-icon:hover {
+ background: #e0e0e0;
+ color: #222;
+}
\ No newline at end of file
diff --git a/frontend/src/components/CharacterCreation/CharacterBasicInfo.vue b/frontend/src/components/CharacterCreation/CharacterBasicInfo.vue
index 11e1a77..15702d6 100644
--- a/frontend/src/components/CharacterCreation/CharacterBasicInfo.vue
+++ b/frontend/src/components/CharacterCreation/CharacterBasicInfo.vue
@@ -549,7 +549,7 @@ export default {
color: #666;
margin-top: 15px;
}
-
+/*
.help-icon {
display: inline-flex;
align-items: center;
@@ -561,7 +561,6 @@ export default {
border-radius: 50%;
font-size: 12px;
line-height: 1;
- /*cursor: help;*/
background: #f5f5f5;
color: #555;
}
@@ -570,4 +569,5 @@ export default {
background: #e0e0e0;
color: #222;
}
+*/
diff --git a/frontend/src/components/DatasheetView.vue b/frontend/src/components/DatasheetView.vue
index a87a32e..7926ba3 100644
--- a/frontend/src/components/DatasheetView.vue
+++ b/frontend/src/components/DatasheetView.vue
@@ -36,6 +36,14 @@
+
{{ $t('char') }}:
diff --git a/frontend/src/components/ForgotPasswordForm.vue b/frontend/src/components/ForgotPasswordForm.vue
index c1f276b..6293c81 100644
--- a/frontend/src/components/ForgotPasswordForm.vue
+++ b/frontend/src/components/ForgotPasswordForm.vue
@@ -1,57 +1,56 @@
-
-
+
+
-
+
-
-
-
- E-Mail gesendet!
+
+
+
+ {{ $t('forgotPassword.successTitle') }}
-
- Falls ein Account mit dieser E-Mail-Adresse existiert, wurde ein Reset-Link gesendet.
+
+ {{ $t('forgotPassword.successInfo') }}
-
- Prüfen Sie Ihre E-Mails und folgen Sie dem Link.
+
+ {{ $t('forgotPassword.successHint') }}
-
-
+
+
{{ error }}
-
-
+
+
@@ -86,7 +85,7 @@ export default {
console.log('Password reset email requested for:', this.email)
} catch (err) {
console.error('Password reset request error:', err)
- this.error = err.response?.data?.error || 'Fehler beim Senden der E-Mail. Versuchen Sie es später erneut.'
+ this.error = err.response?.data?.error || this.$t('forgotPassword.error')
} finally {
this.isLoading = false
}
@@ -95,6 +94,57 @@ export default {
}
-
diff --git a/frontend/src/components/SkillView.vue b/frontend/src/components/SkillView.vue
index b12deae..50a39d2 100644
--- a/frontend/src/components/SkillView.vue
+++ b/frontend/src/components/SkillView.vue
@@ -3,7 +3,16 @@