├── .gitattributes
├── src
├── pages
│ ├── user
│ │ ├── payment
│ │ │ └── index.tsx
│ │ ├── profile
│ │ │ ├── components
│ │ │ │ ├── style.scss
│ │ │ │ └── button-menu.tsx
│ │ │ ├── order
│ │ │ │ ├── list.tsx
│ │ │ │ └── single.tsx
│ │ │ ├── index.tsx
│ │ │ ├── home.tsx
│ │ │ ├── addresses
│ │ │ │ ├── add.tsx
│ │ │ │ ├── list.tsx
│ │ │ │ └── edit.tsx
│ │ │ └── edit.tsx
│ │ ├── categories
│ │ │ └── list.tsx
│ │ ├── product
│ │ │ └── list.tsx
│ │ ├── cart
│ │ │ └── index.tsx
│ │ └── checkout
│ │ │ └── index.tsx
│ ├── admin
│ │ ├── orders
│ │ │ ├── list.tsx
│ │ │ └── single.tsx
│ │ ├── index.tsx
│ │ ├── product
│ │ │ ├── list.tsx
│ │ │ └── add.tsx
│ │ ├── slider
│ │ │ ├── list.tsx
│ │ │ └── add.tsx
│ │ └── categories
│ │ │ ├── add.tsx
│ │ │ ├── list.tsx
│ │ │ └── edit.tsx
│ ├── bot
│ │ ├── index.tsx
│ │ ├── components
│ │ │ └── button-menu.tsx
│ │ ├── masters
│ │ │ ├── list.tsx
│ │ │ ├── add.tsx
│ │ │ └── edit.tsx
│ │ └── setting
│ │ │ └── index.tsx
│ ├── home.tsx
│ └── index.tsx
├── vite-env.d.ts
├── assets
│ ├── images
│ │ ├── chad.jpg
│ │ └── logo.svg
│ └── styles
│ │ ├── counter.scss
│ │ ├── app.scss
│ │ └── index.css
├── components
│ ├── product
│ │ ├── index.tsx
│ │ ├── card.tsx
│ │ ├── item.tsx
│ │ └── list.tsx
│ ├── skeleton
│ │ ├── product-card.tsx
│ │ ├── user-single-product.tsx
│ │ └── products.tsx
│ ├── container
│ │ └── index.tsx
│ └── discount
│ │ └── index.tsx
├── hooks
│ ├── useTelegram.ts
│ ├── useIsReadyTelegram.ts
│ └── useTelegramUser.ts
├── framework
│ ├── api
│ │ ├── utils
│ │ │ ├── api-endpoints.ts
│ │ │ └── api-config.ts
│ │ ├── master
│ │ │ ├── add.ts
│ │ │ ├── get.ts
│ │ │ ├── update.ts
│ │ │ ├── delete.ts
│ │ │ └── get-by-id.ts
│ │ ├── orders
│ │ │ ├── get.ts
│ │ │ ├── update.ts
│ │ │ ├── get-by-user.ts
│ │ │ ├── getById.ts
│ │ │ └── add.ts
│ │ ├── slider
│ │ │ ├── add.ts
│ │ │ ├── get.ts
│ │ │ └── delete.ts
│ │ ├── product
│ │ │ ├── add.ts
│ │ │ ├── update.ts
│ │ │ ├── delete.ts
│ │ │ ├── get-by-id.ts
│ │ │ └── get.ts
│ │ ├── discount
│ │ │ ├── add.ts
│ │ │ ├── delete.ts
│ │ │ └── update.ts
│ │ ├── cart
│ │ │ ├── clear.ts
│ │ │ ├── add.ts
│ │ │ ├── get.ts
│ │ │ └── delete.ts
│ │ ├── bot-setting
│ │ │ ├── update.ts
│ │ │ └── get.ts
│ │ ├── user-information
│ │ │ ├── update.ts
│ │ │ └── get.ts
│ │ ├── photos-upload
│ │ │ ├── add-master.ts
│ │ │ ├── add.ts
│ │ │ └── add-slider.ts
│ │ ├── receipt-photos
│ │ │ └── add.ts
│ │ ├── categories
│ │ │ ├── delete.ts
│ │ │ ├── add.ts
│ │ │ ├── update.ts
│ │ │ └── get.ts
│ │ └── address
│ │ │ ├── delete.ts
│ │ │ ├── add.ts
│ │ │ ├── update.ts
│ │ │ └── get.ts
│ └── types.ts
├── router
│ ├── index.tsx
│ └── routes.tsx
├── main.tsx
├── helpers
│ ├── getFileBase64.tsx
│ └── get-order-status.ts
├── containers
│ ├── 404.tsx
│ ├── hero-slider.tsx
│ ├── product-news.tsx
│ ├── order
│ │ ├── single.tsx
│ │ ├── components
│ │ │ ├── order-list.tsx
│ │ │ ├── customer-detail.tsx
│ │ │ └── order-setting.tsx
│ │ ├── list-admin.tsx
│ │ └── list.tsx
│ └── boxes.tsx
├── layouts
│ ├── header.tsx
│ └── main.tsx
└── App.tsx
├── public
├── hotdog.png
├── images
│ ├── slide-1.jpg
│ ├── slide-2.jpg
│ └── slide-3.jpg
├── fonts
│ ├── digi-sarvenaz.ttf
│ ├── digi-sarvenaz.woff
│ ├── IRANSansWeb(FaNum).eot
│ ├── IRANSansWeb(FaNum).ttf
│ ├── IRANSansWeb(FaNum).woff
│ └── IRANSansWeb(FaNum).woff2
└── vite.svg
├── postcss.config.cjs
├── screenshots
├── app-preview.png
└── extentions.png
├── .stylelintrc.cjs
├── tailwind.config.cjs
├── tsconfig.node.json
├── .prettierrc.cjs
├── .gitignore
├── vite.config.ts
├── .vscode
├── extensions.json
└── settings.json
├── .editorconfig
├── index.html
├── tsconfig.json
├── .drone.yml
├── .eslintrc.cjs
├── package.json
└── README.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | * -text
2 |
--------------------------------------------------------------------------------
/src/pages/user/payment/index.tsx:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/public/hotdog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mojtaba1180/telegram-web-app-shop/HEAD/public/hotdog.png
--------------------------------------------------------------------------------
/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {}
5 | }
6 | };
7 |
--------------------------------------------------------------------------------
/public/images/slide-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mojtaba1180/telegram-web-app-shop/HEAD/public/images/slide-1.jpg
--------------------------------------------------------------------------------
/public/images/slide-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mojtaba1180/telegram-web-app-shop/HEAD/public/images/slide-2.jpg
--------------------------------------------------------------------------------
/public/images/slide-3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mojtaba1180/telegram-web-app-shop/HEAD/public/images/slide-3.jpg
--------------------------------------------------------------------------------
/screenshots/app-preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mojtaba1180/telegram-web-app-shop/HEAD/screenshots/app-preview.png
--------------------------------------------------------------------------------
/screenshots/extentions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mojtaba1180/telegram-web-app-shop/HEAD/screenshots/extentions.png
--------------------------------------------------------------------------------
/src/assets/images/chad.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mojtaba1180/telegram-web-app-shop/HEAD/src/assets/images/chad.jpg
--------------------------------------------------------------------------------
/public/fonts/digi-sarvenaz.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mojtaba1180/telegram-web-app-shop/HEAD/public/fonts/digi-sarvenaz.ttf
--------------------------------------------------------------------------------
/public/fonts/digi-sarvenaz.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mojtaba1180/telegram-web-app-shop/HEAD/public/fonts/digi-sarvenaz.woff
--------------------------------------------------------------------------------
/public/fonts/IRANSansWeb(FaNum).eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mojtaba1180/telegram-web-app-shop/HEAD/public/fonts/IRANSansWeb(FaNum).eot
--------------------------------------------------------------------------------
/public/fonts/IRANSansWeb(FaNum).ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mojtaba1180/telegram-web-app-shop/HEAD/public/fonts/IRANSansWeb(FaNum).ttf
--------------------------------------------------------------------------------
/public/fonts/IRANSansWeb(FaNum).woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mojtaba1180/telegram-web-app-shop/HEAD/public/fonts/IRANSansWeb(FaNum).woff
--------------------------------------------------------------------------------
/public/fonts/IRANSansWeb(FaNum).woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mojtaba1180/telegram-web-app-shop/HEAD/public/fonts/IRANSansWeb(FaNum).woff2
--------------------------------------------------------------------------------
/src/pages/user/profile/components/style.scss:
--------------------------------------------------------------------------------
1 | .profile-menu {
2 | a.active {
3 | @apply bg-[var(--tg-theme-bg-color)];
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/components/product/index.tsx:
--------------------------------------------------------------------------------
1 | import card from "./card";
2 | import item from "./item";
3 | import list from "./list";
4 |
5 | export { card, item, list };
6 |
--------------------------------------------------------------------------------
/.stylelintrc.cjs:
--------------------------------------------------------------------------------
1 | /** @type {import('stylelint').Config} */
2 |
3 | module.exports = {
4 | extends: ["stylelint-config-clean-order", "stylelint-config-prettier"]
5 | };
6 |
--------------------------------------------------------------------------------
/src/hooks/useTelegram.ts:
--------------------------------------------------------------------------------
1 | const useTelegram = () => {
2 | if (window.Telegram) {
3 | return window.Telegram.WebApp;
4 | }
5 | return null;
6 | };
7 | export default useTelegram;
8 |
--------------------------------------------------------------------------------
/src/hooks/useIsReadyTelegram.ts:
--------------------------------------------------------------------------------
1 | const useIsReadyTelegram = () => {
2 | if (window.Telegram) {
3 | return true;
4 | }
5 | return false;
6 | };
7 |
8 | export default useIsReadyTelegram;
9 |
--------------------------------------------------------------------------------
/src/framework/api/utils/api-endpoints.ts:
--------------------------------------------------------------------------------
1 | export const API_ENDPOINTS = {
2 | admin: {
3 | LAST_MOVIE: "/poster/by/filtres/0/0/created/0"
4 | },
5 | user: {
6 | movies: "/movies/get"
7 | }
8 | };
9 |
--------------------------------------------------------------------------------
/src/pages/admin/orders/list.tsx:
--------------------------------------------------------------------------------
1 | import OrderListAdmin from "@containers/order/list-admin";
2 |
3 | function AdminOrderList() {
4 | return ;
5 | }
6 |
7 | export default AdminOrderList;
8 |
--------------------------------------------------------------------------------
/src/pages/user/profile/order/list.tsx:
--------------------------------------------------------------------------------
1 | import OrderList from "@containers/order/list";
2 |
3 | function UserOrderList() {
4 | return ;
5 | }
6 |
7 | export default UserOrderList;
8 |
--------------------------------------------------------------------------------
/src/assets/styles/counter.scss:
--------------------------------------------------------------------------------
1 | .counter {
2 | display: flex;
3 | flex-direction: row;
4 | gap: 4vmin;
5 | align-items: center;
6 | justify-content: center;
7 |
8 | margin-top: 12px;
9 | }
10 |
--------------------------------------------------------------------------------
/src/pages/admin/orders/single.tsx:
--------------------------------------------------------------------------------
1 | import OrdersSingle from "@containers/order/single";
2 |
3 | function AdminOrdersSingle() {
4 | return ;
5 | }
6 |
7 | export default AdminOrdersSingle;
8 |
--------------------------------------------------------------------------------
/src/pages/user/profile/order/single.tsx:
--------------------------------------------------------------------------------
1 | import OrdersSingle from "@containers/order/single";
2 |
3 | function UserOrdersSingle() {
4 | return ;
5 | }
6 | export default UserOrdersSingle;
7 |
--------------------------------------------------------------------------------
/tailwind.config.cjs:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 |
3 | module.exports = {
4 | content: ["./src/**/*.{ts,tsx}", "./index.html"],
5 | theme: {
6 | extend: {}
7 | },
8 | plugins: []
9 | };
10 |
--------------------------------------------------------------------------------
/src/router/index.tsx:
--------------------------------------------------------------------------------
1 | import { RouterProvider } from "react-router-dom";
2 |
3 | import { routes } from "./routes";
4 |
5 | function Router() {
6 | return ;
7 | }
8 |
9 | export default Router;
10 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "allowSyntheticDefaultImports": true
7 | },
8 | "include": ["vite.config.ts"]
9 | }
10 |
11 |
--------------------------------------------------------------------------------
/.prettierrc.cjs:
--------------------------------------------------------------------------------
1 | /** @type {import('prettier').Config} */
2 |
3 | module.exports = {
4 | printWidth: 80,
5 | tabWidth: 2,
6 | singleQuote: false,
7 | trailingComma: "none",
8 | bracketSpacing: true,
9 | jsxBracketSameLine: true,
10 | semi: true,
11 | useTabs: false,
12 | bracketSameLine: false
13 | };
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .idea
17 | .DS_Store
18 | *.suo
19 | *.ntvs*
20 | *.njsproj
21 | *.sln
22 | *.sw?
23 | .env
24 |
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import "@style/index.css";
2 |
3 | import { createRoot } from "react-dom/client";
4 |
5 | import App from "./App";
6 | import { TelegramType } from "./types";
7 |
8 | declare global {
9 | interface Window {
10 | Telegram: TelegramType;
11 | }
12 | }
13 |
14 | createRoot(document.getElementById("root") as HTMLElement).render();
15 |
--------------------------------------------------------------------------------
/src/components/skeleton/product-card.tsx:
--------------------------------------------------------------------------------
1 | function ProductCardSkeleton({ delay }: { delay: number }) {
2 | return (
3 |
7 | );
8 | }
9 |
10 | export default ProductCardSkeleton;
11 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | /** @type {import('vite').UserConfig} */
2 |
3 | import react from "@vitejs/plugin-react";
4 | import { defineConfig } from "vite";
5 | import tsconfigPaths from "vite-tsconfig-paths";
6 |
7 | export default defineConfig({
8 | plugins: [react(), tsconfigPaths()],
9 | server: {
10 | open: false,
11 | port: 3000
12 | },
13 | build: {
14 | minify: "terser"
15 | }
16 | });
17 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "dbaeumer.vscode-eslint",
4 | "esbenp.prettier-vscode",
5 | "stylelint.vscode-stylelint",
6 | "editorconfig.editorconfig",
7 | "orta.vscode-jest",
8 | "styled-components.vscode-styled-components",
9 | "xabikos.ReactSnippets",
10 | "bradlc.vscode-tailwindcss",
11 | "formulahendry.auto-rename-tag",
12 | "miguelsolorio.fluent-icons",
13 | "orta.vscode-twoslash-queries"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/src/framework/api/master/add.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable implicit-arrow-linebreak */
2 | /* eslint-disable camelcase */
3 | import { TypePostMaster } from "@framework/types";
4 | import { useMutation } from "@tanstack/react-query";
5 |
6 | import Api from "../utils/api-config";
7 |
8 | const useAddMaster = () =>
9 | useMutation({
10 | mutationKey: ["add-master"],
11 | mutationFn: (props: TypePostMaster) => Api.post("/master", props)
12 | });
13 |
14 | export default useAddMaster;
15 |
--------------------------------------------------------------------------------
/src/framework/api/orders/get.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable implicit-arrow-linebreak */
2 | import { TypeOrders } from "@framework/types";
3 | import { useQuery } from "@tanstack/react-query";
4 |
5 | import Api from "../utils/api-config";
6 |
7 | const fetch = async ({ queryKey }: any) => {
8 | const [_key] = queryKey;
9 | const { data } = await Api.get("/orders");
10 | return data as TypeOrders;
11 | };
12 |
13 | export const useGetOrders = () => useQuery(["orders"], fetch);
14 |
--------------------------------------------------------------------------------
/src/framework/api/slider/add.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable implicit-arrow-linebreak */
2 | /* eslint-disable camelcase */
3 | import { TypePostSlider } from "@framework/types";
4 | import { useMutation } from "@tanstack/react-query";
5 |
6 | import Api from "../utils/api-config";
7 |
8 | const useAddSlider = () =>
9 | useMutation({
10 | mutationKey: ["add-slider"],
11 | mutationFn: (props: TypePostSlider) => Api.post("/main_slider", props)
12 | });
13 |
14 | export default useAddSlider;
15 |
--------------------------------------------------------------------------------
/src/framework/api/product/add.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable implicit-arrow-linebreak */
2 | /* eslint-disable camelcase */
3 | import { TypeProductPost } from "@framework/types";
4 | import { useMutation } from "@tanstack/react-query";
5 |
6 | import Api from "../utils/api-config";
7 |
8 | const useAddProduct = () =>
9 | useMutation({
10 | mutationKey: ["add-Product"],
11 | mutationFn: (props: TypeProductPost) => Api.post("/products", props)
12 | });
13 |
14 | export default useAddProduct;
15 |
--------------------------------------------------------------------------------
/src/pages/bot/index.tsx:
--------------------------------------------------------------------------------
1 | import { Outlet } from "react-router";
2 |
3 | import UserProfileButtonMenu from "./components/button-menu";
4 |
5 | function BotPanel() {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | {/* */}
15 |
16 | );
17 | }
18 |
19 | export default BotPanel;
20 |
--------------------------------------------------------------------------------
/src/framework/api/discount/add.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable implicit-arrow-linebreak */
2 | /* eslint-disable camelcase */
3 | import { TypePostDiscount } from "@framework/types";
4 | import { useMutation } from "@tanstack/react-query";
5 |
6 | import Api from "../utils/api-config";
7 |
8 | const useAddDiscounts = () =>
9 | useMutation({
10 | mutationKey: ["add-discount"],
11 | mutationFn: (props: TypePostDiscount) => Api.post("/discounts", props)
12 | });
13 |
14 | export default useAddDiscounts;
15 |
--------------------------------------------------------------------------------
/src/framework/api/cart/clear.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable implicit-arrow-linebreak */
2 | /* eslint-disable camelcase */
3 | import { TypeClearCart } from "@framework/types";
4 | import { useMutation } from "@tanstack/react-query";
5 |
6 | import Api from "../utils/api-config";
7 |
8 | const useClearCart = () =>
9 | useMutation({
10 | mutationKey: ["clear-cart"],
11 | mutationFn: ({ user_id }: TypeClearCart) =>
12 | Api.delete(`/carts/${user_id}/clear`)
13 | });
14 |
15 | export default useClearCart;
16 |
--------------------------------------------------------------------------------
/src/framework/api/bot-setting/update.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable implicit-arrow-linebreak */
2 | /* eslint-disable camelcase */
3 | import { TypeUpdateBotSetting } from "@framework/types";
4 | import { useMutation } from "@tanstack/react-query";
5 |
6 | import Api from "../utils/api-config";
7 |
8 | const useUpdateBotSetting = () =>
9 | useMutation({
10 | mutationKey: ["edit-bot-setting"],
11 | mutationFn: (props: TypeUpdateBotSetting) => Api.put("/", props)
12 | });
13 |
14 | export default useUpdateBotSetting;
15 |
--------------------------------------------------------------------------------
/src/framework/api/bot-setting/get.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable implicit-arrow-linebreak */
2 | import { TypeBotSetting } from "@framework/types";
3 | import { useQuery } from "@tanstack/react-query";
4 |
5 | import Api from "../utils/api-config";
6 |
7 | const fetch = async ({ queryKey }: any) => {
8 | const [_key] = queryKey;
9 | const { data } = await Api.get("/");
10 | return data.botData as TypeBotSetting | null;
11 | };
12 | export const useGetBotSetting = () =>
13 | useQuery(["bot-setting"], fetch);
14 |
--------------------------------------------------------------------------------
/src/pages/user/profile/index.tsx:
--------------------------------------------------------------------------------
1 | import { Outlet } from "react-router";
2 |
3 | import UserProfileButtonMenu from "./components/button-menu";
4 |
5 | function UserProfile() {
6 | return (
7 |
8 | {/*
*/}
9 |
10 | {/*
*/}
11 |
12 |
13 |
14 | {/* */}
15 |
16 | );
17 | }
18 |
19 | export default UserProfile;
20 |
--------------------------------------------------------------------------------
/src/framework/api/master/get.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable camelcase */
2 | /* eslint-disable implicit-arrow-linebreak */
3 | import { TypeMasters } from "@framework/types";
4 | import { useQuery } from "@tanstack/react-query";
5 |
6 | import Api from "../utils/api-config";
7 |
8 | const fetch = async ({ queryKey }: any) => {
9 | const [_key] = queryKey;
10 | const { data } = await Api.get("/master");
11 | return data.masters as TypeMasters[];
12 | };
13 |
14 | export const useGetMasters = () => useQuery(["masters"], fetch);
15 |
--------------------------------------------------------------------------------
/src/framework/api/slider/get.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable camelcase */
2 | /* eslint-disable implicit-arrow-linebreak */
3 | import { TypeSlider } from "@framework/types";
4 | import { useQuery } from "@tanstack/react-query";
5 |
6 | import Api from "../utils/api-config";
7 |
8 | const fetch = async ({ queryKey }: any) => {
9 | const [_key] = queryKey;
10 | const { data } = await Api.get("/main_slider");
11 | return data.sliders as TypeSlider[];
12 | };
13 |
14 | export const useGetSliders = () => useQuery(["masters"], fetch);
15 |
--------------------------------------------------------------------------------
/src/framework/api/master/update.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable implicit-arrow-linebreak */
2 | /* eslint-disable camelcase */
3 | import { TypePostMaster } from "@framework/types";
4 | import { useMutation } from "@tanstack/react-query";
5 |
6 | import Api from "../utils/api-config";
7 |
8 | const useUpdateMaster = ({ master_id }: { master_id: string }) =>
9 | useMutation({
10 | mutationKey: ["update-master"],
11 | mutationFn: (props: TypePostMaster) =>
12 | Api.put(`/master/${master_id}`, props)
13 | });
14 |
15 | export default useUpdateMaster;
16 |
--------------------------------------------------------------------------------
/src/framework/api/user-information/update.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable implicit-arrow-linebreak */
2 | /* eslint-disable camelcase */
3 | import { TypePostUserInfo } from "@framework/types";
4 | import { useMutation } from "@tanstack/react-query";
5 |
6 | import Api from "../utils/api-config";
7 |
8 | const useUpdateUser = ({ user_id }: { user_id: string | number }) =>
9 | useMutation({
10 | mutationKey: ["update-user"],
11 | mutationFn: (props: TypePostUserInfo) => Api.put(`/users/${user_id}`, props)
12 | });
13 |
14 | export default useUpdateUser;
15 |
--------------------------------------------------------------------------------
/src/framework/api/photos-upload/add-master.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable implicit-arrow-linebreak */
2 | /* eslint-disable camelcase */
3 | import { TypeProductPhotos } from "@framework/types";
4 | import { useMutation } from "@tanstack/react-query";
5 |
6 | import Api from "../utils/api-config";
7 |
8 | const useAddMasterImage = () =>
9 | useMutation({
10 | mutationKey: ["add-Product-image"],
11 | mutationFn: ({ photo_base64 }: TypeProductPhotos) =>
12 | Api.post("/master/photo", { photo_base64 })
13 | });
14 |
15 | export default useAddMasterImage;
16 |
--------------------------------------------------------------------------------
/src/framework/api/photos-upload/add.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable implicit-arrow-linebreak */
2 | /* eslint-disable camelcase */
3 | import { TypeProductPhotos } from "@framework/types";
4 | import { useMutation } from "@tanstack/react-query";
5 |
6 | import Api from "../utils/api-config";
7 |
8 | const useAddProductImage = () =>
9 | useMutation({
10 | mutationKey: ["add-Product-image"],
11 | mutationFn: ({ photo_base64 }: TypeProductPhotos) =>
12 | Api.post("/product_photos", { photo_base64 })
13 | });
14 |
15 | export default useAddProductImage;
16 |
--------------------------------------------------------------------------------
/src/framework/api/receipt-photos/add.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable implicit-arrow-linebreak */
2 | /* eslint-disable camelcase */
3 | import { TypeProductPhotos } from "@framework/types";
4 | import { useMutation } from "@tanstack/react-query";
5 |
6 | import Api from "../utils/api-config";
7 |
8 | const useAddReceiptPhotos = () =>
9 | useMutation({
10 | mutationKey: ["add-receipt-photos"],
11 | mutationFn: ({ photo_base64 }: TypeProductPhotos) =>
12 | Api.post("/receipt_photos", { photo_base64 })
13 | });
14 |
15 | export default useAddReceiptPhotos;
16 |
--------------------------------------------------------------------------------
/src/helpers/getFileBase64.tsx:
--------------------------------------------------------------------------------
1 | export function getFileBase64(file: any) {
2 | return new Promise((resolve, reject) => {
3 | const reader = new FileReader();
4 | const str = JSON.stringify(file);
5 | const bytes = new TextEncoder().encode(str);
6 | const blob = new Blob([bytes], {
7 | type: "application/json;charset=utf-8"
8 | });
9 | reader.readAsDataURL(blob);
10 | reader.onload = () => {
11 | resolve(reader.result);
12 | };
13 | reader.onerror = (error) => {
14 | reject(error);
15 | };
16 | });
17 | }
18 |
--------------------------------------------------------------------------------
/src/framework/api/cart/add.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable implicit-arrow-linebreak */
2 | /* eslint-disable camelcase */
3 | import { TypeAddToCart } from "@framework/types";
4 | import { useMutation } from "@tanstack/react-query";
5 |
6 | import Api from "../utils/api-config";
7 |
8 | const useAddToCart = () =>
9 | useMutation({
10 | mutationKey: ["add-to-cart"],
11 | mutationFn: ({ user_id, cart_items }: TypeAddToCart) =>
12 | Api.put("/carts", {
13 | user_id,
14 | cart_items
15 | })
16 | });
17 |
18 | export default useAddToCart;
19 |
--------------------------------------------------------------------------------
/src/framework/api/orders/update.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable implicit-arrow-linebreak */
2 | /* eslint-disable camelcase */
3 | import { TypeUpdateOrder } from "@framework/types";
4 | import { useMutation } from "@tanstack/react-query";
5 |
6 | import Api from "../utils/api-config";
7 |
8 | const useUpdateOrder = ({ order_id }: { order_id: string }) =>
9 | useMutation({
10 | mutationKey: ["update-order"],
11 | mutationFn: (props: TypeUpdateOrder) =>
12 | Api.put(`/orders/${order_id}`, props)
13 | });
14 |
15 | export default useUpdateOrder;
16 |
--------------------------------------------------------------------------------
/src/framework/api/utils/api-config.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios"
2 |
3 | const { VITE_API_URL, VITE_API_VERSION, VITE_SHOP_NAME } = import.meta.env
4 |
5 | const Api = axios.create({
6 | baseURL: `${VITE_API_URL}/api/${VITE_API_VERSION}/${VITE_SHOP_NAME}`,
7 | headers: {
8 | Accept: "*/*"
9 | }
10 | })
11 |
12 | Api.interceptors.request.use(
13 | (config) => config,
14 | (err) => Promise.reject(err)
15 | )
16 |
17 | Api.interceptors.response.use(
18 | (response) => response,
19 | (err) => Promise.reject(err)
20 | )
21 |
22 | export default Api
23 |
--------------------------------------------------------------------------------
/src/framework/api/photos-upload/add-slider.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable implicit-arrow-linebreak */
2 | /* eslint-disable camelcase */
3 | import { TypeProductPhotos } from "@framework/types";
4 | import { useMutation } from "@tanstack/react-query";
5 |
6 | import Api from "../utils/api-config";
7 |
8 | const useAddSliderImage = () =>
9 | useMutation({
10 | mutationKey: ["add-main_slider-image"],
11 | mutationFn: ({ photo_base64 }: TypeProductPhotos) =>
12 | Api.post("/main_slider/photo", { photo_base64 })
13 | });
14 |
15 | export default useAddSliderImage;
16 |
--------------------------------------------------------------------------------
/src/framework/api/product/update.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable implicit-arrow-linebreak */
2 | /* eslint-disable camelcase */
3 | import { TypeProductPost } from "@framework/types";
4 | import { useMutation } from "@tanstack/react-query";
5 |
6 | import Api from "../utils/api-config";
7 |
8 | const useUpdateProduct = ({ product_id }: { product_id: string | number }) =>
9 | useMutation({
10 | mutationKey: ["update-Product"],
11 | mutationFn: (props: TypeProductPost) =>
12 | Api.put(`/products/${product_id}`, props)
13 | });
14 |
15 | export default useUpdateProduct;
16 |
--------------------------------------------------------------------------------
/src/framework/api/cart/get.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable camelcase */
2 | /* eslint-disable implicit-arrow-linebreak */
3 | import { TypeCarts } from "@framework/types";
4 | import { useQuery } from "@tanstack/react-query";
5 |
6 | import Api from "../utils/api-config";
7 |
8 | const fetch = async ({ queryKey }: any) => {
9 | const [_key, user_id] = queryKey;
10 | const { data } = await Api.get(`/carts/${user_id}`);
11 | return data.cart as TypeCarts | null;
12 | };
13 |
14 | export const useGetCarts = (user_id: string) =>
15 | useQuery(["carts", user_id], fetch);
16 |
--------------------------------------------------------------------------------
/src/framework/api/categories/delete.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable implicit-arrow-linebreak */
2 | /* eslint-disable camelcase */
3 | import { TypeDeleteCategories } from "@framework/types";
4 | import { useMutation } from "@tanstack/react-query";
5 |
6 | import Api from "../utils/api-config";
7 |
8 | const useDeleteCategories = () =>
9 | useMutation({
10 | mutationKey: ["delete-category"],
11 | mutationFn: ({ user_id, category_id }: TypeDeleteCategories) =>
12 | Api.delete(`/categories/${category_id}?user_id=${user_id}`)
13 | });
14 |
15 | export default useDeleteCategories;
16 |
--------------------------------------------------------------------------------
/src/assets/styles/app.scss:
--------------------------------------------------------------------------------
1 | .app {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 |
6 | // min-height: 100vh;
7 |
8 | color: white;
9 | text-align: center;
10 | }
11 | .app-logo {
12 | pointer-events: none;
13 | scale: 1.3;
14 | animation: app-logo-spin infinite 20s linear;
15 |
16 | @keyframes app-logo-spin {
17 | from {
18 | transform: rotate(0deg);
19 | }
20 |
21 | to {
22 | transform: rotate(360deg);
23 | }
24 | }
25 | }
26 |
27 | .links {
28 | a {
29 | border-bottom: #fff solid 1px;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/framework/api/discount/delete.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable implicit-arrow-linebreak */
2 | /* eslint-disable camelcase */
3 | import { useMutation } from "@tanstack/react-query";
4 |
5 | import Api from "../utils/api-config";
6 |
7 | const useDeleteDiscount = () =>
8 | useMutation({
9 | mutationKey: ["delete-discount"],
10 | mutationFn: ({
11 | discount_id,
12 | user_id
13 | }: {
14 | discount_id: string;
15 | user_id: string;
16 | }) => Api.delete(`/discounts/${discount_id}?user_id=${user_id}`)
17 | });
18 |
19 | export default useDeleteDiscount;
20 |
--------------------------------------------------------------------------------
/src/framework/api/discount/update.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable implicit-arrow-linebreak */
2 | /* eslint-disable camelcase */
3 | import { TypeUpdateDiscount } from "@framework/types";
4 | import { useMutation } from "@tanstack/react-query";
5 |
6 | import Api from "../utils/api-config";
7 |
8 | const useUpdateDiscount = ({ discount_id }: { discount_id: string | number }) =>
9 | useMutation({
10 | mutationKey: ["update-discount"],
11 | mutationFn: (props: TypeUpdateDiscount) =>
12 | Api.put(`/discounts/${discount_id}`, props)
13 | });
14 |
15 | export default useUpdateDiscount;
16 |
--------------------------------------------------------------------------------
/src/framework/api/cart/delete.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable prettier/prettier */
2 | /* eslint-disable implicit-arrow-linebreak */
3 | /* eslint-disable camelcase */
4 | import { TypeDeleteCartItem } from "@framework/types";
5 | import { useMutation } from "@tanstack/react-query";
6 |
7 | import Api from "../utils/api-config";
8 |
9 | const useDeleteCartItem = () =>
10 | useMutation({
11 | mutationKey: ["delete-cart-item"],
12 | mutationFn: ({ user_id, product_id }: TypeDeleteCartItem) =>
13 | Api.delete(`/carts/${user_id}/${product_id}`)
14 | });
15 |
16 | export default useDeleteCartItem;
17 |
--------------------------------------------------------------------------------
/src/framework/api/address/delete.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable prettier/prettier */
2 | /* eslint-disable implicit-arrow-linebreak */
3 | /* eslint-disable camelcase */
4 | import { TypeDeleteAddressItem } from "@framework/types";
5 | import { useMutation } from "@tanstack/react-query";
6 |
7 | import Api from "../utils/api-config";
8 |
9 | const useDeleteAddress = () =>
10 | useMutation({
11 | mutationKey: ["delete-address-item"],
12 | mutationFn: ({ user_id, address_id }: TypeDeleteAddressItem) =>
13 | Api.delete(`/users/${user_id}/addresses/${address_id}`)
14 | });
15 |
16 | export default useDeleteAddress;
17 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # top-most EditorConfig file
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | max_line_length = 80
10 | trim_trailing_whitespace = true
11 | insert_final_newline = true
12 |
13 | [*.md]
14 | max_line_length = 0
15 | trim_trailing_whitespace = false
16 |
17 | # Matches multiple files with brace expansion notation
18 | # Set default charset
19 | [*.{js,jsx,ts,tsx,html}]
20 | charset = utf-8
21 |
22 | # Matches the exact files either package.json or .travis.yml
23 | [{package.json,.travis.yml}]
24 | indent_style = space
25 | indent_size = 2
26 |
--------------------------------------------------------------------------------
/src/containers/404.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Divider } from "antd";
2 | import { useNavigate } from "react-router";
3 |
4 | function NotFoundPage() {
5 | const navigate = useNavigate();
6 | return (
7 |
8 |
404
9 |
10 |
اوه به نظر به جای اشتباهی اومدی میتونی راحت بگردی به صفحه اصلی
11 |
12 |
13 | );
14 | }
15 |
16 | export default NotFoundPage;
17 |
--------------------------------------------------------------------------------
/src/framework/api/orders/get-by-user.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable camelcase */
2 | /* eslint-disable implicit-arrow-linebreak */
3 | import { TypeSingleOrder } from "@framework/types";
4 | import { useQuery } from "@tanstack/react-query";
5 |
6 | import Api from "../utils/api-config";
7 |
8 | const fetch = async ({ queryKey }: any) => {
9 | const [_key, user_id] = queryKey;
10 |
11 | const { data } = await Api.get(`/orders/user/${user_id}`);
12 | return data as TypeSingleOrder;
13 | };
14 |
15 | export const useGetOrderByUser = ({ user_id }: { user_id: string }) =>
16 | useQuery([`order-user-${user_id}`, user_id], fetch);
17 |
--------------------------------------------------------------------------------
/src/framework/api/product/delete.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable implicit-arrow-linebreak */
2 | /* eslint-disable camelcase */
3 | import { TypeDeleteProduct } from "@framework/types";
4 | import { useMutation } from "@tanstack/react-query";
5 |
6 | import Api from "../utils/api-config";
7 |
8 | const useDeleteProduct = () =>
9 | useMutation({
10 | mutationKey: ["delete-product"],
11 | mutationFn: ({ user_id, product_id }: TypeDeleteProduct) =>
12 | Api.delete("/products", {
13 | data: {
14 | user_id: `${user_id}`,
15 | product_id
16 | }
17 | })
18 | });
19 |
20 | export default useDeleteProduct;
21 |
--------------------------------------------------------------------------------
/src/helpers/get-order-status.ts:
--------------------------------------------------------------------------------
1 | export const GetOrderStatus = (e: string) => {
2 | switch (e) {
3 | case "Pending":
4 | return " در انتظار تایید ";
5 | case "Processing":
6 | return "درحال انجام ";
7 | case "Packing":
8 | return " درحال بسته بندی ";
9 | case "CancelledByCustomer":
10 | return "لغو توسط مشتری ";
11 | case "CancelledDueToUnavailability":
12 | return "اتمام موجودی 1 یا چند کالا";
13 | case "CancelledByAdmin":
14 | return "لغو توسط ادمین";
15 | case "Shipped":
16 | return "تحویل داده شده ";
17 | default:
18 | return " در انتظار تایید ";
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/src/framework/api/categories/add.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable implicit-arrow-linebreak */
2 | /* eslint-disable camelcase */
3 | import { TypePostCategories } from "@framework/types";
4 | import { useMutation } from "@tanstack/react-query";
5 |
6 | import Api from "../utils/api-config";
7 |
8 | const useAddCategories = () =>
9 | useMutation({
10 | mutationKey: ["add-categories"],
11 | mutationFn: ({ user_id, category_name, parent_id }: TypePostCategories) =>
12 | Api.post("/categories", {
13 | user_id,
14 | category_name,
15 | parent_id: parent_id || null
16 | })
17 | });
18 |
19 | export default useAddCategories;
20 |
--------------------------------------------------------------------------------
/src/pages/home.tsx:
--------------------------------------------------------------------------------
1 | import Boxes from "@containers/boxes";
2 | import HeroSlider from "@containers/hero-slider";
3 | import ProductNews from "@containers/product-news";
4 | import useTelegram from "@hooks/useTelegram";
5 |
6 | import AppHeader from "../layouts/header";
7 |
8 | function Home() {
9 | const tgApp = useTelegram();
10 |
11 | // const userId = tgApp.initDataUnsafe.user.id;
12 | // const navigate = useNavigate();
13 | return (
14 |
20 | );
21 | }
22 |
23 | export default Home;
24 |
--------------------------------------------------------------------------------
/src/framework/api/orders/getById.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable camelcase */
2 | /* eslint-disable implicit-arrow-linebreak */
3 | import { TypeSingleOrder } from "@framework/types";
4 | import { useQuery } from "@tanstack/react-query";
5 |
6 | import Api from "../utils/api-config";
7 |
8 | const fetch = async ({ queryKey }: any) => {
9 | const [_key, order_Id] = queryKey;
10 | console.log("getttttt");
11 | const { data } = await Api.get(`/orders/${order_Id}`);
12 | return data as TypeSingleOrder;
13 | };
14 |
15 | export const useGetOrderById = ({ order_Id }: { order_Id: string }) =>
16 | useQuery([`order-${order_Id}`, order_Id], fetch);
17 |
--------------------------------------------------------------------------------
/src/framework/api/master/delete.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable implicit-arrow-linebreak */
2 | /* eslint-disable camelcase */
3 | import { useMutation } from "@tanstack/react-query";
4 |
5 | import Api from "../utils/api-config";
6 |
7 | const useDeleteMaster = () =>
8 | useMutation({
9 | mutationKey: ["delete-master"],
10 | mutationFn: ({
11 | master_id,
12 | user_id
13 | }: {
14 | master_id: string;
15 | user_id: string;
16 | }) =>
17 | Api.delete("/master", {
18 | data: {
19 | user_Id: `${user_id}`,
20 | id: master_id
21 | }
22 | })
23 | });
24 |
25 | export default useDeleteMaster;
26 |
--------------------------------------------------------------------------------
/src/pages/user/categories/list.tsx:
--------------------------------------------------------------------------------
1 | import Container from "@components/container";
2 | import ProductLists from "@components/product/list";
3 | import ProductsSkeleton from "@components/skeleton/products";
4 | import { useGetProducts } from "@framework/api/product/get";
5 | import { Suspense } from "react";
6 |
7 | function UserCategoriesList() {
8 | const { data } = useGetProducts({});
9 | return (
10 |
11 | }>
12 |
13 |
14 |
15 | );
16 | }
17 |
18 | export default UserCategoriesList;
19 |
--------------------------------------------------------------------------------
/src/framework/api/slider/delete.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable implicit-arrow-linebreak */
2 | /* eslint-disable camelcase */
3 | import { useMutation } from "@tanstack/react-query";
4 |
5 | import Api from "../utils/api-config";
6 |
7 | const useDeleteSlider = () =>
8 | useMutation({
9 | mutationKey: ["delete-slider"],
10 | mutationFn: ({
11 | master_id,
12 | user_id
13 | }: {
14 | master_id: string;
15 | user_id: string;
16 | }) =>
17 | Api.delete("/main_slider", {
18 | data: {
19 | user_Id: `${user_id}`,
20 | id: master_id
21 | }
22 | })
23 | });
24 |
25 | export default useDeleteSlider;
26 |
--------------------------------------------------------------------------------
/src/framework/api/categories/update.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable implicit-arrow-linebreak */
2 | /* eslint-disable camelcase */
3 | import { useMutation } from "@tanstack/react-query";
4 |
5 | import Api from "../utils/api-config";
6 |
7 | const useUpdateCategory = ({ category_id }: { category_id: string | number }) =>
8 | useMutation({
9 | mutationKey: [`edit-categories-${category_id}`],
10 | mutationFn: ({ user_id, category_name, parent_id }: TypePostCategories) =>
11 | Api.put(`/categories/${category_id}`, {
12 | user_id,
13 | category_name,
14 | parent_id: parent_id || null
15 | })
16 | });
17 |
18 | export default useUpdateCategory;
19 |
--------------------------------------------------------------------------------
/src/framework/api/product/get-by-id.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable camelcase */
2 | /* eslint-disable implicit-arrow-linebreak */
3 | import { Product } from "@framework/types";
4 | import { useQuery } from "@tanstack/react-query";
5 |
6 | import Api from "../utils/api-config";
7 |
8 | interface Props {
9 | product_id: number | string;
10 | }
11 | const fetch = async ({ queryKey }: any) => {
12 | const [_key, product_id] = queryKey;
13 | const { data } = await Api.get(`/products/${product_id}`);
14 | return data as Product;
15 | };
16 |
17 | export const useGetProductsById = ({ product_id }: Props) =>
18 | useQuery([`product-by-id-${product_id}`, product_id], fetch);
19 |
--------------------------------------------------------------------------------
/src/pages/user/product/list.tsx:
--------------------------------------------------------------------------------
1 | import Container from "@components/container";
2 | import ProductLists from "@components/product/list";
3 | import ProductsSkeleton from "@components/skeleton/products";
4 | import { useGetProducts } from "@framework/api/product/get";
5 | import { Suspense } from "react";
6 |
7 | function ProductList() {
8 | const { data } = useGetProducts({});
9 | console.log(data);
10 | return (
11 |
12 | }>
13 |
14 |
15 |
16 | );
17 | }
18 |
19 | export default ProductList;
20 |
--------------------------------------------------------------------------------
/src/framework/api/master/get-by-id.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable camelcase */
2 | /* eslint-disable implicit-arrow-linebreak */
3 | import { TypePostMaster } from "@framework/types";
4 | import { useQuery } from "@tanstack/react-query";
5 |
6 | import Api from "../utils/api-config";
7 |
8 | interface Props {
9 | master_id: number | string;
10 | }
11 | const fetch = async ({ queryKey }: any) => {
12 | const [_key, master_id] = queryKey;
13 | const { data } = await Api.get(`/master/${master_id}`);
14 | return data.master as TypePostMaster;
15 | };
16 |
17 | export const useGetMasterById = ({ master_id }: Props) =>
18 | useQuery([`master-by-id-${master_id}`, master_id], fetch);
19 |
--------------------------------------------------------------------------------
/src/framework/api/user-information/get.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable camelcase */
2 | import { TypeUserInfo } from "@framework/types";
3 | import { useQuery } from "@tanstack/react-query";
4 |
5 | import Api from "../utils/api-config";
6 |
7 | interface QueryProps {
8 | user_Id: string | number;
9 | }
10 |
11 | export const fetch = async ({ queryKey }: any) => {
12 | const [_key, user_Id] = queryKey;
13 | const { data } = await Api.get(`/users/${user_Id}/GetUser`);
14 | return data as TypeUserInfo;
15 | };
16 |
17 | // eslint-disable-next-line prettier/prettier
18 | const useGetUserInfo = ({ user_Id }: QueryProps) => useQuery(["user-info", user_Id], fetch);
19 |
20 | export default useGetUserInfo;
21 |
--------------------------------------------------------------------------------
/src/containers/hero-slider.tsx:
--------------------------------------------------------------------------------
1 | import { useGetSliders } from "@framework/api/slider/get";
2 | import { Carousel } from "antd";
3 |
4 | function HeroSlider() {
5 | const { data } = useGetSliders();
6 | return data?.length === 0 ? (
7 |
8 | ) : (
9 |
10 | {data?.map((item, idx) => (
11 |
19 | ))}
20 |
21 | );
22 | }
23 |
24 | export default HeroSlider;
25 |
--------------------------------------------------------------------------------
/src/hooks/useTelegramUser.ts:
--------------------------------------------------------------------------------
1 | import qs from "query-string";
2 |
3 | interface TGUser {
4 | id: number;
5 | first_name: string;
6 | last_name: string;
7 | username: string;
8 | language_code: string;
9 | }
10 | export interface TypeTGWebAppData {
11 | auth_date: string;
12 | hash: string;
13 | query_id: string;
14 | user: TGUser;
15 | }
16 | const useTelegramUser = () => {
17 | const { tgWebAppData } = JSON.parse(
18 | sessionStorage.getItem("__telegram__initParams") || ""
19 | );
20 | const TGparse = qs.parse(tgWebAppData);
21 | try {
22 | const user: TGUser = JSON.parse(TGparse.user);
23 | return user;
24 | } catch {
25 | return null;
26 | }
27 | };
28 | export default useTelegramUser;
29 |
--------------------------------------------------------------------------------
/src/framework/api/orders/add.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable implicit-arrow-linebreak */
2 | /* eslint-disable camelcase */
3 | import { TypeOrderPost } from "@framework/types";
4 | import { useMutation } from "@tanstack/react-query";
5 |
6 | import Api from "../utils/api-config";
7 |
8 | const useAddOrder = () =>
9 | useMutation({
10 | mutationKey: ["add-Order"],
11 | mutationFn: ({
12 | user_Id,
13 | order_Description,
14 | receipt_Photo_Path,
15 | shipping_Cost,
16 | user_Address_Id
17 | }: TypeOrderPost) =>
18 | Api.post("/orders", {
19 | user_Id,
20 | order_Description,
21 | receipt_Photo_Path,
22 | shipping_Cost,
23 | user_Address_Id
24 | })
25 | });
26 |
27 | export default useAddOrder;
28 |
--------------------------------------------------------------------------------
/src/framework/api/address/add.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable prettier/prettier */
2 | /* eslint-disable implicit-arrow-linebreak */
3 | /* eslint-disable camelcase */
4 | import { TypeAddAddress } from "@framework/types";
5 | import { useMutation } from "@tanstack/react-query";
6 |
7 | import Api from "../utils/api-config";
8 |
9 | const useAddAddress = () =>
10 | useMutation({
11 | mutationKey: ["add-address"],
12 | mutationFn: ({
13 | city,
14 | country,
15 | state,
16 | street,
17 | zipcode,
18 | user_Id
19 | }: TypeAddAddress) =>
20 | Api.post(`/users/${user_Id}/addresses`, {
21 | city,
22 | country,
23 | state,
24 | street,
25 | zipcode
26 | })
27 | });
28 |
29 | export default useAddAddress;
30 |
--------------------------------------------------------------------------------
/src/framework/api/categories/get.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable camelcase */
2 | /* eslint-disable implicit-arrow-linebreak */
3 | import { TypeCategories } from "@framework/types";
4 | import { useQuery } from "@tanstack/react-query";
5 | import qs from "query-string";
6 |
7 | import Api from "../utils/api-config";
8 |
9 | const fetch = async ({ queryKey }: any) => {
10 | const [_key, category_id] = queryKey;
11 | const { data } = await Api.get(
12 | `/categories?${qs.stringify({ category_id })}`
13 | );
14 | return data.categories as TypeCategories[] | null;
15 | };
16 |
17 | export interface Props {
18 | category_id?: number | string;
19 | }
20 |
21 | export const useGetCategories = ({ category_id }: Props) =>
22 | useQuery(["user-info", category_id], fetch);
23 |
--------------------------------------------------------------------------------
/src/framework/api/address/update.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable prettier/prettier */
2 | /* eslint-disable implicit-arrow-linebreak */
3 | /* eslint-disable camelcase */
4 | import { TypeAddAddress } from "@framework/types";
5 | import { useMutation } from "@tanstack/react-query";
6 |
7 | import Api from "../utils/api-config";
8 |
9 | const useUpdateAddress = () =>
10 | useMutation({
11 | mutationKey: ["update-address"],
12 | mutationFn: ({
13 | address_Id,
14 | user_Id,
15 | city,
16 | country,
17 | state,
18 | street,
19 | zipcode
20 | }: TypeAddAddress) =>
21 | Api.put(`/users/${user_Id}/addresses/${address_Id}`, {
22 | city,
23 | country,
24 | state,
25 | street,
26 | zipcode
27 | })
28 | });
29 |
30 | export default useUpdateAddress;
31 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
13 |
14 |
18 | PTI7
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/layouts/header.tsx:
--------------------------------------------------------------------------------
1 | import { ShoppingCartOutlined, UserOutlined } from "@ant-design/icons";
2 | import { Link } from "react-router-dom";
3 |
4 | function AppHeader() {
5 | return (
6 |
7 |
10 |
11 | سبد خرید من
12 |
13 |
16 | حساب کاربری
17 |
18 |
19 |
20 | );
21 | }
22 |
23 | export default AppHeader;
24 |
--------------------------------------------------------------------------------
/src/framework/api/address/get.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable prettier/prettier */
2 | /* eslint-disable semi */
3 | /* eslint-disable implicit-arrow-linebreak */
4 | /* eslint-disable camelcase */
5 | import { TypeAddresses } from "@framework/types";
6 | import { useQuery } from "@tanstack/react-query";
7 |
8 | import Api from "../utils/api-config";
9 |
10 | const fetch = async ({ queryKey }: any) => {
11 | const [_key, user_id, address_id] = queryKey;
12 | const { data } = await Api.get(
13 | `/users/${user_id}/addresses${address_id ? `?address_id=${address_id}` : ""
14 | }`
15 | );
16 | return data as TypeAddresses | null;
17 | };
18 |
19 | export const useGetAddresses = (
20 | user_id: string,
21 | address_id?: string | number | undefined | null
22 | ) =>
23 | useQuery(
24 | ["user-addresses", user_id, address_id],
25 | fetch
26 | );
27 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable indent */
2 | import "@style/app.scss";
3 | import "antd/dist/reset.css";
4 |
5 | import useTelegramUser from "@hooks/useTelegramUser";
6 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
7 |
8 | import Main from "./layouts/main";
9 | import Router from "./router";
10 |
11 | function App() {
12 | const isTG = useTelegramUser();
13 | const queryClient = new QueryClient({
14 | defaultOptions: {
15 | queries: {
16 | refetchOnWindowFocus: true,
17 | refetchOnMount: true,
18 | refetchOnReconnect: true,
19 | retry: false,
20 | staleTime: 5 * 60 * 1000
21 | }
22 | }
23 | });
24 |
25 | if (!isTG) return <>open in telegram app >;
26 | return (
27 |
28 |
29 |
30 |
31 |
32 | );
33 | }
34 |
35 | export default App;
36 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "typescript.suggest.paths": true,
3 | "javascript.suggest.paths": true,
4 | "editor.defaultFormatter": "esbenp.prettier-vscode",
5 | "editor.formatOnSave": true,
6 | "editor.codeActionsOnSave": {
7 | "source.fixAll.eslint": true,
8 | "source.fixAll.stylelint": true
9 | },
10 | "css.validate": false,
11 | "scss.validate": false,
12 | "stylelint.validate": ["css", "postcss", "scss"],
13 | "editor.snippetSuggestions": "inline",
14 | "emmet.includeLanguages": {
15 | "javascript": "javascriptreact"
16 | },
17 | "search.sortOrder": "type",
18 | "explorer.sortOrder": "type",
19 | "cSpell.words": ["Formik", "Signup", "zipcode", "ذخیره"],
20 | "[properties]": {
21 | "editor.defaultFormatter": "foxundermoon.shell-format"
22 | },
23 | "[nginx]": {
24 | "editor.defaultFormatter": "raynigon.nginx-formatter"
25 | },
26 | "[dockercompose]": {
27 | "editor.defaultFormatter": "ms-azuretools.vscode-docker"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/pages/admin/index.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "antd";
2 | import { useNavigate } from "react-router-dom";
3 |
4 | function Admin() {
5 | // const tgApp = useTelegram();
6 |
7 | // const userId = tgApp.initDataUnsafe.user.id;
8 | const navigate = useNavigate();
9 | return (
10 |
11 | منو ادمین
12 | {/*
13 |
14 |
15 |
16 | */}
17 |
18 |
21 |
22 | {/* */}
23 |
24 |
25 | );
26 | }
27 |
28 | export default Admin;
29 |
--------------------------------------------------------------------------------
/src/pages/bot/components/button-menu.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/jsx-one-expression-per-line */
2 | /* eslint-disable prettier/prettier */
3 | /* eslint-disable camelcase */
4 | /* eslint-disable react/jsx-wrap-multilines */
5 | import { UnorderedListOutlined, UserOutlined } from "@ant-design/icons";
6 | import { Link } from "react-router-dom";
7 |
8 | function UserProfileButtonMenu() {
9 | return (
10 |
13 |
16 | تنظیمات ربات
17 |
18 |
21 | اساتید
22 |
23 |
24 | );
25 | }
26 |
27 | export default UserProfileButtonMenu;
28 |
--------------------------------------------------------------------------------
/src/framework/api/product/get.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable implicit-arrow-linebreak */
2 | import { TypeListProducts } from "@framework/types";
3 | import { useQuery } from "@tanstack/react-query";
4 | import qs from "query-string";
5 |
6 | import Api from "../utils/api-config";
7 |
8 | interface Props {
9 | name?: string;
10 | sortBy?: "Product_Name" | "Updated_At" | "Price";
11 | order?: "asc" | "desc";
12 | limit?: number;
13 | page?: number;
14 | categoryId?: number;
15 | }
16 | const fetch = async ({ queryKey }: any) => {
17 | const [_key, categoryId, limit, name, order, page, sortBy] = queryKey;
18 | const { data } = await Api.get(
19 | `/products?${qs.stringify({
20 | categoryId,
21 | limit,
22 | name,
23 | order,
24 | page,
25 | sortBy
26 | })}`
27 | );
28 | return data as TypeListProducts;
29 | };
30 |
31 | export const useGetProducts = ({
32 | categoryId,
33 | limit = 10,
34 | name,
35 | order,
36 | page = 1,
37 | sortBy = "Price"
38 | }: Props) =>
39 | useQuery(
40 | ["products", categoryId, limit, name, order, page, sortBy],
41 | fetch
42 | );
43 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": true,
8 | "esModuleInterop": false,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx",
18 | // define your module paths here:
19 | "paths": {
20 | "@components/*": ["./src/components/*"],
21 | "@image/*": ["./src/assets/images/*"],
22 | "@style/*": ["./src/assets/styles/*"],
23 | "@pages/*": ["./src/pages/*"],
24 | "@router/*": ["./src/router/"],
25 | "@hooks/*": ["./src/hooks/*"],
26 | "@helpers/*": ["./src/helpers/*"],
27 | "@containers/*": ["./src/containers/*"],
28 | "@framework/*": ["./src/framework/*"],
29 | "@layout/*": ["./src/layout/*"]
30 | },
31 | "baseUrl": "."
32 | },
33 | "include": ["src"],
34 | "references": [{ "path": "./tsconfig.node.json" }]
35 | }
36 |
--------------------------------------------------------------------------------
/src/pages/admin/product/list.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-nested-ternary */
2 | /* eslint-disable object-curly-newline */
3 | import Container from "@components/container";
4 | import ProductLists from "@components/product/list";
5 | import { useNavigate } from "react-router";
6 |
7 | function ProductList() {
8 | const navigate = useNavigate();
9 | // const { data, error, refetch, isLoading, isFetching } = useGetProducts({});
10 | // useEffect(() => {
11 | // refetch();
12 | // }, []);
13 |
14 | return (
15 | navigate("/admin/products/add")}>
21 | {/* }> */}
22 | {/* {isLoading || isFetching ? (
23 |
24 | ) : error ? (
25 | <>مشکلی رخ داده>
26 | ) : data?.products.length === 0 ? (
27 |
28 | ) : (
29 |
30 | )} */}
31 |
32 |
33 | {/* */}
34 |
35 | );
36 | }
37 |
38 | export default ProductList;
39 |
--------------------------------------------------------------------------------
/src/containers/product-news.tsx:
--------------------------------------------------------------------------------
1 | import Card from "@components/product/card";
2 | import ProductCardSkeleton from "@components/skeleton/product-card";
3 | import { useGetProducts } from "@framework/api/product/get";
4 | import { Divider } from "antd";
5 |
6 | function ProductNews() {
7 | const { data, isLoading, isFetching } = useGetProducts({
8 | limit: 6,
9 | sortBy: "Updated_At"
10 | });
11 | return (
12 |
13 |
محصولات جدید ما
14 |
15 |
16 | {isLoading || isFetching ? (
17 | <>
18 | {[...Array(8)].map((_, idx) => (
19 |
20 | ))}
21 | >
22 | ) : (
23 | data?.products.map((item) => (
24 |
33 | ))
34 | )}
35 |
36 |
37 | );
38 | }
39 |
40 | export default ProductNews;
41 |
--------------------------------------------------------------------------------
/src/containers/order/single.tsx:
--------------------------------------------------------------------------------
1 | import Container from "@components/container";
2 | import { useGetOrderById } from "@framework/api/orders/getById";
3 | // eslint-disable-next-line object-curly-newline
4 | import { Tabs } from "antd";
5 | import { useParams } from "react-router";
6 |
7 | import CustomerDetail from "./components/customer-detail";
8 | import OrderList from "./components/order-list";
9 | import OrderSetting from "./components/order-setting";
10 |
11 | interface Props {
12 | type: "admin" | "user";
13 | }
14 |
15 | function OrdersSingle({ type }: Props) {
16 | const { order_id } = useParams();
17 | const { data, isLoading } = useGetOrderById({ order_Id: order_id });
18 | const order = data?.order;
19 | const items = [
20 | {
21 | label: "لیست سفارشات",
22 | key: "1",
23 | children:
24 | },
25 | {
26 | label: "اطلاعات تکمیلی ",
27 | key: "3",
28 | children:
29 | },
30 | {
31 | label: "تنظبمات سفارش",
32 | key: "2",
33 | children:
34 | }
35 | ];
36 | return (
37 |
38 |
39 |
40 | );
41 | }
42 |
43 | export default OrdersSingle;
44 |
--------------------------------------------------------------------------------
/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/user/profile/components/button-menu.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/jsx-one-expression-per-line */
2 | /* eslint-disable prettier/prettier */
3 | /* eslint-disable camelcase */
4 | /* eslint-disable react/jsx-wrap-multilines */
5 | import "./style.scss";
6 |
7 | import {
8 | IdcardOutlined,
9 | UnorderedListOutlined,
10 | UserOutlined
11 | } from "@ant-design/icons";
12 | import { NavLink } from "react-router-dom";
13 |
14 | function UserProfileButtonMenu() {
15 | return (
16 |
17 |
20 | حساب کاربری
21 |
22 |
25 | سفارشات
26 |
27 | {/*
28 |
29 |
*/}
30 |
33 | آدرس ها
34 |
35 |
36 | );
37 | }
38 |
39 | export default UserProfileButtonMenu;
40 |
--------------------------------------------------------------------------------
/src/components/skeleton/user-single-product.tsx:
--------------------------------------------------------------------------------
1 | import { Divider } from "antd";
2 |
3 | function UserSingleProductSkeleton() {
4 | return (
5 |
6 |
7 |
8 |
12 |
13 |
21 |
22 | );
23 | }
24 |
25 | export default UserSingleProductSkeleton;
26 |
--------------------------------------------------------------------------------
/.drone.yml:
--------------------------------------------------------------------------------
1 | kind: pipeline
2 | name: telegram-front
3 |
4 | trigger:
5 | branch:
6 | - main
7 | event:
8 | - push
9 | steps:
10 | - name: install
11 | image: node:14
12 | commands:
13 | - npm install
14 | - name: create-env-file
15 | image: alpine:3
16 | environment:
17 | VITE_API_URL:
18 | from_secret: VITE_API_URL
19 | VITE_API_VERSION:
20 | from_secret: VITE_API_VERSION
21 | VITE_SHOP_NAME:
22 | from_secret: VITE_SHOP_NAME
23 | VITE_DEV_MODE:
24 | from_secret: VITE_DEV_MODE
25 | commands:
26 | - echo "VITE_API_URL=$VITE_API_URL" > .env
27 | - echo "VITE_API_VERSION=$VITE_API_VERSION" >> .env
28 | - echo "VITE_SHOP_NAME=$VITE_SHOP_NAME" >> .env
29 | - echo "VITE_DEV_MODE=$VITE_DEV_MODE" >> .env
30 | - name: build
31 | image: node:14
32 | commands:
33 | - npm run build
34 |
35 | - name: deploy
36 | image: ubuntu:20.04
37 | environment:
38 | SSH_KEY:
39 | from_secret: SSH_KEY
40 | SSH_USER:
41 | from_secret: SSH_USER
42 | SSH_HOST:
43 | from_secret: SSH_HOST
44 | SSH_PATH:
45 | from_secret: SSH_PATH
46 | SSH_PASS:
47 | from_secret: SSH_PASS
48 | commands:
49 | - apt-get update && apt-get install -y openssh-client sshpass
50 | - mkdir -p ~/.ssh
51 | - echo "$SSH_KEY" > ~/.ssh/id_rsa
52 | - chmod 600 ~/.ssh/id_rsa
53 | - ssh-keyscan $SSH_HOST >> ~/.ssh/known_hosts
54 | - ls
55 | - sshpass -p $SSH_PASS scp -r dist/* $SSH_USER@$SSH_HOST:$SSH_PATH
56 |
--------------------------------------------------------------------------------
/src/components/skeleton/products.tsx:
--------------------------------------------------------------------------------
1 | function ProductsSkeletonItem({ itemIndex }: { itemIndex: number }) {
2 | return (
3 |
19 | );
20 | }
21 | function ProductsSkeleton() {
22 | return (
23 |
24 | {[...Array(10)].map((_, idx) => (
25 |
26 | ))}
27 |
28 | );
29 | }
30 |
31 | export default ProductsSkeleton;
32 |
--------------------------------------------------------------------------------
/src/layouts/main.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable camelcase */
2 | /* eslint-disable indent */
3 | import { useThemeParams } from "@vkruglikov/react-telegram-web-app";
4 | import { ConfigProvider, theme } from "antd";
5 | import fa_IR from "antd/lib/locale/fa_IR";
6 | import React from "react";
7 |
8 | interface Props {
9 | children: React.ReactNode;
10 | }
11 |
12 | function Main({ children }: Props) {
13 | const [colorScheme, themeParams] = useThemeParams();
14 | // const { id } = useTelegramUser();
15 | // const { data } = useGetUserInfo({ user_Id: id });
16 | const customizeRenderEmpty = () => (
17 |
18 |
اطلاعاتی موجود نیست
19 |
20 | );
21 | return (
22 |
23 |
24 |
43 | {/*
44 |
45 | */}
46 | {children}
47 |
48 |
49 |
50 | );
51 | }
52 |
53 | export default Main;
54 |
--------------------------------------------------------------------------------
/src/components/container/index.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "antd";
2 | import React from "react";
3 | import { useNavigate } from "react-router";
4 |
5 | interface Props {
6 | title: string;
7 | children: React.ReactNode;
8 | backwardUrl?: string | undefined | number;
9 | customButton?: boolean;
10 | customButtonTitle?: string;
11 | customButtonOnClick?: React.MouseEventHandler;
12 | titleType?: "small" | "default";
13 | }
14 |
15 | function Container({
16 | title,
17 | children,
18 | backwardUrl,
19 | customButton,
20 | customButtonTitle,
21 | customButtonOnClick,
22 | titleType
23 | }: Props) {
24 | const navigate = useNavigate();
25 |
26 | // eslint-disable-next-line operator-linebreak
27 | const headerStyle =
28 | titleType === "default"
29 | ? "sticky top-1 z-30 flex items-center justify-between gap-1 rounded-lg bg-[var(--tg-theme-secondary-bg-color)] p-3"
30 | : " z-20 text-sm text-left -mb-2 border-b-[1px] pb-1 ";
31 |
32 | return (
33 |
34 |
35 |
{title}
36 |
37 | {customButton && (
38 |
41 | )}
42 | {backwardUrl && (
43 |
46 | )}
47 |
48 |
{children}
49 |
50 | );
51 | }
52 | Container.defaultProps = {
53 | backwardUrl: undefined,
54 | customButton: false,
55 | customButtonTitle: "",
56 | // eslint-disable-next-line @typescript-eslint/no-empty-function
57 | customButtonOnClick: () => {},
58 | titleType: "default"
59 | };
60 | export default Container;
61 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | /** @type {import("eslint").Linter.BaseConfig} */
2 |
3 | module.exports = {
4 | env: {
5 | browser: true,
6 | es2021: true
7 | },
8 | extends: [
9 | "eslint:recommended",
10 | "plugin:react/recommended",
11 | "plugin:prettier/recommended",
12 | "plugin:@typescript-eslint/recommended",
13 | "airbnb",
14 | "airbnb/hooks"
15 | ],
16 | plugins: ["react", "@typescript-eslint", "simple-import-sort"],
17 | overrides: [],
18 | parser: "@typescript-eslint/parser",
19 | parserOptions: {
20 | ecmaVersion: "latest",
21 | sourceType: "module"
22 | },
23 | rules: {
24 | "no-unused-vars": "off",
25 | "@typescript-eslint/no-unused-vars": "warn",
26 | "import/no-extraneous-dependencies": "off",
27 | "import/prefer-default-export": "off",
28 | "react/jsx-props-no-spreading": "off",
29 | "linebreak-style": ["error", "unix"],
30 | quotes: ["warn", "double"],
31 | "no-console": "off",
32 | "react/jsx-closing-bracket-location": "off",
33 | "react/jsx-uses-react": "off",
34 | "react/react-in-jsx-scope": "off",
35 | "simple-import-sort/imports": "error",
36 | "simple-import-sort/exports": "error",
37 | "comma-dangle": "off",
38 | "import/no-unresolved": "off",
39 | "prettier/prettier": [
40 | "error",
41 | {},
42 | {
43 | usePrettierrc: true
44 | }
45 | ],
46 | "react/jsx-filename-extension": [
47 | "error",
48 | {
49 | extensions: [".ts", ".tsx"]
50 | }
51 | ],
52 | "import/extensions": [
53 | "error",
54 | "ignorePackages",
55 | {
56 | js: "never",
57 | jsx: "never",
58 | ts: "never",
59 | tsx: "never"
60 | }
61 | ]
62 | },
63 | settings: {
64 | "import/resolver": {
65 | node: {
66 | extensions: [".js", ".jsx", ".ts", ".tsx"]
67 | }
68 | }
69 | }
70 | };
71 |
--------------------------------------------------------------------------------
/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable prettier/prettier */
2 | // Admin pages
3 |
4 | export { default as CategoriesAdd } from "./admin/categories/add";
5 | export { default as CategoriesEdit } from "./admin/categories/edit";
6 | export { default as Categories } from "./admin/categories/list";
7 | export { default as AdminHome } from "./admin/index";
8 | export { default as AdminOrders } from "./admin/orders/list";
9 | export { default as AdminOrdersSingle } from "./admin/orders/single";
10 | export { default as ProductAdd } from "./admin/product/add";
11 | export { default as ProductEdit } from "./admin/product/edit";
12 | export { default as AdminProductList } from "./admin/product/list";
13 | export { default as AdminAddSlider } from "./admin/slider/add";
14 | export { default as AdminSlider } from "./admin/slider/list";
15 | // Bot pages
16 | export { default as BotPanel } from "./bot/index";
17 | export { default as BotAddMasters } from "./bot/masters/add";
18 | export { default as BotEditMasters } from "./bot/masters/edit";
19 | export { default as BotMasters } from "./bot/masters/list";
20 | export { default as BotSetting } from "./bot/setting";
21 | export { default as HomePage } from "./home";
22 | // User pages
23 | export { default as UserCart } from "./user/cart/index";
24 | export { default as Checkout } from "./user/checkout/index";
25 | export { default as ProductList } from "./user/product/list";
26 | export { default as ProductSingle } from "./user/product/single";
27 | export { default as UserProfileAddAddresses } from "./user/profile/addresses/add";
28 | export { default as UserProfileEditAddresses } from "./user/profile/addresses/edit";
29 | export { default as UserProfileAddresses } from "./user/profile/addresses/list";
30 | export { default as UserProfileEdit } from "./user/profile/edit";
31 | export { default as UserProfileHome } from "./user/profile/home";
32 | export { default as UserProfile } from "./user/profile/index";
33 | export { default as UserProfileOrder } from "./user/profile/order/list";
34 | export { default as UserProfileOrderSingle } from "./user/profile/order/single";
35 |
--------------------------------------------------------------------------------
/src/assets/styles/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 | :root {
5 | --tg-theme-bg-color: #fff;
6 | --tg-theme-text-color: #0a0a0a;
7 | --tg-theme-hint-color: #929292;
8 | --tg-theme-link-color: #207ae4;
9 | --tg-theme-button-color: #5bc8fb;
10 | --tg-theme-button-text-color: #fffeec;
11 | --tg-theme-secondary-bg-color: #f3f2f9;
12 |
13 | --default-font: -apple-system, "Vazirmatn";
14 | }
15 |
16 | @font-face {
17 | font-family: "iransans";
18 | src: url("/fonts/IRANSansWeb(FaNum).ttf"),
19 | url("/fonts/IRANSansWeb(FaNum).woff") format("woff"),
20 | url("/fonts/IRANSansWeb(FaNum).woff2") format("woff2"),
21 | url("/fonts/IRANSansWeb(FaNum).eot") format("eot");
22 | }
23 |
24 | /* button,
25 | [type="button"],
26 | [type="reset"],
27 | [type="submit"] {
28 | background-color: var(--tg-theme-button-color);
29 | } */
30 |
31 | body {
32 | margin: 0;
33 | padding: 20px 20px;
34 |
35 | font-family: "iransans";
36 |
37 | background: var(--tg-theme-secondary-bg-color);
38 |
39 | -webkit-font-smoothing: antialiased;
40 | -moz-osx-font-smoothing: grayscale;
41 | }
42 |
43 | code,
44 | label,
45 | span,
46 | div,
47 | label {
48 | font-family: "iransans";
49 | }
50 |
51 | .App-logo {
52 | pointer-events: none;
53 | height: 40vmin;
54 | }
55 |
56 | @media (prefers-reduced-motion: no-preference) {
57 | .App-logo {
58 | animation: App-logo-spin infinite 20s linear;
59 | }
60 | }
61 |
62 | .App-header {
63 | display: flex;
64 | flex-direction: column;
65 | align-items: center;
66 | justify-content: center;
67 |
68 | font-size: calc(10px + 2vmin);
69 | color: white;
70 | }
71 |
72 | .contentWrapper {
73 | box-sizing: border-box;
74 | margin: 5px 0;
75 | padding: 20px;
76 |
77 | color: var(--tg-theme-text-color) !important;
78 |
79 | background: var(--tg-theme-bg-color);
80 | border-radius: 10px;
81 | }
82 |
83 | .ant-input,
84 | .ant-select,
85 | .ant-select-item {
86 | background-color: unset !important;
87 | }
88 |
89 | @keyframes App-logo-spin {
90 | from {
91 | transform: rotate(0deg);
92 | }
93 | to {
94 | transform: rotate(360deg);
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/containers/order/components/order-list.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/jsx-one-expression-per-line */
2 | /* eslint-disable react/jsx-wrap-multilines */
3 | /* eslint-disable prettier/prettier */
4 | // eslint-disable-next-line object-curly-newline
5 | import { Order } from "@framework/types";
6 | import { addCommas } from "@persian-tools/persian-tools";
7 | import { Divider, List } from "antd";
8 | import { Link } from "react-router-dom";
9 |
10 | interface Props {
11 | loading: boolean;
12 | orders: Order | undefined;
13 | }
14 |
15 | function OrderList({ loading, orders }: Props) {
16 | return (
17 | <>
18 | (
24 |
25 |
28 | نام محصول :
29 |
30 | {item.product_Name}
31 |
32 | }
33 | />
34 |
35 |
36 |
37 | تومان
38 | {addCommas(item.final_Price)}
39 | قیمت واحد :
40 |
41 |
42 | عدد
43 | {item.quantity}
44 | تعداد :
45 |
46 |
47 |
48 | تومان
49 | {addCommas(item.final_Price * item.quantity)}
50 | قیمت کل :
51 |
52 |
53 |
54 | )}
55 | />
56 | مجموع قیمت
57 |
58 | {addCommas(orders?.total_Price || 0)} تومان
59 |
60 | >
61 | );
62 | }
63 |
64 | export default OrderList;
65 |
--------------------------------------------------------------------------------
/src/containers/order/components/customer-detail.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable operator-linebreak */
2 | /* eslint-disable react/jsx-wrap-multilines */
3 | /* eslint-disable prettier/prettier */
4 | /* eslint-disable jsx-a11y/img-redundant-alt */
5 | import { Order } from "@framework/types";
6 | import { List } from "antd";
7 | import moment from "jalali-moment";
8 |
9 | interface Props {
10 | orders: Order | undefined;
11 | }
12 | function CustomerDetail({ orders }: Props) {
13 | return (
14 |
15 |
16 |
20 |
28 |
29 | }
30 | />
31 |
32 |
33 |
37 |
38 |
39 |
43 |
44 |
45 | {orders?.tracking_Code || "ثبت نشده"}}
48 | />
49 |
50 |
51 |
55 | شمسی : {" "}
56 | {moment(orders?.order_Date).locale("fa").format("YYYY/MM/DD") ||
57 | ""}
58 |
59 | میلادی : {" "}
60 | {moment(orders?.order_Date).locale("en").format("YYYY/MM/DD") ||
61 | ""}
62 |
63 | }
64 | />
65 |
66 |
67 | );
68 | }
69 |
70 | export default CustomerDetail;
71 |
--------------------------------------------------------------------------------
/src/assets/images/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "starter-template",
3 | "version": "0.0.1",
4 | "type": "module",
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "prev": "vite preview",
9 | "lint:eslint": "eslint . --ext .ts,.tsx",
10 | "fix:eslint": "eslint --fix . --ext .ts,.tsx",
11 | "lint:stylelint": "stylelint **/*.{css,scss,tsx}",
12 | "fix:stylelint": "stylelint --fix ./src/**/*.{css,scss,tsx}"
13 | },
14 | "dependencies": {
15 | "@ant-design/icons": "^5.0.1",
16 | "@persian-tools/persian-tools": "^3.4.1",
17 | "@tanstack/react-query": "^4.29.7",
18 | "@vkruglikov/react-telegram-web-app": "^1.8.0",
19 | "antd": "^5.4.2",
20 | "antd-jalali": "^1.0.3",
21 | "axios": "^1.4.0",
22 | "clsx": "^1.2.1",
23 | "dayjs": "^1.11.8",
24 | "eslint-plugin-simple-import-sort": "^9.0.0",
25 | "formik": "^2.2.9",
26 | "jalali-moment": "^3.3.11",
27 | "jotai": "^2.1.0",
28 | "query-string": "^8.1.0",
29 | "react": "^18.2.0",
30 | "react-dom": "^18.2.0",
31 | "react-images-uploading": "^3.1.7",
32 | "react-router": "^6.10.0",
33 | "react-router-dom": "^6.10.0",
34 | "react-telegram-webapp": "^0.1.5",
35 | "yup": "^1.1.0"
36 | },
37 | "devDependencies": {
38 | "@tanstack/eslint-plugin-query": "^4.29.4",
39 | "@types/eslint": "^8.21.1",
40 | "@types/node": "^18.11.18",
41 | "@types/prettier": "^2.7.2",
42 | "@types/react": "^18.0.26",
43 | "@types/react-dom": "^18.0.9",
44 | "@typescript-eslint/eslint-plugin": "^5.48.2",
45 | "@typescript-eslint/parser": "^5.48.2",
46 | "@vitejs/plugin-react": "^3.0.0",
47 | "autoprefixer": "^10.4.13",
48 | "eslint": "^8.32.0",
49 | "eslint-config-airbnb": "^19.0.4",
50 | "eslint-config-prettier": "^8.6.0",
51 | "eslint-plugin-import": "^2.27.5",
52 | "eslint-plugin-jsx-a11y": "^6.7.1",
53 | "eslint-plugin-prettier": "^4.2.1",
54 | "eslint-plugin-react": "^7.32.1",
55 | "eslint-plugin-react-hooks": "^4.6.0",
56 | "postcss": "^8.4.21",
57 | "prettier": "^2.8.3",
58 | "prettier-plugin-tailwindcss": "^0.2.1",
59 | "sass": "^1.57.1",
60 | "stylelint": "^14.16.1",
61 | "stylelint-config-clean-order": "^2.3.1",
62 | "stylelint-config-prettier": "^9.0.4",
63 | "stylelint-config-standard": "^29.0.0",
64 | "stylelint-config-standard-scss": "^6.1.0",
65 | "stylelint-order": "^6.0.1",
66 | "tailwindcss": "^3.2.4",
67 | "terser": "^5.16.1",
68 | "typescript": "^4.9.3",
69 | "vite": "^4.0.0",
70 | "vite-tsconfig-paths": "^4.0.3"
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/containers/order/components/order-setting.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable camelcase */
2 | import useUpdateOrder from "@framework/api/orders/update";
3 | import { Order } from "@framework/types";
4 | import useTelegramUser from "@hooks/useTelegramUser";
5 | import { Button, Divider, Input, message, Radio } from "antd";
6 | import { useState } from "react";
7 |
8 | interface Props {
9 | orders: Order | undefined;
10 | }
11 |
12 | function OrderSetting({ orders }: Props) {
13 | const mutation = useUpdateOrder({ order_id: orders?.order_Id });
14 | const [status, setStatus] = useState(orders?.order_Status);
15 | const [tracking_Code, setTracking_Code] = useState(orders?.tracking_Code);
16 | const { id } = useTelegramUser();
17 | const onChange = (e) => {
18 | setStatus(e.target.value);
19 | };
20 | const handleSubmitStatus = () => {
21 | mutation.mutate(
22 | {
23 | order_Status: status,
24 | tracking_Code: tracking_Code || "",
25 | user_Id: id.toString()
26 | },
27 | {
28 | onSuccess: () => {
29 | message.success("وضعیت تغییر یافت");
30 | },
31 | onError: () => {
32 | message.error("مشکلی رخ داده");
33 | }
34 | }
35 | );
36 | };
37 | return (
38 |
39 |
تعیین وضعیت سفارش
40 |
41 | در انتظار تایید
42 | درحال انجام
43 | درحال بسته بندی
44 | لغو توسط مشتری
45 |
46 | اتمام موجودی 1 یا چند کالا
47 |
48 | لغو توسط ادمین
49 | تحویل داده شده
50 |
51 |
52 |
53 |
کد رهگیری :
54 |
{
56 | setTracking_Code(e.target.value);
57 | }}
58 | value={tracking_Code}
59 | />
60 |
61 |
70 |
71 | );
72 | }
73 |
74 | export default OrderSetting;
75 |
--------------------------------------------------------------------------------
/src/pages/user/profile/home.tsx:
--------------------------------------------------------------------------------
1 | import Container from "@components/container";
2 | import useGetUserInfo from "@framework/api/user-information/get";
3 | import useTelegramUser from "@hooks/useTelegramUser";
4 | import { Alert, List } from "antd";
5 | import { useEffect } from "react";
6 | import { useLocation, useNavigate } from "react-router";
7 | import { Link } from "react-router-dom";
8 |
9 | function StatusBox({ title, total }: { title: string; total: number }) {
10 | return (
11 |
12 |
{total}
13 |
{title}
14 |
15 | );
16 | }
17 |
18 | function UserProfileHome() {
19 | const navigate = useNavigate();
20 | const location = useLocation();
21 | const { id } = useTelegramUser();
22 | const { data, isFetching, isLoading, refetch } = useGetUserInfo({
23 | user_Id: id
24 | });
25 | useEffect(() => {
26 | refetch();
27 | }, [location, refetch]);
28 | const isCompleteProfile =
29 | !data?.phone_Number || !data?.name || !data?.last_Name;
30 | return (
31 | navigate("edit")}
36 | backwardUrl="/">
37 | {/*
38 | {BoxItem.map((item) => (
39 |
40 | ))}
41 |
*/}
42 |
43 | {isCompleteProfile && (
44 |
48 | لطفا حساب کاربری خود را قبل از ثبت سفارش تکمیل کنید
49 |
52 | تکمیل
53 |
54 |
55 | }
56 | />
57 | )}
58 |
59 |
60 |
66 |
67 |
68 |
72 |
73 |
74 |
75 | );
76 | }
77 |
78 | export default UserProfileHome;
79 |
--------------------------------------------------------------------------------
/src/containers/boxes.tsx:
--------------------------------------------------------------------------------
1 | import { QueryCache } from "@tanstack/react-query";
2 | import { Divider } from "antd";
3 | import { Link } from "react-router-dom";
4 |
5 | function Boxes() {
6 | // eslint-disable-next-line prettier/prettier
7 | const queryCache = new QueryCache();
8 |
9 | const query = queryCache.findAll(["user-info"]);
10 |
11 | console.log(query);
12 | const itemClass =
13 | "w-full h-16 border-2 flex gap-3 border-[var(--tg-theme-button-color)] border-opacity-80 items-center justify-center rounded-lg ";
14 | return (
15 |
16 |
منو
17 |
18 |
19 | محصولات
20 |
21 |
34 |
35 |
36 |
37 |
38 | سفارشات من
39 |
40 |
50 |
51 |
52 |
53 |
54 | );
55 | }
56 |
57 | export default Boxes;
58 |
--------------------------------------------------------------------------------
/src/components/product/card.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/jsx-one-expression-per-line */
2 | /* eslint-disable object-curly-newline */
3 | import { addCommas } from "@persian-tools/persian-tools";
4 | import { Button, Divider } from "antd";
5 | import { Link } from "react-router-dom";
6 |
7 | interface Props {
8 | url: string;
9 | title: string;
10 | price: number;
11 | quantity: number;
12 | imageURL: string | [];
13 | discountedPrice: number;
14 | }
15 | function Card({
16 | url,
17 | title,
18 | price,
19 | quantity,
20 | imageURL,
21 | discountedPrice
22 | }: Props) {
23 | const finalPrice =
24 | discountedPrice !== price && 100 - (discountedPrice * 100) / price;
25 | return (
26 |
31 |
36 | {finalPrice && (
37 |
38 | {finalPrice} %
39 |
40 | )}
41 |
42 |
43 |
44 | {title}
45 |
46 |
47 | {/*
48 |
49 | ⭐4.3
50 |
51 |
غذا
52 |
*/}
53 |
54 |
58 | تومان {addCommas(price)}
59 |
60 | {finalPrice && (
61 |
62 | تومان {addCommas(discountedPrice)}
63 |
64 | )}
65 | {/*
تعداد :{quantity} عدد
*/}
66 |
67 |
70 |
71 |
72 | );
73 | }
74 |
75 | export default Card;
76 |
--------------------------------------------------------------------------------
/src/pages/bot/masters/list.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable camelcase */
2 | /* eslint-disable no-nested-ternary */
3 | /* eslint-disable object-curly-newline */
4 | import { DeleteOutlined, EditOutlined } from "@ant-design/icons";
5 | import Container from "@components/container";
6 | import useDeleteMaster from "@framework/api/master/delete";
7 | import { useGetMasters } from "@framework/api/master/get";
8 | import useTelegramUser from "@hooks/useTelegramUser";
9 | import { Button, Popconfirm, Space, Table, message } from "antd";
10 | import { useEffect } from "react";
11 | import { useLocation, useNavigate } from "react-router";
12 | import { Link } from "react-router-dom";
13 |
14 | function BotMastersList() {
15 | const navigate = useNavigate();
16 | const { data, isFetching, isLoading, refetch } = useGetMasters();
17 | const deleteMutation = useDeleteMaster();
18 | const location = useLocation();
19 | const { id: user_id } = useTelegramUser();
20 | const handleDeleteMaster = (id: string) => {
21 | deleteMutation.mutate(
22 | {
23 | master_id: id,
24 | user_id
25 | },
26 | {
27 | onSuccess: () => {
28 | message.success("کاربر حذف شد");
29 | refetch();
30 | },
31 | onError: () => {
32 | message.error(" مشکل برای حذف رخ داد ");
33 | refetch();
34 | }
35 | }
36 | );
37 | };
38 |
39 | const dataSource = data?.map((item) => ({
40 | ...item,
41 | title: `${item.name} ${item.last_Name} `,
42 | key: item.id
43 | }));
44 | useEffect(() => {
45 | refetch();
46 | }, [location, refetch]);
47 |
48 | const columns = [
49 | {
50 | title: "نام",
51 | dataIndex: "title",
52 | key: "name"
53 | },
54 | {
55 | title: "عملیات",
56 | dataIndex: "عملیات",
57 | key: "عملیات",
58 | render: (_, record) => (
59 |
60 |
61 |
62 |
63 | handleDeleteMaster(record.id)}
67 | okText="حذف"
68 | okType="default"
69 | cancelText="انصراف">
70 |
73 |
74 |
75 | )
76 | }
77 | ];
78 | return (
79 | navigate("add")}>
85 |
90 |
91 | );
92 | }
93 |
94 | export default BotMastersList;
95 |
--------------------------------------------------------------------------------
/src/components/product/item.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable operator-linebreak */
2 | /* eslint-disable jsx-a11y/click-events-have-key-events */
3 | /* eslint-disable react/jsx-one-expression-per-line */
4 | import { addCommas } from "@persian-tools/persian-tools";
5 | import { useNavigate } from "react-router";
6 |
7 | interface Props {
8 | url: string;
9 | title: string;
10 | price: number;
11 | quantity: number;
12 | imageURL: string | Array;
13 | pageType: "admin" | "user";
14 | discountedPrice: number;
15 | }
16 |
17 | function ProductItem({
18 | url,
19 | title,
20 | price,
21 | quantity,
22 | imageURL,
23 | pageType,
24 | discountedPrice
25 | }: Props) {
26 | const navigate = useNavigate();
27 |
28 | const handleClick = () => {
29 | navigate(url);
30 | };
31 | const finalPrice =
32 | discountedPrice !== price && 100 - (discountedPrice * 100) / price;
33 | return (
34 | // eslint-disable-next-line jsx-a11y/no-static-element-interactions
35 |
40 |
41 |
42 | {title}
43 |
44 |
45 | {/*
46 |
47 | ⭐4.3
48 |
49 |
غذا
50 |
*/}
51 |
55 | تومان {addCommas(price)}
56 |
57 | {finalPrice && (
58 |
59 | تومان {addCommas(discountedPrice)}
60 |
61 | )}
62 | {pageType === "admin" && (
63 |
تعداد :{quantity} عدد
64 | )}
65 |
66 |
67 |
72 | {finalPrice && (
73 |
74 | {finalPrice} %
75 |
76 | )}
77 |
78 |
79 | );
80 | }
81 |
82 | export default ProductItem;
83 |
--------------------------------------------------------------------------------
/src/pages/admin/slider/list.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react-hooks/rules-of-hooks */
2 | /* eslint-disable camelcase */
3 | /* eslint-disable no-nested-ternary */
4 | /* eslint-disable object-curly-newline */
5 | import { DeleteOutlined } from "@ant-design/icons";
6 | import Container from "@components/container";
7 | import useDeleteSlider from "@framework/api/slider/delete";
8 | import { useGetSliders } from "@framework/api/slider/get";
9 | import useTelegramUser from "@hooks/useTelegramUser";
10 | import { Button, message, Popconfirm, Space, Table } from "antd";
11 | import { useEffect } from "react";
12 | import { useLocation, useNavigate } from "react-router";
13 |
14 | function list() {
15 | const navigate = useNavigate();
16 | const { data, isFetching, isLoading, refetch } = useGetSliders();
17 | const deleteMutation = useDeleteSlider();
18 | const location = useLocation();
19 | const { id: user_id } = useTelegramUser();
20 | const handleDeleteSlide = (id: string) => {
21 | deleteMutation.mutate(
22 | {
23 | master_id: id,
24 | user_id
25 | },
26 | {
27 | onSuccess: () => {
28 | message.success("اسلاید حذف شد");
29 | refetch();
30 | },
31 | onError: () => {
32 | message.error(" مشکل برای حذف رخ داد ");
33 | refetch();
34 | }
35 | }
36 | );
37 | };
38 |
39 | const dataSource = data?.map((item) => ({
40 | ...item,
41 |
42 | key: item.id
43 | }));
44 | useEffect(() => {
45 | refetch();
46 | }, [location, refetch]);
47 |
48 | const columns = [
49 | {
50 | title: "نام",
51 | key: "name",
52 | render: (_, record) => (
53 |
54 |

58 |
59 | )
60 | },
61 | {
62 | title: "عملیات",
63 | key: "عملیات",
64 | render: (_, record) => (
65 |
66 | handleDeleteSlide(record.id)}
70 | okText="حذف"
71 | okType="default"
72 | cancelText="انصراف">
73 |
76 |
77 |
78 | )
79 | }
80 | ];
81 | return (
82 | navigate("add")}>
88 |
93 |
94 | );
95 | }
96 |
97 | export default list;
98 |
--------------------------------------------------------------------------------
/src/containers/order/list-admin.tsx:
--------------------------------------------------------------------------------
1 | import Container from "@components/container";
2 | import { useGetOrders } from "@framework/api/orders/get";
3 | import { GetOrderStatus } from "@helpers/get-order-status";
4 | import { addCommas } from "@persian-tools/persian-tools";
5 | import { Table } from "antd";
6 | import { ColumnsType } from "antd/es/table";
7 | import moment from "jalali-moment";
8 | import { Link } from "react-router-dom";
9 |
10 | interface DataType {
11 | key: string;
12 | code: string;
13 | name: string;
14 | time: string;
15 | status: string;
16 | tracking_code: string;
17 | }
18 |
19 | function OrderListAdmin() {
20 | const { data, isLoading, isFetching } = useGetOrders();
21 | const orders = data?.orders || [];
22 | const dataChangingStructure: DataType[] =
23 | orders.map((item) => ({
24 | key: item.order_Id.toString(),
25 | code: item.order_Id.toString(),
26 | name: item.user_Full_Name,
27 | price: item.total_Price,
28 | status: item.order_Status,
29 | time: item.order_Date,
30 | tracking_code: item.tracking_Code
31 | })) || [];
32 | const columns: ColumnsType = [
33 | {
34 | title: "شماره ",
35 | width: "fit-content",
36 | dataIndex: "code",
37 | key: "code",
38 | render: (text, record) => (
39 |
42 | {text}#
43 |
44 | )
45 | },
46 | {
47 | title: "نام",
48 |
49 | dataIndex: "name",
50 | key: "name",
51 | render: (text, record) => (
52 |
55 | {text}
56 |
57 | )
58 | },
59 | {
60 | title: "مبلغ",
61 |
62 | dataIndex: "price",
63 | key: "price",
64 | render: (text, record) => (
65 |
68 | {addCommas(text || 0)}
69 |
70 | )
71 | },
72 |
73 | {
74 | title: "وضعیت",
75 | dataIndex: "status",
76 | key: "status",
77 | render: (text) => {GetOrderStatus(text)}
78 | },
79 | {
80 | title: "تاریخ ثبت سفارش",
81 | dataIndex: "time",
82 | key: "time",
83 | render: (text) => (
84 | {moment(text).locale("fa").format("YYYY/MM/DD") || ""}
85 | )
86 | }
87 | ];
88 |
89 | return (
90 |
91 |
97 |
98 | );
99 | }
100 |
101 | export default OrderListAdmin;
102 |
--------------------------------------------------------------------------------
/src/pages/user/profile/addresses/add.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable object-curly-newline */
2 | /* eslint-disable prettier/prettier */
3 | /* eslint-disable camelcase */
4 | /* eslint-disable react/jsx-wrap-multilines */
5 | import Container from "@components/container";
6 | import useAddAddress from "@framework/api/address/add";
7 | import { TypeAddAddress } from "@framework/types";
8 | import useTelegramUser from "@hooks/useTelegramUser";
9 | import { Button, Form, Input, message } from "antd";
10 | import { useNavigate } from "react-router";
11 |
12 | function AddAddress() {
13 | const navigate = useNavigate();
14 | const mutation = useAddAddress();
15 | const { id } = useTelegramUser();
16 | const onFinish = ({
17 | city,
18 | country,
19 | state,
20 | street,
21 | user_id,
22 | zipcode
23 | }: TypeAddAddress) => {
24 | mutation.mutate(
25 | {
26 | city,
27 | country,
28 | state,
29 | street,
30 | user_id,
31 | zipcode,
32 | user_Id: `${id}`
33 | },
34 | {
35 | onSuccess: () => {
36 | message.success("آدرس شما با موفقیت ثبت شد");
37 | setTimeout(() => navigate(-1), 1000);
38 | },
39 | onError: () => {
40 | message.error("مشکلی رخ داده است لطفا دوباره تلاش کنید");
41 | }
42 | }
43 | );
44 | };
45 |
46 | const onFinishFailed = (errorInfo: any) => {
47 | console.log("Failed:", errorInfo);
48 | };
49 | return (
50 |
51 |
63 |
64 |
65 |
66 |
70 |
71 |
72 |
76 |
77 |
78 |
82 |
83 |
84 |
88 |
89 |
90 |
91 |
92 |
101 |
102 |
103 |
104 | );
105 | }
106 |
107 | export default AddAddress;
108 |
--------------------------------------------------------------------------------
/src/containers/order/list.tsx:
--------------------------------------------------------------------------------
1 | import Container from "@components/container";
2 | import { useGetOrderByUser } from "@framework/api/orders/get-by-user";
3 | import { GetOrderStatus } from "@helpers/get-order-status";
4 | import useTelegramUser from "@hooks/useTelegramUser";
5 | import { addCommas } from "@persian-tools/persian-tools";
6 | import { Table } from "antd";
7 | import { ColumnsType } from "antd/es/table";
8 | import moment from "jalali-moment";
9 | import { useEffect } from "react";
10 | import { Link, useLocation } from "react-router-dom";
11 |
12 | interface DataType {
13 | key: string;
14 | name: string;
15 | code: string;
16 | time: string;
17 | status: string;
18 | tracking_code: string;
19 | }
20 | interface Props {
21 | type: "profile" | "user";
22 | }
23 |
24 | function OrderList({ type }: Props) {
25 | const { id } = useTelegramUser();
26 | const location = useLocation();
27 | const { data, isLoading, isFetching, refetch } = useGetOrderByUser({
28 | user_id: id
29 | });
30 | useEffect(() => {
31 | refetch();
32 | }, [refetch, location]);
33 | const orders = data?.orders || [];
34 | const dataChangingStructure: DataType[] =
35 | orders.map((item) => ({
36 | key: item.order_Id.toString(),
37 | code: item.order_Id.toString(),
38 | name: item.user_Full_Name,
39 | price: item.total_Price,
40 | status: item.order_Status,
41 | time: item.order_Date,
42 | tracking_code: item.tracking_Code
43 | })) || [];
44 |
45 | const columns: ColumnsType = [
46 | {
47 | title: "شماره ",
48 | width: "fit-content",
49 | dataIndex: "code",
50 | key: "code",
51 | render: (text, record) => (
52 |
55 | {text}#
56 |
57 | )
58 | },
59 | {
60 | title: "نام",
61 |
62 | dataIndex: "name",
63 | key: "name",
64 | render: (text, record) => (
65 |
68 | {text}
69 |
70 | )
71 | },
72 | {
73 | title: "مبلغ",
74 |
75 | dataIndex: "price",
76 | key: "price",
77 | render: (text, record) => (
78 |
81 | {addCommas(text || 0)}
82 |
83 | )
84 | },
85 |
86 | {
87 | title: "وضعیت",
88 | dataIndex: "status",
89 | key: "status",
90 | render: (text) => {GetOrderStatus(text)}
91 | },
92 | {
93 | title: "تاریخ ثبت سفارش",
94 | dataIndex: "time",
95 | key: "time",
96 | render: (text) => (
97 | {moment(text).locale("fa").format("YYYY/MM/DD") || ""}
98 | )
99 | }
100 | ];
101 |
102 | return (
103 |
104 |
110 |
111 | );
112 | }
113 |
114 | export default OrderList;
115 |
--------------------------------------------------------------------------------
/src/pages/admin/categories/add.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable implicit-arrow-linebreak */
2 | /* eslint-disable indent */
3 | /* eslint-disable object-curly-newline */
4 | // eslint-disable-next-line object-curly-newline
5 | import Container from "@components/container";
6 | import useAddCategories from "@framework/api/categories/add";
7 | import useTelegramUser from "@hooks/useTelegramUser";
8 | import { Button, Form, Input, message } from "antd";
9 | import { useNavigate, useParams } from "react-router";
10 |
11 | interface FromProps {
12 | name: string;
13 | description?: string;
14 | }
15 |
16 | function CategoriesAdd() {
17 | const [form] = Form.useForm();
18 | const mutation = useAddCategories();
19 | const { id } = useTelegramUser();
20 | // const { data, refetch, isLoading, isFetching } = useGetCategories();
21 | // const isLoadCategories = isLoading || isFetching;
22 | const { parentId } = useParams();
23 | const navigate = useNavigate();
24 | return (
25 |
26 |
54 |
55 |
56 |
57 | {/*
58 |
59 | */}
60 |
61 | {/*
62 |
75 |
76 | */}
77 | {/*
78 |
79 |
80 | */}
81 |
82 |
93 |
94 |
95 | );
96 | }
97 |
98 | export default CategoriesAdd;
99 |
--------------------------------------------------------------------------------
/src/pages/user/profile/edit.tsx:
--------------------------------------------------------------------------------
1 | import Container from "@components/container";
2 | import useGetUserInfo from "@framework/api/user-information/get";
3 | import useUpdateUser from "@framework/api/user-information/update";
4 | import useTelegramUser from "@hooks/useTelegramUser";
5 | import { phoneNumberValidator } from "@persian-tools/persian-tools";
6 | import { Button, Form, Input, message, Spin } from "antd";
7 | import { useNavigate } from "react-router";
8 |
9 | function EditProfile() {
10 | const [form] = Form.useForm();
11 | const { id } = useTelegramUser();
12 | const { data, isFetching, isLoading } = useGetUserInfo({ user_Id: id });
13 | const mutation = useUpdateUser({ user_id: id });
14 | const navigate = useNavigate();
15 | const dataLoading = isFetching || isLoading;
16 | return (
17 |
18 |
19 | {dataLoading ? (
20 |
21 | ) : (
22 |
56 |
57 |
58 |
62 |
63 |
64 |
65 |
66 |
67 |
71 |
72 |
73 |
84 |
85 |
86 |
87 |
88 |
91 |
92 |
93 | )}
94 |
95 |
96 | );
97 | }
98 |
99 | export default EditProfile;
100 |
--------------------------------------------------------------------------------
/src/pages/user/profile/addresses/list.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react-hooks/exhaustive-deps */
2 | /* eslint-disable prettier/prettier */
3 | /* eslint-disable camelcase */
4 | /* eslint-disable react/jsx-wrap-multilines */
5 |
6 | import Container from "@components/container";
7 | import useDeleteAddress from "@framework/api/address/delete";
8 | import { useGetAddresses } from "@framework/api/address/get";
9 | import useTelegramUser from "@hooks/useTelegramUser";
10 | import { Button, List, message, Popconfirm } from "antd";
11 | import { useEffect } from "react";
12 | import { useLocation, useNavigate } from "react-router";
13 |
14 | function AddessesList() {
15 | const navigate = useNavigate();
16 | const { id } = useTelegramUser();
17 | const { data, isLoading, isFetching, refetch } = useGetAddresses(id);
18 | const deleteMutation = useDeleteAddress();
19 | const location = useLocation();
20 | useEffect(() => {
21 | refetch();
22 | }, [location]);
23 | return (
24 | navigate("add")}
29 | backwardUrl="/">
30 | (
35 |
36 |
41 | // }
42 | title={{item.city}
}
43 | description={
44 |
45 |
46 | {item.state}
47 |
48 |
49 | {item.zipcode}
50 |
51 |
52 | }
53 | />
54 |
55 |
56 |
{
61 | deleteMutation.mutate(
62 | { user_id: id, address_id: item.address_Id },
63 | {
64 | onSuccess: () => {
65 | message.success("آدرس با موفقیت حذف شد");
66 | refetch();
67 | },
68 | onError: (e) => {
69 | console.log(e);
70 | message.error("حذف نشد دوباره تلاش کنید!");
71 | refetch();
72 | }
73 | }
74 | );
75 | }}
76 | okText="حذف"
77 | okType="default"
78 | cancelText="انصراف">
79 |
85 |
86 |
91 |
92 |
93 | )}
94 | />
95 |
96 | );
97 | }
98 |
99 | export default AddessesList;
100 |
--------------------------------------------------------------------------------
/src/router/routes.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable prettier/prettier */
2 | // eslint-disable-next-line object-curly-newline
3 | import NotFoundPage from "@containers/404";
4 | import {
5 | AdminAddSlider,
6 | AdminHome,
7 | AdminOrders,
8 | AdminOrdersSingle,
9 | AdminProductList,
10 | AdminSlider,
11 | BotAddMasters,
12 | BotEditMasters,
13 | BotMasters,
14 | BotPanel,
15 | BotSetting,
16 | Categories,
17 | CategoriesAdd,
18 | CategoriesEdit,
19 | Checkout,
20 | HomePage,
21 | ProductAdd,
22 | ProductEdit,
23 | ProductList,
24 | ProductSingle,
25 | UserCart,
26 | UserProfile,
27 | UserProfileAddAddresses,
28 | UserProfileAddresses,
29 | UserProfileEdit,
30 | UserProfileEditAddresses,
31 | UserProfileHome,
32 | UserProfileOrder,
33 | UserProfileOrderSingle
34 | } from "@pages/index";
35 | import { createBrowserRouter } from "react-router-dom";
36 |
37 | export const routes = createBrowserRouter([
38 | {
39 | path: "/",
40 | element:
41 | },
42 | // admin
43 | { path: "/admin", element: },
44 | // ## products
45 | { path: "/admin/products", element: },
46 | { path: "/admin/products/add", element: },
47 | { path: "/admin/products/:product_id", element: },
48 | // ## categories
49 | { path: "/admin/categories", element: },
50 | { path: "/admin/categories/:parentId", element: },
51 | { path: "/admin/categories/edit/:cat_id", element: },
52 | // ## order
53 | { path: "/admin/orders", element: },
54 | { path: "/admin/orders/:order_id", element: },
55 | // ## slider
56 | { path: "/admin/slider", element: },
57 | { path: "/admin/slider/add", element: },
58 |
59 | // user
60 | {
61 | path: "/products",
62 | element:
63 | },
64 | {
65 | path: "/products/:product_id",
66 | element:
67 | },
68 | {
69 | path: "/categories",
70 | element:
71 | },
72 | {
73 | path: "/cart",
74 | element:
75 | },
76 | {
77 | path: "/checkout",
78 | element:
79 | },
80 |
81 | // ## profile
82 | {
83 | path: "/profile",
84 | element: ,
85 | children: [
86 | {
87 | index: true,
88 | path: "home",
89 | element:
90 | },
91 | {
92 | index: true,
93 | path: "home/edit",
94 | element:
95 | },
96 | {
97 | path: "orders",
98 | element:
99 | },
100 | {
101 | path: "orders/:order_id",
102 | element:
103 | },
104 | {
105 | path: "address",
106 | element:
107 | },
108 | {
109 | path: "address/add",
110 | element:
111 | },
112 | {
113 | path: "address/:address_id",
114 | element:
115 | }
116 | ]
117 | },
118 | {
119 | path: "/bot",
120 | element: ,
121 | children: [
122 | {
123 | path: "",
124 | element:
125 | },
126 | {
127 | path: "masters",
128 | element:
129 | },
130 | {
131 | path: "masters/add",
132 | element:
133 | },
134 | {
135 | path: "masters/:master_id",
136 | element:
137 | }
138 | ]
139 | },
140 |
141 | {
142 | path: "*",
143 | element:
144 | }
145 | ]);
146 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Telegram Web App Shop
2 |
3 | Welcome to the `Telegram Web App Shop` repository! This project is an innovative e-commerce solution built as a Telegram web app using modern web technologies. It integrates with a backend API to deliver a seamless shopping experience.
4 |
5 | ---
6 |
7 | ## 🖥️ Backend Project
8 |
9 | This project relies on a backend API developed by my friend. You can find it here:
10 | [Backend Repository: Ecommerce Project](https://github.com/Ecarin/Ecommerce.Project)
11 |
12 | The backend provides the necessary APIs to handle product management, user authentication, orders, and more.
13 |
14 | ---
15 |
16 | ## 🛠️ Project Overview
17 |
18 | This project is a web-based e-commerce platform featuring:
19 | - **Core Features**:
20 | - Product and category browsing
21 | - User account management
22 | - Cart and order checkout
23 | - Discounts and promotions
24 | - **Architecture**:
25 | - Modular React-based front-end design
26 | - Integration with Telegram WebApp API for seamless user experience
27 | - **Design**:
28 | - Built with **TailwindCSS** for responsive and visually appealing interfaces
29 | - **API Interaction**:
30 | - Connects to backend API endpoints for data operations
31 |
32 | ---
33 |
34 | ## 📂 Project Structure
35 |
36 | ### **Public**
37 | - Contains static assets like fonts, images, and the Telegram WebApp integration script (`telegram.js`).
38 |
39 | ### **Src**
40 | - **Components**: Reusable React components for UI elements, such as product cards and lists.
41 | - **Containers**: Higher-level components organizing page structures, like sliders and admin panels.
42 | - **Framework**: API interaction logic, including endpoints for products, orders, and user data.
43 | - **Pages**: Specific page layouts for different user roles (admin, user) and features (cart, profile).
44 | - **Helpers**: Utility functions to enhance reusability and maintainability.
45 | - **Styles**: SCSS and CSS files for styling components and layouts.
46 |
47 | ---
48 |
49 | ## ⚠️ Known Issues and Limitations
50 |
51 | - **Environment Configuration**:
52 | The `.env` file must be configured to set up Telegram WebApp integration and connect with the backend API.
53 |
54 | - **Development Phase**:
55 | This project is still in development and may have incomplete features or bugs.
56 |
57 | - **Backend Dependency**:
58 | Full functionality requires a running instance of the backend API.
59 |
60 | ---
61 |
62 | ## 🚀 Future Plans
63 |
64 | Planned improvements include:
65 | - **Feature Expansion**: Adding support for analytics and advanced admin controls.
66 | - **Error Handling**: Enhancing error messages and fail-safes for edge cases.
67 | - **Performance Optimization**: Refining front-end and API calls for better responsiveness.
68 |
69 | ---
70 |
71 | ## 📝 How to Use
72 |
73 | Follow these steps to set up and run the project:
74 |
75 | 1. **Clone the repository**:
76 | ```bash
77 | git clone https://github.com/mojtaba1180/telegram-web-app-shop.git
78 | cd telegram-web-app-shop
79 | ```
80 |
81 | 2. **Install dependencies**:
82 | ```bash
83 | pnpm i
84 | ```
85 |
86 | 3. **Configure environment variables**:
87 | Create a `.env` file in the root directory with the following variables:
88 | ```
89 | VITE_APP_BACKEND_URL=
90 | VITE_APP_TELEGRAM_BOT_TOKEN=
91 | ```
92 |
93 | 4. **Run the project**:
94 | Start the development server:
95 | ```bash
96 | pnpm dev
97 | ```
98 |
99 | 5. **Access the application**:
100 | Open the browser at:
101 | ```
102 | http://localhost:3000
103 | ```
104 |
105 | 6. **Integrate with the backend**:
106 | Ensure the backend API is running and accessible at the configured `REACT_APP_BACKEND_URL`.
107 |
108 | ---
109 |
110 | ## 🧰 Tools and Technologies
111 |
112 | - **Frontend Framework**: React
113 | - **Styling**: TailwindCSS, SCSS
114 | - **Build Tool**: Vite
115 | - **API Integration**: Axios
116 |
117 | ---
118 |
119 | ## 📞 Contact
120 |
121 | If you have any questions, suggestions, or feedback about this project, feel free to reach out:
122 |
123 | - **GitHub Profile**: [My GitHub](https://github.com/mojtaba1180)
124 | - **Telegram**: [@mojtaba1180](https://t.me/mojtaba1180)
125 |
126 |
--------------------------------------------------------------------------------
/src/pages/user/profile/addresses/edit.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable object-curly-newline */
2 | /* eslint-disable prettier/prettier */
3 | /* eslint-disable camelcase */
4 | /* eslint-disable react/jsx-wrap-multilines */
5 | import Container from "@components/container";
6 | import { useGetAddresses } from "@framework/api/address/get";
7 | import useUpdateAddress from "@framework/api/address/update";
8 | import { TypeAddAddress } from "@framework/types";
9 | import useTelegramUser from "@hooks/useTelegramUser";
10 | import { Button, Form, Input, message, Spin } from "antd";
11 | import { useEffect } from "react";
12 | import { useLocation, useNavigate, useParams } from "react-router";
13 |
14 | function EditAddress() {
15 | const navigate = useNavigate();
16 | const { address_id } = useParams();
17 | const mutation = useUpdateAddress();
18 | const { id } = useTelegramUser();
19 | const { data, isFetching, refetch, isLoading } = useGetAddresses(
20 | id,
21 | address_id
22 | );
23 | const address = data?.addresses[0];
24 | const location = useLocation();
25 | useEffect(() => {
26 | refetch();
27 | }, [location]);
28 |
29 | const componentDisable = isFetching || isLoading;
30 | const onFinish = ({
31 | city,
32 | country,
33 | state,
34 | street,
35 | user_id,
36 | zipcode
37 | }: TypeAddAddress) => {
38 | mutation.mutate(
39 | {
40 | city,
41 | country,
42 | state,
43 | street,
44 | user_id,
45 | zipcode,
46 | user_Id: `${id}`,
47 | address_Id: address_id
48 | },
49 | {
50 | onSuccess: () => {
51 | message.success("آدرس شما با موفقیت ثبت شد");
52 | navigate(-1);
53 | },
54 | onError: () => {
55 | message.error("مشکلی رخ داده است لطفا دوباره تلاش کنید");
56 | }
57 | }
58 | );
59 | };
60 |
61 | const onFinishFailed = (errorInfo: any) => {
62 | console.log("Failed:", errorInfo);
63 | };
64 | return (
65 |
66 |
67 | {componentDisable ? (
68 |
69 | ) : (
70 |
88 |
89 |
90 |
91 |
95 |
96 |
97 |
101 |
102 |
103 |
107 |
108 |
109 |
113 |
114 |
115 |
116 |
117 |
126 |
127 |
128 | )}
129 |
130 |
131 | );
132 | }
133 |
134 | export default EditAddress;
135 |
--------------------------------------------------------------------------------
/src/pages/admin/categories/list.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable camelcase */
2 | /* eslint-disable object-curly-newline */
3 | import { DeleteOutlined, EditOutlined } from "@ant-design/icons";
4 | // eslint-disable-next-line import/extensions
5 | import Container from "@components/container";
6 | import useDeleteCategories from "@framework/api/categories/delete";
7 | import { useGetCategories } from "@framework/api/categories/get";
8 | import { TypeCategories } from "@framework/types";
9 | import useTelegramUser from "@hooks/useTelegramUser";
10 | import { Button, message, Modal, Space, Table } from "antd";
11 | import { ColumnsType } from "antd/es/table";
12 | import { useEffect } from "react";
13 | import { useNavigate } from "react-router";
14 | import { Link } from "react-router-dom";
15 |
16 | const { confirm } = Modal;
17 | function List() {
18 | const { data, error, isLoading, isFetching, refetch } = useGetCategories({});
19 | const mutationDelete = useDeleteCategories();
20 | const { id } = useTelegramUser();
21 | const navigate = useNavigate();
22 |
23 | const customizeData = () => {
24 | const childHandler = (childItem: any[]) => {
25 | if (childItem.length > 0) {
26 | return childItem.map((item) => {
27 | const c = {
28 | ...item,
29 | key: item.category_Id
30 | };
31 |
32 | if (item.children && item.children.length > 0) {
33 | c.children = childHandler(item.children);
34 | } else {
35 | c.children = null;
36 | }
37 | return c;
38 | });
39 | }
40 | return null;
41 | };
42 | return data?.map((item) => ({
43 | ...item,
44 | key: item.category_Id,
45 | children: childHandler(item.children)
46 | }));
47 | };
48 |
49 | const handleDelete = (cat_id: any) => {
50 | mutationDelete.mutate(
51 | { category_id: cat_id, user_id: id },
52 | {
53 | onSuccess: () => {
54 | message.success("دسته بندی حذف شد");
55 | refetch();
56 | },
57 | onError: (err) => {
58 | if (err.response.status !== 404) {
59 | message.error("حذف با مشکل مواجه شد");
60 | refetch();
61 | } else {
62 | window.location.reload();
63 | }
64 | }
65 | }
66 | );
67 | };
68 | const config = (cat_id) => ({
69 | title: " برای حذف این دسته بندی اطمینان دارید ؟",
70 | content: <>sdd>,
71 | okType: "danger",
72 | cancelText: "انصراف",
73 | okText: "حذف",
74 | onOk: () => handleDelete(cat_id)
75 | });
76 | useEffect(() => {
77 | refetch();
78 | }, []);
79 | const columns: ColumnsType = [
80 | {
81 | title: "نام",
82 | dataIndex: "name",
83 | key: "name",
84 | render: (_, record) => (
85 | {record.category_Name}
86 | )
87 | },
88 | {
89 | title: "عملیات",
90 | key: "action",
91 | render: (_, record) => (
92 |
93 |
96 |
97 |
98 |
107 |
108 | افزودن زیرمجموعه
109 |
110 |
111 | )
112 | }
113 | ];
114 |
115 | return (
116 | navigate("/admin/categories/null")}
121 | title="دسته بندی ها">
122 | {/*
123 |
124 |
125 |
126 |
127 | */}
128 |
129 | {/* */}
130 |
135 | {/* */}
136 | {/* */}
137 |
138 | );
139 | }
140 |
141 | export default List;
142 |
--------------------------------------------------------------------------------
/src/pages/bot/setting/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-expressions */
2 | /* eslint-disable operator-linebreak */
3 | /* eslint-disable react/no-array-index-key */
4 | /* eslint-disable camelcase */
5 |
6 | import { InfoCircleOutlined } from "@ant-design/icons";
7 | import Container from "@components/container";
8 | import { useGetBotSetting } from "@framework/api/bot-setting/get";
9 | import useUpdateBotSetting from "@framework/api/bot-setting/update";
10 | import useTelegramUser from "@hooks/useTelegramUser";
11 | import { Button, Divider, Form, List, message, Popover, Spin } from "antd";
12 | import TextArea from "antd/es/input/TextArea";
13 |
14 | function BotSetting() {
15 | const mutation = useUpdateBotSetting();
16 | const { id } = useTelegramUser();
17 | const { data, isFetching, isLoading, refetch } = useGetBotSetting();
18 | const loading = isFetching || isLoading;
19 | return (
20 |
21 |
22 | {loading ? (
23 |
24 | ) : (
25 | <>
26 |
57 |
58 |
59 |
60 |
66 |
67 |
68 |
69 |
70 |
71 |
81 |
82 |
83 |
84 |
قابل تغییر نمیباشد}
86 | title="Title"
87 | trigger="click">
88 |
89 |
90 | اطلاعات تکمیلی
91 |
92 |
93 |
94 |
95 |
96 |
100 |
101 |
102 |
103 |
107 |
108 |
109 |
113 |
114 |
115 |
119 |
120 |
121 |
122 | >
123 | )}
124 |
125 |
126 | );
127 | }
128 | export default BotSetting;
129 |
--------------------------------------------------------------------------------
/src/components/discount/index.tsx:
--------------------------------------------------------------------------------
1 | import useAddDiscounts from "@framework/api/discount/add";
2 | import useDeleteDiscount from "@framework/api/discount/delete";
3 | import useUpdateDiscount from "@framework/api/discount/update";
4 | import { TypeDiscount } from "@framework/types";
5 | import useTelegramUser from "@hooks/useTelegramUser";
6 | import {
7 | Alert,
8 | Button,
9 | Divider,
10 | Form,
11 | InputNumber,
12 | message,
13 | Popconfirm
14 | } from "antd";
15 | import { DatePicker, useJalaliLocaleListener } from "antd-jalali";
16 | import type { RangePickerProps } from "antd/es/date-picker";
17 | import dayjs from "dayjs";
18 | import moment from "jalali-moment";
19 | import { useState } from "react";
20 |
21 | interface Props {
22 | type: "product" | "category";
23 | id: string;
24 | data: TypeDiscount | null;
25 | }
26 |
27 | function Discount({ type, id, data }: Props) {
28 | const { id: userId } = useTelegramUser();
29 | const mutation = useAddDiscounts();
30 | const updateMutation = useUpdateDiscount({
31 | discount_id: data?.discount_Id || ""
32 | });
33 | const deleteMutation = useDeleteDiscount();
34 | const [checked, setChecked] = useState(false);
35 | const disabledDate: RangePickerProps["disabledDate"] = (current) =>
36 | // Can not select days before today and today
37 | current && current < dayjs().endOf("day");
38 | useJalaliLocaleListener();
39 | // dayjs.calendar("jalali");
40 |
41 | const handleDeleteDiscount = () => {
42 | deleteMutation.mutate(
43 | {
44 | discount_id: data?.discount_Id,
45 | user_id: userId.toString()
46 | },
47 | {
48 | onSuccess: () => {
49 | message.success("تخفیف شما حذف شد ");
50 | window.location.reload();
51 | },
52 | onError: () => {
53 | message.error("حذف تخفیف با مشکل مواجه شد");
54 | }
55 | }
56 | );
57 | };
58 |
59 | return (
60 |
61 |
تخفیفات
62 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | {data && (
122 |
handleDeleteDiscount()}
126 | okText="حذف"
127 | okType="default"
128 | cancelText="انصراف">
129 |
136 |
137 | )}
138 |
148 |
149 |
150 |
151 | );
152 | }
153 |
154 | export default Discount;
155 |
--------------------------------------------------------------------------------
/src/pages/admin/categories/edit.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable camelcase */
2 | /* eslint-disable operator-linebreak */
3 | /* eslint-disable implicit-arrow-linebreak */
4 | /* eslint-disable indent */
5 | /* eslint-disable object-curly-newline */
6 | // eslint-disable-next-line object-curly-newline
7 | import Container from "@components/container";
8 | import Discount from "@components/discount";
9 | import { useGetCategories } from "@framework/api/categories/get";
10 | import useUpdateCategory from "@framework/api/categories/update";
11 | import { TypeCategories } from "@framework/types";
12 | import useTelegramUser from "@hooks/useTelegramUser";
13 | import { Button, Cascader, Form, Input, message, Spin } from "antd";
14 | import { useLocation, useNavigate, useParams } from "react-router";
15 |
16 | interface FromProps {
17 | name: string;
18 | categories?: Array;
19 | }
20 |
21 | function CategoriesEdit() {
22 | const { cat_id } = useParams();
23 | const location = useLocation();
24 | const [form] = Form.useForm();
25 | const mutation = useUpdateCategory({ category_id: cat_id });
26 | const { id } = useTelegramUser();
27 | const { data, isLoading, isFetching } = useGetCategories({});
28 | const {
29 | data: cat_data,
30 | isLoading: catLoading,
31 | isFetching: catFetching
32 | } = useGetCategories({ category_id: cat_id });
33 | const isLoadCategories = isLoading || isFetching || catFetching || catLoading;
34 | const navigate = useNavigate();
35 | const removeChildren = (categories: TypeCategories[]): TypeCategories[] =>
36 | categories?.map((cat) => {
37 | if (cat.children && cat.category_Id === parseInt(cat_id, 10)) {
38 | // If category_Id matches parent_Id, remove the children
39 | // console.log({
40 | // ...cat,
41 | // disabled: true
42 | // });
43 | return {
44 | ...cat,
45 | disabled: true
46 | };
47 | }
48 | if (cat.children && cat.children.length > 0) {
49 | // Recursively remove children for child categories
50 |
51 | return {
52 | ...cat,
53 | children: removeChildren(cat.children)
54 | };
55 | }
56 | return cat;
57 | });
58 | // console.log(location);
59 | return (
60 |
61 |
62 | {isLoadCategories ? (
63 |
64 | ) : (
65 | <>
66 |
104 |
105 |
106 |
107 | {/*
108 |
109 | */}
110 |
111 |
112 |
126 |
127 |
128 | {/*
129 |
130 |
131 | */}
132 |
133 |
143 |
144 |
149 | >
150 | )}
151 |
152 |
153 | );
154 | }
155 |
156 | export default CategoriesEdit;
157 |
--------------------------------------------------------------------------------
/src/framework/types.ts:
--------------------------------------------------------------------------------
1 | // discount
2 | export interface TypePostDiscount {
3 | user_id: string;
4 | discount_type: "percent" | "price";
5 | discount_value: number;
6 | discount_start_date: string;
7 | discount_end_date: string;
8 | product_id: number | null;
9 | category_id: number | null;
10 | }
11 | export interface TypeDiscount {
12 | category_Id: null;
13 | discount_End_Date: string;
14 | discount_Id: number;
15 | discount_Start_Date: string;
16 | discount_Type: "percent" | "price";
17 | discount_Value: number;
18 | product_Id: number;
19 | discountedPrice: number;
20 | }
21 | export interface TypeUpdateDiscount extends TypePostDiscount {
22 | discount_Id: number;
23 | }
24 | export interface TypeUserInfo {
25 | user_Id: string;
26 | name: string;
27 | last_Name: string;
28 | username: string;
29 | email: null;
30 | phone_Number: null;
31 | role: string;
32 | created_At: string;
33 | updated_At: string;
34 | }
35 | export interface TypePostUserInfo {
36 | name: string;
37 | last_name: string;
38 | username: string;
39 | email: null;
40 | phone_number: null;
41 | }
42 | export interface TypeCategories {
43 | category_Id: string | number;
44 | category_Name: string | number;
45 | parent_Id: string | number;
46 | children: Array;
47 | discount: TypeDiscount | null;
48 | }
49 |
50 | export interface TypePostCategories {
51 | user_id: string;
52 | category_name: string;
53 | parent_id: number | string | undefined;
54 | }
55 | export interface TypeDeleteCategories {
56 | user_id: string;
57 | category_id: string;
58 | }
59 | // order lists
60 | export interface OrderItem {
61 | order_Item_Id: number;
62 | order_Id: number;
63 | product_Id: number;
64 | product_Name: string;
65 | tag_Price: number;
66 | final_Price: number;
67 | quantity: number;
68 | }
69 |
70 | export interface Order {
71 | order_Id: number;
72 | user_Id: string;
73 | user_Address_Id: number;
74 | full_Address: string;
75 | shipping_cost: number;
76 | total_Price: number;
77 | order_Description: string;
78 | user_Full_Name: string;
79 | receipt_Photo: string;
80 | order_Status: string;
81 | tracking_Code: string;
82 | order_Date: string;
83 | updated_At: string;
84 | order_Items: OrderItem[];
85 | }
86 |
87 | export interface TypeOrders {
88 | page: number;
89 | limit: number;
90 | totalRows: number;
91 | orders: Order[];
92 | }
93 | // order post request type
94 | export interface TypeOrderPost {
95 | user_Id: string;
96 | user_Address_Id: number;
97 | shipping_Cost: number;
98 | order_Description: string;
99 | receipt_Photo_Path: string;
100 | }
101 |
102 | export interface TypeUpdateOrder {
103 | user_Id: string;
104 | order_Status: string;
105 | tracking_Code: string;
106 | }
107 | // order single page type
108 | export interface TypeSingleOrder {
109 | success: boolean;
110 | message: string;
111 | order: Order;
112 | }
113 |
114 | // Products
115 |
116 | export interface Product {
117 | product_Id: number;
118 | product_Name: string;
119 | price: number;
120 | discountedPrice: number;
121 | quantity: number;
122 | description?: string;
123 | categoryIds: Array;
124 | photo_path: string;
125 | updated_At: string;
126 | discount: TypeDiscount | null;
127 | }
128 | export interface TypeListProducts {
129 | page: number;
130 | limit: number;
131 | totalRows: number;
132 | products: Product[];
133 | }
134 |
135 | // ## Add
136 | export interface TypeProductPost {
137 | user_id: string;
138 | product_name: string;
139 | description: string;
140 | price: number;
141 | quantity: number;
142 | category_ids: number[];
143 | photos: string[];
144 | }
145 |
146 | export interface TypeDeleteProduct {
147 | user_id: string;
148 | product_id: string;
149 | }
150 |
151 | // ## photos
152 | export interface TypeProductPhotos {
153 | photo_base64: string;
154 | }
155 |
156 | // Cart
157 |
158 | export interface TypeAddToCartItems {
159 | product_id: number;
160 | quantity: number;
161 | }
162 | export interface TypeCartItems {
163 | quantity: number;
164 | product_Id: number;
165 | cart_Item_Id: number;
166 | cart_Id: number;
167 | product_Name: string;
168 | price: number;
169 | discountedPrice: number;
170 | }
171 | export interface TypeCarts {
172 | cart_Id: number;
173 | user_Id: string;
174 | totalPrice: number;
175 | cartItems: Array;
176 | }
177 | export interface TypeAddToCart {
178 | user_id: string;
179 | cart_items: TypeAddToCartItems[];
180 | }
181 | export interface TypeClearCart {
182 | user_id: string;
183 | }
184 | export interface TypeDeleteCartItem {
185 | user_id: string;
186 | product_id: string | number;
187 | }
188 |
189 | export interface TypeAddressItems {
190 | address_Id?: number;
191 | user_Id?: string;
192 | country: string;
193 | state: string;
194 | city: string;
195 | street: string;
196 | zipcode: string;
197 | }
198 | export interface TypeAddAddress extends TypeAddressItems {
199 | user_id: string;
200 | }
201 |
202 | export interface TypeAddresses {
203 | addresses: TypeAddressItems[];
204 | }
205 |
206 | export interface TypeDeleteAddressItem {
207 | user_id: string;
208 | address_id: string | number;
209 | }
210 | // Bot Setting
211 | export interface TypeUpdateBotSetting {
212 | user_id: string;
213 | welcome_message: string;
214 | about_us: string;
215 | contact_us: string;
216 | }
217 |
218 | export interface TypeBotSetting {
219 | bot: null | string;
220 | id: number;
221 | owner_id: string;
222 | owner_last_name: string;
223 | owner_phone_number: string;
224 | owner_national_code: string;
225 | shop_name: string;
226 | bot_username: string;
227 | bot_token: string;
228 | active: boolean;
229 | welcome_message: string;
230 | user_profile: string;
231 | user_cart: string;
232 | user_order: string;
233 | admin_panel: string;
234 | about_us: string;
235 | contact_us: string;
236 | bot_settings: string;
237 | special: boolean;
238 | }
239 |
240 | export interface TypePostMaster {
241 | user_Id: string;
242 | name: string;
243 | last_Name: string;
244 | description: string;
245 | photo_Path: string;
246 | }
247 | export interface TypeMasters {
248 | id: string;
249 | name: string;
250 | last_Name: string;
251 | description: string;
252 | photo_Path: string;
253 | }
254 | export interface TypePostSlider {
255 | photo_Path: string;
256 | url: string;
257 | user_Id: string;
258 | }
259 | export interface TypeSlider {
260 | id: string;
261 | photo_Path: string;
262 | url: string;
263 | }
264 |
--------------------------------------------------------------------------------
/src/pages/admin/slider/add.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-expressions */
2 | /* eslint-disable operator-linebreak */
3 | /* eslint-disable react/no-array-index-key */
4 | /* eslint-disable camelcase */
5 | import Container from "@components/container";
6 | import useAddSliderImage from "@framework/api/photos-upload/add-slider";
7 | import useAddSlider from "@framework/api/slider/add";
8 | import useTelegramUser from "@hooks/useTelegramUser";
9 | import { Button, Form, Input, message, Spin } from "antd";
10 | import { useState } from "react";
11 | import ImageUploading from "react-images-uploading";
12 | import { useNavigate } from "react-router";
13 |
14 | const { TextArea } = Input;
15 | function Add() {
16 | const [componentDisabled, setComponentDisabled] = useState(false);
17 | const [priceEnterd, setPriceEnterd] = useState(0);
18 | const mutation = useAddSlider();
19 | const mutationUploadPhotos = useAddSliderImage();
20 | const { id } = useTelegramUser();
21 | const [form] = Form.useForm();
22 | const navigate = useNavigate();
23 | const [imageLinkList, setImageLinkList] = useState>([]);
24 | const [images, setImages] = useState([]);
25 | const onChangeImage = async (imageList) => {
26 | // data for submit
27 | imageList.length &&
28 | (await imageList.map(async (i: { data_url: string }) => {
29 | mutationUploadPhotos.mutate(
30 | { photo_base64: i.data_url.split(",")[1] },
31 | {
32 | onSuccess: (e) => {
33 | setImageLinkList([...imageLinkList, `${e.data}`]);
34 | },
35 | onError: () => {
36 | message.error("افزودن عکس با مشکل مواجه شد");
37 | }
38 | }
39 | );
40 | }));
41 | setImages(imageList);
42 | };
43 | const handleRemoveSingleImage = (idx) => {
44 | const arr = [...imageLinkList];
45 | if (idx !== -1) {
46 | arr.splice(idx, 1);
47 | setImageLinkList(arr);
48 | }
49 | };
50 |
51 | return (
52 |
53 |
85 |
86 |
87 | {/*
88 |
89 | */}
90 |
95 | {mutationUploadPhotos.isLoading ? (
96 |
97 | ) : (
98 |
103 | {({
104 | onImageUpload,
105 | onImageRemoveAll,
106 | onImageRemove,
107 | isDragging,
108 | dragProps
109 | }) => (
110 | // write your building UI
111 |
112 |
113 |
121 |
122 |
131 |
132 |
133 | {imageLinkList?.map((image, index) => (
134 |
135 |

140 |
141 |
151 |
152 |
153 | ))}
154 |
155 |
156 | )}
157 |
158 | )}
159 |
160 |
161 |
171 |
172 |
173 | );
174 | }
175 |
176 | export default Add;
177 |
--------------------------------------------------------------------------------
/src/components/product/list.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react-hooks/exhaustive-deps */
2 | /* eslint-disable react/jsx-no-useless-fragment */
3 | /* eslint-disable object-curly-newline */
4 | /* eslint-disable no-nested-ternary */
5 | import {
6 | FileDoneOutlined,
7 | ReloadOutlined,
8 | SlidersOutlined
9 | } from "@ant-design/icons";
10 | import ProductsSkeleton from "@components/skeleton/products";
11 | import { useGetCategories } from "@framework/api/categories/get";
12 | import { useGetProducts } from "@framework/api/product/get";
13 | import {
14 | Button,
15 | Divider,
16 | Drawer,
17 | Empty,
18 | Input,
19 | Pagination,
20 | Select,
21 | Tree
22 | } from "antd";
23 | import { useState } from "react";
24 |
25 | import ProductItem from "./item";
26 |
27 | interface Props {
28 | pageType: "admin" | "user";
29 | // data: TypeListProducts | undefined;
30 | }
31 | function ProductList({ pageType }: Props) {
32 | const [open, setOpen] = useState(false);
33 |
34 | const [currentPage, setCurrentPage] = useState(1);
35 | const [categoryFilterId, setCategoryFilterId] = useState(
36 | undefined
37 | );
38 | const [search, setSearch] = useState(undefined);
39 | const [Order, setOrder] = useState<"desc" | "asc">("desc");
40 |
41 | const { data, error, refetch, isLoading, isFetching } = useGetProducts({
42 | limit: 10,
43 | page: currentPage,
44 | categoryId: categoryFilterId,
45 | name: search,
46 | order: Order
47 | });
48 | const {
49 | data: catData,
50 | isLoading: isCatLoading,
51 | isFetching: isCatFetching
52 | } = useGetCategories({});
53 | // useEffect(() => {
54 | // refetch();
55 | // }, [refetch, currentPage]);
56 | return (
57 |
58 |
59 |
{
63 | setSearch(e);
64 | refetch();
65 | }}
66 | />
67 |
68 |
69 |
83 |
86 |
87 |
90 |
100 |
110 |
111 | }
112 | title=" فیلتر ها"
113 | placement="bottom"
114 | onClose={() => setOpen(false)}
115 | width="100%"
116 | height="90%"
117 | className="rounded-t-3xl"
118 | open={open}>
119 |
120 |
121 | {
130 | setCategoryFilterId(e);
131 | }}
132 | fieldNames={{
133 | title: "category_Name",
134 | key: "category_Id",
135 | children: "children"
136 | }}
137 | dropdownStyle={{ maxHeight: 400, overflow: "auto" }}
138 | allowClear
139 | multiple
140 | selectable={false}
141 | />
142 |
143 |
144 |
145 |
146 |
147 | {/* }> */}
148 | {/* */}
149 |
150 | {isLoading || isFetching ? (
151 |
152 | ) : error ? (
153 |
154 | مشکلی رخ داده
155 |
158 |
159 | ) : data?.products.length === 0 ? (
160 |
161 |
162 |
163 | ) : (
164 | <>
165 | {data?.products.map((item) => (
166 |
181 | ))}
182 | >
183 | )}
184 |
185 | {
188 | setCurrentPage(e);
189 | refetch();
190 | }}
191 | pageSize={10}
192 | total={data?.totalRows}
193 | />
194 | {/* */}
195 |
196 | );
197 | }
198 |
199 | export default ProductList;
200 |
--------------------------------------------------------------------------------
/src/pages/user/cart/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable prettier/prettier */
2 | /* eslint-disable camelcase */
3 | /* eslint-disable react/jsx-wrap-multilines */
4 | import Container from "@components/container";
5 | import useClearCart from "@framework/api/cart/clear";
6 | import useDeleteCartItem from "@framework/api/cart/delete";
7 | import { useGetCarts } from "@framework/api/cart/get";
8 | import useTelegramUser from "@hooks/useTelegramUser";
9 | import { addCommas } from "@persian-tools/persian-tools";
10 | import { Button, List, message, Popconfirm } from "antd";
11 | import React, { useEffect } from "react";
12 | import { Link, useLocation, useNavigate } from "react-router-dom";
13 |
14 | function Cart() {
15 | const clearCartMutation = useClearCart();
16 | const delCartItemMutation = useDeleteCartItem();
17 | const { id } = useTelegramUser();
18 | const { data, isFetching, isLoading, refetch } = useGetCarts(id);
19 | const [openClearModal, setOpenClearModal] = React.useState(false);
20 | const [confirmLoading, setConfirmLoading] = React.useState(false);
21 | const navigator = useNavigate();
22 | const location = useLocation();
23 |
24 | useEffect(() => {
25 | refetch();
26 | }, [location, refetch]);
27 |
28 | const handleDeleteCartItem = (product_id: string | number) => {
29 | setConfirmLoading(true);
30 |
31 | delCartItemMutation.mutate(
32 | {
33 | user_id: `${id}`,
34 | product_id
35 | },
36 | {
37 | onSuccess: () => {
38 | message.success("حذف شد");
39 | setConfirmLoading(false);
40 | refetch();
41 | },
42 | onError: () => {
43 | message.error("مشکلی رخ داده. دوباره تلاش کنید");
44 | setConfirmLoading(false);
45 | refetch();
46 | }
47 | }
48 | );
49 | };
50 | const handleClearCart = () => {
51 | setConfirmLoading(true);
52 | clearCartMutation.mutate(
53 | {
54 | user_id: `${id}`
55 | },
56 | {
57 | onSuccess: () => {
58 | message.success("سبد شما خالی شد ");
59 | setConfirmLoading(false);
60 | setOpenClearModal(false);
61 | refetch();
62 | },
63 | onError: () => {
64 | message.error("مشکلی رخ داده. دوباره تلاش کنید");
65 | setConfirmLoading(false);
66 | setOpenClearModal(false);
67 | refetch();
68 | }
69 | }
70 | );
71 | };
72 | return (
73 |
74 |
75 |
76 |
(
81 |
82 |
87 | // }
88 | title={
89 |
90 |
91 | {item.product_Name}
92 |
93 |
94 | }
95 | description={
96 |
97 |
98 | تومان
99 | {addCommas(item.discountedPrice)}
100 | : قیمت واحد
101 |
102 |
103 | عدد
104 | {item.quantity}
105 | : تعداد
106 |
107 |
108 |
109 | تومان
110 |
111 | {addCommas(item.discountedPrice * item.quantity)}
112 |
113 | : قیمت کل
114 |
115 |
116 | }
117 | />
118 |
119 |
120 |
handleDeleteCartItem(item.product_Id)}
124 | okText="حذف"
125 | okType="default"
126 | cancelText="انصراف">
127 |
130 |
131 |
132 |
133 | )}
134 | />
135 | setOpenClearModal(false)}>
146 |
154 |
155 |
156 |
157 |
158 |
159 | قیمت کل
160 | {addCommas(data?.totalPrice)}
161 | تومان
162 |
163 |
164 |
165 |
166 |
179 |
180 |
181 |
182 |
183 | );
184 | }
185 |
186 | export default Cart;
187 |
--------------------------------------------------------------------------------
/src/pages/bot/masters/add.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-expressions */
2 | /* eslint-disable operator-linebreak */
3 | /* eslint-disable react/no-array-index-key */
4 | /* eslint-disable camelcase */
5 | import Container from "@components/container";
6 | import useAddMaster from "@framework/api/master/add";
7 | import useAddMasterImage from "@framework/api/photos-upload/add-master";
8 | import { TypePostMaster } from "@framework/types";
9 | import useTelegramUser from "@hooks/useTelegramUser";
10 | import { Button, Form, Input, message, Spin } from "antd";
11 | import { useState } from "react";
12 | import ImageUploading from "react-images-uploading";
13 | import { useNavigate } from "react-router";
14 |
15 | const { TextArea } = Input;
16 | function Add() {
17 | const [componentDisabled, setComponentDisabled] = useState(false);
18 | const [priceEnterd, setPriceEnterd] = useState(0);
19 | const mutation = useAddMaster();
20 | const mutationUploadPhotos = useAddMasterImage();
21 | const { id } = useTelegramUser();
22 | const [form] = Form.useForm();
23 | const navigate = useNavigate();
24 | const [imageLinkList, setImageLinkList] = useState>([]);
25 | const [images, setImages] = useState([]);
26 | const onChangeImage = async (imageList) => {
27 | // data for submit
28 | imageList.length &&
29 | (await imageList.map(async (i: { data_url: string }) => {
30 | mutationUploadPhotos.mutate(
31 | { photo_base64: i.data_url.split(",")[1] },
32 | {
33 | onSuccess: (e) => {
34 | setImageLinkList([...imageLinkList, `${e.data}`]);
35 | },
36 | onError: () => {
37 | message.error("افزودن عکس با مشکل مواجه شد");
38 | }
39 | }
40 | );
41 | }));
42 | setImages(imageList);
43 | };
44 | const handleRemoveSingleImage = (idx) => {
45 | const arr = [...imageLinkList];
46 | if (idx !== -1) {
47 | arr.splice(idx, 1);
48 | setImageLinkList(arr);
49 | }
50 | };
51 |
52 | return (
53 |
54 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 | {/*
99 |
100 | */}
101 |
106 | {mutationUploadPhotos.isLoading ? (
107 |
108 | ) : (
109 |
114 | {({
115 | onImageUpload,
116 | onImageRemoveAll,
117 | onImageRemove,
118 | isDragging,
119 | dragProps
120 | }) => (
121 | // write your building UI
122 |
123 |
124 |
132 |
133 |
142 |
143 |
144 | {imageLinkList?.map((image, index) => (
145 |
146 |

151 |
152 |
162 |
163 |
164 | ))}
165 |
166 |
167 | )}
168 |
169 | )}
170 |
171 |
172 |
182 |
183 |
184 | );
185 | }
186 |
187 | export default Add;
188 |
--------------------------------------------------------------------------------
/src/pages/bot/masters/edit.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-expressions */
2 | /* eslint-disable operator-linebreak */
3 | /* eslint-disable react/no-array-index-key */
4 | /* eslint-disable camelcase */
5 | import Container from "@components/container";
6 | import { useGetMasterById } from "@framework/api/master/get-by-id";
7 | import useUpdateMaster from "@framework/api/master/update";
8 | import useAddMasterImage from "@framework/api/photos-upload/add-master";
9 | import { TypePostMaster } from "@framework/types";
10 | import useTelegramUser from "@hooks/useTelegramUser";
11 | import { Button, Form, Input, message, Spin } from "antd";
12 | import { useEffect, useState } from "react";
13 | import ImageUploading from "react-images-uploading";
14 | import { useLocation, useNavigate, useParams } from "react-router";
15 |
16 | const { TextArea } = Input;
17 | function Edit() {
18 | const [componentDisabled, setComponentDisabled] = useState(false);
19 | const { master_id } = useParams();
20 | const { id } = useTelegramUser();
21 | const location = useLocation();
22 | const mutationUploadPhotos = useAddMasterImage();
23 | const mutation = useUpdateMaster({ master_id });
24 | const { data, isFetching, isLoading, refetch } = useGetMasterById({
25 | master_id
26 | });
27 | const [form] = Form.useForm();
28 | const navigate = useNavigate();
29 | const [imageLinkList, setImageLinkList] = useState>([]);
30 | const [images, setImages] = useState([]);
31 | const onChangeImage = async (imageList) => {
32 | // data for submit
33 | imageList.length &&
34 | (await imageList.map(async (i: { data_url: string }) => {
35 | mutationUploadPhotos.mutate(
36 | { photo_base64: i.data_url.split(",")[1] },
37 | {
38 | onSuccess: (e) => {
39 | setImageLinkList([...imageLinkList, `${e.data}`]);
40 | },
41 | onError: () => {
42 | message.error("افزودن عکس با مشکل مواجه شد");
43 | }
44 | }
45 | );
46 | }));
47 | setImages(imageList);
48 | };
49 | const handleRemoveSingleImage = (idx) => {
50 | const arr = [...imageLinkList];
51 | if (idx !== -1) {
52 | arr.splice(idx, 1);
53 | setImageLinkList(arr);
54 | }
55 | };
56 | useEffect(() => {
57 | if (data?.photo_Path) {
58 | setImageLinkList(data.photo_Path.split(","));
59 | }
60 | }, [isLoading, isFetching, data]);
61 | useEffect(() => {
62 | refetch();
63 | }, [location]);
64 | return (
65 |
66 |
67 | {isLoading || isFetching ? (
68 |
69 | ) : (
70 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 | {/*
118 |
119 | */}
120 |
125 | {mutationUploadPhotos.isLoading ? (
126 |
127 | ) : (
128 |
133 | {({
134 | onImageUpload,
135 | onImageRemoveAll,
136 | onImageRemove,
137 | isDragging,
138 | dragProps
139 | }) => (
140 | // write your building UI
141 |
142 |
143 |
151 |
152 |
161 |
162 |
163 | {imageLinkList?.map((image, index) => (
164 |
165 |

170 |
171 |
181 |
182 |
183 | ))}
184 |
185 |
186 | )}
187 |
188 | )}
189 |
190 |
191 |
201 |
202 | )}
203 |
204 |
205 | );
206 | }
207 |
208 | export default Edit;
209 |
--------------------------------------------------------------------------------
/src/pages/user/checkout/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/jsx-one-expression-per-line */
2 | /* eslint-disable jsx-a11y/click-events-have-key-events */
3 | /* eslint-disable react/jsx-wrap-multilines */
4 | /* eslint-disable jsx-a11y/no-static-element-interactions */
5 | /* eslint-disable operator-linebreak */
6 | /* eslint-disable no-unused-expressions */
7 | /* eslint-disable no-undef */
8 | import Container from "@components/container";
9 | import { useGetAddresses } from "@framework/api/address/get";
10 | import { useGetCarts } from "@framework/api/cart/get";
11 | import useAddOrder from "@framework/api/orders/add";
12 | import useAddReceiptPhotos from "@framework/api/receipt-photos/add";
13 | import useTelegramUser from "@hooks/useTelegramUser";
14 | import { addCommas } from "@persian-tools/persian-tools";
15 | import { Alert, Button, Form, Input, message, Select, Spin } from "antd";
16 | import { useEffect, useState } from "react";
17 | import ImageUploading from "react-images-uploading";
18 | import { useLocation, useNavigate } from "react-router";
19 |
20 | function Checkout() {
21 | const [images, setImages] = useState([]);
22 | const [imagesLoading, setImagesLoading] = useState(false);
23 | const [receiptPhoto, setReceiptPhoto] = useState(null);
24 | const { id } = useTelegramUser();
25 | const { state: locState } = useLocation();
26 | const navigate = useNavigate();
27 | const mutationPhotos = useAddReceiptPhotos();
28 | const mutationOrder = useAddOrder();
29 |
30 | const {
31 | data: CartData,
32 | isFetching: CartFetching,
33 | isLoading: CartLoading
34 | } = useGetCarts(id);
35 |
36 | const onChangeImage = async (imageList) => {
37 | imageList.length && setImagesLoading(true);
38 | // setImages(imageList);
39 | imageList.length &&
40 | mutationPhotos.mutate(
41 | {
42 | photo_base64: imageList[0].data_url.split(",")[1]
43 | },
44 | {
45 | onSuccess: (e) => {
46 | setImages(imageList);
47 | setReceiptPhoto(`${e.data}`);
48 | setImagesLoading(false);
49 | },
50 |
51 | onError: () => {
52 | message.error("در آپلود عکس مشکلی پیش آمده لطفا دوباره تلاش کنید!");
53 | setImagesLoading(false);
54 | }
55 | }
56 | );
57 | };
58 | const onFinishFailed = (errorInfo: any) => {
59 | console.log("Failed:", errorInfo);
60 | };
61 | const { data, error, refetch, isFetching, isLoading } = useGetAddresses(id);
62 |
63 | useEffect(() => {
64 | if (!locState) {
65 | navigate("/cart");
66 | } else {
67 | refetch();
68 | }
69 | }, [locState]);
70 |
71 | useEffect(() => {
72 | if (!data || !data?.addresses) {
73 | // navigate("/profile/address/add");
74 | message.warning({
75 | content: "باید قبل از پرداخت آدرس اضافه کنید ",
76 | duration: 3
77 | });
78 | }
79 | }, [data, error]);
80 | const personCart = {
81 | name: "سینا صالحی",
82 | cartNumber: "6219861065233172"
83 | };
84 | return (
85 |
86 |
87 |
90 | اطلاعات حساب برای واریز مبلغ
91 |
92 |
{
95 | navigator.clipboard.writeText(personCart.cartNumber);
96 | message.success("شماره کارت کپی شد ");
97 | }}>
98 |
شماره کارت : {personCart.cartNumber}
99 |
100 |
101 |
102 |
103 | به نام:
104 | {personCart.name}
105 |
106 |
107 |
108 |
109 | مبلغ واریز:
110 |
111 | {addCommas(CartData?.totalPrice) || 0} تومان
112 |
113 |
114 |
115 |
116 | }
117 | type="info"
118 | showIcon
119 | />
120 |
121 |
126 |
127 |
217 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
243 |
244 |
245 |
246 | );
247 | }
248 |
249 | export default Checkout;
250 |
--------------------------------------------------------------------------------
/src/pages/admin/product/add.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-expressions */
2 | /* eslint-disable operator-linebreak */
3 | /* eslint-disable react/no-array-index-key */
4 | /* eslint-disable camelcase */
5 | import Container from "@components/container";
6 | import { useGetCategories } from "@framework/api/categories/get";
7 | import useAddProductImage from "@framework/api/photos-upload/add";
8 | import useAddProduct from "@framework/api/product/add";
9 | import { TypeProductPost } from "@framework/types";
10 | import useTelegramUser from "@hooks/useTelegramUser";
11 | import {
12 | Button,
13 | Form,
14 | Input,
15 | InputNumber,
16 | message,
17 | Spin,
18 | TreeSelect
19 | } from "antd";
20 | import { useEffect, useState } from "react";
21 | import ImageUploading from "react-images-uploading";
22 | import { useNavigate } from "react-router";
23 |
24 | const { TextArea } = Input;
25 | function Add() {
26 | const [componentDisabled, setComponentDisabled] = useState(false);
27 | // const [priceEnterd, setPriceEnterd] = useState(0);
28 |
29 | const {
30 | data: categoriesData,
31 | isLoading: isCatLoading,
32 | refetch: catRefetch,
33 | isFetching: isCatFetching
34 | } = useGetCategories({});
35 | const mutation = useAddProduct();
36 | const mutationUploadPhotos = useAddProductImage();
37 | const { id } = useTelegramUser();
38 | const [form] = Form.useForm();
39 | const navigate = useNavigate();
40 | useEffect(() => {
41 | catRefetch();
42 | }, []);
43 |
44 | const [imageLinkList, setImageLinkList] = useState>([]);
45 | const [images, setImages] = useState([]);
46 | const onChangeImage = async (imageList) => {
47 | // data for submit
48 | imageList.length &&
49 | (await imageList.map(async (i: { data_url: string }) => {
50 | mutationUploadPhotos.mutate(
51 | { photo_base64: i.data_url.split(",")[1] },
52 | {
53 | onSuccess: (e) => {
54 | setImageLinkList([...imageLinkList, `${e.data}`]);
55 | },
56 | onError: () => {
57 | message.error("افزودن عکس با مشکل مواجه شد");
58 | }
59 | }
60 | );
61 | }));
62 | setImages(imageList);
63 | };
64 | const handleRemoveSingleImage = (idx) => {
65 | const arr = [...imageLinkList];
66 | if (idx !== -1) {
67 | arr.splice(idx, 1);
68 | setImageLinkList(arr);
69 | }
70 | };
71 |
72 | return (
73 |
74 |
114 |
115 |
116 |
117 | {/* */}
131 | console.log(e)}
137 | treeLine
138 | style={{
139 | width: "100%"
140 | }}
141 | fieldNames={{
142 | label: "category_Name",
143 | value: "category_Id",
144 | key: "category_Id",
145 | children: "children"
146 | }}
147 | />
148 |
149 | {/*
150 |
151 |
152 |
153 |
154 | */}
155 |
156 | setPriceEnterd(e || 0)}
158 | formatter={(value) =>
159 | `${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ",")
160 | }
161 | parser={(value) => value.replace(/\$\s?|(,*)/g, "")}
162 | required
163 | className="w-1/2"
164 | // type="number"
165 | />
166 |
167 | {/*
168 | {numberToWords(priceEnterd)} تومان
169 |
*/}
170 |
171 |
172 |
173 | {/*
174 |
175 | */}
176 |
177 |
178 |
179 | {/*
180 |
181 | */}
182 |
187 | {mutationUploadPhotos.isLoading ? (
188 |
189 | ) : (
190 |
195 | {({
196 | onImageUpload,
197 | onImageRemoveAll,
198 | onImageRemove,
199 | isDragging,
200 | dragProps
201 | }) => (
202 | // write your building UI
203 |
204 |
205 |
213 |
214 |
223 |
224 |
225 | {imageLinkList?.map((image, index) => (
226 |
227 |

232 |
233 |
243 |
244 |
245 | ))}
246 |
247 |
248 | )}
249 |
250 | )}
251 |
252 |
253 |
263 |
264 |
265 | );
266 | }
267 |
268 | export default Add;
269 |
--------------------------------------------------------------------------------