Merge pull request '(FEAT): Errors can now be displayed using the RenderErrorBanner fx.' (#44) from feature/errors into master
All checks were successful
Deploy application with Docker / build_and_deploy (push) Successful in 1m56s
All checks were successful
Deploy application with Docker / build_and_deploy (push) Successful in 1m56s
Reviewed-on: #44
This commit is contained in:
commit
1e4cf8f922
@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
domain "github.com/haydenhargreaves/Potion/internal/domain/server"
|
domain "github.com/haydenhargreaves/Potion/internal/domain/server"
|
||||||
|
"github.com/haydenhargreaves/Potion/internal/templates/components"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GoogleLogin directs the user to Googles select user login page. Once the user has selected an
|
// GoogleLogin directs the user to Googles select user login page. Once the user has selected an
|
||||||
@ -21,8 +22,6 @@ func GoogleLogin(ctx *gin.Context) {
|
|||||||
// account. They will be directed here and a JWT is generated. This JWT is stored in the users
|
// account. They will be directed here and a JWT is generated. This JWT is stored in the users
|
||||||
// cookies and will be used by protected routes to validate their login status.
|
// cookies and will be used by protected routes to validate their login status.
|
||||||
//
|
//
|
||||||
// TODO: This route does not do the proper handling, need to work on the redirection or handling.
|
|
||||||
//
|
|
||||||
// We do not need to return all of this data, it is just for testing.
|
// We do not need to return all of this data, it is just for testing.
|
||||||
func GoogleCallback(ctx *gin.Context) {
|
func GoogleCallback(ctx *gin.Context) {
|
||||||
deps := ctx.MustGet("deps").(*domain.InjectedDependencies)
|
deps := ctx.MustGet("deps").(*domain.InjectedDependencies)
|
||||||
@ -32,15 +31,11 @@ func GoogleCallback(ctx *gin.Context) {
|
|||||||
code string = ctx.Query("code")
|
code string = ctx.Query("code")
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: Do something real, not just return data
|
if jwt, err := deps.AuthService.GoogleAuthSuccess(state, code); err != nil {
|
||||||
if jwt, dbUser, googleUserInfo, err := deps.AuthService.GoogleAuthSuccess(state, code); err != nil {
|
components.RenderErrorBanner(ctx, err.Error())
|
||||||
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
} else {
|
} else {
|
||||||
domain.SetCookie(ctx, "jwt_token", jwt, time.Hour*24*7)
|
domain.SetCookie(ctx, "jwt_token", jwt, time.Hour*24*7)
|
||||||
// ctx.JSON(http.StatusOK, gin.H{"jwt": jwt, "googleUserInfo": googleUserInfo, "dbUser": dbUser})
|
|
||||||
_ = dbUser
|
|
||||||
_ = googleUserInfo
|
|
||||||
|
|
||||||
ctx.Redirect(http.StatusSeeOther, "/")
|
ctx.Redirect(http.StatusSeeOther, "/")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
domain "github.com/haydenhargreaves/Potion/internal/domain/server"
|
domain "github.com/haydenhargreaves/Potion/internal/domain/server"
|
||||||
|
"github.com/haydenhargreaves/Potion/internal/templates/components"
|
||||||
)
|
)
|
||||||
|
|
||||||
func EngagementViewRecipe(ctx *gin.Context) {
|
func EngagementViewRecipe(ctx *gin.Context) {
|
||||||
@ -23,6 +24,7 @@ func EngagementViewRecipe(ctx *gin.Context) {
|
|||||||
|
|
||||||
if !domain.IsLoggedIn(ctx) || user == nil {
|
if !domain.IsLoggedIn(ctx) || user == nil {
|
||||||
if _, err := deps.EngagementService.ViewRecipe(recipeId); err != nil {
|
if _, err := deps.EngagementService.ViewRecipe(recipeId); err != nil {
|
||||||
|
components.RenderErrorBanner(ctx, err.Error())
|
||||||
ctx.JSON(http.StatusInternalServerError, gin.H{
|
ctx.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"status": http.StatusInternalServerError,
|
"status": http.StatusInternalServerError,
|
||||||
"message": err.Error(),
|
"message": err.Error(),
|
||||||
@ -36,6 +38,7 @@ func EngagementViewRecipe(ctx *gin.Context) {
|
|||||||
|
|
||||||
// We caught nil already, we can assume the user exists
|
// We caught nil already, we can assume the user exists
|
||||||
if _, err := deps.EngagementService.UserViewRecipe(user.Id, recipeId); err != nil {
|
if _, err := deps.EngagementService.UserViewRecipe(user.Id, recipeId); err != nil {
|
||||||
|
components.RenderErrorBanner(ctx, err.Error())
|
||||||
ctx.JSON(http.StatusInternalServerError, gin.H{
|
ctx.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"status": http.StatusInternalServerError,
|
"status": http.StatusInternalServerError,
|
||||||
"message": err.Error(),
|
"message": err.Error(),
|
||||||
@ -60,6 +63,7 @@ func EngagementShareRecipe(ctx *gin.Context) {
|
|||||||
|
|
||||||
if !domain.IsLoggedIn(ctx) || user == nil {
|
if !domain.IsLoggedIn(ctx) || user == nil {
|
||||||
if _, err := deps.EngagementService.ShareRecipe(recipeId); err != nil {
|
if _, err := deps.EngagementService.ShareRecipe(recipeId); err != nil {
|
||||||
|
components.RenderErrorBanner(ctx, err.Error())
|
||||||
ctx.JSON(http.StatusInternalServerError, gin.H{
|
ctx.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"status": http.StatusInternalServerError,
|
"status": http.StatusInternalServerError,
|
||||||
"message": err.Error(),
|
"message": err.Error(),
|
||||||
@ -71,6 +75,7 @@ func EngagementShareRecipe(ctx *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if _, err := deps.EngagementService.UserShareRecipe(user.Id, recipeId); err != nil {
|
if _, err := deps.EngagementService.UserShareRecipe(user.Id, recipeId); err != nil {
|
||||||
|
components.RenderErrorBanner(ctx, err.Error())
|
||||||
ctx.JSON(http.StatusInternalServerError, gin.H{
|
ctx.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"status": http.StatusInternalServerError,
|
"status": http.StatusInternalServerError,
|
||||||
"message": err.Error(),
|
"message": err.Error(),
|
||||||
@ -101,6 +106,7 @@ func EngagementFavoriteRecipe(ctx *gin.Context) {
|
|||||||
recipeId, _ := strconv.Atoi(id)
|
recipeId, _ := strconv.Atoi(id)
|
||||||
|
|
||||||
if _, err := deps.EngagementService.UserFavoriteRecipe(user.Id, recipeId); err != nil {
|
if _, err := deps.EngagementService.UserFavoriteRecipe(user.Id, recipeId); err != nil {
|
||||||
|
components.RenderErrorBanner(ctx, fmt.Sprintf("Something went wrong. %s.", err.Error()))
|
||||||
ctx.JSON(http.StatusInternalServerError, gin.H{
|
ctx.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"status": http.StatusInternalServerError,
|
"status": http.StatusInternalServerError,
|
||||||
"message": err.Error(),
|
"message": err.Error(),
|
||||||
@ -131,6 +137,7 @@ func EngagementMakeRecipe(ctx *gin.Context) {
|
|||||||
recipeId, _ := strconv.Atoi(id)
|
recipeId, _ := strconv.Atoi(id)
|
||||||
|
|
||||||
if _, err := deps.EngagementService.UserMakeRecipe(user.Id, recipeId); err != nil {
|
if _, err := deps.EngagementService.UserMakeRecipe(user.Id, recipeId); err != nil {
|
||||||
|
components.RenderErrorBanner(ctx, err.Error())
|
||||||
ctx.JSON(http.StatusInternalServerError, gin.H{
|
ctx.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"status": http.StatusInternalServerError,
|
"status": http.StatusInternalServerError,
|
||||||
"message": err.Error(),
|
"message": err.Error(),
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import (
|
|||||||
domainRecipe "github.com/haydenhargreaves/Potion/internal/domain/recipe"
|
domainRecipe "github.com/haydenhargreaves/Potion/internal/domain/recipe"
|
||||||
domain "github.com/haydenhargreaves/Potion/internal/domain/server"
|
domain "github.com/haydenhargreaves/Potion/internal/domain/server"
|
||||||
domainServer "github.com/haydenhargreaves/Potion/internal/domain/server"
|
domainServer "github.com/haydenhargreaves/Potion/internal/domain/server"
|
||||||
|
"github.com/haydenhargreaves/Potion/internal/templates/components"
|
||||||
layouts "github.com/haydenhargreaves/Potion/internal/templates/layouts"
|
layouts "github.com/haydenhargreaves/Potion/internal/templates/layouts"
|
||||||
pages "github.com/haydenhargreaves/Potion/internal/templates/pages"
|
pages "github.com/haydenhargreaves/Potion/internal/templates/pages"
|
||||||
templates "github.com/haydenhargreaves/Potion/internal/templates/pages"
|
templates "github.com/haydenhargreaves/Potion/internal/templates/pages"
|
||||||
@ -41,6 +42,7 @@ func HomePage(ctx *gin.Context) {
|
|||||||
userId := ctx.MustGet("userId").(int)
|
userId := ctx.MustGet("userId").(int)
|
||||||
madeRecipes, err := deps.RecipeService.GetUserMadeRecipes(userId, 6)
|
madeRecipes, err := deps.RecipeService.GetUserMadeRecipes(userId, 6)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
components.RenderErrorBanner(ctx, fmt.Sprintf("Error getting made recipes. %s\n", err.Error()))
|
||||||
ctx.JSON(http.StatusInternalServerError, gin.H{
|
ctx.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"status": http.StatusInternalServerError,
|
"status": http.StatusInternalServerError,
|
||||||
"message": fmt.Sprintf("Error getting made recipes. %s\n", err.Error()),
|
"message": fmt.Sprintf("Error getting made recipes. %s\n", err.Error()),
|
||||||
@ -49,9 +51,10 @@ func HomePage(ctx *gin.Context) {
|
|||||||
}
|
}
|
||||||
viewedRecipes, err := deps.RecipeService.GetUserViewedRecipes(userId, 6)
|
viewedRecipes, err := deps.RecipeService.GetUserViewedRecipes(userId, 6)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
components.RenderErrorBanner(ctx, fmt.Sprintf("Error getting viewed recipes. %s\n", err.Error()))
|
||||||
ctx.JSON(http.StatusInternalServerError, gin.H{
|
ctx.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"status": http.StatusInternalServerError,
|
"status": http.StatusInternalServerError,
|
||||||
"message": fmt.Sprintf("Error getting made recipes. %s\n", err.Error()),
|
"message": fmt.Sprintf("Error getting viewed recipes. %s\n", err.Error()),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -59,9 +62,10 @@ func HomePage(ctx *gin.Context) {
|
|||||||
// Get the recipe of the week
|
// Get the recipe of the week
|
||||||
recipeOfTheWeek, err := deps.RecipeService.GetRecipeOfTheWeek(&userId)
|
recipeOfTheWeek, err := deps.RecipeService.GetRecipeOfTheWeek(&userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
components.RenderErrorBanner(ctx, fmt.Sprintf("Error getting recipe of the week. %s\n", err.Error()))
|
||||||
ctx.JSON(http.StatusInternalServerError, gin.H{
|
ctx.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"status": http.StatusInternalServerError,
|
"status": http.StatusInternalServerError,
|
||||||
"message": fmt.Sprintf("Error getting made recipes. %s\n", err.Error()),
|
"message": fmt.Sprintf("Error getting recipe of the week. %s\n", err.Error()),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -82,9 +86,10 @@ func HomePage(ctx *gin.Context) {
|
|||||||
// Get the recipe of the week
|
// Get the recipe of the week
|
||||||
recipeOfTheWeek, err := deps.RecipeService.GetRecipeOfTheWeek(nil)
|
recipeOfTheWeek, err := deps.RecipeService.GetRecipeOfTheWeek(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
components.RenderErrorBanner(ctx, fmt.Sprintf("Error getting recipe of the week. %s\n", err.Error()))
|
||||||
ctx.JSON(http.StatusInternalServerError, gin.H{
|
ctx.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"status": http.StatusInternalServerError,
|
"status": http.StatusInternalServerError,
|
||||||
"message": fmt.Sprintf("Error getting made recipes. %s\n", err.Error()),
|
"message": fmt.Sprintf("Error getting recipe of the week. %s\n", err.Error()),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -178,6 +183,7 @@ func ProfilePage(ctx *gin.Context) {
|
|||||||
|
|
||||||
recipes, err := deps.RecipeService.GetUserRecipes(user.Id)
|
recipes, err := deps.RecipeService.GetUserRecipes(user.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
components.RenderErrorBanner(ctx, fmt.Sprintf("Error getting recipes. %s\n", err.Error()))
|
||||||
ctx.JSON(http.StatusInternalServerError, gin.H{
|
ctx.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"status": http.StatusInternalServerError,
|
"status": http.StatusInternalServerError,
|
||||||
"message": fmt.Sprintf("Error getting recipes. %s\n", err.Error()),
|
"message": fmt.Sprintf("Error getting recipes. %s\n", err.Error()),
|
||||||
@ -187,9 +193,10 @@ func ProfilePage(ctx *gin.Context) {
|
|||||||
|
|
||||||
favorites, err := deps.RecipeService.GetUserFavoriteRecipes(user.Id)
|
favorites, err := deps.RecipeService.GetUserFavoriteRecipes(user.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
components.RenderErrorBanner(ctx, fmt.Sprintf("Error getting favorite recipes. %s\n", err.Error()))
|
||||||
ctx.JSON(http.StatusInternalServerError, gin.H{
|
ctx.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"status": http.StatusInternalServerError,
|
"status": http.StatusInternalServerError,
|
||||||
"message": fmt.Sprintf("Error getting recipes. %s\n", err.Error()),
|
"message": fmt.Sprintf("Error getting favorite recipes. %s\n", err.Error()),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -197,6 +204,7 @@ func ProfilePage(ctx *gin.Context) {
|
|||||||
// Get the engagement data, not sure what will happen when errors occur
|
// Get the engagement data, not sure what will happen when errors occur
|
||||||
engagements, err := deps.EngagementService.GetUserEngagement(user.Id, 6)
|
engagements, err := deps.EngagementService.GetUserEngagement(user.Id, 6)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
components.RenderErrorBanner(ctx, fmt.Sprintf("Error getting user engagements. %s\n", err.Error()))
|
||||||
ctx.JSON(http.StatusInternalServerError, gin.H{
|
ctx.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"status": http.StatusInternalServerError,
|
"status": http.StatusInternalServerError,
|
||||||
"message": fmt.Sprintf("Error getting user engagements. %s\n", err.Error()),
|
"message": fmt.Sprintf("Error getting user engagements. %s\n", err.Error()),
|
||||||
@ -217,7 +225,6 @@ func ListPage(ctx *gin.Context) {
|
|||||||
ctx.HTML(http.StatusOK, "", layouts.AppLayout(title, page))
|
ctx.HTML(http.StatusOK, "", layouts.AppLayout(title, page))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Figure out how to handle errors, think we just need a simple display.
|
|
||||||
func RecipePage(ctx *gin.Context) {
|
func RecipePage(ctx *gin.Context) {
|
||||||
// Call recipe service to get via ID
|
// Call recipe service to get via ID
|
||||||
deps := ctx.MustGet("deps").(*domainServer.InjectedDependencies)
|
deps := ctx.MustGet("deps").(*domainServer.InjectedDependencies)
|
||||||
@ -226,7 +233,7 @@ func RecipePage(ctx *gin.Context) {
|
|||||||
// Parse ID
|
// Parse ID
|
||||||
parsed, err := strconv.Atoi(id)
|
parsed, err := strconv.Atoi(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("ERROR: %s\n", err.Error())
|
components.RenderErrorBanner(ctx, fmt.Sprintf("ERROR: %s", err.Error()))
|
||||||
ctx.JSON(400, err.Error())
|
ctx.JSON(400, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -251,7 +258,7 @@ func RecipePage(ctx *gin.Context) {
|
|||||||
// Get recipe
|
// Get recipe
|
||||||
recipe, err := deps.RecipeService.GetRecipe(parsed, userId)
|
recipe, err := deps.RecipeService.GetRecipe(parsed, userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("ERROR: %s\n", err.Error())
|
components.RenderErrorBanner(ctx, fmt.Sprintf("ERROR: %s", err.Error()))
|
||||||
ctx.JSON(400, err.Error())
|
ctx.JSON(400, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -259,7 +266,7 @@ func RecipePage(ctx *gin.Context) {
|
|||||||
// Get user (owner)
|
// Get user (owner)
|
||||||
user, err := deps.UserService.GetUser(recipe.UserId)
|
user, err := deps.UserService.GetUser(recipe.UserId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("ERROR: %s\n", err.Error())
|
components.RenderErrorBanner(ctx, fmt.Sprintf("ERROR: %s", err.Error()))
|
||||||
ctx.JSON(400, err.Error())
|
ctx.JSON(400, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
domainRecipe "github.com/haydenhargreaves/Potion/internal/domain/recipe"
|
domainRecipe "github.com/haydenhargreaves/Potion/internal/domain/recipe"
|
||||||
domain "github.com/haydenhargreaves/Potion/internal/domain/server"
|
domain "github.com/haydenhargreaves/Potion/internal/domain/server"
|
||||||
|
"github.com/haydenhargreaves/Potion/internal/templates/components"
|
||||||
templates "github.com/haydenhargreaves/Potion/internal/templates/pages"
|
templates "github.com/haydenhargreaves/Potion/internal/templates/pages"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -24,6 +25,7 @@ func CreateRecipe(ctx *gin.Context) {
|
|||||||
|
|
||||||
recipe, err := deps.RecipeService.CreateRecipe(ctx)
|
recipe, err := deps.RecipeService.CreateRecipe(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
components.RenderErrorBanner(ctx, err.Error())
|
||||||
ctx.String(http.StatusOK, CREATE_ERROR_HTML, err.Error())
|
ctx.String(http.StatusOK, CREATE_ERROR_HTML, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -89,6 +91,7 @@ func SearchRecipes(ctx *gin.Context) {
|
|||||||
// We don't care about favorite status, so use false
|
// We don't care about favorite status, so use false
|
||||||
recipes, err := deps.RecipeService.SearchRecipes(filters, userId, false)
|
recipes, err := deps.RecipeService.SearchRecipes(filters, userId, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
components.RenderErrorBanner(ctx, err.Error())
|
||||||
ctx.JSON(http.StatusOK, gin.H{"error": err.Error()})
|
ctx.JSON(http.StatusOK, gin.H{"error": err.Error()})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,6 +129,7 @@ func SearchRecipesFavorites(ctx *gin.Context) {
|
|||||||
// TODO: Error here if they're not logged in?
|
// TODO: Error here if they're not logged in?
|
||||||
// Get user data (they should be logged in)
|
// Get user data (they should be logged in)
|
||||||
if !domain.IsLoggedIn(ctx) {
|
if !domain.IsLoggedIn(ctx) {
|
||||||
|
components.RenderErrorBanner(ctx, "User is not logged in. User will be nil.")
|
||||||
ctx.JSON(http.StatusOK, gin.H{"error": "User is not logged in. User will be nil."})
|
ctx.JSON(http.StatusOK, gin.H{"error": "User is not logged in. User will be nil."})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,6 +137,7 @@ func SearchRecipesFavorites(ctx *gin.Context) {
|
|||||||
|
|
||||||
recipes, err := deps.RecipeService.SearchRecipes(filters, &userId, true)
|
recipes, err := deps.RecipeService.SearchRecipes(filters, &userId, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
components.RenderErrorBanner(ctx, err.Error())
|
||||||
ctx.JSON(http.StatusOK, gin.H{"error": err.Error()})
|
ctx.JSON(http.StatusOK, gin.H{"error": err.Error()})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
domain "github.com/haydenhargreaves/Potion/internal/domain/server"
|
domain "github.com/haydenhargreaves/Potion/internal/domain/server"
|
||||||
|
"github.com/haydenhargreaves/Potion/internal/templates/components"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetUserRecipes(ctx *gin.Context) {
|
func GetUserRecipes(ctx *gin.Context) {
|
||||||
@ -21,6 +22,7 @@ func GetUserRecipes(ctx *gin.Context) {
|
|||||||
|
|
||||||
// Ensure logged in
|
// Ensure logged in
|
||||||
if !domain.IsLoggedIn(ctx) || user == nil {
|
if !domain.IsLoggedIn(ctx) || user == nil {
|
||||||
|
components.RenderErrorBanner(ctx, "User is not authorized to access this endpoint. Please login to continue.")
|
||||||
ctx.JSON(http.StatusUnauthorized, gin.H{
|
ctx.JSON(http.StatusUnauthorized, gin.H{
|
||||||
"status": http.StatusUnauthorized,
|
"status": http.StatusUnauthorized,
|
||||||
"message": "User is not authorized to access this endpoint. Please login to continue.",
|
"message": "User is not authorized to access this endpoint. Please login to continue.",
|
||||||
@ -31,6 +33,7 @@ func GetUserRecipes(ctx *gin.Context) {
|
|||||||
|
|
||||||
recipes, err := deps.RecipeService.GetUserRecipes(user.Id)
|
recipes, err := deps.RecipeService.GetUserRecipes(user.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
components.RenderErrorBanner(ctx, fmt.Sprintf("Could not get user recipes. %s", err.Error()))
|
||||||
ctx.JSON(http.StatusBadRequest, gin.H{
|
ctx.JSON(http.StatusBadRequest, gin.H{
|
||||||
"status": http.StatusBadRequest,
|
"status": http.StatusBadRequest,
|
||||||
"message": fmt.Sprintf("Could not get user recipes. %s", err.Error()),
|
"message": fmt.Sprintf("Could not get user recipes. %s", err.Error()),
|
||||||
@ -59,6 +62,7 @@ func GetUserFavoriteRecipes(ctx *gin.Context) {
|
|||||||
|
|
||||||
// Ensure logged in
|
// Ensure logged in
|
||||||
if !domain.IsLoggedIn(ctx) || user == nil {
|
if !domain.IsLoggedIn(ctx) || user == nil {
|
||||||
|
components.RenderErrorBanner(ctx, "User is not authorized to access this endpoint. Please login to continue.")
|
||||||
ctx.JSON(http.StatusUnauthorized, gin.H{
|
ctx.JSON(http.StatusUnauthorized, gin.H{
|
||||||
"status": http.StatusUnauthorized,
|
"status": http.StatusUnauthorized,
|
||||||
"message": "User is not authorized to access this endpoint. Please login to continue.",
|
"message": "User is not authorized to access this endpoint. Please login to continue.",
|
||||||
@ -69,9 +73,10 @@ func GetUserFavoriteRecipes(ctx *gin.Context) {
|
|||||||
|
|
||||||
recipes, err := deps.RecipeService.GetUserFavoriteRecipes(user.Id)
|
recipes, err := deps.RecipeService.GetUserFavoriteRecipes(user.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
components.RenderErrorBanner(ctx, fmt.Sprintf("Could not get favorite recipes. %s", err.Error()))
|
||||||
ctx.JSON(http.StatusBadRequest, gin.H{
|
ctx.JSON(http.StatusBadRequest, gin.H{
|
||||||
"status": http.StatusBadRequest,
|
"status": http.StatusBadRequest,
|
||||||
"message": fmt.Sprintf("Could not get user recipes. %s", err.Error()),
|
"message": fmt.Sprintf("Could not get favorite recipes. %s", err.Error()),
|
||||||
"recipes": nil,
|
"recipes": nil,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
|
|||||||
@ -60,48 +60,48 @@ func (s *AuthService) GetGoogleAuthUrl() string {
|
|||||||
|
|
||||||
// GoogleAuthSuccess accepts the data from the Google login endpoint and uses it to fetch the users
|
// GoogleAuthSuccess accepts the data from the Google login endpoint and uses it to fetch the users
|
||||||
// data. The data is then used to log the user in or create an account.
|
// data. The data is then used to log the user in or create an account.
|
||||||
func (s *AuthService) GoogleAuthSuccess(state, code string) (string, domain.User, domain.GoogleUserInfo, error) {
|
func (s *AuthService) GoogleAuthSuccess(state, code string) (string, error) {
|
||||||
// Ensure the state matches, prevents M.I.T.M. attacks
|
// Ensure the state matches, prevents M.I.T.M. attacks
|
||||||
if state != "randomstate" {
|
if state != "randomstate" {
|
||||||
return "", domain.User{}, domain.GoogleUserInfo{}, fmt.Errorf("States don't match, received %s", state)
|
return "", fmt.Errorf("States don't match, received %s", state)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get access token from Google
|
// Get access token from Google
|
||||||
token, err := auth.GoogleAuthConfig.Exchange(context.Background(), code)
|
token, err := auth.GoogleAuthConfig.Exchange(context.Background(), code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", domain.User{}, domain.GoogleUserInfo{}, fmt.Errorf("Code exchange failed: %s", err.Error())
|
return "", fmt.Errorf("Code exchange failed: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the access token to get user data
|
// Use the access token to get user data
|
||||||
googleUserInfo, err := auth.GetUserData(token.AccessToken)
|
googleUserInfo, err := auth.GetUserData(token.AccessToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", domain.User{}, domain.GoogleUserInfo{}, err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to get the user, user is nil when they don't exit
|
// Attempt to get the user, user is nil when they don't exit
|
||||||
user, err := s.userRepository.GetGoogleUser(googleUserInfo.Id)
|
user, err := s.userRepository.GetGoogleUser(googleUserInfo.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", domain.User{}, domain.GoogleUserInfo{}, fmt.Errorf("Failed to get db user: %s", err)
|
return "", fmt.Errorf("Failed to get db user: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A user was found
|
// A user was found
|
||||||
if user != nil {
|
if user != nil {
|
||||||
jwt, err := generateJwt(user.Id, user.Email, s.jwtSecret)
|
jwt, err := generateJwt(user.Id, user.Email, s.jwtSecret)
|
||||||
return jwt, *user, googleUserInfo, err
|
return jwt, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// user did not exist, need to create one
|
// user did not exist, need to create one
|
||||||
newUser, err := s.userRepository.CreateGoogleUser(&googleUserInfo, token.RefreshToken)
|
newUser, err := s.userRepository.CreateGoogleUser(&googleUserInfo, token.RefreshToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", domain.User{}, domain.GoogleUserInfo{}, fmt.Errorf("Repository failed to create user: %s", err.Error())
|
return "", fmt.Errorf("Repository failed to create user: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
jwt, err := generateJwt(newUser.Id, newUser.Email, s.jwtSecret)
|
jwt, err := generateJwt(newUser.Id, newUser.Email, s.jwtSecret)
|
||||||
return jwt, newUser, googleUserInfo, err
|
return jwt, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateJwt requires user data and returns a JSON web token which can be stored in the browsers
|
// generateJwt requires user data and returns a JSON web token which can be stored in the browsers
|
||||||
// cookies. This token is used to log a user into the application and allow access to protected
|
// cookies. This token is used to log a user into the application and allow access to protected
|
||||||
// routes.
|
// routes.
|
||||||
func generateJwt(userId int, email string, jwtSecret []byte) (string, error) {
|
func generateJwt(userId int, email string, jwtSecret []byte) (string, error) {
|
||||||
expiration := time.Now().Add(7 * 24 * time.Hour)
|
expiration := time.Now().Add(7 * 24 * time.Hour)
|
||||||
|
|||||||
@ -1,10 +1,6 @@
|
|||||||
package domain
|
package domain
|
||||||
|
|
||||||
import (
|
|
||||||
domain "github.com/haydenhargreaves/Potion/internal/domain/user"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AuthService interface {
|
type AuthService interface {
|
||||||
GetGoogleAuthUrl() string
|
GetGoogleAuthUrl() string
|
||||||
GoogleAuthSuccess(state, code string) (string, domain.User, domain.GoogleUserInfo, error)
|
GoogleAuthSuccess(state, code string) (string, error)
|
||||||
}
|
}
|
||||||
|
|||||||
50
internal/templates/components/error.templ
Normal file
50
internal/templates/components/error.templ
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package components
|
||||||
|
|
||||||
|
import "github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
templ errorIcon() {
|
||||||
|
<svg class="h-6 text-red-500" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M12 16.99V17M12 7V14M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z"
|
||||||
|
stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||||
|
</svg>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ closeButton() {
|
||||||
|
<button onclick="hideError();"
|
||||||
|
class="text-red-500 ml-auto hover:bg-red-200 p-1 rounded-sm transition-all duration-300 cursor-pointer">
|
||||||
|
<svg class="h-4" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||||
|
d="M19.207 6.207a1 1 0 0 0-1.414-1.414L12 10.586 6.207 4.793a1 1 0 0 0-1.414 1.414L10.586 12l-5.793 5.793a1 1 0 1 0 1.414 1.414L12 13.414l5.793 5.793a1 1 0 0 0 1.414-1.414L13.414 12l5.793-5.793z"
|
||||||
|
fill="currentColor"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ errorBanner(message string) {
|
||||||
|
<div id="error-toast" hx-swap-oob="outerHTML"
|
||||||
|
class="fixed z-20 border border-red-500 rounded-sm right-0 top-0 m-4 p-4 bg-red-100 shadow shadow-red-200 transition-all duration-300 text-sm md:w-1/3">
|
||||||
|
<div class="flex items-center gap-x-2 pb-1">
|
||||||
|
@errorIcon()
|
||||||
|
<h1 class="text-red-500 font-semibold">Error</h1>
|
||||||
|
@closeButton()
|
||||||
|
</div>
|
||||||
|
<div class="ml-8">
|
||||||
|
<p class="text-red-500 text-sm">
|
||||||
|
{ message }
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenderErrorBanner renders the error banner. However, this function must ONLY be called by an
|
||||||
|
// HTMX route. Otherwise, there is no promise it will work (may result in undefined behavior).
|
||||||
|
// Just writes a piece of content to the response.
|
||||||
|
//
|
||||||
|
// If this is called from an NON-HTMX request, it will display (and functionality seems to work)
|
||||||
|
// but it will be loaded at an unknown position in the DOM. It will not swap with the proper
|
||||||
|
// element.
|
||||||
|
func RenderErrorBanner(ctx *gin.Context, message string) {
|
||||||
|
ctx.Writer.Header().Set("Content-Type", "text/html")
|
||||||
|
errorBanner(message).Render(ctx.Request.Context(), ctx.Writer)
|
||||||
|
}
|
||||||
141
internal/templates/components/error_templ.go
Normal file
141
internal/templates/components/error_templ.go
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
// Code generated by templ - DO NOT EDIT.
|
||||||
|
|
||||||
|
// templ: version: v0.3.937
|
||||||
|
package components
|
||||||
|
|
||||||
|
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||||
|
|
||||||
|
import "github.com/a-h/templ"
|
||||||
|
import templruntime "github.com/a-h/templ/runtime"
|
||||||
|
|
||||||
|
import "github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
func errorIcon() templ.Component {
|
||||||
|
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
|
return templ_7745c5c3_CtxErr
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
defer func() {
|
||||||
|
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err == nil {
|
||||||
|
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var1 == nil {
|
||||||
|
templ_7745c5c3_Var1 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<svg class=\"h-6 text-red-500\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M12 16.99V17M12 7V14M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"></path></svg>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func closeButton() templ.Component {
|
||||||
|
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
|
return templ_7745c5c3_CtxErr
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
defer func() {
|
||||||
|
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err == nil {
|
||||||
|
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var2 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var2 == nil {
|
||||||
|
templ_7745c5c3_Var2 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<button onclick=\"hideError();\" class=\"text-red-500 ml-auto hover:bg-red-200 p-1 rounded-sm transition-all duration-300 cursor-pointer\"><svg class=\"h-4\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M19.207 6.207a1 1 0 0 0-1.414-1.414L12 10.586 6.207 4.793a1 1 0 0 0-1.414 1.414L10.586 12l-5.793 5.793a1 1 0 1 0 1.414 1.414L12 13.414l5.793 5.793a1 1 0 0 0 1.414-1.414L13.414 12l5.793-5.793z\" fill=\"currentColor\"></path></svg></button>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func errorBanner(message string) templ.Component {
|
||||||
|
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
|
return templ_7745c5c3_CtxErr
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
defer func() {
|
||||||
|
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err == nil {
|
||||||
|
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var3 == nil {
|
||||||
|
templ_7745c5c3_Var3 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<div id=\"error-toast\" hx-swap-oob=\"outerHTML\" class=\"fixed z-20 border border-red-500 rounded-sm right-0 top-0 m-4 p-4 bg-red-100 shadow shadow-red-200 transition-all duration-300 text-sm md:w-1/3\"><div class=\"flex items-center gap-x-2 pb-1\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = errorIcon().Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<h1 class=\"text-red-500 font-semibold\">Error</h1>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = closeButton().Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</div><div class=\"ml-8\"><p class=\"text-red-500 text-sm\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var4 string
|
||||||
|
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(message)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/components/error.templ`, Line: 34, Col: 15}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</p></div></div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenderErrorBanner renders the error banner. However, this function must ONLY be called by an
|
||||||
|
// HTMX route. Otherwise, there is no promise it will work (may result in undefined behavior).
|
||||||
|
// Just writes a piece of content to the response.
|
||||||
|
//
|
||||||
|
// If this is called from an NON-HTMX request, it will display (and functionality seems to work)
|
||||||
|
// but it will be loaded at an unknown position in the DOM. It will not swap with the proper
|
||||||
|
// element.
|
||||||
|
func RenderErrorBanner(ctx *gin.Context, message string) {
|
||||||
|
ctx.Writer.Header().Set("Content-Type", "text/html")
|
||||||
|
errorBanner(message).Render(ctx.Request.Context(), ctx.Writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = templruntime.GeneratedTemplate
|
||||||
@ -3,20 +3,24 @@ package templates
|
|||||||
// AppLayout is the main application layout, this does not contain any content other than
|
// AppLayout is the main application layout, this does not contain any content other than
|
||||||
// meta data, links, scripts and whatever is passed into it as a component.
|
// meta data, links, scripts and whatever is passed into it as a component.
|
||||||
templ AppLayout(title string, child templ.Component) {
|
templ AppLayout(title string, child templ.Component) {
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
<head>
|
||||||
<head>
|
<meta charset="UTF-8"/>
|
||||||
<meta charset="UTF-8" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<title>{ title }</title>
|
||||||
<title>{ title }</title>
|
<link rel="stylesheet" href="/v1/web/static/css/tailwind.css"/>
|
||||||
<link rel="stylesheet" href="/v1/web/static/css/tailwind.css" />
|
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
||||||
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
</head>
|
||||||
</head>
|
<body class="bg-gray-100">
|
||||||
|
<div id="error-toast"></div>
|
||||||
<body class="bg-gray-100">
|
@child
|
||||||
@child
|
<script>
|
||||||
</body>
|
function hideError() {
|
||||||
|
const err = document.getElementById("error-toast");
|
||||||
</html>
|
err.classList.add("hidden");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,13 +38,13 @@ func AppLayout(title string, child templ.Component) templ.Component {
|
|||||||
var templ_7745c5c3_Var2 string
|
var templ_7745c5c3_Var2 string
|
||||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title)
|
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/layouts/app_layout.templ`, Line: 12, Col: 16}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/layouts/app_layout.templ`, Line: 11, Col: 17}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</title><link rel=\"stylesheet\" href=\"/v1/web/static/css/tailwind.css\"><script src=\"https://unpkg.com/htmx.org@2.0.4\"></script></head><body class=\"bg-gray-100\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</title><link rel=\"stylesheet\" href=\"/v1/web/static/css/tailwind.css\"><script src=\"https://unpkg.com/htmx.org@2.0.4\"></script></head><body class=\"bg-gray-100\"><div id=\"error-toast\"></div>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@ -52,7 +52,7 @@ func AppLayout(title string, child templ.Component) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</body></html>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<script>\n function hideError() {\n const err = document.getElementById(\"error-toast\");\n err.classList.add(\"hidden\");\n }\n </script></body></html>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
|
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
|
||||||
monospace;
|
monospace;
|
||||||
--color-red-100: oklch(93.6% 0.032 17.717);
|
--color-red-100: oklch(93.6% 0.032 17.717);
|
||||||
|
--color-red-200: oklch(88.5% 0.062 18.334);
|
||||||
--color-red-500: oklch(63.7% 0.237 25.331);
|
--color-red-500: oklch(63.7% 0.237 25.331);
|
||||||
--color-green-500: oklch(72.3% 0.219 149.579);
|
--color-green-500: oklch(72.3% 0.219 149.579);
|
||||||
--color-blue-50: oklch(97% 0.014 254.604);
|
--color-blue-50: oklch(97% 0.014 254.604);
|
||||||
@ -235,18 +236,27 @@
|
|||||||
.absolute {
|
.absolute {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
.fixed {
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
.relative {
|
.relative {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
.static {
|
.static {
|
||||||
position: static;
|
position: static;
|
||||||
}
|
}
|
||||||
|
.top-0 {
|
||||||
|
top: calc(var(--spacing) * 0);
|
||||||
|
}
|
||||||
.top-1\/2 {
|
.top-1\/2 {
|
||||||
top: calc(1/2 * 100%);
|
top: calc(1/2 * 100%);
|
||||||
}
|
}
|
||||||
.top-\[100\%\] {
|
.top-\[100\%\] {
|
||||||
top: 100%;
|
top: 100%;
|
||||||
}
|
}
|
||||||
|
.right-0 {
|
||||||
|
right: calc(var(--spacing) * 0);
|
||||||
|
}
|
||||||
.left-0 {
|
.left-0 {
|
||||||
left: calc(var(--spacing) * 0);
|
left: calc(var(--spacing) * 0);
|
||||||
}
|
}
|
||||||
@ -262,6 +272,9 @@
|
|||||||
.z-20 {
|
.z-20 {
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
}
|
}
|
||||||
|
.m-4 {
|
||||||
|
margin: calc(var(--spacing) * 4);
|
||||||
|
}
|
||||||
.mx-2 {
|
.mx-2 {
|
||||||
margin-inline: calc(var(--spacing) * 2);
|
margin-inline: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
@ -337,6 +350,12 @@
|
|||||||
.mb-16 {
|
.mb-16 {
|
||||||
margin-bottom: calc(var(--spacing) * 16);
|
margin-bottom: calc(var(--spacing) * 16);
|
||||||
}
|
}
|
||||||
|
.ml-8 {
|
||||||
|
margin-left: calc(var(--spacing) * 8);
|
||||||
|
}
|
||||||
|
.ml-auto {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
.block {
|
.block {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
@ -694,6 +713,9 @@
|
|||||||
--tw-gradient-to: var(--color-purple-200);
|
--tw-gradient-to: var(--color-purple-200);
|
||||||
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||||
}
|
}
|
||||||
|
.p-1 {
|
||||||
|
padding: calc(var(--spacing) * 1);
|
||||||
|
}
|
||||||
.p-2 {
|
.p-2 {
|
||||||
padding: calc(var(--spacing) * 2);
|
padding: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
@ -939,6 +961,12 @@
|
|||||||
--tw-shadow-color: color-mix(in oklab, var(--color-gray-300) var(--tw-shadow-alpha), transparent);
|
--tw-shadow-color: color-mix(in oklab, var(--color-gray-300) var(--tw-shadow-alpha), transparent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.shadow-red-200 {
|
||||||
|
--tw-shadow-color: oklch(88.5% 0.062 18.334);
|
||||||
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
|
--tw-shadow-color: color-mix(in oklab, var(--color-red-200) var(--tw-shadow-alpha), transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
.outline {
|
.outline {
|
||||||
outline-style: var(--tw-outline-style);
|
outline-style: var(--tw-outline-style);
|
||||||
outline-width: 1px;
|
outline-width: 1px;
|
||||||
@ -1141,6 +1169,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.hover\:bg-red-200 {
|
||||||
|
&:hover {
|
||||||
|
@media (hover: hover) {
|
||||||
|
background-color: var(--color-red-200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.hover\:text-blue-400 {
|
.hover\:text-blue-400 {
|
||||||
&:hover {
|
&:hover {
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user