ai cooking
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #64 from paulgmiller/copilot/set-favorite-store-feature

Add favorite store selection from locations page and display store name on profile

authored by

Paul Miller and committed by
GitHub
5ea5b1f0 c72709b8

+69 -23
+3 -3
cmd/careme/web.go
··· 40 40 } 41 41 mux := http.NewServeMux() 42 42 43 - userHandler := users.NewHandler(userStorage, clarityScript) 44 - userHandler.Register(mux) 45 - 46 43 locationserver, err := locations.New(context.TODO(), cfg) 47 44 if err != nil { 48 45 return fmt.Errorf("failed to create location server: %w", err) 49 46 } 50 47 locationserver.Register(mux) 48 + 49 + userHandler := users.NewHandler(userStorage, clarityScript, locationserver) 50 + userHandler.Register(mux) 51 51 52 52 recipeHandler := recipes.NewHandler(cfg, userStorage, generator, clarityScript, locationserver) 53 53 recipeHandler.Register(mux)
+2 -2
go.mod
··· 8 8 9 9 require ( 10 10 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2 11 + github.com/alpkeskin/gotoon v0.1.1 11 12 github.com/invopop/jsonschema v0.13.0 12 13 github.com/openai/openai-go/v2 v2.7.1 13 14 github.com/samber/slog-multi v1.5.0 15 + github.com/sendgrid/rest v2.6.9+incompatible 14 16 github.com/sendgrid/sendgrid-go v3.16.1+incompatible 15 17 golang.org/x/net v0.42.0 16 18 ) ··· 18 20 require ( 19 21 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 // indirect 20 22 github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect 21 - github.com/alpkeskin/gotoon v0.1.1 // indirect 22 23 github.com/bahlo/generic-list-go v0.2.0 // indirect 23 24 github.com/buger/jsonparser v1.1.1 // indirect 24 25 github.com/samber/slog-common v0.19.0 // indirect 25 - github.com/sendgrid/rest v2.6.9+incompatible // indirect 26 26 github.com/stretchr/testify v1.11.1 // indirect 27 27 github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect 28 28 golang.org/x/sync v0.16.0 // indirect
-2
go.sum
··· 104 104 github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= 105 105 github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= 106 106 github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= 107 - github.com/openai/openai-go/v2 v2.3.1 h1:ETbQy/21NaUeGmWD/ykOPX4q/Xin6eVi3J+cXwAbeKI= 108 - github.com/openai/openai-go/v2 v2.3.1/go.mod h1:sIUkR+Cu/PMUVkSKhkk742PRURkQOCFhiwJ7eRSBqmk= 109 107 github.com/openai/openai-go/v2 v2.7.1 h1:/tfvTJhfv7hTSL8mWwc5VL4WLLSDL5yn9VqVykdu9r8= 110 108 github.com/openai/openai-go/v2 v2.7.1/go.mod h1:jrJs23apqJKKbT+pqtFgNKpRju/KP9zpUTZhz3GElQE= 111 109 github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
+15 -6
internal/templates/locations.html
··· 20 20 21 21 <ul class="divide-y divide-brand-100"> 22 22 {{range .Locations}} 23 - <li> 24 - <a href="/recipes?location={{.ID}}" 25 - class="flex flex-col gap-1 rounded-xl px-8 py-6 transition hover:bg-brand-50 focus:bg-brand-50 focus:outline-none focus:ring-2 focus:ring-brand-400 focus:ring-offset-2"> 26 - <span class="text-lg font-semibold text-brand-700">{{.Name}}</span> 27 - <span class="text-sm text-gray-600">{{.Address}}</span> 28 - </a> 23 + <li class="px-8 py-6"> 24 + <div class="flex items-start justify-between gap-4"> 25 + <a href="/recipes?location={{.ID}}" 26 + class="flex flex-1 flex-col gap-1 rounded-xl py-2 transition hover:bg-brand-50 focus:bg-brand-50 focus:outline-none focus:ring-2 focus:ring-brand-400 focus:ring-offset-2"> 27 + <span class="text-lg font-semibold text-brand-700">{{.Name}}</span> 28 + <span class="text-sm text-gray-600">{{.Address}}</span> 29 + </a> 30 + <form method="POST" action="/user"> 31 + <input type="hidden" name="favorite_store" value="{{.ID}}" /> 32 + <button type="submit" 33 + class="rounded-lg border border-brand-300 bg-white px-3 py-2 text-sm font-medium text-brand-600 shadow-sm transition hover:bg-brand-50 focus:outline-none focus:ring-2 focus:ring-brand-400 focus:ring-offset-2"> 34 + Set as Favorite 35 + </button> 36 + </form> 37 + </div> 29 38 </li> 30 39 {{end}} 31 40 </ul>
+10 -1
internal/templates/user.html
··· 50 50 <form method="POST" action="/user" class="space-y-6"> 51 51 <div class="space-y-2"> 52 52 <label for="favorite_store" class="text-sm font-medium text-gray-700">Favorite Store</label> 53 + {{if .FavoriteStoreName}} 54 + <div class="w-full max-w-md rounded-lg border border-gray-300 bg-gray-50 px-3 py-2 text-gray-900"> 55 + {{.FavoriteStoreName}} 56 + </div> 57 + <input type="hidden" name="favorite_store" value="{{.User.FavoriteStore}}" /> 58 + <p class="text-xs text-gray-500">To change your favorite store, visit the <a href="/" class="text-brand-600 hover:underline">home page</a> and search for locations.</p> 59 + {{else}} 53 60 <input id="favorite_store" 54 61 name="favorite_store" 55 62 type="text" 56 63 value="{{.User.FavoriteStore}}" 57 - placeholder="Store ID or name" 64 + placeholder="Store ID" 58 65 class="w-full max-w-md rounded-lg border border-gray-300 bg-white px-3 py-2 text-gray-900 shadow-sm focus:border-brand-500 focus:outline-none focus:ring-2 focus:ring-brand-400" /> 66 + <p class="text-xs text-gray-500">Set your favorite store from the <a href="/" class="text-brand-600 hover:underline">home page</a> by searching for locations.</p> 67 + {{end}} 59 68 </div> 60 69 61 70 <div class="space-y-2">
+39 -9
internal/users/server.go
··· 1 1 package users 2 2 3 3 import ( 4 + "careme/internal/locations" 4 5 "careme/internal/templates" 6 + "context" 5 7 "errors" 6 8 "html/template" 7 9 "log/slog" ··· 10 12 "time" 11 13 ) 12 14 15 + type locationGetter interface { 16 + GetLocationByID(ctx context.Context, locationID string) (*locations.Location, error) 17 + } 18 + 13 19 type server struct { 14 20 storage *Storage 15 21 clarityScript template.HTML 16 22 userTmpl *template.Template //just remove or is htis useful? 23 + locGetter locationGetter 17 24 } 18 25 19 26 // NewHandler returns an http.Handler that serves the user related routes under /user. 20 - func NewHandler(storage *Storage, clarityScript template.HTML) *server { 27 + func NewHandler(storage *Storage, clarityScript template.HTML, locGetter locationGetter) *server { 21 28 return &server{ 22 29 storage: storage, 23 30 clarityScript: clarityScript, 24 31 userTmpl: templates.User, 32 + locGetter: locGetter, 25 33 } 26 34 } 27 35 ··· 102 110 http.Error(w, "invalid form submission", http.StatusBadRequest) 103 111 return 104 112 } 105 - currentUser.FavoriteStore = strings.TrimSpace(r.FormValue("favorite_store")) 106 - currentUser.ShoppingDay = strings.TrimSpace(r.FormValue("shopping_day")) 113 + 114 + // Only update favorite_store if provided 115 + if favoriteStore := strings.TrimSpace(r.FormValue("favorite_store")); favoriteStore != "" || r.Form.Has("favorite_store") { 116 + currentUser.FavoriteStore = favoriteStore 117 + } 118 + 119 + // Only update shopping_day if provided 120 + if shoppingDay := strings.TrimSpace(r.FormValue("shopping_day")); shoppingDay != "" { 121 + currentUser.ShoppingDay = shoppingDay 122 + } 107 123 108 124 if err := s.storage.Update(currentUser); err != nil { 109 125 slog.ErrorContext(ctx, "failed to update user", "error", err) ··· 113 129 success = true 114 130 } 115 131 132 + // Fetch location name if favorite store is set 133 + var favoriteStoreName string 134 + if currentUser.FavoriteStore != "" && s.locGetter != nil { 135 + loc, err := s.locGetter.GetLocationByID(ctx, currentUser.FavoriteStore) 136 + if err != nil { 137 + slog.WarnContext(ctx, "failed to get location name for favorite store", "location_id", currentUser.FavoriteStore, "error", err) 138 + favoriteStoreName = currentUser.FavoriteStore // fallback to ID 139 + } else { 140 + favoriteStoreName = loc.Name 141 + } 142 + } 143 + 116 144 data := struct { 117 - ClarityScript template.HTML 118 - User *User 119 - Success bool 145 + ClarityScript template.HTML 146 + User *User 147 + Success bool 148 + FavoriteStoreName string 120 149 }{ 121 - ClarityScript: s.clarityScript, 122 - User: currentUser, 123 - Success: success, 150 + ClarityScript: s.clarityScript, 151 + User: currentUser, 152 + Success: success, 153 + FavoriteStoreName: favoriteStoreName, 124 154 } 125 155 if err := s.userTmpl.Execute(w, data); err != nil { 126 156 slog.ErrorContext(ctx, "user template execute error", "error", err)