feat: add page profile with orders

This commit is contained in:
2026-06-24 12:04:25 +03:00
parent a450f32272
commit 3c24f5b115
5 changed files with 126 additions and 4 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ type Order struct {
TotalPrice float64 `json:"total_price" gorm:"not null"` TotalPrice float64 `json:"total_price" gorm:"not null"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
Items []OrderItem Items []OrderItem `json:"items"`
} }
type OrderItem struct { type OrderItem struct {
+1 -1
View File
@@ -4,7 +4,7 @@ import "gorm.io/gorm"
type User struct { type User struct {
gorm.Model `json:"-"` gorm.Model `json:"-"`
Username string `gorm:"unique;not null" binding:"required"` Username string `json:"username" gorm:"unique;not null" binding:"required"`
Password string `gorm:"not null" json:"-" binding:"required,min=8"` Password string `gorm:"not null" json:"-" binding:"required,min=8"`
Role string `gorm:"default:'customer'" json:"role"` Role string `gorm:"default:'customer'" json:"role"`
FirstName string `json:"first_name" binding:"required"` FirstName string `json:"first_name" binding:"required"`
+2 -1
View File
@@ -7,6 +7,7 @@ import Shop from "./pages/Shop";
import NotFoundPage from "./pages/NotFoundPage"; import NotFoundPage from "./pages/NotFoundPage";
import Contact from "./pages/Contact"; import Contact from "./pages/Contact";
import Cart from "./pages/Cart"; import Cart from "./pages/Cart";
import Profile from "./pages/Profile";
const PrivateRoute = ({ children }) => { const PrivateRoute = ({ children }) => {
const token = localStorage.getItem("token"); const token = localStorage.getItem("token");
@@ -27,6 +28,7 @@ export default function App() {
<Route path="/about" element={<About />} /> <Route path="/about" element={<About />} />
<Route path="/shop" element={<Shop />} /> <Route path="/shop" element={<Shop />} />
<Route path="/contact" element={<Contact />} /> <Route path="/contact" element={<Contact />} />
<Route path="/orders" element={<Profile />} />
<Route <Route
path="/cart" path="/cart"
element={ element={
@@ -35,7 +37,6 @@ export default function App() {
</PrivateRoute> </PrivateRoute>
} }
/> />
<Route path="*" element={<NotFoundPage />} /> <Route path="*" element={<NotFoundPage />} />
</Routes> </Routes>
</BrowserRouter> </BrowserRouter>
+4 -1
View File
@@ -45,5 +45,8 @@ export const updateCartItem = (id, data) =>
axios.post(`${API_URL}/cart/${id}`, data); axios.post(`${API_URL}/cart/${id}`, data);
export const removeFromCart = (id) => axios.delete(`${API_URL}/cart/${id}`); export const removeFromCart = (id) => axios.delete(`${API_URL}/cart/${id}`);
export const createOrder = (data) => axios.post(`${API_URL}/orders/`, data); export const createOrder = (data) => axios.post(`${API_URL}/orders/`, data);
export const getUserOrders = () => axios.get(`${API_URL}/orders/`); export const getUserOrders = () => axios.get(`${API_URL}/orders`);
export const getOrder = (id) => axios.get(`${API_URL}/orders/${id}`); export const getOrder = (id) => axios.get(`${API_URL}/orders/${id}`);
export const updateProfile = () => axios.patch(`${API_URL}/profile`);
export const getUserInfo = () => axios.get(`${API_URL}/profile`);
+118
View File
@@ -0,0 +1,118 @@
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import PageLayout from "../components/PageLayout";
import { getUserInfo, getUserOrders } from "../api";
export default function Profile() {
const [user, setUser] = useState(null);
const [orders, setOrders] = useState([]);
const [loading, setLoading] = useState(true);
const navigate = useNavigate();
useEffect(() => {
Promise.all([getUserInfo(), getUserOrders()])
.then(([userRes, ordersRes]) => {
setUser(userRes.data);
setOrders(ordersRes.data);
})
.catch(() => { })
.finally(() => setLoading(false));
}, []);
const handleLogout = () => {
localStorage.removeItem("token");
localStorage.removeItem("user");
navigate("/");
};
const statusLabels = {
new: "Новый",
packed: "Собирают",
delivered: "Доставлен",
cancel: "Отменен",
};
if (loading) {
return (
<PageLayout>
<p className="text-white p-6 md:p-16">Загрузка..</p>
</PageLayout>
);
}
return (
<PageLayout>
<main className="px-6 md:px-16 grow w-full mb-10 text-white">
<div className="flex items-center justify-between mb-8">
<h1 className="text-2xl md:text-4xl font-medium uppercase">
Личный кабинет
</h1>
<button
onClick={handleLogout}
className="text-white/60 hover:text-red-400 transition-colors text-sm uppercase"
>
Выйти
</button>
</div>
<section className="mb-10 bg-white/5 rounded-lg p-6 border border-white/10">
<h2 className="text-white/80 space-y-2">
{user && (
<div className="text-white/80 space-y-2">
<p>
Имя: {user.first_name || "не указано"}{" "}
{user.last_name || "не указано"}
</p>
<p>Телефон: {user.phone || "не указано"}</p>
<p>Логин: {user.username}</p>
</div>
)}
</h2>
</section>
<section>
<h2 className="text-xl font-medium mb-4">Мои заказы</h2>
{orders.length === 0 ? (
<p>У вас пока нет заказов</p>
) : (
<div className="flex flex-col gap-4">
{orders.map((order) => (
<div
className="bg-white/5 rounded-lg p-4 border border-white/10"
key={order.id}
>
<div className="flex justify-between mb-2">
<span className="font-medium">Заказ #{order.id}</span>
<span className="text-white/60 text-sm uppercase">
{statusLabels[order.status]}
</span>
</div>
<div className="flex flex-col gap-1 mb-3">
{order.items?.map((oi) => (
<div
key={oi.id}
className="flex justify-between pt-2 border-t border-white/10 text-sm"
>
<span>{oi.item?.name}</span>
<span>
{oi.quantity} {oi.item?.unit} x {oi.price}р
</span>
</div>
))}
</div>
<div className="flex justify-between pt-2 border-t border-white/10 text-sm">
<span className="text-white/50">
{order.delivery_address}
</span>
<span className="font-medium">
Итого: {order.total_price}
</span>
</div>
</div>
))}
</div>
)}
</section>
</main>
</PageLayout>
);
}