Merging in the React Refactor #56

Merged
azpect merged 51 commits from refactor/react into master 2025-12-28 22:27:52 -07:00
6 changed files with 67 additions and 3 deletions
Showing only changes of commit 00acb981b0 - Show all commits

View File

@ -209,6 +209,7 @@ func (s *Server) Setup() *Server {
router_api_v2.GET("/auth/logout", s.LogoutHandlerV2) router_api_v2.GET("/auth/logout", s.LogoutHandlerV2)
router_api_v2.GET("/user", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenticatedUserHandlerV2) router_api_v2.GET("/user", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenticatedUserHandlerV2)
router_api_v2.GET("/user/:id", s.GetUserV2)
router_api_v2.GET("/user/recipes", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenicatedUserRecipesV2) router_api_v2.GET("/user/recipes", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenicatedUserRecipesV2)
router_api_v2.GET("/user/favorites", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenicatedUserFavoritesV2) router_api_v2.GET("/user/favorites", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenicatedUserFavoritesV2)
router_api_v2.GET("/user/engagement", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenicatedUserEngagementV2) router_api_v2.GET("/user/engagement", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenicatedUserEngagementV2)

View File

@ -3,11 +3,39 @@ package server
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"strconv"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
domain "github.com/haydenhargreaves/Potion/internal/domain/user" domain "github.com/haydenhargreaves/Potion/internal/domain/user"
) )
func (s *Server) GetUserV2(ctx *gin.Context) {
id := ctx.Param("id")
parsedId, err := strconv.Atoi(id)
if err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{
"status": http.StatusBadRequest,
"message": fmt.Sprintf("[ERROR] Failed to parse ID parameter. %s", err.Error()),
})
return
}
user, err := s.deps.UserService.GetUser(parsedId)
if err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{
"status": http.StatusBadRequest,
"message": fmt.Sprintf("[ERROR] Failed to get the target user. %s", err.Error()),
})
return
}
ctx.JSON(http.StatusOK, gin.H{
"status": http.StatusOK,
"message": "[OK] Successfully retrieved target user.",
"user": user,
})
}
func (s *Server) GetAuthenticatedUserHandlerV2(ctx *gin.Context) { func (s *Server) GetAuthenticatedUserHandlerV2(ctx *gin.Context) {
s.withAuthenticatedUser(ctx, func(ctx *gin.Context, user *domain.User) { s.withAuthenticatedUser(ctx, func(ctx *gin.Context, user *domain.User) {
ctx.JSON(http.StatusOK, gin.H{ ctx.JSON(http.StatusOK, gin.H{

View File

@ -13,7 +13,6 @@ interface FavoriteButtonProps {
export default function FavoriteButton({ favorite, id }: FavoriteButtonProps) { export default function FavoriteButton({ favorite, id }: FavoriteButtonProps) {
// CONTEXT // CONTEXT
const { isLoggedIn } = use(AuthContext); const { isLoggedIn } = use(AuthContext);
const navigate = useNavigate(); const navigate = useNavigate();
const [_favorite, setFavorite] = useState<boolean>(); const [_favorite, setFavorite] = useState<boolean>();

View File

@ -13,6 +13,8 @@ import TagList from "../components/items/TagList";
import IngredientList from "../components/items/IngredientList"; import IngredientList from "../components/items/IngredientList";
import InstructionList from "../components/items/InstructionList"; import InstructionList from "../components/items/InstructionList";
import Spinner from "../components/Spinner"; import Spinner from "../components/Spinner";
import { GetUser } from "../services/UserService";
import type { User } from "../types/user";
export default function RecipePage() { export default function RecipePage() {
// Url params // Url params
@ -20,6 +22,7 @@ export default function RecipePage() {
// Page state // Page state
const [recipe, setRecipe] = useState<Recipe | null>(null); const [recipe, setRecipe] = useState<Recipe | null>(null);
const [author, setAuthor] = useState<User | null>(null);
const [error, setError] = useState<string>(""); const [error, setError] = useState<string>("");
useEffect(() => { useEffect(() => {
@ -34,6 +37,20 @@ export default function RecipePage() {
void fetch(); void fetch();
}, [id]); }, [id]);
useEffect(() => {
async function fetch() {
if (!recipe) return;
const result: User | ApiError = await GetUser(recipe.UserId);
if (isApiError(result)) {
setError(result.message);
} else {
setAuthor(result);
}
}
void fetch();
}, [recipe]);
// BUG: Prob remove // BUG: Prob remove
useEffect(() => { useEffect(() => {
if (error) if (error)
@ -45,7 +62,7 @@ export default function RecipePage() {
<img className="bg-gray-100 w-full h-96 mx-auto mb-8" src={RecipePlaceholder} /> <img className="bg-gray-100 w-full h-96 mx-auto mb-8" src={RecipePlaceholder} />
<div className="px-4 py-8 md:px-8"> <div className="px-4 py-8 md:px-8">
<h1 className="text-3xl md:text-4xl font-bold text-gray-800">{recipe.Title}</h1> <h1 className="text-3xl md:text-4xl font-bold text-gray-800">{recipe.Title}</h1>
<p className="text-sm mt-2 mb-1 text-gray-700">Author: loading...</p> <p className="text-sm mt-2 mb-1 text-gray-700">{author ? author.Name : "Loading..."}</p>
<p className="text-sm mb-2 text-gray-700">Category: {recipe.Category}</p> <p className="text-sm mb-2 text-gray-700">Category: {recipe.Category}</p>
</div> </div>
<RecipeMetaData recipe={recipe} /> <RecipeMetaData recipe={recipe} />

View File

@ -1,10 +1,23 @@
import axios from "axios"; import axios from "axios";
import type { ApiError } from "../types/api/error"; import type { ApiError } from "../types/api/error";
import type { User } from "../types/user"; import type { User } from "../types/user";
import type { GetAuthenticateUserEngagementResponse, GetAuthenticateUserFavoritesResponse, GetAuthenticateUserMadeRecipesResponse, GetAuthenticateUserRecipesResponse, GetAuthenticateUserResponse, GetAuthenticateUserViewedRecipesResponse } from "../types/api/user"; import type { GetAuthenticateUserEngagementResponse, GetAuthenticateUserFavoritesResponse, GetAuthenticateUserMadeRecipesResponse, GetAuthenticateUserRecipesResponse, GetAuthenticateUserResponse, GetAuthenticateUserViewedRecipesResponse, GetUserResponse } from "../types/api/user";
import type { Recipe } from "../types/recipe"; import type { Recipe } from "../types/recipe";
import type { Engagement } from "../types/engagement"; import type { Engagement } from "../types/engagement";
export async function GetUser(id: number): Promise<User | ApiError> {
const response = await axios.get<GetUserResponse>(`http://localhost:3000/v2/api/user/${id}`);
if (response.data.status !== 200 || response.data.user === undefined) {
const err: ApiError = {
status: response.data.status,
message: response.data.message
};
return err;
}
return response.data.user;
}
export async function GetAuthenticatedUser(): Promise<User | ApiError> { export async function GetAuthenticatedUser(): Promise<User | ApiError> {
const response = await axios.get<GetAuthenticateUserResponse>("http://localhost:3000/v2/api/user"); const response = await axios.get<GetAuthenticateUserResponse>("http://localhost:3000/v2/api/user");

View File

@ -2,6 +2,12 @@ import type { Engagement } from "../engagement";
import type { Recipe } from "../recipe"; import type { Recipe } from "../recipe";
import type { User } from "../user"; import type { User } from "../user";
export interface GetUserResponse {
status: number;
message: string;
user?: User;
}
export interface GetAuthenticateUserResponse { export interface GetAuthenticateUserResponse {
status: number; status: number;
message: string; message: string;