Charakter creation bugfixing
This commit is contained in:
+173
-12
@@ -2370,14 +2370,26 @@ func calculateSkillLearningCostsOld(skill models.Skill, character models.Char, r
|
||||
|
||||
// CreateCharacterSession erstellt eine neue Charakter-Erstellungssession
|
||||
func CreateCharacterSession(c *gin.Context) {
|
||||
logger.Debug("CreateCharacterSession aufgerufen")
|
||||
|
||||
// Debug: Alle Kontext-Keys anzeigen
|
||||
keys := make([]string, 0)
|
||||
for key := range c.Keys {
|
||||
keys = append(keys, fmt.Sprintf("%s=%v", key, c.Keys[key]))
|
||||
}
|
||||
logger.Debug("CreateCharacterSession: Verfügbare Kontext-Keys: [%s]", strings.Join(keys, ", "))
|
||||
|
||||
userID := c.GetUint("userID")
|
||||
logger.Debug("CreateCharacterSession: UserID = %d", userID)
|
||||
|
||||
if userID == 0 {
|
||||
logger.Warn("CreateCharacterSession: Unauthorized - UserID ist 0")
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
sessionID := fmt.Sprintf("char_create_%d_%d", userID, time.Now().Unix())
|
||||
logger.Debug("CreateCharacterSession: Generierte SessionID = %s", sessionID)
|
||||
|
||||
session := models.CharacterCreationSession{
|
||||
ID: sessionID,
|
||||
@@ -2389,22 +2401,26 @@ func CreateCharacterSession(c *gin.Context) {
|
||||
Glaube: "",
|
||||
Attributes: models.AttributesData{},
|
||||
DerivedValues: models.DerivedValuesData{},
|
||||
Skills: []models.CharacterCreationSkill{},
|
||||
Spells: []models.CharacterCreationSpell{},
|
||||
Skills: models.CharacterCreationSkills{},
|
||||
Spells: models.CharacterCreationSpells{},
|
||||
SkillPoints: models.SkillPointsData{},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
ExpiresAt: time.Now().AddDate(0, 0, 14), // 14 Tage
|
||||
CurrentStep: 1,
|
||||
}
|
||||
logger.Debug("CreateCharacterSession: Session-Struktur erstellt, ExpiresAt = %s", session.ExpiresAt.Format(time.RFC3339))
|
||||
|
||||
// Session in Datenbank speichern
|
||||
logger.Debug("CreateCharacterSession: Speichere Session in Datenbank...")
|
||||
err := database.DB.Create(&session).Error
|
||||
if err != nil {
|
||||
logger.Error("CreateCharacterSession: Fehler beim Erstellen der Session: %s", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create session"})
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("CreateCharacterSession: Session erfolgreich erstellt - SessionID: %s, UserID: %d", sessionID, userID)
|
||||
c.JSON(http.StatusCreated, gin.H{
|
||||
"session_id": sessionID,
|
||||
"expires_at": session.ExpiresAt,
|
||||
@@ -2413,25 +2429,35 @@ func CreateCharacterSession(c *gin.Context) {
|
||||
|
||||
// ListCharacterSessions gibt alle aktiven Sessions für einen Benutzer zurück
|
||||
func ListCharacterSessions(c *gin.Context) {
|
||||
logger.Debug("ListCharacterSessions aufgerufen")
|
||||
|
||||
userID := c.GetUint("userID")
|
||||
logger.Debug("ListCharacterSessions: UserID = %d", userID)
|
||||
|
||||
if userID == 0 {
|
||||
logger.Warn("ListCharacterSessions: Unauthorized - UserID ist 0")
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
// Sessions aus Datenbank laden
|
||||
logger.Debug("ListCharacterSessions: Lade Sessions für UserID %d aus Datenbank...", userID)
|
||||
sessions, err := models.GetUserSessions(database.DB, userID)
|
||||
if err != nil {
|
||||
logger.Error("ListCharacterSessions: Fehler beim Laden der Sessions: %s", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to load sessions"})
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debug("ListCharacterSessions: Gefundene Sessions: %d", len(sessions))
|
||||
|
||||
// Sessions für Frontend formatieren
|
||||
var formattedSessions []gin.H
|
||||
for _, session := range sessions {
|
||||
for i, session := range sessions {
|
||||
// Schritt-Text bestimmen
|
||||
progressText := getProgressText(session.CurrentStep)
|
||||
logger.Debug("ListCharacterSessions: Formatiere Session %d - ID: %s, Step: %d, Name: %s",
|
||||
i+1, session.ID, session.CurrentStep, session.Name)
|
||||
|
||||
formattedSessions = append(formattedSessions, gin.H{
|
||||
"session_id": session.ID,
|
||||
@@ -2447,6 +2473,7 @@ func ListCharacterSessions(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
logger.Info("ListCharacterSessions: Sessions erfolgreich geladen für UserID %d - Anzahl: %d", userID, len(formattedSessions))
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"sessions": formattedSessions,
|
||||
"count": len(formattedSessions),
|
||||
@@ -2455,48 +2482,72 @@ func ListCharacterSessions(c *gin.Context) {
|
||||
|
||||
// getProgressText gibt den Schritt-Text für die Frontend-Anzeige zurück
|
||||
func getProgressText(step int) string {
|
||||
logger.Debug("getProgressText: Ermittle Text für Schritt %d", step)
|
||||
|
||||
var text string
|
||||
switch step {
|
||||
case 1:
|
||||
return "Grundinformationen"
|
||||
text = "Grundinformationen"
|
||||
case 2:
|
||||
return "Attribute"
|
||||
text = "Attribute"
|
||||
case 3:
|
||||
return "Abgeleitete Werte"
|
||||
text = "Abgeleitete Werte"
|
||||
case 4:
|
||||
return "Fertigkeiten"
|
||||
text = "Fertigkeiten"
|
||||
case 5:
|
||||
return "Zauber"
|
||||
text = "Zauber"
|
||||
default:
|
||||
return "Unbekannt"
|
||||
text = "Unbekannt"
|
||||
logger.Warn("getProgressText: Unbekannter Schritt %d", step)
|
||||
}
|
||||
|
||||
logger.Debug("getProgressText: Schritt %d = '%s'", step, text)
|
||||
return text
|
||||
}
|
||||
|
||||
// GetCharacterSession gibt Session-Daten zurück
|
||||
func GetCharacterSession(c *gin.Context) {
|
||||
logger.Debug("GetCharacterSession aufgerufen")
|
||||
|
||||
sessionID := c.Param("sessionId")
|
||||
userID := c.GetUint("userID")
|
||||
logger.Debug("GetCharacterSession: SessionID = %s, UserID = %d", sessionID, userID)
|
||||
|
||||
if userID == 0 {
|
||||
logger.Warn("GetCharacterSession: Unauthorized - UserID ist 0")
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
// Session aus Datenbank laden
|
||||
logger.Debug("GetCharacterSession: Lade Session aus Datenbank...")
|
||||
var session models.CharacterCreationSession
|
||||
err := database.DB.Where("id = ? AND user_id = ?", sessionID, userID).First(&session).Error
|
||||
if err != nil {
|
||||
logger.Error("GetCharacterSession: Session nicht gefunden - SessionID: %s, UserID: %d, Error: %s",
|
||||
sessionID, userID, err.Error())
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Session not found"})
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debug("GetCharacterSession: Session gefunden - Name: %s, Step: %d, ExpiresAt: %s",
|
||||
session.Name, session.CurrentStep, session.ExpiresAt.Format(time.RFC3339))
|
||||
|
||||
// Prüfen ob Session noch gültig ist
|
||||
if session.ExpiresAt.Before(time.Now()) {
|
||||
logger.Warn("GetCharacterSession: Session abgelaufen - SessionID: %s, ExpiresAt: %s",
|
||||
sessionID, session.ExpiresAt.Format(time.RFC3339))
|
||||
|
||||
// Abgelaufene Session löschen
|
||||
logger.Debug("GetCharacterSession: Lösche abgelaufene Session...")
|
||||
database.DB.Delete(&session)
|
||||
|
||||
c.JSON(http.StatusGone, gin.H{"error": "Session expired"})
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("GetCharacterSession: Session erfolgreich geladen - SessionID: %s, UserID: %d, Step: %d",
|
||||
sessionID, userID, session.CurrentStep)
|
||||
c.JSON(http.StatusOK, session)
|
||||
}
|
||||
|
||||
@@ -2511,28 +2562,42 @@ type UpdateBasicInfoRequest struct {
|
||||
|
||||
// UpdateCharacterBasicInfo speichert Grundinformationen
|
||||
func UpdateCharacterBasicInfo(c *gin.Context) {
|
||||
logger.Debug("UpdateCharacterBasicInfo aufgerufen")
|
||||
|
||||
sessionID := c.Param("sessionId")
|
||||
userID := c.GetUint("userID")
|
||||
logger.Debug("UpdateCharacterBasicInfo: SessionID = %s, UserID = %d", sessionID, userID)
|
||||
|
||||
if userID == 0 {
|
||||
logger.Warn("UpdateCharacterBasicInfo: Unauthorized - UserID ist 0")
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
var request UpdateBasicInfoRequest
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
logger.Error("UpdateCharacterBasicInfo: Ungültige Eingabedaten - %s", err.Error())
|
||||
respondWithError(c, http.StatusBadRequest, "Ungültige Eingabedaten: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debug("UpdateCharacterBasicInfo: Request-Daten - Name: %s, Rasse: %s, Typ: %s, Herkunft: %s, Glaube: %s",
|
||||
request.Name, request.Rasse, request.Typ, request.Herkunft, request.Glaube)
|
||||
|
||||
// Session aus Datenbank laden
|
||||
logger.Debug("UpdateCharacterBasicInfo: Lade Session aus Datenbank...")
|
||||
var session models.CharacterCreationSession
|
||||
err := database.DB.Where("id = ? AND user_id = ?", sessionID, userID).First(&session).Error
|
||||
if err != nil {
|
||||
logger.Error("UpdateCharacterBasicInfo: Session nicht gefunden - SessionID: %s, UserID: %d, Error: %s",
|
||||
sessionID, userID, err.Error())
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Session not found"})
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debug("UpdateCharacterBasicInfo: Aktueller Session-Status - Step: %d, Name: %s",
|
||||
session.CurrentStep, session.Name)
|
||||
|
||||
// Grundinformationen aktualisieren
|
||||
session.Name = request.Name
|
||||
session.Rasse = request.Rasse
|
||||
@@ -2542,13 +2607,19 @@ func UpdateCharacterBasicInfo(c *gin.Context) {
|
||||
session.CurrentStep = 2
|
||||
session.UpdatedAt = time.Now()
|
||||
|
||||
logger.Debug("UpdateCharacterBasicInfo: Session aktualisiert, setze CurrentStep auf 2")
|
||||
|
||||
// Session in Datenbank aktualisieren
|
||||
logger.Debug("UpdateCharacterBasicInfo: Speichere Session in Datenbank...")
|
||||
err = database.DB.Save(&session).Error
|
||||
if err != nil {
|
||||
logger.Error("UpdateCharacterBasicInfo: Fehler beim Speichern der Session: %s", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update session"})
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("UpdateCharacterBasicInfo: Grundinformationen erfolgreich gespeichert - SessionID: %s, Name: %s",
|
||||
sessionID, request.Name)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Grundinformationen gespeichert",
|
||||
"session_id": sessionID,
|
||||
@@ -2571,28 +2642,41 @@ type UpdateAttributesRequest struct {
|
||||
|
||||
// UpdateCharacterAttributes speichert Grundwerte
|
||||
func UpdateCharacterAttributes(c *gin.Context) {
|
||||
logger.Debug("UpdateCharacterAttributes aufgerufen")
|
||||
|
||||
sessionID := c.Param("sessionId")
|
||||
userID := c.GetUint("userID")
|
||||
logger.Debug("UpdateCharacterAttributes: SessionID = %s, UserID = %d", sessionID, userID)
|
||||
|
||||
if userID == 0 {
|
||||
logger.Warn("UpdateCharacterAttributes: Unauthorized - UserID ist 0")
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
var request UpdateAttributesRequest
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
logger.Error("UpdateCharacterAttributes: Ungültige Attributswerte - %s", err.Error())
|
||||
respondWithError(c, http.StatusBadRequest, "Ungültige Attributswerte: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debug("UpdateCharacterAttributes: Attribute - ST:%d GS:%d GW:%d KO:%d IN:%d ZT:%d AU:%d PA:%d WK:%d",
|
||||
request.ST, request.GS, request.GW, request.KO, request.IN, request.ZT, request.AU, request.PA, request.WK)
|
||||
|
||||
// Session aus Datenbank laden
|
||||
logger.Debug("UpdateCharacterAttributes: Lade Session aus Datenbank...")
|
||||
var session models.CharacterCreationSession
|
||||
err := database.DB.Where("id = ? AND user_id = ?", sessionID, userID).First(&session).Error
|
||||
if err != nil {
|
||||
logger.Error("UpdateCharacterAttributes: Session nicht gefunden - SessionID: %s, UserID: %d, Error: %s",
|
||||
sessionID, userID, err.Error())
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Session not found"})
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debug("UpdateCharacterAttributes: Session geladen - CurrentStep: %d", session.CurrentStep)
|
||||
|
||||
// Attribute aktualisieren
|
||||
session.Attributes = models.AttributesData{
|
||||
ST: request.ST,
|
||||
@@ -2608,13 +2692,18 @@ func UpdateCharacterAttributes(c *gin.Context) {
|
||||
session.CurrentStep = 3
|
||||
session.UpdatedAt = time.Now()
|
||||
|
||||
logger.Debug("UpdateCharacterAttributes: Attribute gesetzt, CurrentStep auf 3 aktualisiert")
|
||||
|
||||
// Session in Datenbank aktualisieren
|
||||
logger.Debug("UpdateCharacterAttributes: Speichere Session in Datenbank...")
|
||||
err = database.DB.Save(&session).Error
|
||||
if err != nil {
|
||||
logger.Error("UpdateCharacterAttributes: Fehler beim Speichern der Session: %s", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update session"})
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("UpdateCharacterAttributes: Grundwerte erfolgreich gespeichert - SessionID: %s", sessionID)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Grundwerte gespeichert",
|
||||
"session_id": sessionID,
|
||||
@@ -2634,28 +2723,41 @@ type UpdateDerivedValuesRequest struct {
|
||||
|
||||
// UpdateCharacterDerivedValues speichert abgeleitete Werte
|
||||
func UpdateCharacterDerivedValues(c *gin.Context) {
|
||||
logger.Debug("UpdateCharacterDerivedValues aufgerufen")
|
||||
|
||||
sessionID := c.Param("sessionId")
|
||||
userID := c.GetUint("userID")
|
||||
logger.Debug("UpdateCharacterDerivedValues: SessionID = %s, UserID = %d", sessionID, userID)
|
||||
|
||||
if userID == 0 {
|
||||
logger.Warn("UpdateCharacterDerivedValues: Unauthorized - UserID ist 0")
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
var request UpdateDerivedValuesRequest
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
logger.Error("UpdateCharacterDerivedValues: Ungültige abgeleitete Werte - %s", err.Error())
|
||||
respondWithError(c, http.StatusBadRequest, "Ungültige abgeleitete Werte: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debug("UpdateCharacterDerivedValues: Werte - LP_Max:%d AP_Max:%d B_Max:%d SG:%d GG:%d GP:%d",
|
||||
request.LP_Max, request.AP_Max, request.B_Max, request.SG, request.GG, request.GP)
|
||||
|
||||
// Session aus Datenbank laden
|
||||
logger.Debug("UpdateCharacterDerivedValues: Lade Session aus Datenbank...")
|
||||
var session models.CharacterCreationSession
|
||||
err := database.DB.Where("id = ? AND user_id = ?", sessionID, userID).First(&session).Error
|
||||
if err != nil {
|
||||
logger.Error("UpdateCharacterDerivedValues: Session nicht gefunden - SessionID: %s, UserID: %d, Error: %s",
|
||||
sessionID, userID, err.Error())
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Session not found"})
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debug("UpdateCharacterDerivedValues: Session geladen - CurrentStep: %d", session.CurrentStep)
|
||||
|
||||
// Abgeleitete Werte aktualisieren
|
||||
session.DerivedValues = models.DerivedValuesData{
|
||||
LPMax: request.LP_Max,
|
||||
@@ -2668,13 +2770,18 @@ func UpdateCharacterDerivedValues(c *gin.Context) {
|
||||
session.CurrentStep = 4
|
||||
session.UpdatedAt = time.Now()
|
||||
|
||||
logger.Debug("UpdateCharacterDerivedValues: Abgeleitete Werte gesetzt, CurrentStep auf 4 aktualisiert")
|
||||
|
||||
// Session in Datenbank aktualisieren
|
||||
logger.Debug("UpdateCharacterDerivedValues: Speichere Session in Datenbank...")
|
||||
err = database.DB.Save(&session).Error
|
||||
if err != nil {
|
||||
logger.Error("UpdateCharacterDerivedValues: Fehler beim Speichern der Session: %s", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update session"})
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("UpdateCharacterDerivedValues: Abgeleitete Werte erfolgreich gespeichert - SessionID: %s", sessionID)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Abgeleitete Werte gespeichert",
|
||||
"session_id": sessionID,
|
||||
@@ -2684,35 +2791,48 @@ func UpdateCharacterDerivedValues(c *gin.Context) {
|
||||
|
||||
// UpdateSkillsRequest
|
||||
type UpdateSkillsRequest struct {
|
||||
Skills []models.CharacterCreationSkill `json:"skills"`
|
||||
Spells []models.CharacterCreationSpell `json:"spells"`
|
||||
SkillPoints models.SkillPointsData `json:"skill_points"` // Verbleibende Punkte pro Kategorie
|
||||
Skills models.CharacterCreationSkills `json:"skills"`
|
||||
Spells models.CharacterCreationSpells `json:"spells"`
|
||||
SkillPoints models.SkillPointsData `json:"skill_points"` // Verbleibende Punkte pro Kategorie
|
||||
}
|
||||
|
||||
// UpdateCharacterSkills speichert Fertigkeiten und Zauber
|
||||
func UpdateCharacterSkills(c *gin.Context) {
|
||||
logger.Debug("UpdateCharacterSkills aufgerufen")
|
||||
|
||||
sessionID := c.Param("sessionId")
|
||||
userID := c.GetUint("userID")
|
||||
logger.Debug("UpdateCharacterSkills: SessionID = %s, UserID = %d", sessionID, userID)
|
||||
|
||||
if userID == 0 {
|
||||
logger.Warn("UpdateCharacterSkills: Unauthorized - UserID ist 0")
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
var request UpdateSkillsRequest
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
logger.Error("UpdateCharacterSkills: Ungültige Fertigkeitsdaten - %s", err.Error())
|
||||
respondWithError(c, http.StatusBadRequest, "Ungültige Fertigkeitsdaten: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debug("UpdateCharacterSkills: Skills-Anzahl: %d, Spells-Anzahl: %d",
|
||||
len(request.Skills), len(request.Spells))
|
||||
|
||||
// Session aus Datenbank laden
|
||||
logger.Debug("UpdateCharacterSkills: Lade Session aus Datenbank...")
|
||||
var session models.CharacterCreationSession
|
||||
err := database.DB.Where("id = ? AND user_id = ?", sessionID, userID).First(&session).Error
|
||||
if err != nil {
|
||||
logger.Error("UpdateCharacterSkills: Session nicht gefunden - SessionID: %s, UserID: %d, Error: %s",
|
||||
sessionID, userID, err.Error())
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Session not found"})
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debug("UpdateCharacterSkills: Session geladen - CurrentStep: %d", session.CurrentStep)
|
||||
|
||||
// Fertigkeiten und Zauber aktualisieren
|
||||
session.Skills = request.Skills
|
||||
session.Spells = request.Spells
|
||||
@@ -2720,13 +2840,19 @@ func UpdateCharacterSkills(c *gin.Context) {
|
||||
session.CurrentStep = 5
|
||||
session.UpdatedAt = time.Now()
|
||||
|
||||
logger.Debug("UpdateCharacterSkills: Skills/Spells gesetzt, CurrentStep auf 5 aktualisiert")
|
||||
|
||||
// Session in Datenbank aktualisieren
|
||||
logger.Debug("UpdateCharacterSkills: Speichere Session in Datenbank...")
|
||||
err = database.DB.Save(&session).Error
|
||||
if err != nil {
|
||||
logger.Error("UpdateCharacterSkills: Fehler beim Speichern der Session: %s", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update session"})
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("UpdateCharacterSkills: Fertigkeiten erfolgreich gespeichert - SessionID: %s, Skills: %d, Spells: %d",
|
||||
sessionID, len(request.Skills), len(request.Spells))
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Fertigkeiten gespeichert",
|
||||
"session_id": sessionID,
|
||||
@@ -2736,28 +2862,41 @@ func UpdateCharacterSkills(c *gin.Context) {
|
||||
|
||||
// FinalizeCharacterCreation schließt die Charakter-Erstellung ab
|
||||
func FinalizeCharacterCreation(c *gin.Context) {
|
||||
logger.Debug("FinalizeCharacterCreation aufgerufen")
|
||||
|
||||
sessionID := c.Param("sessionId")
|
||||
userID := c.GetUint("userID")
|
||||
logger.Debug("FinalizeCharacterCreation: SessionID = %s, UserID = %d", sessionID, userID)
|
||||
|
||||
if userID == 0 {
|
||||
logger.Warn("FinalizeCharacterCreation: Unauthorized - UserID ist 0")
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
// Session laden
|
||||
logger.Debug("FinalizeCharacterCreation: Lade Session aus Datenbank...")
|
||||
var session models.CharacterCreationSession
|
||||
err := database.DB.Where("id = ? AND user_id = ?", sessionID, userID).First(&session).Error
|
||||
if err != nil {
|
||||
logger.Error("FinalizeCharacterCreation: Session nicht gefunden - SessionID: %s, UserID: %d, Error: %s",
|
||||
sessionID, userID, err.Error())
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Session not found"})
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debug("FinalizeCharacterCreation: Session geladen - Name: %s, CurrentStep: %d",
|
||||
session.Name, session.CurrentStep)
|
||||
|
||||
// Session validieren
|
||||
if session.CurrentStep < 5 {
|
||||
logger.Warn("FinalizeCharacterCreation: Charakter-Erstellung unvollständig - CurrentStep: %d (erwartet: 5)",
|
||||
session.CurrentStep)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Character creation not complete"})
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debug("FinalizeCharacterCreation: Erstelle Charakter-Struktur...")
|
||||
// Character erstellen
|
||||
char := models.Char{
|
||||
BamortBase: models.BamortBase{
|
||||
@@ -2808,16 +2947,27 @@ func FinalizeCharacterCreation(c *gin.Context) {
|
||||
{Name: "Wk", Value: session.Attributes.WK},
|
||||
}
|
||||
|
||||
logger.Debug("FinalizeCharacterCreation: Charakter-Struktur erstellt mit %d Eigenschaften",
|
||||
len(char.Eigenschaften))
|
||||
|
||||
// Character in Datenbank speichern
|
||||
logger.Debug("FinalizeCharacterCreation: Speichere Charakter in Datenbank...")
|
||||
err = char.Create()
|
||||
if err != nil {
|
||||
logger.Error("FinalizeCharacterCreation: Fehler beim Erstellen des Charakters: %s", err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create character"})
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debug("FinalizeCharacterCreation: Charakter erfolgreich erstellt mit ID: %d", char.ID)
|
||||
|
||||
// Session löschen
|
||||
logger.Debug("FinalizeCharacterCreation: Lösche Session aus Datenbank...")
|
||||
database.DB.Delete(&session)
|
||||
|
||||
logger.Info("FinalizeCharacterCreation: Charakter-Erstellung abgeschlossen - CharacterID: %d, SessionID: %s, Name: %s",
|
||||
char.ID, sessionID, session.Name)
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{
|
||||
"message": "Charakter erfolgreich erstellt",
|
||||
"character_id": char.ID,
|
||||
@@ -2827,26 +2977,37 @@ func FinalizeCharacterCreation(c *gin.Context) {
|
||||
|
||||
// DeleteCharacterSession löscht eine Session
|
||||
func DeleteCharacterSession(c *gin.Context) {
|
||||
logger.Debug("DeleteCharacterSession aufgerufen")
|
||||
|
||||
sessionID := c.Param("sessionId")
|
||||
userID := c.GetUint("userID")
|
||||
logger.Debug("DeleteCharacterSession: SessionID = %s, UserID = %d", sessionID, userID)
|
||||
|
||||
if userID == 0 {
|
||||
logger.Warn("DeleteCharacterSession: Unauthorized - UserID ist 0")
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
// Session aus Datenbank löschen (nur eigene Sessions)
|
||||
logger.Debug("DeleteCharacterSession: Lösche Session aus Datenbank...")
|
||||
result := database.DB.Where("id = ? AND user_id = ?", sessionID, userID).Delete(&models.CharacterCreationSession{})
|
||||
if result.Error != nil {
|
||||
logger.Error("DeleteCharacterSession: Fehler beim Löschen der Session: %s", result.Error.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete session"})
|
||||
return
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
logger.Warn("DeleteCharacterSession: Session nicht gefunden oder bereits gelöscht - SessionID: %s, UserID: %d",
|
||||
sessionID, userID)
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Session not found"})
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("DeleteCharacterSession: Session erfolgreich gelöscht - SessionID: %s, UserID: %d, RowsAffected: %d",
|
||||
sessionID, userID, result.RowsAffected)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Session gelöscht",
|
||||
"session_id": sessionID,
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bamort/database"
|
||||
"bamort/models"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("Starte Migration...")
|
||||
|
||||
database.ConnectDatabase()
|
||||
|
||||
err := database.DB.AutoMigrate(&models.CharacterCreationSession{})
|
||||
if err != nil {
|
||||
fmt.Printf("Migration Fehler: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("Migration erfolgreich!")
|
||||
}
|
||||
}
|
||||
@@ -10,23 +10,23 @@ import (
|
||||
|
||||
// CharacterCreationSession speichert den Fortschritt der Charakter-Erstellung
|
||||
type CharacterCreationSession struct {
|
||||
ID string `json:"id" gorm:"primaryKey"`
|
||||
UserID uint `json:"user_id" gorm:"index;not null"`
|
||||
User user.User `json:"user" gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
|
||||
Name string `json:"name"`
|
||||
Rasse string `json:"rasse"`
|
||||
Typ string `json:"typ"`
|
||||
Herkunft string `json:"herkunft"`
|
||||
Glaube string `json:"glaube"`
|
||||
Attributes AttributesData `json:"attributes" gorm:"type:json"`
|
||||
DerivedValues DerivedValuesData `json:"derived_values" gorm:"type:json"`
|
||||
Skills []CharacterCreationSkill `json:"skills" gorm:"type:json"`
|
||||
Spells []CharacterCreationSpell `json:"spells" gorm:"type:json"`
|
||||
SkillPoints SkillPointsData `json:"skill_points" gorm:"type:json"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
CurrentStep int `json:"current_step"` // 1=Basic, 2=Attributes, 3=Derived, 4=Skills
|
||||
ID string `json:"id" gorm:"primaryKey"`
|
||||
UserID uint `json:"user_id" gorm:"index;not null"`
|
||||
User user.User `json:"user" gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
|
||||
Name string `json:"name"`
|
||||
Rasse string `json:"rasse"`
|
||||
Typ string `json:"typ"`
|
||||
Herkunft string `json:"herkunft"`
|
||||
Glaube string `json:"glaube"`
|
||||
Attributes AttributesData `json:"attributes" gorm:"type:text;serializer:json"`
|
||||
DerivedValues DerivedValuesData `json:"derived_values" gorm:"type:text;serializer:json"`
|
||||
Skills CharacterCreationSkills `json:"skills" gorm:"type:text;serializer:json"`
|
||||
Spells CharacterCreationSpells `json:"spells" gorm:"type:text;serializer:json"`
|
||||
SkillPoints SkillPointsData `json:"skill_points" gorm:"type:text;serializer:json"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
CurrentStep int `json:"current_step"` // 1=Basic, 2=Attributes, 3=Derived, 4=Skills
|
||||
}
|
||||
|
||||
// AttributesData speichert die Grundwerte
|
||||
@@ -69,6 +69,56 @@ type CharacterCreationSpell struct {
|
||||
Cost int `json:"cost"`
|
||||
}
|
||||
|
||||
// Slice types for GORM JSON handling
|
||||
type CharacterCreationSkills []CharacterCreationSkill
|
||||
type CharacterCreationSpells []CharacterCreationSpell
|
||||
|
||||
// JSON scanning methods for Skills slice
|
||||
func (s *CharacterCreationSkills) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
*s = make([]CharacterCreationSkill, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
bytes, ok := value.([]byte)
|
||||
if !ok {
|
||||
*s = make([]CharacterCreationSkill, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
return json.Unmarshal(bytes, s)
|
||||
}
|
||||
|
||||
func (s CharacterCreationSkills) Value() (interface{}, error) {
|
||||
if len(s) == 0 {
|
||||
return "[]", nil
|
||||
}
|
||||
return json.Marshal(s)
|
||||
}
|
||||
|
||||
// JSON scanning methods for Spells slice
|
||||
func (sp *CharacterCreationSpells) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
*sp = make([]CharacterCreationSpell, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
bytes, ok := value.([]byte)
|
||||
if !ok {
|
||||
*sp = make([]CharacterCreationSpell, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
return json.Unmarshal(bytes, sp)
|
||||
}
|
||||
|
||||
func (sp CharacterCreationSpells) Value() (interface{}, error) {
|
||||
if len(sp) == 0 {
|
||||
return "[]", nil
|
||||
}
|
||||
return json.Marshal(sp)
|
||||
}
|
||||
|
||||
func (object *CharacterCreationSession) TableName() string {
|
||||
dbPrefix := "char"
|
||||
return dbPrefix + "_" + "char_creation_session"
|
||||
|
||||
@@ -139,12 +139,17 @@ func AuthMiddleware() gin.HandlerFunc {
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
if CheckToken(token) == nil {
|
||||
|
||||
user := CheckToken(token)
|
||||
if user == nil {
|
||||
respondWithError(c, http.StatusUnauthorized, "Unauthorized.")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
// Add token validation logic here
|
||||
|
||||
// Set user information in context
|
||||
c.Set("userID", user.UserID)
|
||||
c.Set("username", user.Username)
|
||||
|
||||
c.Next()
|
||||
}
|
||||
|
||||
@@ -169,10 +169,64 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
handleNext(data) {
|
||||
async handleNext(data) {
|
||||
// Merge the new data
|
||||
this.sessionData = { ...this.sessionData, ...data }
|
||||
|
||||
// Save progress for current step before moving to next
|
||||
await this.saveProgressForStep(this.currentStep, data)
|
||||
|
||||
// Move to next step
|
||||
this.currentStep++
|
||||
this.saveProgress()
|
||||
},
|
||||
|
||||
async saveProgressForStep(step, data) {
|
||||
try {
|
||||
const token = localStorage.getItem('token')
|
||||
|
||||
let endpoint = ''
|
||||
let payload = {}
|
||||
|
||||
switch (step) {
|
||||
case 1:
|
||||
endpoint = `/api/characters/create-session/${this.sessionId}/basic`
|
||||
payload = {
|
||||
name: data.name || this.sessionData.name,
|
||||
rasse: data.rasse || this.sessionData.rasse,
|
||||
typ: data.typ || this.sessionData.typ,
|
||||
herkunft: data.herkunft || this.sessionData.herkunft,
|
||||
glaube: data.glaube || this.sessionData.glaube,
|
||||
}
|
||||
break
|
||||
case 2:
|
||||
endpoint = `/api/characters/create-session/${this.sessionId}/attributes`
|
||||
payload = data.attributes || data
|
||||
break
|
||||
case 3:
|
||||
endpoint = `/api/characters/create-session/${this.sessionId}/derived`
|
||||
payload = data.derived_values || data
|
||||
break
|
||||
case 4:
|
||||
endpoint = `/api/characters/create-session/${this.sessionId}/skills`
|
||||
payload = {
|
||||
skills: data.skills || this.sessionData.skills,
|
||||
spells: data.spells || this.sessionData.spells,
|
||||
skill_points: data.skill_points || this.sessionData.skill_points,
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if (endpoint) {
|
||||
console.log('Saving progress for step', step, 'with payload:', payload)
|
||||
const response = await API.put(endpoint, payload, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
})
|
||||
console.log('Save response:', response.data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving progress for step', step, ':', error)
|
||||
throw error // Re-throw to handle in calling function
|
||||
}
|
||||
},
|
||||
|
||||
handlePrevious() {
|
||||
@@ -189,56 +243,8 @@ export default {
|
||||
},
|
||||
|
||||
async saveProgress() {
|
||||
try {
|
||||
const token = localStorage.getItem('token')
|
||||
|
||||
// Bestimme den korrekten Endpoint basierend auf dem aktuellen Schritt
|
||||
let endpoint = ''
|
||||
let payload = {}
|
||||
|
||||
switch (this.currentStep) {
|
||||
case 1:
|
||||
endpoint = `/api/characters/create-session/${this.sessionId}/basic`
|
||||
payload = {
|
||||
name: this.sessionData.name,
|
||||
rasse: this.sessionData.rasse,
|
||||
typ: this.sessionData.typ,
|
||||
herkunft: this.sessionData.herkunft,
|
||||
glaube: this.sessionData.glaube,
|
||||
}
|
||||
break
|
||||
case 2:
|
||||
endpoint = `/api/characters/create-session/${this.sessionId}/attributes`
|
||||
payload = this.sessionData.attributes
|
||||
break
|
||||
case 3:
|
||||
endpoint = `/api/characters/create-session/${this.sessionId}/derived`
|
||||
payload = this.sessionData.derived_values
|
||||
break
|
||||
case 4:
|
||||
endpoint = `/api/characters/create-session/${this.sessionId}/skills`
|
||||
payload = {
|
||||
skills: this.sessionData.skills,
|
||||
skill_points: this.sessionData.skill_points,
|
||||
}
|
||||
break
|
||||
case 5:
|
||||
endpoint = `/api/characters/create-session/${this.sessionId}/spells`
|
||||
payload = {
|
||||
spells: this.sessionData.spells,
|
||||
spell_points: this.sessionData.spell_points,
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if (endpoint) {
|
||||
await API.put(endpoint, payload, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving progress:', error)
|
||||
}
|
||||
// Save progress for current step with current sessionData
|
||||
await this.saveProgressForStep(this.currentStep, this.sessionData)
|
||||
},
|
||||
|
||||
async handleFinalize() {
|
||||
|
||||
@@ -3,80 +3,99 @@
|
||||
<h2>Skills & Spells</h2>
|
||||
<p class="instruction">Select skills and spells for your character. Each category has a limited number of learning points.</p>
|
||||
|
||||
<!-- Debug Info -->
|
||||
<div v-if="true" style="background: #f0f0f0; padding: 10px; margin: 10px 0; border-radius: 4px; font-size: 12px;">
|
||||
<strong>Debug Info:</strong><br>
|
||||
skillCategories.length: {{ skillCategories.length }}<br>
|
||||
selectedCategory: {{ selectedCategory }}<br>
|
||||
availableSkills.length: {{ availableSkills.length }}<br>
|
||||
selectedSkills.length: {{ selectedSkills.length }}
|
||||
</div>
|
||||
|
||||
<div class="skills-content">
|
||||
<!-- Skill Categories -->
|
||||
<div class="categories-section">
|
||||
<h3>Skill Categories</h3>
|
||||
<div class="categories-grid">
|
||||
<div
|
||||
v-for="category in skillCategories"
|
||||
:key="category.name"
|
||||
:class="['category-card', { active: selectedCategory === category.name }]"
|
||||
@click="selectCategory(category.name)"
|
||||
v-if="category.name !== 'zauber'"
|
||||
>
|
||||
<div class="category-header">
|
||||
<h4>{{ category.display_name }}</h4>
|
||||
<div class="points-info">
|
||||
<span class="remaining">{{ category.points }}</span> /
|
||||
<span class="total">{{ category.max_points }}</span>
|
||||
<!-- Left Column: Categories and Skills -->
|
||||
<div class="left-column">
|
||||
<!-- Skill Categories -->
|
||||
<div class="categories-section">
|
||||
<h3>Skill Categories</h3>
|
||||
|
||||
<div v-if="skillCategories.length === 0" class="no-categories">
|
||||
<p>No skill categories available. Loading...</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="categories-grid">
|
||||
<div
|
||||
v-for="category in skillCategories"
|
||||
:key="category.name"
|
||||
:class="['category-card', { active: selectedCategory === category.name }]"
|
||||
@click="selectCategory(category.name)"
|
||||
v-if="category.name !== 'zauber'"
|
||||
>
|
||||
<div class="category-header">
|
||||
<h4>{{ category.display_name }}</h4>
|
||||
<div class="points-info">
|
||||
<span class="remaining">{{ category.points }}</span> /
|
||||
<span class="total">{{ category.max_points }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress-bar">
|
||||
<div
|
||||
class="progress-fill"
|
||||
:style="{ width: ((category.max_points - category.points) / category.max_points * 100) + '%' }"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress-bar">
|
||||
<div
|
||||
class="progress-fill"
|
||||
:style="{ width: ((category.max_points - category.points) / category.max_points * 100) + '%' }"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Skills List for Selected Category -->
|
||||
<div v-if="selectedCategory" class="skills-section">
|
||||
<h3>{{ getCategoryDisplayName(selectedCategory) }} Skills</h3>
|
||||
<div class="skills-list">
|
||||
<div
|
||||
v-for="skill in availableSkills"
|
||||
:key="skill.name"
|
||||
class="skill-item"
|
||||
>
|
||||
<div class="skill-info">
|
||||
<span class="skill-name">{{ skill.name }}</span>
|
||||
<span class="skill-cost">Cost: {{ skill.cost }} EP</span>
|
||||
</div>
|
||||
<button
|
||||
@click="addSkill(skill)"
|
||||
:disabled="!canAddSkill(skill)"
|
||||
class="add-btn"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Selected Skills -->
|
||||
<div class="selected-section">
|
||||
<h3>Selected Skills</h3>
|
||||
|
||||
<div v-if="selectedSkills.length > 0" class="selected-skills">
|
||||
<div class="selected-list">
|
||||
<!-- Skills List for Selected Category -->
|
||||
<div v-if="selectedCategory" class="skills-section">
|
||||
<h3>{{ getCategoryDisplayName(selectedCategory) }} Skills</h3>
|
||||
<div class="skills-list">
|
||||
<div
|
||||
v-for="skill in selectedSkills"
|
||||
v-for="skill in availableSkills"
|
||||
:key="skill.name"
|
||||
class="selected-item"
|
||||
class="skill-item"
|
||||
>
|
||||
<span class="item-name">{{ skill.name }}</span>
|
||||
<span class="item-category">{{ skill.category }}</span>
|
||||
<span class="item-cost">{{ skill.cost }} EP</span>
|
||||
<button @click="removeSkill(skill)" class="remove-btn">×</button>
|
||||
<div class="skill-info">
|
||||
<span class="skill-name">{{ skill.name }}</span>
|
||||
<span class="skill-cost">Cost: {{ skill.cost }} EP</span>
|
||||
</div>
|
||||
<button
|
||||
@click="addSkill(skill)"
|
||||
:disabled="!canAddSkill(skill)"
|
||||
class="add-btn"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedSkills.length === 0" class="no-selection">
|
||||
No skills selected yet. Click on a category above to start selecting skills.
|
||||
<!-- Right Column: Selected Skills -->
|
||||
<div class="right-column">
|
||||
<div class="selected-section">
|
||||
<h3>Selected Skills</h3>
|
||||
|
||||
<div v-if="selectedSkills.length > 0" class="selected-skills">
|
||||
<div class="selected-list">
|
||||
<div
|
||||
v-for="skill in selectedSkills"
|
||||
:key="skill.name"
|
||||
class="selected-item"
|
||||
>
|
||||
<span class="item-name">{{ skill.name }}</span>
|
||||
<span class="item-category">{{ skill.category }}</span>
|
||||
<span class="item-cost">{{ skill.cost }} EP</span>
|
||||
<button @click="removeSkill(skill)" class="remove-btn">×</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedSkills.length === 0" class="no-selection">
|
||||
No skills selected yet. Click on a category above to start selecting skills.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -116,12 +135,17 @@ export default {
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
console.log('CharacterSkills created, sessionData:', this.sessionData)
|
||||
console.log('CharacterSkills created, skillCategories:', this.skillCategories)
|
||||
|
||||
// Initialize with session data
|
||||
if (this.sessionData.skills) {
|
||||
this.selectedSkills = [...this.sessionData.skills]
|
||||
console.log('Initialized selectedSkills:', this.selectedSkills)
|
||||
}
|
||||
|
||||
this.updateCategoryPoints()
|
||||
console.log('Updated skillCategories after points update:', this.skillCategories)
|
||||
},
|
||||
methods: {
|
||||
async selectCategory(categoryName) {
|
||||
@@ -238,11 +262,23 @@ export default {
|
||||
|
||||
.skills-content {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
gap: 30px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.left-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.right-column {
|
||||
position: sticky;
|
||||
top: 20px;
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
.categories-section h3, .skills-section h3, .spells-section h3, .selected-section h3 {
|
||||
margin-bottom: 15px;
|
||||
color: #333;
|
||||
@@ -254,6 +290,13 @@ export default {
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.no-categories {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.category-card {
|
||||
padding: 15px;
|
||||
border: 2px solid #ddd;
|
||||
@@ -386,10 +429,10 @@ export default {
|
||||
}
|
||||
|
||||
.selected-section {
|
||||
grid-column: span 2;
|
||||
padding: 20px;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 8px;
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
.selected-skills, .selected-spells {
|
||||
|
||||
Reference in New Issue
Block a user