feat: add page profile with orders
This commit is contained in:
@@ -12,7 +12,7 @@ type Order struct {
|
||||
TotalPrice float64 `json:"total_price" gorm:"not null"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Items []OrderItem
|
||||
Items []OrderItem `json:"items"`
|
||||
}
|
||||
|
||||
type OrderItem struct {
|
||||
|
||||
@@ -4,7 +4,7 @@ import "gorm.io/gorm"
|
||||
|
||||
type User struct {
|
||||
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"`
|
||||
Role string `gorm:"default:'customer'" json:"role"`
|
||||
FirstName string `json:"first_name" binding:"required"`
|
||||
|
||||
@@ -7,6 +7,7 @@ import Shop from "./pages/Shop";
|
||||
import NotFoundPage from "./pages/NotFoundPage";
|
||||
import Contact from "./pages/Contact";
|
||||
import Cart from "./pages/Cart";
|
||||
import Profile from "./pages/Profile";
|
||||
|
||||
const PrivateRoute = ({ children }) => {
|
||||
const token = localStorage.getItem("token");
|
||||
@@ -27,6 +28,7 @@ export default function App() {
|
||||
<Route path="/about" element={<About />} />
|
||||
<Route path="/shop" element={<Shop />} />
|
||||
<Route path="/contact" element={<Contact />} />
|
||||
<Route path="/orders" element={<Profile />} />
|
||||
<Route
|
||||
path="/cart"
|
||||
element={
|
||||
@@ -35,7 +37,6 @@ export default function App() {
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route path="*" element={<NotFoundPage />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
|
||||
@@ -45,5 +45,8 @@ export const updateCartItem = (id, data) =>
|
||||
axios.post(`${API_URL}/cart/${id}`, data);
|
||||
export const removeFromCart = (id) => axios.delete(`${API_URL}/cart/${id}`);
|
||||
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 updateProfile = () => axios.patch(`${API_URL}/profile`);
|
||||
export const getUserInfo = () => axios.get(`${API_URL}/profile`);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user