(DOC/FEAT): Updated doc comments and completed the search redirection!
The search is nearly complete for the initial implementation. Just need to figure out what to do with the text search provided, make any required UI changes, and eventual implement pagination via a "load more" button.
This commit is contained in:
parent
d950e75540
commit
9ac7356668
@ -42,7 +42,7 @@ func GoogleCallback(ctx *gin.Context) {
|
||||
jwt,
|
||||
int(time.Now().Add(7*24*time.Hour).Sub(time.Now()).Seconds()),
|
||||
"/",
|
||||
"localhost",
|
||||
"", // TODO: Real live domain
|
||||
false, // TODO: True in prod
|
||||
true,
|
||||
)
|
||||
@ -60,6 +60,6 @@ func GoogleCallback(ctx *gin.Context) {
|
||||
// This route will direct the user back to the home page.
|
||||
func Logout(ctx *gin.Context) {
|
||||
// TODO: Use same values as the GoogleCallback function
|
||||
ctx.SetCookie("jwt_token", "", -1, "/", "localhost", false, true)
|
||||
ctx.SetCookie("jwt_token", "", -1, "/", "", false, true) // TODO: Update settings
|
||||
ctx.Redirect(http.StatusSeeOther, domain.WEB_HOME)
|
||||
}
|
||||
|
||||
@ -47,6 +47,7 @@ func toBits(arr []string) (bits int) {
|
||||
func SearchRecipes(ctx *gin.Context) {
|
||||
deps := ctx.MustGet("deps").(*domain.InjectedDependencies)
|
||||
|
||||
|
||||
// create filters
|
||||
filters := domainRecipe.SearchFilters{
|
||||
Search: ctx.PostForm("search"), // string, search query for titles
|
||||
@ -61,14 +62,21 @@ func SearchRecipes(ctx *gin.Context) {
|
||||
ctx.SetCookie(
|
||||
"search-filters",
|
||||
string(bytes),
|
||||
int(time.Now().Add(2*time.Hour).Sub(time.Now()).Seconds()),
|
||||
int(time.Now().Add(24 * time.Hour).Sub(time.Now()).Seconds()),
|
||||
"/",
|
||||
"localhost", // TODO: real domain
|
||||
"", // TODO: Need an actual domain
|
||||
false, // TODO: True in prod
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
redirect := ctx.PostForm("redirect")
|
||||
if redirect == "true" {
|
||||
ctx.Header("HX-Redirect", domain.WEB_SEARCH)
|
||||
ctx.Status(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
recipes, err := deps.RecipeService.SearchRecipes(filters)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusOK, gin.H{"error": err.Error()})
|
||||
|
||||
@ -6,11 +6,12 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
domain "github.com/haydenhargreaves/Potion/internal/domain/server"
|
||||
)
|
||||
|
||||
const TAG_HTML = `
|
||||
<li
|
||||
hx-post="/v1/web/state/tags/delete"
|
||||
hx-post="%s"
|
||||
hx-trigger="click"
|
||||
hx-target="#tag-list"
|
||||
hx-swap="innerHTML"
|
||||
@ -41,7 +42,7 @@ func NewTag(ctx *gin.Context) {
|
||||
var cleaned_tags []string
|
||||
for _, tag := range tags {
|
||||
if tag != "" {
|
||||
html += fmt.Sprintf(TAG_HTML, tag, tag)
|
||||
html += fmt.Sprintf(TAG_HTML, domain.STATE_TAGS_DELETE, tag, tag)
|
||||
|
||||
// Ensure that the list provided does not contain blank spaces.
|
||||
// This is another measure to ensure this state is bulletproof.
|
||||
@ -63,7 +64,7 @@ func DeleteTag(ctx *gin.Context) {
|
||||
var new_tags []string
|
||||
for _, tag := range tags {
|
||||
if tag != target && tag != "" {
|
||||
html += fmt.Sprintf(TAG_HTML, tag, tag)
|
||||
html += fmt.Sprintf(TAG_HTML, domain.STATE_TAGS_DELETE ,tag, tag)
|
||||
new_tags = append(new_tags, tag)
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,9 +98,11 @@ func (s *AuthService) GoogleAuthSuccess(state, code string) (string, domain.User
|
||||
|
||||
jwt, err := generateJwt(newUser.Id, newUser.Email, s.jwtSecret)
|
||||
return jwt, newUser, googleUserInfo, err
|
||||
|
||||
}
|
||||
|
||||
// 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
|
||||
// routes.
|
||||
func generateJwt(userId int, email string, jwtSecret []byte) (string, error) {
|
||||
expiration := time.Now().Add(7 * 24 * time.Hour)
|
||||
|
||||
|
||||
@ -130,18 +130,20 @@ func (s *RecipeService) GetRecipe(id int) (*domain.Recipe, error) {
|
||||
return recipe, err
|
||||
}
|
||||
|
||||
// SearchRecipes will search the database using the filters provided. The recipes can be passed into
|
||||
// a template and displayed in the UI as the search result. A more detailed definition of the
|
||||
// filters is provided below.
|
||||
//
|
||||
// Each input is given a bit value (e.g., 00001 for 1) and will be passed
|
||||
// back to this handler as an array. The values are then added together
|
||||
// and will result in a integer which represents bit values. These bits
|
||||
// can then be passed to the repository and are then parsed to determine
|
||||
// which filters should be applied.
|
||||
// Parsing these is simple, for each filter option, use the bitwise and (&)
|
||||
// operator with the value we expect for the filter. When 1, we can ensure
|
||||
// the filter is provided.
|
||||
// A function `isBitActive` in the recipe repository provides an example of
|
||||
// testing of testing the filter parsing.
|
||||
func (s *RecipeService) SearchRecipes(filters domain.SearchFilters) ([]domain.Recipe, error) {
|
||||
// NOTE: How are the filters handled?
|
||||
// Each input is given a bit value (e.g., 00001 for 1) and will be passed
|
||||
// back to this handler as an array. The values are then added together
|
||||
// and will result in a integer which represents bit values. These bits
|
||||
// can then be passed to the repository and are then parsed to determine
|
||||
// which filters should be applied.
|
||||
// Parsing these is simple, for each filter option, use the bitwise and (&)
|
||||
// operator with the value we expect for the filter. When 1, we can ensure
|
||||
// the filter is provided.
|
||||
// A function `isBitActive` in the recipe repository provides an example of
|
||||
// testing of testing the filter parsing.
|
||||
|
||||
return s.recipeRepository.SearchRecipes(filters)
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ package domain
|
||||
const VERSION = "/v1"
|
||||
const WEB = "/web"
|
||||
const API = "/api"
|
||||
const STATE = "/state"
|
||||
|
||||
// Web prefixed routes
|
||||
const WEB_LOGIN = VERSION + WEB + "/login"
|
||||
@ -14,6 +15,7 @@ const WEB_CREATE = VERSION + WEB + "/create"
|
||||
const WEB_PROFIlE = VERSION + WEB + "/profile"
|
||||
const WEB_LIST = VERSION + WEB + "/list"
|
||||
const WEB_RECIPE = VERSION + WEB + "/recipe/%d"
|
||||
const WEB_SEARCH = VERSION + WEB + "/search"
|
||||
|
||||
// API prefixed routes
|
||||
const API_AUTH_LOGIN = VERSION + API + "/auth/login"
|
||||
@ -21,3 +23,7 @@ const API_AUTH_CALLBACK = VERSION + API + "/auth/callback"
|
||||
const API_AUTH_LOGOUT = VERSION + API + "/auth/logout"
|
||||
const API_CREATE_RECIPE = VERSION + API + "/recipe"
|
||||
const API_SEARCH_RECIPES = VERSION + API + "/recipe/search"
|
||||
|
||||
// State prefixed routes
|
||||
const STATE_TAGS_CREATE = VERSION + WEB + STATE + "/tags"
|
||||
const STATE_TAGS_DELETE = VERSION + WEB + STATE + "/tags/delete"
|
||||
|
||||
@ -8,12 +8,16 @@ import (
|
||||
domainUser "github.com/haydenhargreaves/Potion/internal/domain/user"
|
||||
)
|
||||
|
||||
// InjectedDependencies is a collection of dependencies that are injected into the application. They
|
||||
// are stored in the context and can be accessed by handlers via the context.
|
||||
type InjectedDependencies struct {
|
||||
UserService domainUser.UserService
|
||||
AuthService domainAuth.AuthService
|
||||
RecipeService domainRecipe.RecipeService
|
||||
}
|
||||
|
||||
// JwtClaims is the data stored in the JSON web token. All that is needed is the users ID and their
|
||||
// Google email provided.
|
||||
type JwtClaims struct {
|
||||
UserId int `json:"id"`
|
||||
Email string `json:"email"`
|
||||
|
||||
@ -159,6 +159,12 @@ func isBitActive(bits, pos int) bool {
|
||||
return (bits>>pos)&1 == 1
|
||||
}
|
||||
|
||||
// SearchRecipes will search the recipe table using the provided filters and return an unbound list
|
||||
// of recipes. The filters are fairly complex, they are stored as bit masks. A more details
|
||||
// description can be found in the recipe service implementation. Any errors will be bubbled to the
|
||||
// caller.
|
||||
//
|
||||
// TODO: Pagination is required, to provide infinite scroll.
|
||||
func (r *RecipeRepository) SearchRecipes(filters domain.SearchFilters) ([]domain.Recipe, error) {
|
||||
tx, err := r.db.Begin()
|
||||
if err != nil {
|
||||
|
||||
@ -1,18 +1,15 @@
|
||||
package components
|
||||
|
||||
import "fmt"
|
||||
import domainRecipe "github.com/haydenhargreaves/Potion/internal/domain/recipe"
|
||||
|
||||
// isBitActive returns true when the bit at pos (0 indexed) is true.
|
||||
func isBitActive(bits, pos int) bool {
|
||||
x := (bits>>pos)&1 == 1
|
||||
fmt.Printf("BITS: %d, POS: %d, VAL: %v\n", bits, pos, x)
|
||||
return x
|
||||
return (bits>>pos)&1 == 1
|
||||
}
|
||||
|
||||
templ dropdownButton(content, name, value string, selected bool) {
|
||||
<label class="inline-block cursor-pointer select-none">
|
||||
<input type="checkbox" name={ name } value={ value } class="sr-only peer"
|
||||
<input type="checkbox" name={ name } value={ value } class="sr-only peer"
|
||||
if selected {
|
||||
checked
|
||||
} />
|
||||
@ -89,5 +86,11 @@ templ FilterDropdown(filters domainRecipe.SearchFilters) {
|
||||
@dropdownButton("8+", "serving", "16", isBitActive(filters.ServingSize, 4))
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full pt-2 flex justify-end items-end">
|
||||
<button type="submit"
|
||||
class="w-full text-sm md:text-base text-white rounded-lg py-1.5 md:py-2 bg-blue-600 hover:bg-blue-700 duration-300">
|
||||
Apply Filters
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@ -8,14 +8,11 @@ package components
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import "fmt"
|
||||
import domainRecipe "github.com/haydenhargreaves/Potion/internal/domain/recipe"
|
||||
|
||||
// isBitActive returns true when the bit at pos (0 indexed) is true.
|
||||
func isBitActive(bits, pos int) bool {
|
||||
x := (bits>>pos)&1 == 1
|
||||
fmt.Printf("BITS: %d, POS: %d, VAL: %v\n", bits, pos, x)
|
||||
return x
|
||||
return (bits>>pos)&1 == 1
|
||||
}
|
||||
|
||||
func dropdownButton(content, name, value string, selected bool) templ.Component {
|
||||
@ -46,7 +43,7 @@ func dropdownButton(content, name, value string, selected bool) templ.Component
|
||||
var templ_7745c5c3_Var2 string
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(name)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/components/dropdowns.templ`, Line: 15, Col: 36}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/components/dropdowns.templ`, Line: 12, Col: 36}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@ -59,7 +56,7 @@ func dropdownButton(content, name, value string, selected bool) templ.Component
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(value)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/components/dropdowns.templ`, Line: 15, Col: 52}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/components/dropdowns.templ`, Line: 12, Col: 52}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@ -82,7 +79,7 @@ func dropdownButton(content, name, value string, selected bool) templ.Component
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(content)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/components/dropdowns.templ`, Line: 21, Col: 13}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/components/dropdowns.templ`, Line: 18, Col: 13}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@ -221,7 +218,7 @@ func FilterDropdown(filters domainRecipe.SearchFilters) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</div></div></div>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</div></div><div class=\"w-full pt-2 flex justify-end items-end\"><button type=\"submit\" class=\"w-full text-sm md:text-base text-white rounded-lg py-1.5 md:py-2 bg-blue-600 hover:bg-blue-700 duration-300\">Apply Filters</button></div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ package components
|
||||
import domainServer "github.com/haydenhargreaves/Potion/internal/domain/server"
|
||||
import domainRecipe "github.com/haydenhargreaves/Potion/internal/domain/recipe"
|
||||
|
||||
templ SearchBar(filters domainRecipe.SearchFilters) {
|
||||
templ SearchBar(filters domainRecipe.SearchFilters, redirect bool) {
|
||||
<form
|
||||
hx-post={ domainServer.API_SEARCH_RECIPES }
|
||||
hx-swap="innerHTML"
|
||||
@ -14,6 +14,7 @@ templ SearchBar(filters domainRecipe.SearchFilters) {
|
||||
>
|
||||
<div class="flex w-full gap-x-2">
|
||||
<div class="relative w-full">
|
||||
<input type="hidden" name="redirect" value={redirect} />
|
||||
<input
|
||||
type="search"
|
||||
name="search"
|
||||
|
||||
@ -11,7 +11,7 @@ import templruntime "github.com/a-h/templ/runtime"
|
||||
import domainServer "github.com/haydenhargreaves/Potion/internal/domain/server"
|
||||
import domainRecipe "github.com/haydenhargreaves/Potion/internal/domain/recipe"
|
||||
|
||||
func SearchBar(filters domainRecipe.SearchFilters) templ.Component {
|
||||
func SearchBar(filters domainRecipe.SearchFilters, redirect bool) 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 {
|
||||
@ -45,20 +45,33 @@ func SearchBar(filters domainRecipe.SearchFilters) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" hx-swap=\"innerHTML\" hx-target=\"#result-list\" hx-trigger=\"submit\" hx-encoding=\"multipart/form-data\" class=\"w-full px-4 my-8\"><div class=\"flex w-full gap-x-2\"><div class=\"relative w-full\"><input type=\"search\" name=\"search\" placeholder=\"Search recipes, ingredients...\" value=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" hx-swap=\"innerHTML\" hx-target=\"#result-list\" hx-trigger=\"submit\" hx-encoding=\"multipart/form-data\" class=\"w-full px-4 my-8\"><div class=\"flex w-full gap-x-2\"><div class=\"relative w-full\"><input type=\"hidden\" name=\"redirect\" value=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(filters.Search)
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(redirect)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/components/search_bar.templ`, Line: 21, Col: 27}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/components/search_bar.templ`, Line: 17, Col: 60}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\" class=\"w-full pr-4 pl-10 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500\"> <button type=\"submit\" class=\"absolute left-3 top-1/2 -translate-y-1/2\"><svg class=\"h-5 w-5 text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\"></path></svg></button></div>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\"> <input type=\"search\" name=\"search\" placeholder=\"Search recipes, ingredients...\" value=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(filters.Search)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/components/search_bar.templ`, Line: 22, Col: 27}
|
||||
}
|
||||
_, 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, 4, "\" class=\"w-full pr-4 pl-10 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500\"> <button type=\"submit\" class=\"absolute left-3 top-1/2 -translate-y-1/2\"><svg class=\"h-5 w-5 text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\"></path></svg></button></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -66,7 +79,7 @@ func SearchBar(filters domainRecipe.SearchFilters) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</div>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -74,7 +87,7 @@ func SearchBar(filters domainRecipe.SearchFilters) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</form>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</form>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -98,12 +111,12 @@ func filterButton() templ.Component {
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var4 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var4 == nil {
|
||||
templ_7745c5c3_Var4 = templ.NopComponent
|
||||
templ_7745c5c3_Var5 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var5 == nil {
|
||||
templ_7745c5c3_Var5 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<button type=\"button\" id=\"filter-dropdown-button\" onclick=\"toggleDropdown()\" class=\"text-gray-400 border border-gray-300 rounded-lg p-2 focus:outline-none focus:ring-2 focus:ring-blue-500\"><svg class=\"h-6\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M6 11.1707L6 4C6 3.44771 5.55228 3 5 3C4.44771 3 4 3.44771 4 4L4 11.1707C2.83481 11.5825 2 12.6938 2 14C2 15.3062 2.83481 16.4175 4 16.8293L4 20C4 20.5523 4.44772 21 5 21C5.55228 21 6 20.5523 6 20L6 16.8293C7.16519 16.4175 8 15.3062 8 14C8 12.6938 7.16519 11.5825 6 11.1707ZM5 13C4.44772 13 4 13.4477 4 14C4 14.5523 4.44772 15 5 15C5.55228 15 6 14.5523 6 14C6 13.4477 5.55228 13 5 13Z\" fill=\"currentColor\"></path> <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M19 21C18.4477 21 18 20.5523 18 20L18 18C18 17.9435 18.0047 17.8881 18.0137 17.8341C16.8414 17.4262 16 16.3113 16 15C16 13.6887 16.8414 12.5738 18.0137 12.1659C18.0047 12.1119 18 12.0565 18 12L18 4C18 3.44771 18.4477 3 19 3C19.5523 3 20 3.44771 20 4L20 12C20 12.0565 19.9953 12.1119 19.9863 12.1659C21.1586 12.5738 22 13.6887 22 15C22 16.3113 21.1586 17.4262 19.9863 17.8341C19.9953 17.8881 20 17.9435 20 18V20C20 20.5523 19.5523 21 19 21ZM18 15C18 14.4477 18.4477 14 19 14C19.5523 14 20 14.4477 20 15C20 15.5523 19.5523 16 19 16C18.4477 16 18 15.5523 18 15Z\" fill=\"currentColor\"></path> <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M9 9C9 7.69378 9.83481 6.58254 11 6.17071V4C11 3.44772 11.4477 3 12 3C12.5523 3 13 3.44772 13 4V6.17071C14.1652 6.58254 15 7.69378 15 9C15 10.3113 14.1586 11.4262 12.9863 11.8341C12.9953 11.8881 13 11.9435 13 12L13 20C13 20.5523 12.5523 21 12 21C11.4477 21 11 20.5523 11 20L11 12C11 11.9435 11.0047 11.8881 11.0137 11.8341C9.84135 11.4262 9 10.3113 9 9ZM11 9C11 8.44772 11.4477 8 12 8C12.5523 8 13 8.44772 13 9C13 9.55229 12.5523 10 12 10C11.4477 10 11 9.55229 11 9Z\" fill=\"currentColor\"></path></svg></button>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<button type=\"button\" id=\"filter-dropdown-button\" onclick=\"toggleDropdown()\" class=\"text-gray-400 border border-gray-300 rounded-lg p-2 focus:outline-none focus:ring-2 focus:ring-blue-500\"><svg class=\"h-6\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M6 11.1707L6 4C6 3.44771 5.55228 3 5 3C4.44771 3 4 3.44771 4 4L4 11.1707C2.83481 11.5825 2 12.6938 2 14C2 15.3062 2.83481 16.4175 4 16.8293L4 20C4 20.5523 4.44772 21 5 21C5.55228 21 6 20.5523 6 20L6 16.8293C7.16519 16.4175 8 15.3062 8 14C8 12.6938 7.16519 11.5825 6 11.1707ZM5 13C4.44772 13 4 13.4477 4 14C4 14.5523 4.44772 15 5 15C5.55228 15 6 14.5523 6 14C6 13.4477 5.55228 13 5 13Z\" fill=\"currentColor\"></path> <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M19 21C18.4477 21 18 20.5523 18 20L18 18C18 17.9435 18.0047 17.8881 18.0137 17.8341C16.8414 17.4262 16 16.3113 16 15C16 13.6887 16.8414 12.5738 18.0137 12.1659C18.0047 12.1119 18 12.0565 18 12L18 4C18 3.44771 18.4477 3 19 3C19.5523 3 20 3.44771 20 4L20 12C20 12.0565 19.9953 12.1119 19.9863 12.1659C21.1586 12.5738 22 13.6887 22 15C22 16.3113 21.1586 17.4262 19.9863 17.8341C19.9953 17.8881 20 17.9435 20 18V20C20 20.5523 19.5523 21 19 21ZM18 15C18 14.4477 18.4477 14 19 14C19.5523 14 20 14.4477 20 15C20 15.5523 19.5523 16 19 16C18.4477 16 18 15.5523 18 15Z\" fill=\"currentColor\"></path> <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M9 9C9 7.69378 9.83481 6.58254 11 6.17071V4C11 3.44772 11.4477 3 12 3C12.5523 3 13 3.44772 13 4V6.17071C14.1652 6.58254 15 7.69378 15 9C15 10.3113 14.1586 11.4262 12.9863 11.8341C12.9953 11.8881 13 11.9435 13 12L13 20C13 20.5523 12.5523 21 12 21C11.4477 21 11 20.5523 11 20L11 12C11 11.9435 11.0047 11.8881 11.0137 11.8341C9.84135 11.4262 9 10.3113 9 9ZM11 9C11 8.44772 11.4477 8 12 8C12.5523 8 13 8.44772 13 9C13 9.55229 12.5523 10 12 10C11.4477 10 11 9.55229 11 9Z\" fill=\"currentColor\"></path></svg></button>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
||||
@ -78,7 +78,7 @@ templ Page() {
|
||||
<input
|
||||
onkeydown="return event.key != 'Enter';"
|
||||
class="border border-gray-300 px-4 py-2 rounded-lg focus:outline-none focus:ring-blue-500 focus:ring-2 duration-200 ease-in-out transition-all shadow-sm"
|
||||
hx-post="/v1/web/state/tags"
|
||||
hx-post={domain.STATE_TAGS_CREATE}
|
||||
maxlength="32"
|
||||
hx-trigger="keyup[keyCode==13]"
|
||||
hx-on::after-request="this.value=''"
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -29,7 +29,8 @@ templ introSection() {
|
||||
templ searchSection() {
|
||||
<section class="w-full flex flex-col items-center justify-center my-8 py-4">
|
||||
@components.BannerText("Craving Something Specific?")
|
||||
@components.SearchBar(domainRecipe.SearchFilters{})
|
||||
@components.SearchBar(domainRecipe.SearchFilters{}, true)
|
||||
<div class="hidden" id="result-list"></div>
|
||||
</section>
|
||||
}
|
||||
|
||||
|
||||
@ -70,11 +70,11 @@ func searchSection() templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = components.SearchBar(domainRecipe.SearchFilters{}).Render(ctx, templ_7745c5c3_Buffer)
|
||||
templ_7745c5c3_Err = components.SearchBar(domainRecipe.SearchFilters{}, true).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</section>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<div class=\"hidden\" id=\"result-list\"></div></section>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ templ SearchPage(filters domainRecipe.SearchFilters) {
|
||||
bg-white flex flex-col items-center"
|
||||
>
|
||||
@components.BannerText("Recipe Search")
|
||||
@components.SearchBar(filters)
|
||||
@components.SearchBar(filters, false)
|
||||
<hr class="text-gray-300 w-full"/>
|
||||
@ResultList(nil)
|
||||
</div>
|
||||
|
||||
@ -49,7 +49,7 @@ func SearchPage(filters domainRecipe.SearchFilters) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = components.SearchBar(filters).Render(ctx, templ_7745c5c3_Buffer)
|
||||
templ_7745c5c3_Err = components.SearchBar(filters, false).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
'Noto Color Emoji';
|
||||
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
|
||||
monospace;
|
||||
--color-red-50: oklch(97.1% 0.013 17.38);
|
||||
--color-red-100: oklch(93.6% 0.032 17.717);
|
||||
--color-red-500: oklch(63.7% 0.237 25.331);
|
||||
--color-green-100: oklch(96.2% 0.044 156.743);
|
||||
@ -34,7 +33,6 @@
|
||||
--color-black: #000;
|
||||
--color-white: #fff;
|
||||
--spacing: 0.25rem;
|
||||
--container-xl: 36rem;
|
||||
--container-2xl: 42rem;
|
||||
--text-xs: 0.75rem;
|
||||
--text-xs--line-height: calc(1 / 0.75);
|
||||
@ -236,9 +234,6 @@
|
||||
.static {
|
||||
position: static;
|
||||
}
|
||||
.top-1 {
|
||||
top: calc(var(--spacing) * 1);
|
||||
}
|
||||
.top-1\/2 {
|
||||
top: calc(1/2 * 100%);
|
||||
}
|
||||
@ -248,9 +243,6 @@
|
||||
.left-0 {
|
||||
left: calc(var(--spacing) * 0);
|
||||
}
|
||||
.left-1 {
|
||||
left: calc(var(--spacing) * 1);
|
||||
}
|
||||
.left-1\/2 {
|
||||
left: calc(1/2 * 100%);
|
||||
}
|
||||
@ -272,6 +264,9 @@
|
||||
.mx-auto {
|
||||
margin-inline: auto;
|
||||
}
|
||||
.my-0 {
|
||||
margin-block: calc(var(--spacing) * 0);
|
||||
}
|
||||
.my-1 {
|
||||
margin-block: calc(var(--spacing) * 1);
|
||||
}
|
||||
@ -361,18 +356,10 @@
|
||||
width: calc(var(--spacing) * 10);
|
||||
height: calc(var(--spacing) * 10);
|
||||
}
|
||||
.size-28 {
|
||||
width: calc(var(--spacing) * 28);
|
||||
height: calc(var(--spacing) * 28);
|
||||
}
|
||||
.size-32 {
|
||||
width: calc(var(--spacing) * 32);
|
||||
height: calc(var(--spacing) * 32);
|
||||
}
|
||||
.size-40 {
|
||||
width: calc(var(--spacing) * 40);
|
||||
height: calc(var(--spacing) * 40);
|
||||
}
|
||||
.size-56 {
|
||||
width: calc(var(--spacing) * 56);
|
||||
height: calc(var(--spacing) * 56);
|
||||
@ -414,18 +401,12 @@
|
||||
.min-h-screen {
|
||||
min-height: 100vh;
|
||||
}
|
||||
.w-1 {
|
||||
width: calc(var(--spacing) * 1);
|
||||
}
|
||||
.w-1\/3 {
|
||||
width: calc(1/3 * 100%);
|
||||
}
|
||||
.w-1\/4 {
|
||||
width: calc(1/4 * 100%);
|
||||
}
|
||||
.w-3 {
|
||||
width: calc(var(--spacing) * 3);
|
||||
}
|
||||
.w-3\/4 {
|
||||
width: calc(3/4 * 100%);
|
||||
}
|
||||
@ -438,9 +419,6 @@
|
||||
.w-5 {
|
||||
width: calc(var(--spacing) * 5);
|
||||
}
|
||||
.w-9 {
|
||||
width: calc(var(--spacing) * 9);
|
||||
}
|
||||
.w-9\/10 {
|
||||
width: calc(9/10 * 100%);
|
||||
}
|
||||
@ -459,30 +437,16 @@
|
||||
.max-w-2xl {
|
||||
max-width: var(--container-2xl);
|
||||
}
|
||||
.flex-shrink {
|
||||
flex-shrink: 1;
|
||||
}
|
||||
.flex-shrink-0 {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.flex-grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.border-collapse {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.-translate-x-1 {
|
||||
--tw-translate-x: calc(var(--spacing) * -1);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
.-translate-x-1\/2 {
|
||||
--tw-translate-x: calc(calc(1/2 * 100%) * -1);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
.-translate-y-1 {
|
||||
--tw-translate-y: calc(var(--spacing) * -1);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
.-translate-y-1\/2 {
|
||||
--tw-translate-y: calc(calc(1/2 * 100%) * -1);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
@ -490,9 +454,6 @@
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.resize {
|
||||
resize: both;
|
||||
}
|
||||
.resize-none {
|
||||
resize: none;
|
||||
}
|
||||
@ -523,6 +484,9 @@
|
||||
.justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
.justify-end {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.justify-start {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
@ -550,15 +514,9 @@
|
||||
.gap-y-1 {
|
||||
row-gap: calc(var(--spacing) * 1);
|
||||
}
|
||||
.gap-y-2 {
|
||||
row-gap: calc(var(--spacing) * 2);
|
||||
}
|
||||
.gap-y-3 {
|
||||
row-gap: calc(var(--spacing) * 3);
|
||||
}
|
||||
.gap-y-4 {
|
||||
row-gap: calc(var(--spacing) * 4);
|
||||
}
|
||||
.overflow-hidden {
|
||||
overflow: hidden;
|
||||
}
|
||||
@ -646,6 +604,9 @@
|
||||
.bg-blue-500 {
|
||||
background-color: var(--color-blue-500);
|
||||
}
|
||||
.bg-blue-600 {
|
||||
background-color: var(--color-blue-600);
|
||||
}
|
||||
.bg-gray-50 {
|
||||
background-color: var(--color-gray-50);
|
||||
}
|
||||
@ -655,9 +616,6 @@
|
||||
.bg-gray-200 {
|
||||
background-color: var(--color-gray-200);
|
||||
}
|
||||
.bg-green-100 {
|
||||
background-color: var(--color-green-100);
|
||||
}
|
||||
.bg-red-100 {
|
||||
background-color: var(--color-red-100);
|
||||
}
|
||||
@ -747,6 +705,9 @@
|
||||
.py-8 {
|
||||
padding-block: calc(var(--spacing) * 8);
|
||||
}
|
||||
.pt-2 {
|
||||
padding-top: calc(var(--spacing) * 2);
|
||||
}
|
||||
.pr-4 {
|
||||
padding-right: calc(var(--spacing) * 4);
|
||||
}
|
||||
@ -851,9 +812,6 @@
|
||||
.text-gray-800 {
|
||||
color: var(--color-gray-800);
|
||||
}
|
||||
.text-green-600 {
|
||||
color: var(--color-green-600);
|
||||
}
|
||||
.text-red-500 {
|
||||
color: var(--color-red-500);
|
||||
}
|
||||
@ -863,9 +821,6 @@
|
||||
.uppercase {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.underline {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
.shadow {
|
||||
--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
|
||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||
@ -1042,6 +997,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:bg-blue-700 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
background-color: var(--color-blue-700);
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:bg-gray-50 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
@ -1170,6 +1132,11 @@
|
||||
margin-block: calc(var(--spacing) * 0);
|
||||
}
|
||||
}
|
||||
.md\:my-2 {
|
||||
@media (width >= 48rem) {
|
||||
margin-block: calc(var(--spacing) * 2);
|
||||
}
|
||||
}
|
||||
.md\:flex {
|
||||
@media (width >= 48rem) {
|
||||
display: flex;
|
||||
@ -1275,11 +1242,21 @@
|
||||
padding-block: calc(var(--spacing) * 0);
|
||||
}
|
||||
}
|
||||
.md\:py-2 {
|
||||
@media (width >= 48rem) {
|
||||
padding-block: calc(var(--spacing) * 2);
|
||||
}
|
||||
}
|
||||
.md\:py-12 {
|
||||
@media (width >= 48rem) {
|
||||
padding-block: calc(var(--spacing) * 12);
|
||||
}
|
||||
}
|
||||
.md\:pt-2 {
|
||||
@media (width >= 48rem) {
|
||||
padding-top: calc(var(--spacing) * 2);
|
||||
}
|
||||
}
|
||||
.md\:pt-14 {
|
||||
@media (width >= 48rem) {
|
||||
padding-top: calc(var(--spacing) * 14);
|
||||
@ -1308,6 +1285,12 @@
|
||||
line-height: var(--tw-leading, var(--text-4xl--line-height));
|
||||
}
|
||||
}
|
||||
.md\:text-base {
|
||||
@media (width >= 48rem) {
|
||||
font-size: var(--text-base);
|
||||
line-height: var(--tw-leading, var(--text-base--line-height));
|
||||
}
|
||||
}
|
||||
.md\:text-lg {
|
||||
@media (width >= 48rem) {
|
||||
font-size: var(--text-lg);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user