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"`
|
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 {
|
||||||
|
|||||||
@@ -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"`
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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`);
|
||||||
|
|||||||
@@ -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