first commit
This commit is contained in:
1 parent
2e6f1c7e66
commit
95bf469f86
85 files changed
+6698
-332
No files matched your search
@@ -0,0 +1 @@
|
|||||||
|
NEXT_PUBLIC_API_URL=https://lgg-bashgah-api.liara.run
|
||||||
@@ -6,6 +6,8 @@
|
|||||||
.pnp.js
|
.pnp.js
|
||||||
.yarn/install-state.gz
|
.yarn/install-state.gz
|
||||||
|
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
# testing
|
# testing
|
||||||
/coverage
|
/coverage
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
|
"style": "default",
|
||||||
|
"rsc": true,
|
||||||
|
"tsx": true,
|
||||||
|
"tailwind": {
|
||||||
|
"config": "tailwind.config.ts",
|
||||||
|
"css": "src/app/globals.css",
|
||||||
|
"baseColor": "slate",
|
||||||
|
"cssVariables": true,
|
||||||
|
"prefix": ""
|
||||||
|
},
|
||||||
|
"aliases": {
|
||||||
|
"components": "@/components",
|
||||||
|
"utils": "@/lib/utils"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"platform": "next",
|
||||||
|
"app": "mojrian"
|
||||||
|
}
|
||||||
+33
-1
@@ -1,4 +1,36 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {};
|
const nextConfig = {
|
||||||
|
typescript: {
|
||||||
|
// !! WARN !!
|
||||||
|
// Dangerously allow production builds to successfully complete even if
|
||||||
|
// your project has type errors.
|
||||||
|
// !! WARN !!
|
||||||
|
ignoreBuildErrors: true,
|
||||||
|
},
|
||||||
|
eslint: {
|
||||||
|
// Warning: This allows production builds to successfully complete even if
|
||||||
|
// your project has ESLint errors.
|
||||||
|
ignoreDuringBuilds: true,
|
||||||
|
},
|
||||||
|
images: {
|
||||||
|
remotePatterns: [
|
||||||
|
{
|
||||||
|
protocol: 'https',
|
||||||
|
hostname: 'lgg-bashgah-api.liara.run',
|
||||||
|
port: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
protocol: 'https',
|
||||||
|
hostname: 'mojrian-api.loolegostar.com',
|
||||||
|
port: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
protocol: 'http',
|
||||||
|
hostname: '127.0.0.1',
|
||||||
|
port: '8000'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
Generated
+1810
-152
File diff suppressed because it is too large.
Load diff
+31
-5
@@ -3,25 +3,51 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev -p 3001",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@radix-ui/react-accordion": "^1.1.2",
|
||||||
|
"@radix-ui/react-alert-dialog": "^1.0.5",
|
||||||
|
"@radix-ui/react-avatar": "^1.0.4",
|
||||||
|
"@radix-ui/react-dialog": "^1.0.5",
|
||||||
|
"@radix-ui/react-label": "^2.0.2",
|
||||||
|
"@radix-ui/react-menubar": "^1.0.4",
|
||||||
|
"@radix-ui/react-popover": "^1.1.1",
|
||||||
|
"@radix-ui/react-progress": "^1.0.3",
|
||||||
|
"@radix-ui/react-radio-group": "^1.1.3",
|
||||||
|
"@radix-ui/react-select": "^2.0.0",
|
||||||
|
"@radix-ui/react-slot": "^1.0.2",
|
||||||
|
"@radix-ui/react-switch": "^1.0.3",
|
||||||
|
"@radix-ui/react-tabs": "^1.0.4",
|
||||||
|
"@tanstack/react-query": "^5.28.4",
|
||||||
|
"axios": "^1.6.8",
|
||||||
|
"class-variance-authority": "^0.7.0",
|
||||||
|
"clsx": "^2.1.0",
|
||||||
|
"input-otp": "^1.2.0",
|
||||||
|
"lucide-react": "^0.358.0",
|
||||||
|
"moment-jalaali": "^0.10.0",
|
||||||
|
"next": "14.1.3",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"next": "14.1.3"
|
"react-hook-form": "^7.51.1",
|
||||||
|
"react-icons": "^5.0.1",
|
||||||
|
"tailwind-merge": "^2.2.1",
|
||||||
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
"vaul": "^0.9.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5",
|
"@types/moment-jalaali": "^0.7.9",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
"autoprefixer": "^10.0.1",
|
"autoprefixer": "^10.0.1",
|
||||||
|
"eslint": "^8",
|
||||||
|
"eslint-config-next": "14.1.3",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
"tailwindcss": "^3.3.0",
|
"tailwindcss": "^3.3.0",
|
||||||
"eslint": "^8",
|
"typescript": "^5"
|
||||||
"eslint-config-next": "14.1.3"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
CREATE TABLE "CUser" (
|
||||||
|
"id" bigint,
|
||||||
|
"mobile" bigint,
|
||||||
|
"name_family" varchar,
|
||||||
|
"step" int,
|
||||||
|
"type" varchar
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE "Profile" (
|
||||||
|
"codemeli" bigint,
|
||||||
|
"city" bigint,
|
||||||
|
"address" varchar,
|
||||||
|
"codeposti" int,
|
||||||
|
"ax_mojri" varchar,
|
||||||
|
"ax_meli" varchar,
|
||||||
|
"ax_fani" varchar,
|
||||||
|
"ax_sayer" varchar
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE "ProductCategory" (
|
||||||
|
"codemeli" bigint,
|
||||||
|
"city" bigint,
|
||||||
|
"address" varchar,
|
||||||
|
"codeposti" int,
|
||||||
|
"ax_mojri" varchar,
|
||||||
|
"ax_meli" varchar,
|
||||||
|
"ax_fani" varchar,
|
||||||
|
"ax_sayer" varchar
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE "follows" (
|
||||||
|
"following_user_id" integer,
|
||||||
|
"followed_user_id" integer,
|
||||||
|
"created_at" timestamp
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE "users" (
|
||||||
|
"id" integer PRIMARY KEY,
|
||||||
|
"username" varchar,
|
||||||
|
"role" varchar,
|
||||||
|
"created_at" timestamp
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE "posts" (
|
||||||
|
"id" integer PRIMARY KEY,
|
||||||
|
"title" varchar,
|
||||||
|
"body" text,
|
||||||
|
"user_id" integer,
|
||||||
|
"status" varchar,
|
||||||
|
"created_at" timestamp
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMENT ON COLUMN "posts"."body" IS 'Content of the post';
|
||||||
|
|
||||||
|
ALTER TABLE "posts" ADD FOREIGN KEY ("user_id") REFERENCES "users" ("id");
|
||||||
|
|
||||||
|
ALTER TABLE "follows" ADD FOREIGN KEY ("following_user_id") REFERENCES "users" ("id");
|
||||||
|
|
||||||
|
ALTER TABLE "follows" ADD FOREIGN KEY ("followed_user_id") REFERENCES "users" ("id");
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 117 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 118 KiB |
@@ -0,0 +1,11 @@
|
|||||||
|
import axiosInstance from "@/axios/axiosInstance";
|
||||||
|
|
||||||
|
|
||||||
|
export const getCitys = async () => {
|
||||||
|
try{
|
||||||
|
const res = await axiosInstance.get('app/citys/')
|
||||||
|
return res.data
|
||||||
|
}catch(e){
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import axiosInstance from "@/axios/axiosInstance";
|
||||||
|
|
||||||
|
|
||||||
|
export const getEmtiyazat = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axiosInstance.get('emtiyazat/list/')
|
||||||
|
return response.data
|
||||||
|
}catch (error:any){
|
||||||
|
console.log(error)
|
||||||
|
throw new Error(error.response.data.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import axiosInstance from "@/axios/axiosInstance";
|
||||||
|
|
||||||
|
export const getMojriInfo = async () => {
|
||||||
|
return await axiosInstance.get('mojri/info/')
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import axiosInstance from "@/axios/axiosInstance";
|
||||||
|
// import axios error type
|
||||||
|
import {AxiosError} from "axios";
|
||||||
|
|
||||||
|
export const createOrder = async (product_id: number) => {
|
||||||
|
const url = 'order/create/';
|
||||||
|
try {
|
||||||
|
return await axiosInstance.post(url, {
|
||||||
|
product_id: product_id,
|
||||||
|
});
|
||||||
|
} catch (e:any) {
|
||||||
|
throw e.response.data
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getOrder = async (order_id: number) => {
|
||||||
|
try{
|
||||||
|
const result = await axiosInstance.get(`order/${order_id}/`);
|
||||||
|
return result.data
|
||||||
|
} catch (e:any) {
|
||||||
|
console.log(e)
|
||||||
|
throw e.response.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getOrders = async () => {
|
||||||
|
try{
|
||||||
|
const result = await axiosInstance.get(`order/list/`);
|
||||||
|
return result.data
|
||||||
|
} catch (e:any) {
|
||||||
|
console.log(e)
|
||||||
|
throw e.response.data
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import axiosInstance from "@/axios/axiosInstance";
|
||||||
|
|
||||||
|
|
||||||
|
export const getCitys = async () => {
|
||||||
|
return await axiosInstance.get('project/city-list/')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const createProject = async (data:any) => {
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data'
|
||||||
|
},
|
||||||
|
timeout: 300000,
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await axiosInstance.post('project/create/', data, config)
|
||||||
|
} catch (error:any) {
|
||||||
|
console.log(error?.response)
|
||||||
|
throw new Error("error create project")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getProjects = async () => {
|
||||||
|
return await axiosInstance.get('project/list/')
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getProject = async (id:number) => {
|
||||||
|
return await axiosInstance.get(`project/${id}/`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateProjectSeller = async (data:any) => {
|
||||||
|
try{
|
||||||
|
return await axiosInstance.patch(`project/${data.id}/update-seller/`, data.fields)
|
||||||
|
}catch(err){
|
||||||
|
alert('خطا در ثبت پروژه')
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import axiosInstance from "@/axios/axiosInstance";
|
||||||
|
|
||||||
|
|
||||||
|
export const getProjectCodeValidation = async (project_code:string, setProjectCodeMessage:any) => {
|
||||||
|
try{
|
||||||
|
const result = await axiosInstance.get(`projectcode/validation/${project_code}/`)
|
||||||
|
setProjectCodeMessage(null)
|
||||||
|
return result.data
|
||||||
|
}catch(e){
|
||||||
|
setProjectCodeMessage("کد ثبت پروژه معتبر نیست از فروشنده کد معتبر بخواهید")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import axiosInstance from "@/axios/axiosInstance";
|
||||||
|
import axios, {AxiosResponse} from "axios";
|
||||||
|
import {useRouter} from "next/navigation";
|
||||||
|
|
||||||
|
export const registerUser = async (data:any) => {
|
||||||
|
try{
|
||||||
|
const result: AxiosResponse = await axios.post(`${process.env.NEXT_PUBLIC_API_URL}/register/`,data)
|
||||||
|
return result
|
||||||
|
}catch(err){
|
||||||
|
console.log(err)
|
||||||
|
throw new Error("error create project")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import axiosInstance from "@/axios/axiosInstance";
|
||||||
|
|
||||||
|
export const getUserProfile = async () => {
|
||||||
|
try {
|
||||||
|
const result = await axiosInstance.get('userprofile/')
|
||||||
|
return result.data
|
||||||
|
} catch (error:any) {
|
||||||
|
console.log(error?.response)
|
||||||
|
throw new Error("error get userprofile")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateUserProfile = async (data:any) => {
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await axiosInstance.patch('userprofile/update/', data, config)
|
||||||
|
} catch (error:any) {
|
||||||
|
console.log(error?.response)
|
||||||
|
throw new Error("error create project")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { BsHourglassSplit } from "react-icons/bs"
|
||||||
|
import { FaArrowDown, FaArrowUp } from "react-icons/fa6"
|
||||||
|
import {useListEmtiyazatQuery} from "@/queries/emtiyazat";
|
||||||
|
import { IEmtiyazatPagination, IEmtiyazat } from '@/types/emtiyazat'
|
||||||
|
import { IPagination } from '@/types/pagination'
|
||||||
|
|
||||||
|
|
||||||
|
const Page =()=>{
|
||||||
|
|
||||||
|
const {data:emtiyazat, isError, isSuccess, isPending, isLoading}:{
|
||||||
|
data:IEmtiyazatPagination | any,
|
||||||
|
isError:boolean,
|
||||||
|
isSuccess:boolean,
|
||||||
|
isPending:boolean,
|
||||||
|
isLoading:boolean
|
||||||
|
} = useListEmtiyazatQuery()
|
||||||
|
|
||||||
|
if(isLoading){
|
||||||
|
return (
|
||||||
|
<div>درحال دریافت اطلاعات</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isError){
|
||||||
|
return (
|
||||||
|
<div>خطایی رخ داده است</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-y-2 pt-2 pr-2 pl-2 pb-2">
|
||||||
|
<div className="bg-white p-2 rounded-sm text-right">
|
||||||
|
گزارش امتیازات
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{emtiyazat.results.map((emtiyaz: IEmtiyazat, key: number)=>{
|
||||||
|
return (
|
||||||
|
<div key={key} className="bg-white flex flex-row justify-between gap-y-4 p-2 rounded-sm">
|
||||||
|
<div className="flex flex-col gap-y-1 justify-between">
|
||||||
|
<div>
|
||||||
|
<h2>
|
||||||
|
{emtiyaz.desc}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm">
|
||||||
|
تاریخ:
|
||||||
|
1402/09/26
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-row gap-x-2 justify-between items-center">
|
||||||
|
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="flex flex-col justify-center">
|
||||||
|
|
||||||
|
{emtiyaz.credit ? (
|
||||||
|
<div
|
||||||
|
className="bg-green-800 text-sm leading-[2rem] text-white text-center justify-center self-center place-content-center rounded-full w-[2rem] h-[2rem]">
|
||||||
|
{emtiyaz.credit}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{emtiyaz.debit ? (
|
||||||
|
<div
|
||||||
|
className="bg-red-800 text-sm leading-[2rem] text-white text-center justify-center self-center place-content-center rounded-full w-[2rem] h-[2rem]">
|
||||||
|
{emtiyaz.debit}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
|
||||||
|
<div className="text-sm">
|
||||||
|
امتیاز
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="status">
|
||||||
|
{emtiyaz.credit ? (
|
||||||
|
<FaArrowUp size={30} color="green"/>
|
||||||
|
) : null }
|
||||||
|
|
||||||
|
{emtiyaz.debit ? (
|
||||||
|
<FaArrowDown size={30} color="red" />
|
||||||
|
) : null }
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Page
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
'use client'
|
||||||
|
import moment from "moment-jalaali";
|
||||||
|
import JavayezComponent from "@/components/main/javayez"
|
||||||
|
import { Input } from "@/components/ui/input"
|
||||||
|
import Image from "next/image"
|
||||||
|
import Link from "next/link"
|
||||||
|
import {useGetOrder} from "@/queries/order";
|
||||||
|
import {IOrder} from '@/types/order'
|
||||||
|
import { AxiosResponse } from 'axios'
|
||||||
|
|
||||||
|
const Page = ({params}: {params: {id:number}} )=>{
|
||||||
|
|
||||||
|
const {data:order, isLoading, isError} = useGetOrder(params.id)
|
||||||
|
|
||||||
|
if(isLoading) return <div>Loading...</div>
|
||||||
|
if(isError) return <div>Error</div>
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-y-1 pt-2 pr-2 pl-2 pb-2">
|
||||||
|
|
||||||
|
<div className="bg-white drop-shadow-lg p-2 rounded-sm text-right">
|
||||||
|
وضعیت درخواست
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex flex-col gap-y-4 mt-2'>
|
||||||
|
|
||||||
|
|
||||||
|
<div className='flex flex-col bg-white drop-shadow-lg rounded-sm'>
|
||||||
|
<div className='flex flex-row gap-x-4 justify-between p-3'>
|
||||||
|
|
||||||
|
<div className='flex flex-col gap-y-2 justify-between'>
|
||||||
|
<div>
|
||||||
|
<h2 className='text-md'>
|
||||||
|
{order?.product?.name}
|
||||||
|
</h2>
|
||||||
|
<p className='bg-green-800 text-white text-[0.7rem] pr-[0.3rem] pl-[0.3rem] rounded-sm'>
|
||||||
|
{order?.product?.desc}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="text-xs">تاریخ:
|
||||||
|
{moment(order?.created_at).format('jYYYY/jM/jD')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Image className={`rounded-sm`} src={`${process.env.NEXT_PUBLIC_API_URL}/${order?.product?.image}`} width={120} height={100} alt='star' />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mb-2 border-b-[1px] border-b-gray-200'></div>
|
||||||
|
|
||||||
|
<div className='flex flex-row gap-x-4 justify-between p-3 items-center'>
|
||||||
|
|
||||||
|
<div className='flex flex-row gap-x-1 font-bold items-center'>
|
||||||
|
<div>{order?.emtiyaz}</div>
|
||||||
|
<div>امتیاز</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span className="text-sm bg-green-900 px-2 text-white rounded-sm">{order?.status_text}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{order?.status=='approve' ? (
|
||||||
|
<div className="flex flex-col gap-y-4 text-center bg-white drop-shadow-lg rounded-sm p-4">
|
||||||
|
|
||||||
|
<Input defaultValue={order?.code?.toString()} className="text-sm text-center" placeholder="کد" />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span className="text-center text-sm text-white bg-gray-800 px-2 py-1 rounded-sm">کپی</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="text-right text-sm">
|
||||||
|
توضیحات:
|
||||||
|
{order?.desc}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex flex-col gap-y-4 text-sm text-center bg-white drop-shadow-lg rounded-sm py-2">
|
||||||
|
|
||||||
|
<p>در انتظار برسی و تایید توسط کارشناسان شرکت</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Page
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import JavayezComponent from "@/components/main/javayez"
|
||||||
|
import Image from "next/image"
|
||||||
|
import Link from "next/link"
|
||||||
|
import {useGetOrders} from "@/queries/order";
|
||||||
|
import {IOrder} from '@/types/order'
|
||||||
|
import moment from 'moment-jalaali'
|
||||||
|
|
||||||
|
const Page = ()=>{
|
||||||
|
const {data:orders, isLoading, isError} = useGetOrders()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-y-1 pt-2 pr-2 pl-2 pb-2">
|
||||||
|
|
||||||
|
<div className="bg-white drop-shadow-lg p-2 rounded-sm text-right">
|
||||||
|
جوایز دریافتی
|
||||||
|
</div>
|
||||||
|
{orders?.results.map((order:IOrder, index:number)=>(
|
||||||
|
<div key={index} className='flex flex-col gap-y-4 mt-2'>
|
||||||
|
|
||||||
|
|
||||||
|
<div className='flex flex-col bg-white drop-shadow-lg rounded-sm'>
|
||||||
|
<div className='flex flex-row gap-x-4 justify-between p-3'>
|
||||||
|
|
||||||
|
<div className='flex flex-col gap-y-2 justify-between'>
|
||||||
|
<div>
|
||||||
|
<h2 className='text-md'>
|
||||||
|
{order.product.name}
|
||||||
|
</h2>
|
||||||
|
<p className='bg-green-800 text-white text-[0.7rem] pr-[0.3rem] pl-[0.3rem] rounded-sm'>
|
||||||
|
{order.product.desc}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="text-xs">تاریخ:
|
||||||
|
{moment(order.created_at).format('jYYYY/jM/jD')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Image className={`rounded-sm`} src={`${order?.product?.image}`} width={120} height={100}
|
||||||
|
alt='star'/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mb-2 border-b-[1px] border-b-gray-200'></div>
|
||||||
|
|
||||||
|
<div className='flex flex-row gap-x-4 justify-between p-3 items-center'>
|
||||||
|
|
||||||
|
<div className='flex flex-row gap-x-1 font-bold items-center'>
|
||||||
|
<div>
|
||||||
|
{order.emtiyaz}
|
||||||
|
</div>
|
||||||
|
<div>امتیاز</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={'flex flex-row gap-x-2 text-sm'}>
|
||||||
|
<div>
|
||||||
|
{order.status_text}
|
||||||
|
</div>
|
||||||
|
<Link href={`/javayez/${order.id}`}>
|
||||||
|
<span className="text-sm bg-gray-800 px-2 text-white rounded-sm">مشاهده</span>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Page
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import { Inter } from "next/font/google";
|
||||||
|
import "../globals.css";
|
||||||
|
import localFont from 'next/font/local'
|
||||||
|
import ReactQueryProvider from "@/providers/react_query";
|
||||||
|
import TopMenu from "@/components/main/topmenu";
|
||||||
|
|
||||||
|
|
||||||
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "باشگاه مجریان لوله گستر گلپایگان",
|
||||||
|
description: "باشگاه مجریان",
|
||||||
|
};
|
||||||
|
|
||||||
|
const myFont = localFont({
|
||||||
|
src: '../fonts/woff2/IRANSansX-Regular.woff2',
|
||||||
|
display: 'swap',
|
||||||
|
})
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) {
|
||||||
|
return (
|
||||||
|
<html lang="fa" dir="rtl" className={myFont.className}>
|
||||||
|
<body>
|
||||||
|
<ReactQueryProvider>
|
||||||
|
|
||||||
|
<div className="bg-gray-200 flex flex-col m-auto">
|
||||||
|
<TopMenu />
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</ReactQueryProvider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import axiosInstance from "@/axios/axiosInstance";
|
||||||
|
|
||||||
|
const add_new_project = async (data:any) => {
|
||||||
|
return await axiosInstance.post('product/add/', data)
|
||||||
|
}
|
||||||
@@ -0,0 +1,458 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { FaCircleUser, FaImage, FaLocationCrosshairs, FaLocationDot, FaMapLocationDot, FaMoneyCheckDollar } from "react-icons/fa6";
|
||||||
|
import { IoAddCircleSharp } from "react-icons/io5";
|
||||||
|
import { TbSquareRoundedNumber1Filled, TbSquareRoundedNumber2Filled, TbSquareRoundedNumber3Filled } from "react-icons/tb";
|
||||||
|
import { SiZalando } from "react-icons/si";
|
||||||
|
import { MdApartment } from "react-icons/md";
|
||||||
|
import { TiHome } from "react-icons/ti";
|
||||||
|
|
||||||
|
import {useQuery} from "@tanstack/react-query";
|
||||||
|
import axiosInstance from "@/axios/axiosInstance";
|
||||||
|
import {useEffect, useState} from "react";
|
||||||
|
import { useForm, SubmitHandler } from "react-hook-form"
|
||||||
|
import { useRouter } from 'next/navigation'
|
||||||
|
import {useCityQuery, useCreateProjectMutation} from "@/queries/project";
|
||||||
|
|
||||||
|
|
||||||
|
// regex for number only
|
||||||
|
const numberOnly = /^[0-9]*$/
|
||||||
|
|
||||||
|
type City = {
|
||||||
|
id: number,
|
||||||
|
name: string,
|
||||||
|
ostan: number,
|
||||||
|
is_ostan: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Inputs = {
|
||||||
|
city: number
|
||||||
|
address: string
|
||||||
|
factor_price: number
|
||||||
|
zirbana: number
|
||||||
|
tabaghat: number
|
||||||
|
vahed: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const Page = ()=>{
|
||||||
|
const router = useRouter()
|
||||||
|
const [ostanha, setOstan] = useState<City[] | null>(null)
|
||||||
|
const [shahrha, setShahrha] = useState<City[] | null>(null)
|
||||||
|
const [imageCount, setImageCount] = useState<number>(3)
|
||||||
|
const [ufiles, setUFiles] = useState<[] | any>([])
|
||||||
|
|
||||||
|
const { isError,isSuccess,isLoading,data:citys} = useCityQuery()
|
||||||
|
const {mutate: createProject,isPending: createIsPending, status: createStatus , data:resultCreateProject, isError:crError, isSuccess: crIsSuccess} = useCreateProjectMutation()
|
||||||
|
|
||||||
|
const onChangeCity = (e:any) => {
|
||||||
|
setShahrha(citys?.data.filter((item:City) => item.ostan == e.target.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const convertToEnglishDigits = (value) => {
|
||||||
|
const persianArabicToEnglish = {
|
||||||
|
'\u0660': '0', '\u0661': '1', '\u0662': '2', '\u0663': '3', '\u0664': '4',
|
||||||
|
'\u0665': '5', '\u0666': '6', '\u0667': '7', '\u0668': '8', '\u0669': '9',
|
||||||
|
'\u06F0': '0', '\u06F1': '1', '\u06F2': '2', '\u06F3': '3', '\u06F4': '4',
|
||||||
|
'\u06F5': '5', '\u06F6': '6', '\u06F7': '7', '\u06F8': '8', '\u06F9': '9'
|
||||||
|
};
|
||||||
|
return value.replace(/[\u0660-\u0669\u06F0-\u06F9]/g, (digit) => persianArabicToEnglish[digit]);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const formatWithCommas = (value:any) => {
|
||||||
|
const numValue = parseInt(value, 10);
|
||||||
|
if (isNaN(numValue)) return '';
|
||||||
|
return numValue.toLocaleString();
|
||||||
|
};
|
||||||
|
|
||||||
|
const fileHandler = (e:any) => {
|
||||||
|
const file = e.target.files[0]
|
||||||
|
setUFiles((prevState:[]) => [...prevState, file])
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
watch,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<Inputs>()
|
||||||
|
|
||||||
|
|
||||||
|
const onSubmit: SubmitHandler<Inputs> = async (data) => {
|
||||||
|
|
||||||
|
if(ufiles.length >= 3){
|
||||||
|
|
||||||
|
const formData = new FormData()
|
||||||
|
|
||||||
|
data.factor_price = Number(data.factor_price.toString().replace(/,/g, ''))
|
||||||
|
|
||||||
|
formData.append('city', data.city.toString())
|
||||||
|
formData.append('address', data.address)
|
||||||
|
formData.append('factor_price', data.factor_price.toString())
|
||||||
|
formData.append('zirbana', data.zirbana.toString())
|
||||||
|
formData.append('tabaghat', data.tabaghat.toString())
|
||||||
|
formData.append('vahed', data.vahed.toString())
|
||||||
|
formData.delete("project_images")
|
||||||
|
|
||||||
|
|
||||||
|
await ufiles?.map((file:any) => {
|
||||||
|
formData.append('project_images', file)
|
||||||
|
})
|
||||||
|
|
||||||
|
createProject(formData)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
alert('حداکثر 3 عکس اضافه کنید')
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if(isError){
|
||||||
|
return (<p>خطا</p>)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isLoading){
|
||||||
|
return (<p>در حال بارگذاری</p>)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-y-3 py-3 px-4">
|
||||||
|
|
||||||
|
<div className="flex flex-row gap-x-2 drop-shadow-md items-center justify-center bg-white p-2 rounded-sm text-center">
|
||||||
|
|
||||||
|
<h1>ثبت پروژه جدید</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* step1 address */}
|
||||||
|
<div className="bg-white drop-shadow-md flex flex-col gap-y-4 px-3 py-2 rounded-sm ">
|
||||||
|
|
||||||
|
{/* title: step1 address */}
|
||||||
|
<div className="flex flex-row gap-x-3 items-center border-b-[1px] border-gray-300 pb-1">
|
||||||
|
<div>
|
||||||
|
<TbSquareRoundedNumber1Filled size={25}/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-x-1">
|
||||||
|
<h4 className="text-md">آدرس پروژه</h4>
|
||||||
|
<p className="text-xs">جایی که از محصولات لوله گستر گلپایگان استفاده کرده اید</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ostan va shahr select option */}
|
||||||
|
<div className="flex flex-row gap-x-2">
|
||||||
|
|
||||||
|
<div className="w-full">
|
||||||
|
|
||||||
|
<Label>
|
||||||
|
|
||||||
|
<div className="flex flex-row mb-1 items-center gap-x-1">
|
||||||
|
<FaLocationDot color={"#959595"} size={15}/>
|
||||||
|
استان
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<select id={'city'} onChange={onChangeCity} className="w-full border p-2 rounded-md">
|
||||||
|
|
||||||
|
<option value={''}>انتخاب کنید</option>
|
||||||
|
{citys?.data.filter((item: City,) => item.is_ostan).map((city: City) => {
|
||||||
|
return <option key={city.id} value={city.id}>{city.name}</option>
|
||||||
|
})}
|
||||||
|
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<p className="text-sm text-red-700 mt-1">
|
||||||
|
{errors?.city?.message}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full">
|
||||||
|
|
||||||
|
<Label>
|
||||||
|
|
||||||
|
<div className="flex flex-row mb-1 items-center gap-x-1">
|
||||||
|
<FaMapLocationDot color={"#959595"} size={15}/>
|
||||||
|
شهر
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<select {...register("city", {
|
||||||
|
required: {
|
||||||
|
value: true,
|
||||||
|
message: 'اجباری'
|
||||||
|
}
|
||||||
|
|
||||||
|
})} className="w-full border p-2 rounded-md">
|
||||||
|
<option value={''}>انتخاب کنید</option>
|
||||||
|
{shahrha?.map((city: City) => {
|
||||||
|
return <option key={city.id} value={city.id}>{city.name}</option>
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<p className="text-sm text-red-700 mt-1">
|
||||||
|
{errors?.city?.message}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{/* address TextArea */}
|
||||||
|
<div className="w-full">
|
||||||
|
|
||||||
|
<Label>
|
||||||
|
|
||||||
|
<div className="flex flex-row mb-1 items-center gap-x-1">
|
||||||
|
<FaLocationCrosshairs color={"#959595"} size={15}/>
|
||||||
|
آدرس
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Textarea {...register("address", {
|
||||||
|
required: {
|
||||||
|
value: true,
|
||||||
|
message: 'آدرس را وارد کنید'
|
||||||
|
},
|
||||||
|
minLength: {
|
||||||
|
value: 20,
|
||||||
|
message: 'آدرس را کامل وارد کنید'
|
||||||
|
},
|
||||||
|
})}></Textarea>
|
||||||
|
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<p className="text-sm text-red-700 mt-1">
|
||||||
|
{errors?.address?.message}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div className={'w-full grid grid-cols-2 gap-x-2 gap-y-4'}>
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<Label>
|
||||||
|
|
||||||
|
<div className="flex flex-row mb-1 items-center gap-x-1">
|
||||||
|
<SiZalando color={"#959595"} size={15}/>
|
||||||
|
زیر بنا
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Input {...register('zirbana', {
|
||||||
|
required: {
|
||||||
|
value: true,
|
||||||
|
message: 'زیر بنا را وارد کنید'
|
||||||
|
},
|
||||||
|
pattern: {
|
||||||
|
value: /^[0-9\u0660-\u0669\u06F0-\u06F9]*$/,
|
||||||
|
message: 'متراژ زیر بنا را به درستی وارد کنید'
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
className="focus-visible:ring-1 focus-visible:ring-blue-800 focus-visible:outline-none focus-visible:ring-offset-1 drop-shadow-sm w-full rounded-md"/>
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<p className="text-sm text-red-700 mt-1">
|
||||||
|
{errors?.zirbana?.message}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="tabaghat">
|
||||||
|
|
||||||
|
<Label>
|
||||||
|
|
||||||
|
<div className="flex flex-row mb-1 items-center gap-x-1">
|
||||||
|
<MdApartment color={"#959595"} size={15}/>
|
||||||
|
تعداد طبقات
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Input {...register('tabaghat', {
|
||||||
|
required: {
|
||||||
|
value: true,
|
||||||
|
message: 'تعداد طبقات را وارد کنید'
|
||||||
|
},
|
||||||
|
pattern: {
|
||||||
|
value: /^[0-9\u0660-\u0669\u06F0-\u06F9]*$/,
|
||||||
|
message: 'تعداد طبقات را به درستی وارد کنید'
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
className="focus-visible:ring-1 focus-visible:ring-blue-800 focus-visible:outline-none focus-visible:ring-offset-1 drop-shadow-sm w-full rounded-md"/>
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<p className="text-sm text-red-700 mt-1">
|
||||||
|
{errors?.tabaghat?.message}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label>
|
||||||
|
|
||||||
|
<div className="flex flex-row mb-1 items-center gap-x-1">
|
||||||
|
<TiHome color={"#959595"} size={15}/>
|
||||||
|
تعداد واحد
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Input {...register('vahed', {
|
||||||
|
required: {
|
||||||
|
value: true,
|
||||||
|
message: 'تعداد واحد را وارد کنید'
|
||||||
|
},
|
||||||
|
pattern: {
|
||||||
|
value: /^[0-9\u0660-\u0669\u06F0-\u06F9]*$/,
|
||||||
|
message: 'تعداد واحد را به درستی وارد کنید'
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
className="focus-visible:ring-1 focus-visible:ring-blue-800 focus-visible:outline-none focus-visible:ring-offset-1 drop-shadow-sm w-full rounded-md"/>
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<p className="text-sm text-red-700 mt-1">
|
||||||
|
{errors?.vahed?.message}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={'flex'}>
|
||||||
|
<p className={'text-sm text-red-800'}>
|
||||||
|
نکته:
|
||||||
|
منظور تعداد طبقات و تعداد واحد های که شما در آن از محصولات لوله گستر گلپایگان استفاده کرده اید
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* step2 axha */}
|
||||||
|
<div className="bg-white drop-shadow-md flex flex-col gap-y-4 px-3 py-2 rounded-sm ">
|
||||||
|
|
||||||
|
{/* title: step2 tasavir */}
|
||||||
|
<div className="flex flex-row gap-x-3 items-center border-b-[1px] border-gray-300 pb-1">
|
||||||
|
<div>
|
||||||
|
<TbSquareRoundedNumber2Filled size={25} />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-x-1">
|
||||||
|
<h4 className="text-md">تصاویر پروژه</h4>
|
||||||
|
<p className="text-xs">تصاویر پروژه و محصولات لوله گستر گلپایگان</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* take pic */}
|
||||||
|
<div className="grid grid-cols-1 gap-x-4 gap-y-2 ">
|
||||||
|
|
||||||
|
{/* pic 1 */}
|
||||||
|
{[...Array(imageCount)].map((item, key) => {
|
||||||
|
return (
|
||||||
|
<div key={key} className="w-full">
|
||||||
|
<Label>
|
||||||
|
<div className="flex flex-row mb-1 items-center gap-x-1">
|
||||||
|
<FaImage color={"#959595"} size={22}/>
|
||||||
|
{`عکس شماره ${key+1}`}
|
||||||
|
</div>
|
||||||
|
{/* accept="image/*" capture={'environment'} */}
|
||||||
|
<Input onChange={(e)=>{fileHandler(e)}} type="file"
|
||||||
|
accept="image/*"
|
||||||
|
className="focus-visible:ring-1 focus-visible:ring-blue-800 focus-visible:outline-none focus-visible:ring-offset-1 drop-shadow-sm w-full rounded-md"/>
|
||||||
|
</Label>
|
||||||
|
<p className="text-sm text-red-700 mt-1"></p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* + add image button */}
|
||||||
|
<div className="flex flex-row justify-between mb-2">
|
||||||
|
<div className="text-sm">
|
||||||
|
حداقل 3 تصویر اضافه کنید
|
||||||
|
</div>
|
||||||
|
<div onClick={()=>{setImageCount(imageCount+1)}} className="">
|
||||||
|
<IoAddCircleSharp color="red" size={25}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{/* step3 arzesh proje */}
|
||||||
|
<div className="bg-white drop-shadow-md flex flex-col gap-y-4 px-3 py-2 rounded-sm ">
|
||||||
|
|
||||||
|
{/* title: step3 arzeshProje */}
|
||||||
|
<div className="flex flex-row gap-x-3 items-center border-b-[1px] border-gray-300 pb-1">
|
||||||
|
<div>
|
||||||
|
<TbSquareRoundedNumber3Filled size={25} />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-x-1">
|
||||||
|
<h4 className="text-md">ارزش پروژه</h4>
|
||||||
|
<p className="text-xs">مبلغ فاکتور محصولات لوله گستر گلپایگان</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* arzeshProje */}
|
||||||
|
<div className="grid grid-cols-1 gap-x-4">
|
||||||
|
|
||||||
|
{/* arzeshProje Input */}
|
||||||
|
<div className="w-full">
|
||||||
|
<Label>
|
||||||
|
<div className="flex flex-row mb-1 items-center gap-x-1">
|
||||||
|
<FaMoneyCheckDollar color={"#959595"} size={22} />
|
||||||
|
مبلغ فاکتور به تومان
|
||||||
|
</div>
|
||||||
|
<Input {...register("factor_price", {
|
||||||
|
required: {
|
||||||
|
value: true,
|
||||||
|
message: 'مبلغ فاکتور را وارد کنید'
|
||||||
|
},
|
||||||
|
minLength:{
|
||||||
|
value: 5,
|
||||||
|
message: 'مبلغ فاکتور را به درستی وارد کنید'
|
||||||
|
}
|
||||||
|
})} type="text"
|
||||||
|
onChange={(e) => {
|
||||||
|
let value = e.target.value.replace(/,/g, '');
|
||||||
|
value = convertToEnglishDigits(value);
|
||||||
|
if (/^[0-9]*$/.test(value)) {
|
||||||
|
e.target.value = Number(value).toLocaleString('en-US');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="focus-visible:ring-1 focus-visible:ring-blue-800 focus-visible:outline-none focus-visible:ring-offset-1 drop-shadow-sm w-full rounded-md" />
|
||||||
|
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<p className="text-sm text-red-700 mt-1">
|
||||||
|
{errors?.factor_price?.message}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="text-sm">
|
||||||
|
لطفا در ثبت اطلاعات دقت کنید، در بعضی از موارد کارشناسان شرکت لوله گستر گلپایگان برای صحت سنجی به محل پروژه مراجعه می کنند
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{createIsPending ? <p className={'text-center text-red-700 mt-2'}>در حال ثبت پروژه لطفا صبر کنید...</p> : (
|
||||||
|
<button className="bg-blue-500 text-white p-2 rounded-md w-full mt-2">ثبت پروژه</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import JavayezComponent from '@/components/main/javayez';
|
||||||
|
import { useQuery, } from '@tanstack/react-query'
|
||||||
|
import Image from 'next/image';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import axiosInstance from "@/axios/axiosInstance";
|
||||||
|
import {useMojriQuery} from "@/queries/home";
|
||||||
|
|
||||||
|
const Page = ()=>{
|
||||||
|
|
||||||
|
const {isSuccess, isLoading, isError, data: products} = useQuery(
|
||||||
|
{
|
||||||
|
queryKey: ['list-ja'],
|
||||||
|
queryFn: async ()=> {
|
||||||
|
return await axiosInstance.get('product/list/')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const {data: mojri_info,isError: mojriIsError,isSuccess: mojriIsSuccess} = useMojriQuery()
|
||||||
|
|
||||||
|
|
||||||
|
if(isLoading){
|
||||||
|
return <div>در حال بارگذاری</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isError){
|
||||||
|
return <div>خطا</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='p-2'>
|
||||||
|
<div className={`flex flex-row justify-between drop-shadow-md pt-2 pb-2 pr-4 pl-4 rounded-md bg-white`}>
|
||||||
|
|
||||||
|
|
||||||
|
<div className='flex flex-row w-full items-center gap-x-2'>
|
||||||
|
|
||||||
|
{ mojri_info?.data?.image ? (
|
||||||
|
<Image style={{
|
||||||
|
width: '50px',
|
||||||
|
height: '50px',
|
||||||
|
}} src={`${process.env.NEXT_PUBLIC_API_URL}${mojri_info?.data?.image}`} width={100} height={100} className='rounded-full' alt='profile' />
|
||||||
|
|
||||||
|
) : null }
|
||||||
|
|
||||||
|
<h2 className='w-full'>
|
||||||
|
{mojriIsSuccess ? mojri_info?.data?.name_family : null}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex flex-row text-center gap-x-6'>
|
||||||
|
{mojriIsSuccess ? (
|
||||||
|
<>
|
||||||
|
<div className='flex flex-col gap-y-0'>
|
||||||
|
<div>{mojri_info?.data?.project_count}</div>
|
||||||
|
<div>پروژه</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex flex-col gap-y-0'>
|
||||||
|
<div>{mojri_info?.data?.emtiyaz}</div>
|
||||||
|
<div>امتیاز</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex flex-row justify-between text-sm mt-2 items-center'>
|
||||||
|
<p>میدونی با ثبت پروژه ها میتونی کلی امتیاز و جایزه بگیری؟</p>
|
||||||
|
<Link className='bg-slate-200 rounded-sm underline text-red-600' href={'/'}>راهنما</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex flex-col gap-y-4 mt-4'>
|
||||||
|
|
||||||
|
{products?.data.map((item:any, index:number) => {
|
||||||
|
return <JavayezComponent key={index} data={item} />
|
||||||
|
})}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,462 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { FaAddressCard, FaCircleUser, FaLocationDot, FaMobileButton, FaSignsPost, FaUser } from "react-icons/fa6";
|
||||||
|
import { PiCertificateFill } from "react-icons/pi";
|
||||||
|
import { useForm, SubmitHandler } from "react-hook-form"
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { useMutation } from "@tanstack/react-query";
|
||||||
|
import axiosInstance from "@/axios/axiosInstance";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectGroup,
|
||||||
|
SelectItem,
|
||||||
|
SelectLabel,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select"
|
||||||
|
import { IoDocumentAttach, IoHome } from "react-icons/io5";
|
||||||
|
import {useGetCitys} from "@/queries/citys";
|
||||||
|
import {useEffect, useState} from "react";
|
||||||
|
import {useGetUserProfile, useUpdateUserProfile} from "@/queries/userprofile";
|
||||||
|
import {useMojriQuery} from "@/queries/home";
|
||||||
|
import {getUserProfile} from "@/api/userprofile";
|
||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
type Inputs = {
|
||||||
|
name_family: string
|
||||||
|
mobile: number
|
||||||
|
codemeli: number
|
||||||
|
city: any
|
||||||
|
address: string
|
||||||
|
codeposti: number
|
||||||
|
ax_mojri: any
|
||||||
|
ax_meli: any
|
||||||
|
ax_fani: any
|
||||||
|
ax_sayer: any
|
||||||
|
}
|
||||||
|
|
||||||
|
const Page = ()=>{
|
||||||
|
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationFn: (data: Inputs) => axiosInstance.post('/api/profile', data),
|
||||||
|
})
|
||||||
|
|
||||||
|
const citys = useGetCitys()
|
||||||
|
|
||||||
|
const [requiredStatus, setRequiredStatus] = useState<boolean>(true)
|
||||||
|
|
||||||
|
const mojri_info = useMojriQuery()
|
||||||
|
const {data:userprofile, isSuccess:userprofileIsSuccess, isError: userprofileIsError, isLoading:userprofileIsLoading} = useGetUserProfile()
|
||||||
|
const [ostan, setOstan] = useState<any | null>('')
|
||||||
|
const [fileha, setFileHa] = useState<any | null>([])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (userprofileIsSuccess){
|
||||||
|
setValue('name_family', userprofile?.cuser?.name_family)
|
||||||
|
setValue('mobile', userprofile?.cuser?.mobile)
|
||||||
|
setValue('codemeli', userprofile?.codemeli)
|
||||||
|
setValue('city', userprofile?.city)
|
||||||
|
setValue('address', userprofile?.address)
|
||||||
|
setValue('codeposti', userprofile?.codeposti)
|
||||||
|
setOstan(userprofile?.city?.ostan?.id)
|
||||||
|
|
||||||
|
//onChangeOstan({target:{value:userprofile?.city?.ostan?.id}})
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
if(citys.isSuccess){
|
||||||
|
if(userprofile?.city){
|
||||||
|
setCity(userprofile?.city?.ostan?.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mojri_info.data?.data?.step == 1){
|
||||||
|
setRequiredStatus(true)
|
||||||
|
}else{
|
||||||
|
setRequiredStatus(false)
|
||||||
|
}
|
||||||
|
}, [mojri_info.data, userprofileIsSuccess, citys.isSuccess]);
|
||||||
|
|
||||||
|
const updateUserProfile = useUpdateUserProfile()
|
||||||
|
|
||||||
|
const [shahrha, setShahrha] = useState<any | null>(null)
|
||||||
|
|
||||||
|
const onChangeOstan = (e:any)=>{
|
||||||
|
const ostan_id = e.target.value
|
||||||
|
const shahrha = citys.data?.filter((item:any)=>item.ostan == ostan_id)
|
||||||
|
setShahrha(shahrha)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onChangeFile = (e:any, name:string)=>{
|
||||||
|
const xfile = {
|
||||||
|
'name': name,
|
||||||
|
'file': e.target.files[0]
|
||||||
|
}
|
||||||
|
setFileHa((prev:any)=>[...prev, xfile])
|
||||||
|
}
|
||||||
|
|
||||||
|
const setCity = async (ostan:any)=>{
|
||||||
|
|
||||||
|
|
||||||
|
setTimeout(()=>{
|
||||||
|
const shahrha = citys.data?.filter((item:any)=>item.ostan == ostan)
|
||||||
|
setShahrha(shahrha)
|
||||||
|
}, 10)
|
||||||
|
|
||||||
|
setTimeout(()=>{
|
||||||
|
setValue('city', `${userprofile?.city?.id}`)
|
||||||
|
}, 100)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
watch,
|
||||||
|
setValue,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<Inputs>()
|
||||||
|
|
||||||
|
const onSubmit: SubmitHandler<Inputs> = async (data) => {
|
||||||
|
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('name_family', data.name_family)
|
||||||
|
formData.append('mobile', data.mobile.toString())
|
||||||
|
formData.append('codemeli', data.codemeli.toString())
|
||||||
|
formData.append('city', data.city)
|
||||||
|
formData.append('address', data.address)
|
||||||
|
formData.append('codeposti', data.codeposti.toString())
|
||||||
|
|
||||||
|
if(data.ax_mojri.length>=1){
|
||||||
|
formData.append('ax_mojri', data.ax_mojri[0])
|
||||||
|
}
|
||||||
|
if(data.ax_meli.length>=1){
|
||||||
|
formData.append('ax_meli', data.ax_meli[0])
|
||||||
|
}
|
||||||
|
if(data.ax_fani.length>=1){
|
||||||
|
formData.append('ax_fani', data.ax_fani[0])
|
||||||
|
}
|
||||||
|
if(data.ax_sayer.length>=1){
|
||||||
|
formData.append('ax_sayer', data.ax_sayer[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUserProfile.mutate(formData)
|
||||||
|
//mutation.mutate(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(userprofileIsLoading){
|
||||||
|
return (<p>درحال دریافت اطلاعات</p>)
|
||||||
|
}
|
||||||
|
if(userprofileIsError){
|
||||||
|
return (<p>خطا در دریافت اطلاعات</p>)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-y-2 pt-2 pr-2 pl-2 pb-2">
|
||||||
|
|
||||||
|
<div className="bg-white p-2 rounded-sm text-center">
|
||||||
|
مشخصات
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white flex flex-col gap-y-4 p-2 rounded-sm text-center">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<Label>
|
||||||
|
|
||||||
|
<div className="flex flex-row mb-1 items-center gap-x-1">
|
||||||
|
<FaUser color={"#959595"} size={20} />
|
||||||
|
نام و نام خانوادگی
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Input {...register("name_family", {
|
||||||
|
required: {
|
||||||
|
value: requiredStatus,
|
||||||
|
message: 'پرکردن این فیلد ضروری است',
|
||||||
|
},
|
||||||
|
minLength: {
|
||||||
|
value: 4,
|
||||||
|
message: 'حداقل طول نام 4 کاراکتر است',
|
||||||
|
}
|
||||||
|
} )} type="text" className="focus-visible:ring-1 focus-visible:ring-blue-800 focus-visible:outline-none focus-visible:ring-offset-1 drop-shadow-sm w-full rounded-md" />
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<p className="text-sm text-red-700 mt-1">{errors?.name_family?.message}</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-x-4 gap-y-4">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<Label>
|
||||||
|
|
||||||
|
<div className="flex flex-row mb-1 items-center gap-x-1">
|
||||||
|
<FaMobileButton color={"#959595"} size={22} />
|
||||||
|
موبایل
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Input {...register("mobile", {
|
||||||
|
required: {
|
||||||
|
value: requiredStatus,
|
||||||
|
message: 'پرکردن این فیلد ضروری است',
|
||||||
|
},
|
||||||
|
pattern: {
|
||||||
|
value: /^(09\d{9}|9\d{9})$/,
|
||||||
|
message: 'شماره وارد شده معتبر نیست',
|
||||||
|
}
|
||||||
|
} )} type="text" className="focus-visible:ring-1 focus-visible:ring-blue-800 focus-visible:outline-none focus-visible:ring-offset-1 drop-shadow-sm w-full rounded-md" />
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<p className="text-sm text-red-700 mt-1">{errors?.mobile?.message}</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<Label>
|
||||||
|
|
||||||
|
<div className="flex flex-row mb-1 items-center gap-x-1">
|
||||||
|
<FaAddressCard color={"#959595"} size={22} />
|
||||||
|
کدملی
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Input {...register("codemeli", {
|
||||||
|
required: {
|
||||||
|
value: requiredStatus,
|
||||||
|
message: 'پرکردن این فیلد ضروری است',
|
||||||
|
},
|
||||||
|
pattern: {
|
||||||
|
value: /^[0-9]{10}$/,
|
||||||
|
message: 'کدملی وارد شده معتبر نیست',
|
||||||
|
}
|
||||||
|
} )} type="text" className="focus-visible:ring-1 focus-visible:ring-blue-800 focus-visible:outline-none focus-visible:ring-offset-1 drop-shadow-sm w-full rounded-md" />
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<p className="text-sm text-red-700 mt-1">{errors?.codemeli?.message}</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<Label>
|
||||||
|
|
||||||
|
<div className="flex flex-row mb-1 items-center gap-x-1">
|
||||||
|
<FaLocationDot color={"#959595"} size={22} />
|
||||||
|
استان
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<select value={ostan} onChange={onChangeOstan} className="w-full border p-2 rounded-md">
|
||||||
|
<option value={''}>انتخاب کنید</option>
|
||||||
|
{citys.data?.map((city:any, key:number)=>{
|
||||||
|
|
||||||
|
if(city.is_ostan){
|
||||||
|
return (
|
||||||
|
<option key={key} value={city.id}>{city.name}</option>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<p className="text-sm text-red-700 mt-1"></p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<Label>
|
||||||
|
|
||||||
|
<div className="flex flex-row mb-1 items-center gap-x-1">
|
||||||
|
<FaLocationDot color={"#959595"} size={22} />
|
||||||
|
شهر
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<select {...register("city", {required: requiredStatus})} className="w-full border p-2 rounded-md">
|
||||||
|
<option value={''}>انتخاب کنید</option>
|
||||||
|
{shahrha?.map((shahr:any, key:number)=>{
|
||||||
|
return (
|
||||||
|
<option key={key} value={shahr.id}>{shahr.name}</option>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<p className="text-sm text-red-700 mt-1">{errors?.city?.message}</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label>
|
||||||
|
|
||||||
|
<div className="flex flex-row mb-1 items-center gap-x-1">
|
||||||
|
<IoHome color={"#959595"} size={22} />
|
||||||
|
آدرس
|
||||||
|
</div>
|
||||||
|
<Textarea {...register("address", {
|
||||||
|
required: {
|
||||||
|
value: requiredStatus,
|
||||||
|
message: 'پرکردن این فیلد ضروری است',
|
||||||
|
}
|
||||||
|
} )} className="focus-visible:ring-1 focus-visible:ring-blue-800 focus-visible:outline-none focus-visible:ring-offset-1 drop-shadow-sm w-full rounded-md" />
|
||||||
|
|
||||||
|
</Label>
|
||||||
|
<p className="text-sm text-red-700 mt-1">{errors?.address?.message}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label>
|
||||||
|
<div className="flex flex-row mb-1 items-center gap-x-1">
|
||||||
|
<FaSignsPost color={"#959595"} size={22} />
|
||||||
|
کد پستی
|
||||||
|
</div>
|
||||||
|
<Input {...register("codeposti", {
|
||||||
|
required: {
|
||||||
|
value: requiredStatus,
|
||||||
|
message: 'پرکردن این فیلد ضروری است',
|
||||||
|
}
|
||||||
|
} )} type="text" className="focus-visible:ring-1 focus-visible:ring-blue-800 focus-visible:outline-none focus-visible:ring-offset-1 drop-shadow-sm w-full rounded-md" />
|
||||||
|
</Label>
|
||||||
|
<p className="text-sm text-red-700 mt-1">{errors?.codeposti?.message}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label>
|
||||||
|
<div className="flex flex-row mb-1 items-center gap-x-1">
|
||||||
|
<FaCircleUser color={"#959595"} size={22} />
|
||||||
|
عکس مجری
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{userprofile?.ax_mojri ? (
|
||||||
|
<Link target="_blank" href={`${process.env.NEXT_PUBLIC_API_URL}${userprofile?.ax_mojri}`}>
|
||||||
|
<div className={"mojri_image mb-1"}>
|
||||||
|
<Image src={`${process.env.NEXT_PUBLIC_API_URL}${userprofile?.ax_mojri}`} alt={'a'}
|
||||||
|
width={100} height={100}/>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<Input {...register('ax_mojri', {
|
||||||
|
required: {
|
||||||
|
value: requiredStatus,
|
||||||
|
message: 'پرکردن این فیلد ضروری است',
|
||||||
|
},
|
||||||
|
})} type="file" className="focus-visible:ring-1 focus-visible:ring-blue-800 focus-visible:outline-none focus-visible:ring-offset-1 drop-shadow-sm w-full rounded-md" />
|
||||||
|
</Label>
|
||||||
|
<p className="text-sm text-red-700 mt-1">{errors?.ax_mojri?.message}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label>
|
||||||
|
<div className="flex flex-row mb-1 items-center gap-x-1">
|
||||||
|
<FaAddressCard color={"#959595"} size={22}/>
|
||||||
|
عکس کارت ملی
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{userprofile?.ax_meli ? (
|
||||||
|
<Link target="_blank" href={`${process.env.NEXT_PUBLIC_API_URL}${userprofile?.ax_meli}`}>
|
||||||
|
<div className={"mojri_image mb-1"}>
|
||||||
|
<Image src={`${process.env.NEXT_PUBLIC_API_URL}${userprofile?.ax_meli}`} alt={'a'}
|
||||||
|
width={100} height={100}/>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<Input {...register('ax_meli', {
|
||||||
|
required: {
|
||||||
|
value: requiredStatus,
|
||||||
|
message: 'پرکردن این فیلد ضروری است',
|
||||||
|
},
|
||||||
|
})} type="file"
|
||||||
|
className="focus-visible:ring-1 focus-visible:ring-blue-800 focus-visible:outline-none focus-visible:ring-offset-1 drop-shadow-sm w-full rounded-md"/>
|
||||||
|
</Label>
|
||||||
|
<p className="text-sm text-red-700 mt-1">{errors?.ax_meli?.message}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label>
|
||||||
|
<div className="flex flex-row mb-1 items-center gap-x-1">
|
||||||
|
<PiCertificateFill color={"#959595"} size={22}/>
|
||||||
|
عکس گواهینامه فنی حرفه ای
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{userprofile?.ax_fani ? (
|
||||||
|
<Link target="_blank" href={`${process.env.NEXT_PUBLIC_API_URL}${userprofile?.ax_fani}`}>
|
||||||
|
<div className={"mojri_image mb-1"}>
|
||||||
|
<Image src={`${process.env.NEXT_PUBLIC_API_URL}${userprofile?.ax_fani}`} alt={'a'}
|
||||||
|
width={100} height={100}/>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
):null}
|
||||||
|
|
||||||
|
<Input {...register('ax_fani', {
|
||||||
|
required: {
|
||||||
|
value: false,
|
||||||
|
message: 'پرکردن این فیلد ضروری است',
|
||||||
|
},
|
||||||
|
})} type="file"
|
||||||
|
className="focus-visible:ring-1 focus-visible:ring-blue-800 focus-visible:outline-none focus-visible:ring-offset-1 drop-shadow-sm w-full rounded-md"/>
|
||||||
|
</Label>
|
||||||
|
<p className="text-sm text-red-700 mt-1">{errors?.ax_fani?.message}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label>
|
||||||
|
<div className="flex flex-row mb-1 items-center gap-x-1">
|
||||||
|
<IoDocumentAttach color={"#959595"} size={22}/>
|
||||||
|
سایر مدارک
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{userprofile?.ax_sayer ? (
|
||||||
|
|
||||||
|
<Link target="_blank" href={`${process.env.NEXT_PUBLIC_API_URL}${userprofile?.ax_sayer}`}>
|
||||||
|
<div className={"mojri_image mb-1"}>
|
||||||
|
<Image src={`${process.env.NEXT_PUBLIC_API_URL}${userprofile?.ax_sayer}`} alt={'a'}
|
||||||
|
width={100} height={100}/>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
|
||||||
|
<Input {...register('ax_sayer', {
|
||||||
|
required: {
|
||||||
|
value: false,
|
||||||
|
message: 'پرکردن این فیلد ضروری است',
|
||||||
|
},
|
||||||
|
})} type="file"
|
||||||
|
className="focus-visible:ring-1 focus-visible:ring-blue-800 focus-visible:outline-none focus-visible:ring-offset-1 drop-shadow-sm w-full rounded-md"/>
|
||||||
|
</Label>
|
||||||
|
<p className="text-sm text-red-700 mt-1">{errors?.ax_sayer?.message}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button onClick={handleSubmit(onSubmit)}
|
||||||
|
className="bg-blue-600 text-white p-2 rounded-md w-full">ثبت
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,236 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { BsHourglassSplit } from "react-icons/bs"
|
||||||
|
import { FaImage, FaLocationCrosshairs, FaMobile, FaMoneyCheckDollar, FaUser } from "react-icons/fa6";
|
||||||
|
import { TbSquareRoundedNumber3Filled, TbSquareRoundedNumber4Filled } from "react-icons/tb";
|
||||||
|
import {useProjectQuery, useUpdateProjectSellerMutation} from "@/queries/project";
|
||||||
|
import ProjectStatus from "@/components/main/project/projectstatus";
|
||||||
|
import { useForm, SubmitHandler } from "react-hook-form"
|
||||||
|
import {IoBarcode} from "react-icons/io5";
|
||||||
|
import {getProjectCodeValidation} from "@/api/projectcode";
|
||||||
|
import {useState} from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
// regex for validation iranian mobile number start with 09 or 98 or 9
|
||||||
|
const iraninanMobileRegex = /^(\+98|0)?9\d{9}$/
|
||||||
|
|
||||||
|
type InputParams = {
|
||||||
|
params: {
|
||||||
|
id: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type FormInputs = {
|
||||||
|
seller_name: string
|
||||||
|
seller_address: string
|
||||||
|
seller_mobile: number
|
||||||
|
projectcode: string
|
||||||
|
}
|
||||||
|
const project_code_regex = /^[a-z]{3}\d{8}$/
|
||||||
|
|
||||||
|
const Page = ({params} : InputParams)=>{
|
||||||
|
const {data: project, isError, isSuccess, isLoading} = useProjectQuery(params.id)
|
||||||
|
const {mutate: updateProject, data: update_data, isError: updateIsError, isSuccess: updateIsSuccess} = useUpdateProjectSellerMutation()
|
||||||
|
const { register, handleSubmit, formState: { errors } } = useForm<FormInputs>();
|
||||||
|
const [projectCodeMessage, setProjectCodeMessage] = useState<string | null>(null)
|
||||||
|
const onSubmit: SubmitHandler<FormInputs> = async (data) => {
|
||||||
|
|
||||||
|
const project_code_test : boolean = project_code_regex.test(data.projectcode)
|
||||||
|
|
||||||
|
if(project_code_test){
|
||||||
|
|
||||||
|
const result = await getProjectCodeValidation(data.projectcode, setProjectCodeMessage)
|
||||||
|
if(result?.project_code_id){
|
||||||
|
//console.log(result)
|
||||||
|
updateProject({id: params.id, fields: data})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
//updateProject({id: params.id, fields: data})
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isLoading) return <div>loading...</div>
|
||||||
|
if(isError) return <div>error</div>
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-y-2 pt-2 pr-2 pl-2 pb-2">
|
||||||
|
|
||||||
|
<div className={` ${project?.data?.status === 'AwaitComp' ? 'bg-blue-800 text-white': 'bg-white'} p-2 rounded-sm text-right`}>
|
||||||
|
|
||||||
|
{project?.data?.status === 'AwaitComp' ? (
|
||||||
|
<p>در انتظار تکمیل اطلاعات...</p>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{project?.data?.status != 'AwaitComp' ? (
|
||||||
|
<p>مشاهده پروژه</p>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<ProjectStatus project={project?.data} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* project images */}
|
||||||
|
<div className="bg-white flex flex-row gap-x-2 p-2 rounded-sm text-right">
|
||||||
|
<div>تصاویر:</div>
|
||||||
|
{project?.data.project_image.map((image:any, key:number)=>{
|
||||||
|
return (
|
||||||
|
<Link href={`${process.env.NEXT_PUBLIC_API_URL}${image.image}`} key={key} target={'_blank'}>
|
||||||
|
<div key={key} className={'bg-gray-500 text-white px-2 rounded-xl'}>
|
||||||
|
{key+1}
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* step4 foroshande va factor */}
|
||||||
|
{project?.data?.status === 'AwaitComp'? (
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)} className="bg-white drop-shadow-md border-red-800 border-[1px] flex flex-col gap-y-4 px-3 py-2 rounded-sm ">
|
||||||
|
|
||||||
|
{/* title: step4 foroshande va factor */}
|
||||||
|
<div className="flex flex-row gap-x-3 items-center border-b-[1px] border-gray-300 pb-1">
|
||||||
|
<div>
|
||||||
|
<TbSquareRoundedNumber4Filled color={'#d9133f'} size={25} />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-x-1">
|
||||||
|
<h4 className="text-md">تکمیل اطلاعات فروشنده</h4>
|
||||||
|
<p className="text-xs">اطلاعات مربوط به فروشنده را با دقت تکمیل کنید</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* foroshande va factor Values */}
|
||||||
|
<div className="grid grid-cols-2 gap-x-4">
|
||||||
|
|
||||||
|
{/* name Input */}
|
||||||
|
<div className="w-full">
|
||||||
|
<Label>
|
||||||
|
<div className="flex flex-row mb-1 items-center gap-x-1">
|
||||||
|
<FaUser color={"#959595"} size={22} />
|
||||||
|
نام فروشنده
|
||||||
|
</div>
|
||||||
|
<Input {...register('seller_name',
|
||||||
|
{
|
||||||
|
required: "نام فروشنده الزامی است",
|
||||||
|
minLength: {value: 5, message: "نام فروشنده را کامل وارد کنید"},
|
||||||
|
}
|
||||||
|
)} type="text"
|
||||||
|
className="focus-visible:ring-1 focus-visible:ring-blue-800 focus-visible:outline-none focus-visible:ring-offset-1 drop-shadow-sm w-full rounded-md" />
|
||||||
|
|
||||||
|
</Label>
|
||||||
|
<p className="text-sm text-red-700 mt-1">
|
||||||
|
{errors.seller_name?.message}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* mobile Input */}
|
||||||
|
<div className="w-full">
|
||||||
|
<Label>
|
||||||
|
<div className="flex flex-row mb-1 items-center gap-x-1">
|
||||||
|
<FaMobile color={"#959595"} size={22} />
|
||||||
|
موبایل فروشنده
|
||||||
|
</div>
|
||||||
|
<Input {...register('seller_mobile',{
|
||||||
|
required: "شماره موبایل الزامی است",
|
||||||
|
pattern: {
|
||||||
|
value: /^(\+98|0)?9\d{9}$/,
|
||||||
|
message: "شماره موبایل وارد شده صحیح نیست"
|
||||||
|
}
|
||||||
|
})} type="text"
|
||||||
|
className="focus-visible:ring-1 focus-visible:ring-blue-800 focus-visible:outline-none focus-visible:ring-offset-1 drop-shadow-sm w-full rounded-md" />
|
||||||
|
|
||||||
|
</Label>
|
||||||
|
<p className="text-sm text-red-700 mt-1">
|
||||||
|
{errors.seller_mobile?.message}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* address */}
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<div className="w-full">
|
||||||
|
<Label>
|
||||||
|
<div className="flex flex-row mb-1 items-center gap-x-1">
|
||||||
|
<FaLocationCrosshairs color={"#959595"} size={22} />
|
||||||
|
آدرس دقیق فروشگاه
|
||||||
|
</div>
|
||||||
|
<Textarea {...register('seller_address', {
|
||||||
|
required: "آدرس فروشنده الزامی است",
|
||||||
|
minLength: {value: 10, message: "آدرس فروشنده را کامل وارد کنید"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="focus-visible:ring-1 focus-visible:ring-blue-800 focus-visible:outline-none focus-visible:ring-offset-1 drop-shadow-sm w-full rounded-md"
|
||||||
|
></Textarea>
|
||||||
|
|
||||||
|
</Label>
|
||||||
|
<p className="text-sm text-red-700 mt-1">
|
||||||
|
{errors.seller_address?.message}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* project code */}
|
||||||
|
<div className={'flex flex-row'}>
|
||||||
|
<div className="w-full">
|
||||||
|
<Label>
|
||||||
|
<div className="flex flex-row mb-1 items-center gap-x-1">
|
||||||
|
<IoBarcode color={"#959595"} size={22} />
|
||||||
|
<div>کد ثبت پروژه</div>
|
||||||
|
</div>
|
||||||
|
<Input {...register('projectcode',{
|
||||||
|
required: {
|
||||||
|
value: true,
|
||||||
|
message: "کد ثبت پروژه الزامی است"
|
||||||
|
},
|
||||||
|
pattern: {
|
||||||
|
value: /^[a-z]{3}\d{8}$/,
|
||||||
|
message: "کد وارد شده صحیح نیست از فروشنده کد معتبر بخواهید"
|
||||||
|
},
|
||||||
|
})} type="text"
|
||||||
|
className="focus-visible:ring-1 focus-visible:ring-blue-800 focus-visible:outline-none focus-visible:ring-offset-1 drop-shadow-sm w-full rounded-md" />
|
||||||
|
|
||||||
|
|
||||||
|
</Label>
|
||||||
|
<p className="text-sm text-red-700 mt-1">
|
||||||
|
{projectCodeMessage? (projectCodeMessage) : (errors.projectcode?.message)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* factor pic */}
|
||||||
|
|
||||||
|
<div className="hidden flex flex-row">
|
||||||
|
<div className="w-full">
|
||||||
|
<Label>
|
||||||
|
<div className="flex flex-row mb-1 items-center gap-x-1">
|
||||||
|
<FaImage color={"#959595"} size={22} />
|
||||||
|
تصویر فاکتور داری مهر و امضاء
|
||||||
|
</div>
|
||||||
|
<Input type="file" className="focus-visible:ring-1 focus-visible:ring-blue-800 focus-visible:outline-none focus-visible:ring-offset-1 drop-shadow-sm w-full rounded-md" />
|
||||||
|
</Label>
|
||||||
|
<p className="text-sm text-red-700 mt-1"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-xs">
|
||||||
|
<p>لطفا در ثبت اطلاعات دقت کنید، در بعضی از موارد نماینده یا کارشناسان شرکت لوله گستر گلپایگان برای صحت سنجی به محل پروژه مراجعه می کنند</p>
|
||||||
|
</div>
|
||||||
|
{/* submit button */}
|
||||||
|
<button type="submit" className="bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded-md">
|
||||||
|
موافقم، ثبت کن
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Page
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import {useProjectsQuery} from "@/queries/project";
|
||||||
|
import ProjectStatus from "@/components/main/project/projectstatus";
|
||||||
|
import Link from 'next/link'
|
||||||
|
|
||||||
|
|
||||||
|
// create function to seprate number with comma
|
||||||
|
function numberWithCommas(x:number) {
|
||||||
|
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||||
|
}
|
||||||
|
|
||||||
|
const Page = ()=>{
|
||||||
|
const {data:projects, isLoading, isError, isSuccess} = useProjectsQuery()
|
||||||
|
if (isLoading) return <div>در حال بارگذاری</div>
|
||||||
|
if (isError) return <div>خطا</div>
|
||||||
|
return(
|
||||||
|
<div className="flex flex-col gap-y-2 pt-2 pr-2 pl-2 pb-2">
|
||||||
|
<div className="bg-white p-2 rounded-sm text-right">
|
||||||
|
پروژه های ثبت شده
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{projects?.data.map((project:any, key:number)=>{
|
||||||
|
return (
|
||||||
|
<Link href={`/projects/${project.id}`} key={key}>
|
||||||
|
<ProjectStatus project={project} key={key} />
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default Page
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import "../globals.css";
|
||||||
|
import localFont from 'next/font/local'
|
||||||
|
import Script from 'next/script';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "باشگاه مجریان لوله گستر گلپایگان",
|
||||||
|
description: "باشگاه مجریان",
|
||||||
|
};
|
||||||
|
|
||||||
|
const myFont = localFont({
|
||||||
|
src: '../fonts/woff2/IRANSansX-Regular.woff2',
|
||||||
|
display: 'swap',
|
||||||
|
})
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) {
|
||||||
|
return (
|
||||||
|
<html lang="fa" dir="rtl" className={myFont.className}>
|
||||||
|
<body>{children}</body>
|
||||||
|
<Script
|
||||||
|
id="goftino-widget"
|
||||||
|
strategy="afterInteractive"
|
||||||
|
src="https://www.goftino.com/widget/6JNZy5"
|
||||||
|
/>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { useForm, SubmitHandler } from "react-hook-form"
|
||||||
|
import axios, {AxiosInstance, AxiosResponse} from "axios";
|
||||||
|
import {useEffect, useState} from "react";
|
||||||
|
import Image from "next/image";
|
||||||
|
import {useRouter} from "next/navigation";
|
||||||
|
|
||||||
|
function setCookie(name:any,value:any,days:any) {
|
||||||
|
var expires = "";
|
||||||
|
if (days) {
|
||||||
|
var date = new Date();
|
||||||
|
date.setTime(date.getTime() + (days*24*60*60*1000));
|
||||||
|
expires = "; expires=" + date.toUTCString();
|
||||||
|
}
|
||||||
|
document.cookie = name + "=" + (value || "") + expires + "; path=/";
|
||||||
|
}
|
||||||
|
|
||||||
|
type InputParams = {
|
||||||
|
mobile: any
|
||||||
|
}
|
||||||
|
|
||||||
|
type Inputs = {
|
||||||
|
otp_code: number
|
||||||
|
}
|
||||||
|
const Page = ({params}: {params: InputParams})=>{
|
||||||
|
|
||||||
|
const [errorMessage, setErrorMessage] = useState<string | null>(null)
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
setValue,
|
||||||
|
watch,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<Inputs>()
|
||||||
|
const onSubmit: SubmitHandler<Inputs> = async (data:Inputs) => {
|
||||||
|
|
||||||
|
try {
|
||||||
|
setErrorMessage(null)
|
||||||
|
const result: AxiosResponse = await axios.post(`${process.env.NEXT_PUBLIC_API_URL}/auth/mojri/mobile/verify/`, {
|
||||||
|
otp_code: data.otp_code,
|
||||||
|
mobile: params.mobile[0]
|
||||||
|
})
|
||||||
|
if (result.status === 200) {
|
||||||
|
setCookie('isAuthAp', 'true', 365)
|
||||||
|
localStorage.setItem('rt', result.data.refresh)
|
||||||
|
localStorage.setItem('at', result.data.access)
|
||||||
|
router.push('/')
|
||||||
|
} else {
|
||||||
|
setErrorMessage("کد ورورد معتبر نیست!")
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(e.response.data)
|
||||||
|
localStorage.removeItem('rt')
|
||||||
|
localStorage.removeItem('at')
|
||||||
|
setErrorMessage("کد ورورد معتبر نیست!")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const [otpCode, setOtpCode] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if ('OTPCredential' in window) {
|
||||||
|
// Only proceed if the WebOTP API is available
|
||||||
|
const ac = new AbortController();
|
||||||
|
|
||||||
|
navigator.credentials
|
||||||
|
.get({
|
||||||
|
otp: { transport: ['sms'] },
|
||||||
|
signal: ac.signal,
|
||||||
|
})
|
||||||
|
.then((otp:any) => {
|
||||||
|
if (otp) {
|
||||||
|
setOtpCode(otp.code);
|
||||||
|
setValue('otp_code', otp.code);
|
||||||
|
// Optionally, you can auto-submit the form here
|
||||||
|
// form.submit();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
return () => {
|
||||||
|
ac.abort();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-slate-200 h-screen pt-4">
|
||||||
|
<div className="drop-shadow-md flex flex-col w-[90%] md:w-[400px] m-auto p-4 bg-white rounded-lg">
|
||||||
|
|
||||||
|
<div className="flex flex-col mb-8 text-center items-center justify-center">
|
||||||
|
|
||||||
|
<Image src={'/logo.png'} width={200} height={100} alt={'a'}/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-y-2">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label>
|
||||||
|
کد تایید پیامک شده را وارد کنید
|
||||||
|
<Input className={"border-red-600"} type={'text'} {...register("otp_code", {
|
||||||
|
required: "کد تایید الزامی است",
|
||||||
|
pattern: {
|
||||||
|
value: /^\d{6}$/,
|
||||||
|
message: "کد تایید صحیح نیست"
|
||||||
|
}
|
||||||
|
})}/>
|
||||||
|
</Label>
|
||||||
|
<p className={'text-sm mt-1 text-red-800'}>{errors?.otp_code?.message}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div className="text-center">
|
||||||
|
<Button className="bg-green-800">ورود به باشگاه</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>{errorMessage}</p>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div className="border-b-[1px] border-slate-200 mt-4 mb-4"></div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-y-2">
|
||||||
|
|
||||||
|
<p className="text-center text-xs">در صورتی که هنوز عضو نیستید ابتدا ثبت نام کنید</p>
|
||||||
|
|
||||||
|
<div className="text-center">
|
||||||
|
<Link href="/register" className="bg-red-800 text-xs p-[0.4rem] m-0 rounded-md text-white">ثبت
|
||||||
|
نام در باشگاه</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { useForm, SubmitHandler } from "react-hook-form"
|
||||||
|
import axios, {AxiosInstance, AxiosResponse} from "axios";
|
||||||
|
import {useState} from "react";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover"
|
||||||
|
|
||||||
|
import {useRouter} from "next/navigation";
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
|
type Inputs = {
|
||||||
|
mobile: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const Page = ()=>{
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const [errorMessage, setErrorMessage] = useState<string | null>(null)
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
watch,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<Inputs>()
|
||||||
|
const onSubmit: SubmitHandler<Inputs> = async (data) => {
|
||||||
|
|
||||||
|
try {
|
||||||
|
setErrorMessage(null)
|
||||||
|
const result: AxiosResponse = await axios.post(`${process.env.NEXT_PUBLIC_API_URL}/auth/mojri/mobile/`,data)
|
||||||
|
if (result.status === 200) {
|
||||||
|
router.push(`/login/${data.mobile}/`)
|
||||||
|
} else {
|
||||||
|
setErrorMessage("خطا در ارسال کد ورورد")
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
setErrorMessage("شماره موبایل صحیح نیست!")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-slate-200 h-screen pt-4">
|
||||||
|
<div className="drop-shadow-md flex flex-col w-[90%] md:w-[400px] m-auto p-4 bg-white rounded-lg">
|
||||||
|
|
||||||
|
<div className="flex flex-col mb-8 text-center items-center justify-center">
|
||||||
|
|
||||||
|
<Image src={'/logo.png'} width={200} height={100} alt={'a'}/>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-y-2">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label>
|
||||||
|
شماره موبایل
|
||||||
|
<Input type={'text'} {...register("mobile", {
|
||||||
|
required: "شماره موبایل الزامی است",
|
||||||
|
pattern: {
|
||||||
|
value: /^(\+98|0)?9\d{9}$/,
|
||||||
|
message: "شماره موبایل وارد شده صحیح نیست"
|
||||||
|
}
|
||||||
|
})}/>
|
||||||
|
</Label>
|
||||||
|
<p className={'text-sm mt-1'}>{errors?.mobile?.message}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div className="text-center">
|
||||||
|
<Button className="bg-blue-600">ارسال کد ورود</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>{errorMessage}</p>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div className="border-b-[1px] border-slate-200 mt-4 mb-4"></div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-y-2">
|
||||||
|
|
||||||
|
<p className="text-center text-xs">در صورتی که هنوز عضو نیستید ابتدا ثبت نام کنید</p>
|
||||||
|
|
||||||
|
<div className="text-center">
|
||||||
|
<Link href="/register" className="bg-red-800 text-xs p-[0.4rem] m-0 rounded-md text-white">ثبت نام در باشگاه</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={'flex flex-row justify-center'}>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger className={'text-center m-auto'}>
|
||||||
|
<p className={'text-center text-gray-500 text-sm mt-2 underline'}>
|
||||||
|
برنامه نویسی و توسعه: واحد فناوری اطلاعات (محمد جواد فصاحت)
|
||||||
|
</p>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className={'text-center text-sm m-auto'}>
|
||||||
|
تلفن تماس : 09202202129
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { HiMiniUser } from "react-icons/hi2";
|
||||||
|
import { FaMobileButton } from "react-icons/fa6";
|
||||||
|
import { useForm, SubmitHandler } from "react-hook-form"
|
||||||
|
import {registerUser} from "@/api/register";
|
||||||
|
import {useRouter} from "next/navigation";
|
||||||
|
import axios, {AxiosResponse} from "axios";
|
||||||
|
import {Popover, PopoverContent, PopoverTrigger} from "@/components/ui/popover";
|
||||||
|
|
||||||
|
type Inputs = {
|
||||||
|
name: string
|
||||||
|
mobile: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const Page = ()=>{
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
watch,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<Inputs>()
|
||||||
|
|
||||||
|
const onSubmit: SubmitHandler<Inputs> = async (data) => {
|
||||||
|
try {
|
||||||
|
const result: AxiosResponse = await axios.post(`${process.env.NEXT_PUBLIC_API_URL}/register/`, data)
|
||||||
|
if (result.status == 201) {
|
||||||
|
alert('ثبت نام با موفقیت انجام شد، لطفا وارد شوید')
|
||||||
|
router.push(`/login/`)
|
||||||
|
} else {
|
||||||
|
alert('لطفا وارد شوید')
|
||||||
|
router.push(`/login/`)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
alert('لطفا وارد شوید')
|
||||||
|
router.push(`/login/`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-slate-200 h-screen pt-4">
|
||||||
|
<div className="drop-shadow-md flex flex-col w-[90%] md:w-[400px] m-auto p-4 bg-white rounded-lg">
|
||||||
|
|
||||||
|
<div className="mb-8">
|
||||||
|
<h2 className="text-center">عضویت در باشگاه مجریان لوله گستر گلپایگان</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-y-4">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label>
|
||||||
|
|
||||||
|
<div className="flex flex-row mb-1 items-center gap-x-1">
|
||||||
|
<HiMiniUser color={"#959595"} size={25}/>
|
||||||
|
نام و نام خانوادگی
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Input {...register("name", {
|
||||||
|
required: {
|
||||||
|
value: true,
|
||||||
|
message: 'پرکردن این فیلد ضروری است',
|
||||||
|
},
|
||||||
|
minLength: {
|
||||||
|
value: 4,
|
||||||
|
message: 'نام و نام خانوادگی وارد شده معتبر نیست',
|
||||||
|
}
|
||||||
|
})} type="text"
|
||||||
|
className="focus-visible:ring-1 focus-visible:ring-blue-800 focus-visible:outline-none focus-visible:ring-offset-1 drop-shadow-sm w-full rounded-md"/>
|
||||||
|
</Label>
|
||||||
|
<p className="text-sm text-red-700 mt-1">{errors?.name?.message}</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label>
|
||||||
|
|
||||||
|
<div className="flex flex-row mb-1 items-center gap-x-1">
|
||||||
|
<FaMobileButton color={"#959595"} size={22}/>
|
||||||
|
شماره موبایل
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Input {...register("mobile", {
|
||||||
|
required: {
|
||||||
|
value: true,
|
||||||
|
message: 'پرکردن این فیلد ضروری است',
|
||||||
|
},
|
||||||
|
pattern: {
|
||||||
|
value: /^(09\d{9}|9\d{9})$/,
|
||||||
|
message: 'شماره وارد شده معتبر نیست',
|
||||||
|
}
|
||||||
|
})} type="text"
|
||||||
|
className="focus-visible:ring-1 focus-visible:ring-blue-800 focus-visible:outline-none focus-visible:ring-offset-1 drop-shadow-sm w-full rounded-md"/>
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<p className="text-sm text-red-700 mt-1">{errors?.mobile?.message}</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div className="text-center mt-2">
|
||||||
|
<Button className=" drop-shadow-lg bg-red-800">عضویت در باشگاه</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div className="border-b-[1px] border-slate-200 mt-4 mb-4"></div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-y-2">
|
||||||
|
|
||||||
|
<p className="text-center text-xs">در صورتی که قبلا عضو شده اید وارد شوید</p>
|
||||||
|
|
||||||
|
<div className="text-center">
|
||||||
|
<Link href="/login" className="bg-blue-600 text-xs p-[0.4rem] m-0 rounded-md text-white">ورورد
|
||||||
|
به باشگاه مجریان</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={'flex flex-row justify-center'}>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger className={'text-center m-auto'}>
|
||||||
|
<p className={'text-center text-gray-500 text-sm mt-2 underline'}>
|
||||||
|
برنامه نویسی و توسعه: واحد فناوری اطلاعات (محمد جواد فصاحت)
|
||||||
|
</p>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className={'text-center text-sm m-auto'}>
|
||||||
|
تلفن تماس : 09202202129
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Page;
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+69
-26
@@ -1,33 +1,76 @@
|
|||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
:root {
|
@layer base {
|
||||||
--foreground-rgb: 0, 0, 0;
|
|
||||||
--background-start-rgb: 214, 219, 220;
|
|
||||||
--background-end-rgb: 255, 255, 255;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
:root {
|
||||||
--foreground-rgb: 255, 255, 255;
|
--background: 0 0% 100%;
|
||||||
--background-start-rgb: 0, 0, 0;
|
--foreground: 222.2 84% 4.9%;
|
||||||
--background-end-rgb: 0, 0, 0;
|
|
||||||
|
--card: 0 0% 100%;
|
||||||
|
--card-foreground: 222.2 84% 4.9%;
|
||||||
|
|
||||||
|
--popover: 0 0% 100%;
|
||||||
|
--popover-foreground: 222.2 84% 4.9%;
|
||||||
|
|
||||||
|
--primary: 222.2 47.4% 11.2%;
|
||||||
|
--primary-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--secondary: 210 40% 96.1%;
|
||||||
|
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||||
|
|
||||||
|
--muted: 210 40% 96.1%;
|
||||||
|
--muted-foreground: 215.4 16.3% 46.9%;
|
||||||
|
|
||||||
|
--accent: 210 40% 96.1%;
|
||||||
|
--accent-foreground: 222.2 47.4% 11.2%;
|
||||||
|
|
||||||
|
--destructive: 0 84.2% 60.2%;
|
||||||
|
--destructive-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--border: 214.3 31.8% 91.4%;
|
||||||
|
--input: 214.3 31.8% 91.4%;
|
||||||
|
--ring: 222.2 84% 4.9%;
|
||||||
|
|
||||||
|
--radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--background: 222.2 84% 4.9%;
|
||||||
|
--foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--card: 222.2 84% 4.9%;
|
||||||
|
--card-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--popover: 222.2 84% 4.9%;
|
||||||
|
--popover-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--primary: 210 40% 98%;
|
||||||
|
--primary-foreground: 222.2 47.4% 11.2%;
|
||||||
|
|
||||||
|
--secondary: 217.2 32.6% 17.5%;
|
||||||
|
--secondary-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--muted: 217.2 32.6% 17.5%;
|
||||||
|
--muted-foreground: 215 20.2% 65.1%;
|
||||||
|
|
||||||
|
--accent: 217.2 32.6% 17.5%;
|
||||||
|
--accent-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--destructive: 0 62.8% 30.6%;
|
||||||
|
--destructive-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--border: 217.2 32.6% 17.5%;
|
||||||
|
--input: 217.2 32.6% 17.5%;
|
||||||
|
--ring: 212.7 26.8% 83.9%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
@layer base {
|
||||||
color: rgb(var(--foreground-rgb));
|
* {
|
||||||
background: linear-gradient(
|
@apply border-border;
|
||||||
to bottom,
|
|
||||||
transparent,
|
|
||||||
rgb(var(--background-end-rgb))
|
|
||||||
)
|
|
||||||
rgb(var(--background-start-rgb));
|
|
||||||
}
|
|
||||||
|
|
||||||
@layer utilities {
|
|
||||||
.text-balance {
|
|
||||||
text-wrap: balance;
|
|
||||||
}
|
}
|
||||||
}
|
body {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import type { Metadata } from "next";
|
|
||||||
import { Inter } from "next/font/google";
|
|
||||||
import "./globals.css";
|
|
||||||
|
|
||||||
const inter = Inter({ subsets: ["latin"] });
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
|
||||||
title: "Create Next App",
|
|
||||||
description: "Generated by create next app",
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function RootLayout({
|
|
||||||
children,
|
|
||||||
}: Readonly<{
|
|
||||||
children: React.ReactNode;
|
|
||||||
}>) {
|
|
||||||
return (
|
|
||||||
<html lang="en">
|
|
||||||
<body className={inter.className}>{children}</body>
|
|
||||||
</html>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
import Image from "next/image";
|
|
||||||
|
|
||||||
export default function Home() {
|
|
||||||
return (
|
|
||||||
<main className="flex min-h-screen flex-col items-center justify-between p-24">
|
|
||||||
<div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex">
|
|
||||||
<p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">
|
|
||||||
Get started by editing
|
|
||||||
<code className="font-mono font-bold">src/app/page.tsx</code>
|
|
||||||
</p>
|
|
||||||
<div className="fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:h-auto lg:w-auto lg:bg-none">
|
|
||||||
<a
|
|
||||||
className="pointer-events-none flex place-items-center gap-2 p-8 lg:pointer-events-auto lg:p-0"
|
|
||||||
href="https://vercel.com?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
By{" "}
|
|
||||||
<Image
|
|
||||||
src="/vercel.svg"
|
|
||||||
alt="Vercel Logo"
|
|
||||||
className="dark:invert"
|
|
||||||
width={100}
|
|
||||||
height={24}
|
|
||||||
priority
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="relative flex place-items-center before:absolute before:h-[300px] before:w-full sm:before:w-[480px] before:-translate-x-1/2 before:rounded-full before:bg-gradient-radial before:from-white before:to-transparent before:blur-2xl before:content-[''] after:absolute after:-z-20 after:h-[180px] after:w-full sm:after:w-[240px] after:translate-x-1/3 after:bg-gradient-conic after:from-sky-200 after:via-blue-200 after:blur-2xl after:content-[''] before:dark:bg-gradient-to-br before:dark:from-transparent before:dark:to-blue-700 before:dark:opacity-10 after:dark:from-sky-900 after:dark:via-[#0141ff] after:dark:opacity-40 before:lg:h-[360px] z-[-1]">
|
|
||||||
<Image
|
|
||||||
className="relative dark:drop-shadow-[0_0_0.3rem_#ffffff70] dark:invert"
|
|
||||||
src="/next.svg"
|
|
||||||
alt="Next.js Logo"
|
|
||||||
width={180}
|
|
||||||
height={37}
|
|
||||||
priority
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mb-32 grid text-center lg:max-w-5xl lg:w-full lg:mb-0 lg:grid-cols-4 lg:text-left">
|
|
||||||
<a
|
|
||||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
|
||||||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<h2 className={`mb-3 text-2xl font-semibold`}>
|
|
||||||
Docs{" "}
|
|
||||||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
|
||||||
->
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
|
|
||||||
Find in-depth information about Next.js features and API.
|
|
||||||
</p>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a
|
|
||||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<h2 className={`mb-3 text-2xl font-semibold`}>
|
|
||||||
Learn{" "}
|
|
||||||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
|
||||||
->
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
|
|
||||||
Learn about Next.js in an interactive course with quizzes!
|
|
||||||
</p>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a
|
|
||||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
|
||||||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<h2 className={`mb-3 text-2xl font-semibold`}>
|
|
||||||
Templates{" "}
|
|
||||||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
|
||||||
->
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
|
|
||||||
Explore starter templates for Next.js.
|
|
||||||
</p>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a
|
|
||||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
|
||||||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<h2 className={`mb-3 text-2xl font-semibold`}>
|
|
||||||
Deploy{" "}
|
|
||||||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
|
||||||
->
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
<p className={`m-0 max-w-[30ch] text-sm opacity-50 text-balance`}>
|
|
||||||
Instantly deploy your Next.js site to a shareable URL with Vercel.
|
|
||||||
</p>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
|
||||||
|
//let accessToken = localStorage.getItem('access') ? localStorage.getItem('access') : null
|
||||||
|
function setCookieFn(name:string, value:string, daysToExpire:number) {
|
||||||
|
const expires = new Date();
|
||||||
|
expires.setTime(expires.getTime() + (daysToExpire * 24 * 60 * 60 * 1000));
|
||||||
|
document.cookie = `${name}=${value};expires=${expires.toUTCString()};path=/`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const axiosInstance = axios.create({
|
||||||
|
baseURL: `${process.env.NEXT_PUBLIC_API_URL}/`,
|
||||||
|
timeout: 10000,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
//'Authorization': `Bearer ${accessToken}`,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
axiosInstance.interceptors.request.use(async req => {
|
||||||
|
const accessT = localStorage.getItem('at')
|
||||||
|
req.headers.Authorization = `Bearer ${accessT}`
|
||||||
|
return req
|
||||||
|
})
|
||||||
|
|
||||||
|
axiosInstance.interceptors.response.use((response)=> {
|
||||||
|
return response
|
||||||
|
}, async (error) =>{
|
||||||
|
|
||||||
|
const originalRequest = error.config;
|
||||||
|
|
||||||
|
if (error.response.status === 401 && !originalRequest._retry) {
|
||||||
|
|
||||||
|
originalRequest._retry = true; // Prevent infinite loops
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
if (localStorage.getItem('rt') != 'undefined'){
|
||||||
|
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/auth/refresh-token/`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=UTF-8',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
'refresh': localStorage.getItem('rt')
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if(res.status==200){
|
||||||
|
setCookieFn('isAuth', 'true', 365)
|
||||||
|
const tokenData = await res.json()
|
||||||
|
localStorage.setItem('at', tokenData.access)
|
||||||
|
localStorage.setItem('rt',tokenData.refresh)
|
||||||
|
originalRequest.headers.Authorization = `Bearer ${tokenData.access}`
|
||||||
|
return axiosInstance(originalRequest)
|
||||||
|
}else{
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
localStorage.removeItem('at');
|
||||||
|
localStorage.removeItem('rt');
|
||||||
|
document.cookie = "isAuth=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
|
||||||
|
setTimeout(()=>{
|
||||||
|
window.location.replace('/login')
|
||||||
|
}, 200)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch(refreshError) {
|
||||||
|
|
||||||
|
localStorage.removeItem('at');
|
||||||
|
localStorage.removeItem('rt');
|
||||||
|
document.cookie = "isAuth=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
|
||||||
|
setTimeout(()=>{
|
||||||
|
window.location.replace('/login')
|
||||||
|
}, 200)
|
||||||
|
|
||||||
|
throw refreshError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a Promise rejection if the status code is not 401
|
||||||
|
return Promise.reject(error);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export default axiosInstance
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
showTrans: boolean,
|
||||||
|
setShowMenu: (show: boolean)=>void,
|
||||||
|
}
|
||||||
|
|
||||||
|
const BgTrans = (props: Props)=>{
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div onClick={()=>{props.setShowMenu(!props.showTrans)}} className={`fixed h-screen ${props.showTrans ? '' : 'hidden'} z-40 right-0 top-0 w-full bg-gray-800 opacity-50 p-2`}>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BgTrans
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState } from "react"
|
||||||
|
import BgTrans from "./bgtrans"
|
||||||
|
import Link from "next/link"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
showMenu: boolean,
|
||||||
|
setShowMenu: (show: boolean)=>void,
|
||||||
|
}
|
||||||
|
|
||||||
|
const HamberMenu = (props: Props)=>{
|
||||||
|
|
||||||
|
const menuHandler = ()=>{
|
||||||
|
|
||||||
|
props.setShowMenu(!props.showMenu)
|
||||||
|
}
|
||||||
|
|
||||||
|
const logout = ()=>{
|
||||||
|
localStorage.removeItem('at')
|
||||||
|
localStorage.removeItem('rt')
|
||||||
|
document.cookie = "isAuthAp=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
|
||||||
|
<BgTrans setShowMenu={props.setShowMenu} showTrans={props.showMenu} />
|
||||||
|
|
||||||
|
<div className={`fixed h-screen ${props.showMenu ? '' : 'hidden'} z-40 right-0 top-0 w-[50%] bg-gray-800 p-2 text-white`}>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="text-center flex flex-col gap-y-2 border-b-[1px] border-b-gray-600 pb-2">
|
||||||
|
<h2 className="text-sm">باشگاه مجریان</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<ul className="flex flex-col gap-y-2 p-2">
|
||||||
|
<Link onClick={menuHandler} href={'/'}><li className="bg-slate-600 p-2 text-center rounded-sm text-sm">خانه</li></Link>
|
||||||
|
<Link onClick={menuHandler} href={'/projects'}><li className="bg-slate-600 p-2 text-center rounded-sm text-sm">پروژه ها</li></Link>
|
||||||
|
<Link onClick={menuHandler} href={'/profile'}><li className="bg-slate-600 p-2 text-center rounded-sm text-sm">پروفایل</li></Link>
|
||||||
|
<Link onClick={menuHandler} href={'/emtiyazat'}><li className="bg-slate-600 p-2 text-center rounded-sm text-sm">امتیازات</li></Link>
|
||||||
|
<Link onClick={menuHandler} href={'/javayez'}><li className="bg-slate-600 p-2 text-center rounded-sm text-sm">جوایز دریافتی</li></Link>
|
||||||
|
|
||||||
|
<li className="bg-slate-600 p-2 text-center rounded-sm text-sm">تماس با ما</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="flex flex-col gap-y-2 p-2">
|
||||||
|
<div onClick={logout} className="bg-pink-800 p-2 text-center rounded-sm text-sm">خروج</div>
|
||||||
|
<div className="text-center text-xs mt-1">
|
||||||
|
<p>واحد فناوری اطلاعات لوله گستر گلپایگان</p>
|
||||||
|
<p>فصاحت: 09202202129</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HamberMenu
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
import Image from 'next/image'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
AlertDialogTrigger,
|
||||||
|
} from "@/components/ui/alert-dialog"
|
||||||
|
import {useState} from "react";
|
||||||
|
import {useCreateOrder} from "@/queries/order";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
data: any
|
||||||
|
}
|
||||||
|
const JavayezComponent = (props: Props) => {
|
||||||
|
|
||||||
|
const [orderIsOpen, setOrderIsOpen] = useState<boolean>(false)
|
||||||
|
const [errorMessage, setErrorMessage] = useState<string | null>(null)
|
||||||
|
const {mutate: createOrder, isPending, isSuccess, isError, data: orderData,error} = useCreateOrder(setOrderIsOpen, setErrorMessage)
|
||||||
|
|
||||||
|
const addToOrder = async ()=>{
|
||||||
|
createOrder(props.data.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onOpenChange = (open: boolean)=>{
|
||||||
|
console.log('open', open)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col bg-white drop-shadow-lg rounded-sm'>
|
||||||
|
|
||||||
|
<div className='flex flex-row gap-x-4 justify-between p-3'>
|
||||||
|
|
||||||
|
<div className='flex flex-col gap-y-2'>
|
||||||
|
<h2 className='text-md'>
|
||||||
|
{props.data.name}
|
||||||
|
</h2>
|
||||||
|
<p className='bg-green-800 text-white text-[0.7rem] pr-[0.3rem] pl-[0.3rem] rounded-sm'>
|
||||||
|
{props.data.desc}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Image className={`rounded-sm`} src={`${process.env.NEXT_PUBLIC_API_URL}${props.data.image}`} width={120} height={100} alt='star' />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mb-2 border-b-[1px] border-b-gray-200'></div>
|
||||||
|
|
||||||
|
<div className='flex flex-row gap-x-4 justify-between p-3 items-center'>
|
||||||
|
|
||||||
|
<div className='flex flex-row gap-x-1 font-bold items-center'>
|
||||||
|
<div>
|
||||||
|
{props.data.get_emtiyaz}
|
||||||
|
</div>
|
||||||
|
<div>امتیاز</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<AlertDialog open={orderIsOpen} onOpenChange={(open)=>{ onOpenChange(open) }}>
|
||||||
|
<AlertDialogTrigger onClick={()=>{setOrderIsOpen(true)}} className={'rounded bg-red-800 text-white px-2 py-1'}>انتخاب</AlertDialogTrigger>
|
||||||
|
|
||||||
|
<AlertDialogContent className={'rounded-xl p-4'}>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{isError ? (
|
||||||
|
<div className={'text-red-500'}>{errorMessage}</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<AlertDialogTitle>آیا مطمئن هستید؟</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
{`با انتخاب ${props.data.name} ${props.data.emtiyaz} امتیاز از حساب کاربری شما کسر خواهد شد`}
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</>
|
||||||
|
) }
|
||||||
|
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter className={'flex flex-row gap-2'}>
|
||||||
|
|
||||||
|
{isError ? (
|
||||||
|
<button onClick={()=>{setOrderIsOpen(false)}} className={'cursor-pointer w-full bg-blue-700 text-white rounded text-center px-2 py-2'}>
|
||||||
|
بستن
|
||||||
|
</button>
|
||||||
|
): (
|
||||||
|
<>
|
||||||
|
<button onClick={()=>{setOrderIsOpen(false)}} className={'cursor-pointer w-full bg-blue-700 text-white rounded text-center px-2 py-2'}>
|
||||||
|
خیر
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{isPending ? (
|
||||||
|
<div className={'cursor-pointer w-full bg-blue-900 text-white rounded text-center px-2 py-2'}>بله</div>
|
||||||
|
|
||||||
|
) : (
|
||||||
|
<div onClick={addToOrder} className={'cursor-pointer w-full bg-blue-900 text-white rounded text-center px-2 py-2'}>بله</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default JavayezComponent
|
||||||
Whitespace-only changes.
@@ -0,0 +1,33 @@
|
|||||||
|
import React, { useRef } from 'react';
|
||||||
|
|
||||||
|
const TakePicture = () => {
|
||||||
|
const videoRef = useRef<null | any>(null);
|
||||||
|
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
||||||
|
|
||||||
|
const startCamera = async () => {
|
||||||
|
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
||||||
|
alert("bba");
|
||||||
|
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
|
||||||
|
videoRef.current.srcObject = stream;
|
||||||
|
videoRef.current.play();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const takePicture = () => {
|
||||||
|
const context = canvasRef.current?.getContext('2d');
|
||||||
|
if (context) {
|
||||||
|
context.drawImage(videoRef.current, 0, 0, 640, 480);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<button onClick={startCamera}>Start Camera</button>
|
||||||
|
<button onClick={takePicture}>Take Picture</button>
|
||||||
|
<video ref={videoRef} width="640" height="480"></video>
|
||||||
|
<canvas ref={canvasRef} width="640" height="480"></canvas>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TakePicture;
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import {FcApproval, FcCancel, FcInfo} from "react-icons/fc";
|
||||||
|
import {BsHourglassSplit} from "react-icons/bs";
|
||||||
|
function numberWithCommas(x:number) {
|
||||||
|
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
project: any
|
||||||
|
}
|
||||||
|
const ProjectStatus = ( props:Props ) => {
|
||||||
|
return (
|
||||||
|
<div className="bg-white flex flex-row justify-between gap-y-4 p-2 rounded-sm">
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-y-2 w-[60%]">
|
||||||
|
<div>
|
||||||
|
<h2>
|
||||||
|
{`ارزش پروژه: ${numberWithCommas(props.project.factor_price)} تومان `}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="text-sm truncate">
|
||||||
|
آدرس:
|
||||||
|
{props.project.address}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="text-sm">
|
||||||
|
تاریخ ثبت:
|
||||||
|
1403/02/09
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-y-2 justify-between">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="flex flex-row justify-center pt-4">
|
||||||
|
|
||||||
|
{props.project.status === 'AwaitComp' ? (
|
||||||
|
<FcInfo size={30}/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{props.project.status === 'waiting' ? (
|
||||||
|
<BsHourglassSplit size={30}/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{props.project.status === 'approved' ? (
|
||||||
|
<FcApproval size={30}/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{props.project.status === 'rejected' ? (
|
||||||
|
<FcCancel size={30}/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="status">
|
||||||
|
|
||||||
|
{props.project.status === 'AwaitComp' ? (
|
||||||
|
<span className="bg-gray-500 text-white text-center rounded-md px-2 text-sm">
|
||||||
|
{props.project.status === 'AwaitComp' ? 'در انتظار تکمیل' : null}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{props.project.status === 'waiting' ? (
|
||||||
|
<span className="bg-gray-500 text-white text-center rounded-md px-2 text-sm">
|
||||||
|
{props.project.status === 'waiting' ? 'در انتظار تایید' : null}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{props.project.status === 'approved' ? (
|
||||||
|
<span className="bg-green-900 text-white text-center rounded-md px-2 text-sm">
|
||||||
|
{props.project.status === 'approved' ? 'تایید شده' : null}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{props.project.status === 'rejected' ? (
|
||||||
|
<span className="bg-red-500 text-white text-center rounded-md px-2 text-sm">
|
||||||
|
{props.project.status === 'rejected' ? 'رد شده' : null}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProjectStatus;
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { IoMenu } from "react-icons/io5";
|
||||||
|
import HamberMenu from "./hambermenu";
|
||||||
|
import BgTrans from "./bgtrans";
|
||||||
|
import { useState } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
const TopMenu = ()=>{
|
||||||
|
|
||||||
|
const [showMenu, setShowMenu] = useState<boolean>(false)
|
||||||
|
const menuHandler = ()=>{
|
||||||
|
setShowMenu(!showMenu)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row justify-between gap-x-2 bg-blue-600 p-2 text-white items-center">
|
||||||
|
|
||||||
|
<div className="flex flex-row gap-x-2 items-center">
|
||||||
|
<div onClick={menuHandler}>
|
||||||
|
<IoMenu size={30} color="#fff" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<HamberMenu setShowMenu={setShowMenu} showMenu={showMenu} />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
باشگاه مجریان لوله گستر گلپایگان
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Link href={'/new'} className="bg-green-500 text-white px-2 py-1 rounded" >
|
||||||
|
ثبت پروژه
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default TopMenu
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
||||||
|
import { ChevronDown } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Accordion = AccordionPrimitive.Root
|
||||||
|
|
||||||
|
const AccordionItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AccordionPrimitive.Item>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AccordionPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
className={cn("border-b", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AccordionItem.displayName = "AccordionItem"
|
||||||
|
|
||||||
|
const AccordionTrigger = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<AccordionPrimitive.Header className="flex">
|
||||||
|
<AccordionPrimitive.Trigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
|
||||||
|
</AccordionPrimitive.Trigger>
|
||||||
|
</AccordionPrimitive.Header>
|
||||||
|
))
|
||||||
|
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
|
||||||
|
|
||||||
|
const AccordionContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AccordionPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<AccordionPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<div className={cn("pb-4 pt-0", className)}>{children}</div>
|
||||||
|
</AccordionPrimitive.Content>
|
||||||
|
))
|
||||||
|
|
||||||
|
AccordionContent.displayName = AccordionPrimitive.Content.displayName
|
||||||
|
|
||||||
|
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { buttonVariants } from "@/components/ui/button"
|
||||||
|
|
||||||
|
const AlertDialog = AlertDialogPrimitive.Root
|
||||||
|
|
||||||
|
const AlertDialogTrigger = AlertDialogPrimitive.Trigger
|
||||||
|
|
||||||
|
const AlertDialogPortal = AlertDialogPrimitive.Portal
|
||||||
|
|
||||||
|
const AlertDialogOverlay = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPrimitive.Overlay
|
||||||
|
className={cn(
|
||||||
|
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
|
||||||
|
|
||||||
|
const AlertDialogContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AlertDialogPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPortal>
|
||||||
|
<AlertDialogOverlay />
|
||||||
|
<AlertDialogPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</AlertDialogPortal>
|
||||||
|
))
|
||||||
|
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
|
||||||
|
|
||||||
|
const AlertDialogHeader = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col space-y-2 text-center sm:text-left",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
AlertDialogHeader.displayName = "AlertDialogHeader"
|
||||||
|
|
||||||
|
const AlertDialogFooter = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
AlertDialogFooter.displayName = "AlertDialogFooter"
|
||||||
|
|
||||||
|
const AlertDialogTitle = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AlertDialogPrimitive.Title>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPrimitive.Title
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-lg font-semibold", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
|
||||||
|
|
||||||
|
const AlertDialogDescription = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AlertDialogPrimitive.Description>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPrimitive.Description
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDialogDescription.displayName =
|
||||||
|
AlertDialogPrimitive.Description.displayName
|
||||||
|
|
||||||
|
const AlertDialogAction = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AlertDialogPrimitive.Action>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPrimitive.Action
|
||||||
|
ref={ref}
|
||||||
|
className={cn(buttonVariants(), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
|
||||||
|
|
||||||
|
const AlertDialogCancel = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPrimitive.Cancel
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
buttonVariants({ variant: "outline" }),
|
||||||
|
"mt-2 sm:mt-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
|
||||||
|
|
||||||
|
export {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogPortal,
|
||||||
|
AlertDialogOverlay,
|
||||||
|
AlertDialogTrigger,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogTitle,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const alertVariants = cva(
|
||||||
|
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-background text-foreground",
|
||||||
|
destructive:
|
||||||
|
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const Alert = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
|
||||||
|
>(({ className, variant, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
role="alert"
|
||||||
|
className={cn(alertVariants({ variant }), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Alert.displayName = "Alert"
|
||||||
|
|
||||||
|
const AlertTitle = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLHeadingElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<h5
|
||||||
|
ref={ref}
|
||||||
|
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertTitle.displayName = "AlertTitle"
|
||||||
|
|
||||||
|
const AlertDescription = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm [&_p]:leading-relaxed", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDescription.displayName = "AlertDescription"
|
||||||
|
|
||||||
|
export { Alert, AlertTitle, AlertDescription }
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Avatar = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AvatarPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AvatarPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Avatar.displayName = AvatarPrimitive.Root.displayName
|
||||||
|
|
||||||
|
const AvatarImage = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AvatarPrimitive.Image>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AvatarPrimitive.Image
|
||||||
|
ref={ref}
|
||||||
|
className={cn("aspect-square h-full w-full", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AvatarImage.displayName = AvatarPrimitive.Image.displayName
|
||||||
|
|
||||||
|
const AvatarFallback = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AvatarPrimitive.Fallback>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AvatarPrimitive.Fallback
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex h-full w-full items-center justify-center rounded-full bg-muted",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
|
||||||
|
|
||||||
|
export { Avatar, AvatarImage, AvatarFallback }
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const buttonVariants = cva(
|
||||||
|
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||||
|
destructive:
|
||||||
|
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||||
|
outline:
|
||||||
|
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||||
|
secondary:
|
||||||
|
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
|
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||||
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: "h-10 px-4 py-2",
|
||||||
|
sm: "h-9 rounded-md px-3",
|
||||||
|
lg: "h-11 rounded-md px-8",
|
||||||
|
icon: "h-10 w-10",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
size: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export interface ButtonProps
|
||||||
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
|
VariantProps<typeof buttonVariants> {
|
||||||
|
asChild?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
|
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||||
|
const Comp = asChild ? Slot : "button"
|
||||||
|
return (
|
||||||
|
<Comp
|
||||||
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Button.displayName = "Button"
|
||||||
|
|
||||||
|
export { Button, buttonVariants }
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Card = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Card.displayName = "Card"
|
||||||
|
|
||||||
|
const CardHeader = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardHeader.displayName = "CardHeader"
|
||||||
|
|
||||||
|
const CardTitle = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLHeadingElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<h3
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"text-2xl font-semibold leading-none tracking-tight",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardTitle.displayName = "CardTitle"
|
||||||
|
|
||||||
|
const CardDescription = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<p
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardDescription.displayName = "CardDescription"
|
||||||
|
|
||||||
|
const CardContent = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
||||||
|
))
|
||||||
|
CardContent.displayName = "CardContent"
|
||||||
|
|
||||||
|
const CardFooter = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn("flex items-center p-6 pt-0", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardFooter.displayName = "CardFooter"
|
||||||
|
|
||||||
|
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
||||||
|
import { X } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Dialog = DialogPrimitive.Root
|
||||||
|
|
||||||
|
const DialogTrigger = DialogPrimitive.Trigger
|
||||||
|
|
||||||
|
const DialogPortal = DialogPrimitive.Portal
|
||||||
|
|
||||||
|
const DialogClose = DialogPrimitive.Close
|
||||||
|
|
||||||
|
const DialogOverlay = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DialogPrimitive.Overlay
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
||||||
|
|
||||||
|
const DialogContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<DialogPortal>
|
||||||
|
<DialogOverlay />
|
||||||
|
<DialogPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
<span className="sr-only">Close</span>
|
||||||
|
</DialogPrimitive.Close>
|
||||||
|
</DialogPrimitive.Content>
|
||||||
|
</DialogPortal>
|
||||||
|
))
|
||||||
|
DialogContent.displayName = DialogPrimitive.Content.displayName
|
||||||
|
|
||||||
|
const DialogHeader = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col space-y-1.5 text-center sm:text-left",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
DialogHeader.displayName = "DialogHeader"
|
||||||
|
|
||||||
|
const DialogFooter = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
DialogFooter.displayName = "DialogFooter"
|
||||||
|
|
||||||
|
const DialogTitle = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DialogPrimitive.Title>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DialogPrimitive.Title
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"text-lg font-semibold leading-none tracking-tight",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DialogTitle.displayName = DialogPrimitive.Title.displayName
|
||||||
|
|
||||||
|
const DialogDescription = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DialogPrimitive.Description>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DialogPrimitive.Description
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DialogDescription.displayName = DialogPrimitive.Description.displayName
|
||||||
|
|
||||||
|
export {
|
||||||
|
Dialog,
|
||||||
|
DialogPortal,
|
||||||
|
DialogOverlay,
|
||||||
|
DialogClose,
|
||||||
|
DialogTrigger,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogFooter,
|
||||||
|
DialogTitle,
|
||||||
|
DialogDescription,
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { Drawer as DrawerPrimitive } from "vaul"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Drawer = ({
|
||||||
|
shouldScaleBackground = true,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
|
||||||
|
<DrawerPrimitive.Root
|
||||||
|
shouldScaleBackground={shouldScaleBackground}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
Drawer.displayName = "Drawer"
|
||||||
|
|
||||||
|
const DrawerTrigger = DrawerPrimitive.Trigger
|
||||||
|
|
||||||
|
const DrawerPortal = DrawerPrimitive.Portal
|
||||||
|
|
||||||
|
const DrawerClose = DrawerPrimitive.Close
|
||||||
|
|
||||||
|
const DrawerOverlay = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DrawerPrimitive.Overlay>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DrawerPrimitive.Overlay
|
||||||
|
ref={ref}
|
||||||
|
className={cn("fixed inset-0 z-50 bg-black/80", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
|
||||||
|
|
||||||
|
const DrawerContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DrawerPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<DrawerPortal>
|
||||||
|
<DrawerOverlay />
|
||||||
|
<DrawerPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
|
||||||
|
{children}
|
||||||
|
</DrawerPrimitive.Content>
|
||||||
|
</DrawerPortal>
|
||||||
|
))
|
||||||
|
DrawerContent.displayName = "DrawerContent"
|
||||||
|
|
||||||
|
const DrawerHeader = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
DrawerHeader.displayName = "DrawerHeader"
|
||||||
|
|
||||||
|
const DrawerFooter = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
DrawerFooter.displayName = "DrawerFooter"
|
||||||
|
|
||||||
|
const DrawerTitle = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DrawerPrimitive.Title>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DrawerPrimitive.Title
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"text-lg font-semibold leading-none tracking-tight",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DrawerTitle.displayName = DrawerPrimitive.Title.displayName
|
||||||
|
|
||||||
|
const DrawerDescription = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DrawerPrimitive.Description>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DrawerPrimitive.Description
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DrawerDescription.displayName = DrawerPrimitive.Description.displayName
|
||||||
|
|
||||||
|
export {
|
||||||
|
Drawer,
|
||||||
|
DrawerPortal,
|
||||||
|
DrawerOverlay,
|
||||||
|
DrawerTrigger,
|
||||||
|
DrawerClose,
|
||||||
|
DrawerContent,
|
||||||
|
DrawerHeader,
|
||||||
|
DrawerFooter,
|
||||||
|
DrawerTitle,
|
||||||
|
DrawerDescription,
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { OTPInput, SlotProps } from "input-otp"
|
||||||
|
import { Dot } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const InputOTP = React.forwardRef<
|
||||||
|
React.ElementRef<typeof OTPInput>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof OTPInput>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<OTPInput
|
||||||
|
ref={ref}
|
||||||
|
containerClassName={cn("flex items-center gap-2", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
InputOTP.displayName = "InputOTP"
|
||||||
|
|
||||||
|
const InputOTPGroup = React.forwardRef<
|
||||||
|
React.ElementRef<"div">,
|
||||||
|
React.ComponentPropsWithoutRef<"div">
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div ref={ref} className={cn("flex items-center", className)} {...props} />
|
||||||
|
))
|
||||||
|
InputOTPGroup.displayName = "InputOTPGroup"
|
||||||
|
|
||||||
|
const InputOTPSlot = React.forwardRef<
|
||||||
|
React.ElementRef<"div">,
|
||||||
|
SlotProps & React.ComponentPropsWithoutRef<"div">
|
||||||
|
>(({ char, hasFakeCaret, isActive, className, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex h-10 w-10 items-center justify-center border-y border-r border-input text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md",
|
||||||
|
isActive && "z-10 ring-2 ring-offset-background ring-ring",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{char}
|
||||||
|
{hasFakeCaret && (
|
||||||
|
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
|
||||||
|
<div className="animate-caret-blink h-4 w-px bg-foreground duration-1000" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
InputOTPSlot.displayName = "InputOTPSlot"
|
||||||
|
|
||||||
|
const InputOTPSeparator = React.forwardRef<
|
||||||
|
React.ElementRef<"div">,
|
||||||
|
React.ComponentPropsWithoutRef<"div">
|
||||||
|
>(({ ...props }, ref) => (
|
||||||
|
<div ref={ref} role="separator" {...props}>
|
||||||
|
<Dot />
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
InputOTPSeparator.displayName = "InputOTPSeparator"
|
||||||
|
|
||||||
|
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
export interface InputProps
|
||||||
|
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||||
|
|
||||||
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||||
|
({ className, type, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type={type}
|
||||||
|
className={cn(
|
||||||
|
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Input.displayName = "Input"
|
||||||
|
|
||||||
|
export { Input }
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const labelVariants = cva(
|
||||||
|
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
)
|
||||||
|
|
||||||
|
const Label = React.forwardRef<
|
||||||
|
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||||
|
VariantProps<typeof labelVariants>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<LabelPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(labelVariants(), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Label.displayName = LabelPrimitive.Root.displayName
|
||||||
|
|
||||||
|
export { Label }
|
||||||
@@ -0,0 +1,236 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as MenubarPrimitive from "@radix-ui/react-menubar"
|
||||||
|
import { Check, ChevronRight, Circle } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const MenubarMenu = MenubarPrimitive.Menu
|
||||||
|
|
||||||
|
const MenubarGroup = MenubarPrimitive.Group
|
||||||
|
|
||||||
|
const MenubarPortal = MenubarPrimitive.Portal
|
||||||
|
|
||||||
|
const MenubarSub = MenubarPrimitive.Sub
|
||||||
|
|
||||||
|
const MenubarRadioGroup = MenubarPrimitive.RadioGroup
|
||||||
|
|
||||||
|
const Menubar = React.forwardRef<
|
||||||
|
React.ElementRef<typeof MenubarPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<MenubarPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex h-10 items-center space-x-1 rounded-md border bg-background p-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Menubar.displayName = MenubarPrimitive.Root.displayName
|
||||||
|
|
||||||
|
const MenubarTrigger = React.forwardRef<
|
||||||
|
React.ElementRef<typeof MenubarPrimitive.Trigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<MenubarPrimitive.Trigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex cursor-default select-none items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName
|
||||||
|
|
||||||
|
const MenubarSubTrigger = React.forwardRef<
|
||||||
|
React.ElementRef<typeof MenubarPrimitive.SubTrigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {
|
||||||
|
inset?: boolean
|
||||||
|
}
|
||||||
|
>(({ className, inset, children, ...props }, ref) => (
|
||||||
|
<MenubarPrimitive.SubTrigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
|
||||||
|
inset && "pl-8",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<ChevronRight className="ml-auto h-4 w-4" />
|
||||||
|
</MenubarPrimitive.SubTrigger>
|
||||||
|
))
|
||||||
|
MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName
|
||||||
|
|
||||||
|
const MenubarSubContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof MenubarPrimitive.SubContent>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<MenubarPrimitive.SubContent
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName
|
||||||
|
|
||||||
|
const MenubarContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof MenubarPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>
|
||||||
|
>(
|
||||||
|
(
|
||||||
|
{ className, align = "start", alignOffset = -4, sideOffset = 8, ...props },
|
||||||
|
ref
|
||||||
|
) => (
|
||||||
|
<MenubarPrimitive.Portal>
|
||||||
|
<MenubarPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
align={align}
|
||||||
|
alignOffset={alignOffset}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn(
|
||||||
|
"z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</MenubarPrimitive.Portal>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
MenubarContent.displayName = MenubarPrimitive.Content.displayName
|
||||||
|
|
||||||
|
const MenubarItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof MenubarPrimitive.Item>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {
|
||||||
|
inset?: boolean
|
||||||
|
}
|
||||||
|
>(({ className, inset, ...props }, ref) => (
|
||||||
|
<MenubarPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
|
inset && "pl-8",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
MenubarItem.displayName = MenubarPrimitive.Item.displayName
|
||||||
|
|
||||||
|
const MenubarCheckboxItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>
|
||||||
|
>(({ className, children, checked, ...props }, ref) => (
|
||||||
|
<MenubarPrimitive.CheckboxItem
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
checked={checked}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||||
|
<MenubarPrimitive.ItemIndicator>
|
||||||
|
<Check className="h-4 w-4" />
|
||||||
|
</MenubarPrimitive.ItemIndicator>
|
||||||
|
</span>
|
||||||
|
{children}
|
||||||
|
</MenubarPrimitive.CheckboxItem>
|
||||||
|
))
|
||||||
|
MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName
|
||||||
|
|
||||||
|
const MenubarRadioItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof MenubarPrimitive.RadioItem>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<MenubarPrimitive.RadioItem
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||||
|
<MenubarPrimitive.ItemIndicator>
|
||||||
|
<Circle className="h-2 w-2 fill-current" />
|
||||||
|
</MenubarPrimitive.ItemIndicator>
|
||||||
|
</span>
|
||||||
|
{children}
|
||||||
|
</MenubarPrimitive.RadioItem>
|
||||||
|
))
|
||||||
|
MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName
|
||||||
|
|
||||||
|
const MenubarLabel = React.forwardRef<
|
||||||
|
React.ElementRef<typeof MenubarPrimitive.Label>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {
|
||||||
|
inset?: boolean
|
||||||
|
}
|
||||||
|
>(({ className, inset, ...props }, ref) => (
|
||||||
|
<MenubarPrimitive.Label
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"px-2 py-1.5 text-sm font-semibold",
|
||||||
|
inset && "pl-8",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
MenubarLabel.displayName = MenubarPrimitive.Label.displayName
|
||||||
|
|
||||||
|
const MenubarSeparator = React.forwardRef<
|
||||||
|
React.ElementRef<typeof MenubarPrimitive.Separator>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<MenubarPrimitive.Separator
|
||||||
|
ref={ref}
|
||||||
|
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName
|
||||||
|
|
||||||
|
const MenubarShortcut = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"ml-auto text-xs tracking-widest text-muted-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
MenubarShortcut.displayname = "MenubarShortcut"
|
||||||
|
|
||||||
|
export {
|
||||||
|
Menubar,
|
||||||
|
MenubarMenu,
|
||||||
|
MenubarTrigger,
|
||||||
|
MenubarContent,
|
||||||
|
MenubarItem,
|
||||||
|
MenubarSeparator,
|
||||||
|
MenubarLabel,
|
||||||
|
MenubarCheckboxItem,
|
||||||
|
MenubarRadioGroup,
|
||||||
|
MenubarRadioItem,
|
||||||
|
MenubarPortal,
|
||||||
|
MenubarSubContent,
|
||||||
|
MenubarSubTrigger,
|
||||||
|
MenubarGroup,
|
||||||
|
MenubarSub,
|
||||||
|
MenubarShortcut,
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { ButtonProps, buttonVariants } from "@/components/ui/button"
|
||||||
|
|
||||||
|
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
|
||||||
|
<nav
|
||||||
|
role="navigation"
|
||||||
|
aria-label="pagination"
|
||||||
|
className={cn("mx-auto flex w-full justify-center", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
Pagination.displayName = "Pagination"
|
||||||
|
|
||||||
|
const PaginationContent = React.forwardRef<
|
||||||
|
HTMLUListElement,
|
||||||
|
React.ComponentProps<"ul">
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<ul
|
||||||
|
ref={ref}
|
||||||
|
className={cn("flex flex-row items-center gap-1", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
PaginationContent.displayName = "PaginationContent"
|
||||||
|
|
||||||
|
const PaginationItem = React.forwardRef<
|
||||||
|
HTMLLIElement,
|
||||||
|
React.ComponentProps<"li">
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<li ref={ref} className={cn("", className)} {...props} />
|
||||||
|
))
|
||||||
|
PaginationItem.displayName = "PaginationItem"
|
||||||
|
|
||||||
|
type PaginationLinkProps = {
|
||||||
|
isActive?: boolean
|
||||||
|
} & Pick<ButtonProps, "size"> &
|
||||||
|
React.ComponentProps<"a">
|
||||||
|
|
||||||
|
const PaginationLink = ({
|
||||||
|
className,
|
||||||
|
isActive,
|
||||||
|
size = "icon",
|
||||||
|
...props
|
||||||
|
}: PaginationLinkProps) => (
|
||||||
|
<a
|
||||||
|
aria-current={isActive ? "page" : undefined}
|
||||||
|
className={cn(
|
||||||
|
buttonVariants({
|
||||||
|
variant: isActive ? "outline" : "ghost",
|
||||||
|
size,
|
||||||
|
}),
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
PaginationLink.displayName = "PaginationLink"
|
||||||
|
|
||||||
|
const PaginationPrevious = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PaginationLink>) => (
|
||||||
|
<PaginationLink
|
||||||
|
aria-label="Go to previous page"
|
||||||
|
size="default"
|
||||||
|
className={cn("gap-1 pl-2.5", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChevronLeft className="h-4 w-4" />
|
||||||
|
<span>Previous</span>
|
||||||
|
</PaginationLink>
|
||||||
|
)
|
||||||
|
PaginationPrevious.displayName = "PaginationPrevious"
|
||||||
|
|
||||||
|
const PaginationNext = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PaginationLink>) => (
|
||||||
|
<PaginationLink
|
||||||
|
aria-label="Go to next page"
|
||||||
|
size="default"
|
||||||
|
className={cn("gap-1 pr-2.5", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span>Next</span>
|
||||||
|
<ChevronRight className="h-4 w-4" />
|
||||||
|
</PaginationLink>
|
||||||
|
)
|
||||||
|
PaginationNext.displayName = "PaginationNext"
|
||||||
|
|
||||||
|
const PaginationEllipsis = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"span">) => (
|
||||||
|
<span
|
||||||
|
aria-hidden
|
||||||
|
className={cn("flex h-9 w-9 items-center justify-center", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<MoreHorizontal className="h-4 w-4" />
|
||||||
|
<span className="sr-only">More pages</span>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
PaginationEllipsis.displayName = "PaginationEllipsis"
|
||||||
|
|
||||||
|
export {
|
||||||
|
Pagination,
|
||||||
|
PaginationContent,
|
||||||
|
PaginationEllipsis,
|
||||||
|
PaginationItem,
|
||||||
|
PaginationLink,
|
||||||
|
PaginationNext,
|
||||||
|
PaginationPrevious,
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Popover = PopoverPrimitive.Root
|
||||||
|
|
||||||
|
const PopoverTrigger = PopoverPrimitive.Trigger
|
||||||
|
|
||||||
|
const PopoverContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||||
|
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
||||||
|
<PopoverPrimitive.Portal>
|
||||||
|
<PopoverPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
align={align}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn(
|
||||||
|
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</PopoverPrimitive.Portal>
|
||||||
|
))
|
||||||
|
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
||||||
|
|
||||||
|
export { Popover, PopoverTrigger, PopoverContent }
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as ProgressPrimitive from "@radix-ui/react-progress"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Progress = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ProgressPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
|
||||||
|
>(({ className, value, ...props }, ref) => (
|
||||||
|
<ProgressPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative h-4 w-full overflow-hidden rounded-full bg-secondary",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ProgressPrimitive.Indicator
|
||||||
|
className="h-full w-full flex-1 bg-primary transition-all"
|
||||||
|
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
||||||
|
/>
|
||||||
|
</ProgressPrimitive.Root>
|
||||||
|
))
|
||||||
|
Progress.displayName = ProgressPrimitive.Root.displayName
|
||||||
|
|
||||||
|
export { Progress }
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
|
||||||
|
import { Circle } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const RadioGroup = React.forwardRef<
|
||||||
|
React.ElementRef<typeof RadioGroupPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<RadioGroupPrimitive.Root
|
||||||
|
className={cn("grid gap-2", className)}
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
|
||||||
|
|
||||||
|
const RadioGroupItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof RadioGroupPrimitive.Item>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<RadioGroupPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
|
||||||
|
<Circle className="h-2.5 w-2.5 fill-current text-current" />
|
||||||
|
</RadioGroupPrimitive.Indicator>
|
||||||
|
</RadioGroupPrimitive.Item>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
|
||||||
|
|
||||||
|
export { RadioGroup, RadioGroupItem }
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as SelectPrimitive from "@radix-ui/react-select"
|
||||||
|
import { Check, ChevronDown, ChevronUp } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Select = SelectPrimitive.Root
|
||||||
|
|
||||||
|
const SelectGroup = SelectPrimitive.Group
|
||||||
|
|
||||||
|
const SelectValue = SelectPrimitive.Value
|
||||||
|
|
||||||
|
const SelectTrigger = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Trigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<SelectPrimitive.Icon asChild>
|
||||||
|
<ChevronDown className="h-4 w-4 opacity-50" />
|
||||||
|
</SelectPrimitive.Icon>
|
||||||
|
</SelectPrimitive.Trigger>
|
||||||
|
))
|
||||||
|
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
||||||
|
|
||||||
|
const SelectScrollUpButton = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.ScrollUpButton
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex cursor-default items-center justify-center py-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChevronUp className="h-4 w-4" />
|
||||||
|
</SelectPrimitive.ScrollUpButton>
|
||||||
|
))
|
||||||
|
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
|
||||||
|
|
||||||
|
const SelectScrollDownButton = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.ScrollDownButton
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex cursor-default items-center justify-center py-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChevronDown className="h-4 w-4" />
|
||||||
|
</SelectPrimitive.ScrollDownButton>
|
||||||
|
))
|
||||||
|
SelectScrollDownButton.displayName =
|
||||||
|
SelectPrimitive.ScrollDownButton.displayName
|
||||||
|
|
||||||
|
const SelectContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||||
|
>(({ className, children, position = "popper", ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Portal>
|
||||||
|
<SelectPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
position === "popper" &&
|
||||||
|
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
position={position}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<SelectScrollUpButton />
|
||||||
|
<SelectPrimitive.Viewport
|
||||||
|
className={cn(
|
||||||
|
"p-1",
|
||||||
|
position === "popper" &&
|
||||||
|
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</SelectPrimitive.Viewport>
|
||||||
|
<SelectScrollDownButton />
|
||||||
|
</SelectPrimitive.Content>
|
||||||
|
</SelectPrimitive.Portal>
|
||||||
|
))
|
||||||
|
SelectContent.displayName = SelectPrimitive.Content.displayName
|
||||||
|
|
||||||
|
const SelectLabel = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Label
|
||||||
|
ref={ref}
|
||||||
|
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
||||||
|
|
||||||
|
const SelectItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||||
|
<SelectPrimitive.ItemIndicator>
|
||||||
|
<Check className="h-4 w-4" />
|
||||||
|
</SelectPrimitive.ItemIndicator>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||||
|
</SelectPrimitive.Item>
|
||||||
|
))
|
||||||
|
SelectItem.displayName = SelectPrimitive.Item.displayName
|
||||||
|
|
||||||
|
const SelectSeparator = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Separator
|
||||||
|
ref={ref}
|
||||||
|
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
|
||||||
|
|
||||||
|
export {
|
||||||
|
Select,
|
||||||
|
SelectGroup,
|
||||||
|
SelectValue,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectContent,
|
||||||
|
SelectLabel,
|
||||||
|
SelectItem,
|
||||||
|
SelectSeparator,
|
||||||
|
SelectScrollUpButton,
|
||||||
|
SelectScrollDownButton,
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function Skeleton({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn("animate-pulse rounded-md bg-muted", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Skeleton }
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as SwitchPrimitives from "@radix-ui/react-switch"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Switch = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SwitchPrimitives.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SwitchPrimitives.Root
|
||||||
|
className={cn(
|
||||||
|
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
|
<SwitchPrimitives.Thumb
|
||||||
|
className={cn(
|
||||||
|
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</SwitchPrimitives.Root>
|
||||||
|
))
|
||||||
|
Switch.displayName = SwitchPrimitives.Root.displayName
|
||||||
|
|
||||||
|
export { Switch }
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Table = React.forwardRef<
|
||||||
|
HTMLTableElement,
|
||||||
|
React.HTMLAttributes<HTMLTableElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div className="relative w-full overflow-auto">
|
||||||
|
<table
|
||||||
|
ref={ref}
|
||||||
|
className={cn("w-full caption-bottom text-sm", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
Table.displayName = "Table"
|
||||||
|
|
||||||
|
const TableHeader = React.forwardRef<
|
||||||
|
HTMLTableSectionElement,
|
||||||
|
React.HTMLAttributes<HTMLTableSectionElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
|
||||||
|
))
|
||||||
|
TableHeader.displayName = "TableHeader"
|
||||||
|
|
||||||
|
const TableBody = React.forwardRef<
|
||||||
|
HTMLTableSectionElement,
|
||||||
|
React.HTMLAttributes<HTMLTableSectionElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<tbody
|
||||||
|
ref={ref}
|
||||||
|
className={cn("[&_tr:last-child]:border-0", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TableBody.displayName = "TableBody"
|
||||||
|
|
||||||
|
const TableFooter = React.forwardRef<
|
||||||
|
HTMLTableSectionElement,
|
||||||
|
React.HTMLAttributes<HTMLTableSectionElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<tfoot
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TableFooter.displayName = "TableFooter"
|
||||||
|
|
||||||
|
const TableRow = React.forwardRef<
|
||||||
|
HTMLTableRowElement,
|
||||||
|
React.HTMLAttributes<HTMLTableRowElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<tr
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TableRow.displayName = "TableRow"
|
||||||
|
|
||||||
|
const TableHead = React.forwardRef<
|
||||||
|
HTMLTableCellElement,
|
||||||
|
React.ThHTMLAttributes<HTMLTableCellElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<th
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TableHead.displayName = "TableHead"
|
||||||
|
|
||||||
|
const TableCell = React.forwardRef<
|
||||||
|
HTMLTableCellElement,
|
||||||
|
React.TdHTMLAttributes<HTMLTableCellElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<td
|
||||||
|
ref={ref}
|
||||||
|
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TableCell.displayName = "TableCell"
|
||||||
|
|
||||||
|
const TableCaption = React.forwardRef<
|
||||||
|
HTMLTableCaptionElement,
|
||||||
|
React.HTMLAttributes<HTMLTableCaptionElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<caption
|
||||||
|
ref={ref}
|
||||||
|
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TableCaption.displayName = "TableCaption"
|
||||||
|
|
||||||
|
export {
|
||||||
|
Table,
|
||||||
|
TableHeader,
|
||||||
|
TableBody,
|
||||||
|
TableFooter,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
TableCell,
|
||||||
|
TableCaption,
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Tabs = TabsPrimitive.Root
|
||||||
|
|
||||||
|
const TabsList = React.forwardRef<
|
||||||
|
React.ElementRef<typeof TabsPrimitive.List>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<TabsPrimitive.List
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TabsList.displayName = TabsPrimitive.List.displayName
|
||||||
|
|
||||||
|
const TabsTrigger = React.forwardRef<
|
||||||
|
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<TabsPrimitive.Trigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
|
||||||
|
|
||||||
|
const TabsContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof TabsPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<TabsPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TabsContent.displayName = TabsPrimitive.Content.displayName
|
||||||
|
|
||||||
|
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
export interface TextareaProps
|
||||||
|
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
||||||
|
|
||||||
|
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||||
|
({ className, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<textarea
|
||||||
|
className={cn(
|
||||||
|
"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Textarea.displayName = "Textarea"
|
||||||
|
|
||||||
|
export { Textarea }
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { type ClassValue, clsx } from "clsx"
|
||||||
|
import { twMerge } from "tailwind-merge"
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs))
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { NextResponse } from 'next/server'
|
||||||
|
import type { NextRequest } from 'next/server'
|
||||||
|
|
||||||
|
// This function can be marked `async` if using `await` inside
|
||||||
|
export function middleware(request: NextRequest) {
|
||||||
|
|
||||||
|
let isAuth:any = request.cookies.get('isAuthAp')
|
||||||
|
|
||||||
|
try {
|
||||||
|
if(isAuth.value != "true"){
|
||||||
|
return NextResponse.redirect(new URL('/login', request.url))
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return NextResponse.redirect(new URL('/login', request.url))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
matcher: [
|
||||||
|
/*
|
||||||
|
* Match all request paths except for the ones starting with:
|
||||||
|
* - api (API routes)
|
||||||
|
* - _next/static (static files)
|
||||||
|
* - _next/image (image optimization files)
|
||||||
|
* - favicon.ico (favicon file)
|
||||||
|
*/
|
||||||
|
'/((?!api|_next/static|_next/image|logo.png|favicon.ico|login|login/verify|register).*)',
|
||||||
|
],
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import {
|
||||||
|
useQuery,
|
||||||
|
useMutation,
|
||||||
|
useQueryClient,
|
||||||
|
QueryClient,
|
||||||
|
QueryClientProvider,
|
||||||
|
} from '@tanstack/react-query'
|
||||||
|
|
||||||
|
|
||||||
|
const queryClient = new QueryClient()
|
||||||
|
|
||||||
|
|
||||||
|
const ReactQueryProvider = ({children} : {children: React.ReactNode})=>{
|
||||||
|
return (
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
|
{ children }
|
||||||
|
</QueryClientProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ReactQueryProvider
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import {useQuery} from "@tanstack/react-query";
|
||||||
|
import {getCitys} from "@/api/citys";
|
||||||
|
|
||||||
|
export const useGetCitys = () => {
|
||||||
|
return useQuery({queryKey: ['citys'], queryFn: getCitys})
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import {useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import {getEmtiyazat} from "@/api/emtiyazat";
|
||||||
|
|
||||||
|
export const useListEmtiyazatQuery = ()=>{
|
||||||
|
return useQuery({queryKey: ['list-emtiyazat'], queryFn: getEmtiyazat})
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import {useQuery} from "@tanstack/react-query";
|
||||||
|
import {getCitys} from "@/api/project";
|
||||||
|
import {getMojriInfo} from "@/api/home";
|
||||||
|
|
||||||
|
|
||||||
|
export const useMojriQuery = () => {
|
||||||
|
return useQuery({queryKey: ['mojri-info'], queryFn: getMojriInfo,})
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
|
import {createOrder, getOrder, getOrders} from "@/api/order";
|
||||||
|
import { AxiosResponse } from 'axios'
|
||||||
|
import { IOrder, IOrderPagination } from '@/types/order'
|
||||||
|
import { useRouter } from 'next/navigation'
|
||||||
|
|
||||||
|
export const useGetOrders = ()=>{
|
||||||
|
return useQuery<IOrderPagination, Error>({
|
||||||
|
queryKey: ['orders'],
|
||||||
|
queryFn: ()=>{ return getOrders() }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useGetOrder = (order_id: number)=>{
|
||||||
|
return useQuery<IOrder, Error>({
|
||||||
|
queryKey: ['order', order_id],
|
||||||
|
queryFn: ()=>{ return getOrder(order_id) }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useCreateOrder = (open_order:any, setErrorMessage:any)=>{
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
const router = useRouter()
|
||||||
|
return useMutation({
|
||||||
|
mutationKey: ['createOrder'],
|
||||||
|
mutationFn: (product_id:number)=>{ return createOrder(product_id) },
|
||||||
|
onSuccess: (result:AxiosResponse)=>{
|
||||||
|
open_order(false)
|
||||||
|
//console.log('result', result)
|
||||||
|
const order_id = result.data.order
|
||||||
|
router.push(`/javayez/${order_id}`)
|
||||||
|
queryClient.invalidateQueries({queryKey: ['mojri-info']})
|
||||||
|
|
||||||
|
},
|
||||||
|
onError: async (e:any)=>{
|
||||||
|
if(e?.error.includes('not enough')){
|
||||||
|
setErrorMessage(
|
||||||
|
'امتیاز شما برای این درخواست کافی نیست'
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
setErrorMessage('خطا در ثبت در خواست')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import {useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import {createProject, getCitys, getProject, getProjects, updateProjectSeller} from "@/api/project";
|
||||||
|
|
||||||
|
export const useCityQuery = () => {
|
||||||
|
return useQuery({queryKey: ['project-form-data'], queryFn: getCitys})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useCreateProjectMutation = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
|
||||||
|
mutationFn: createProject,
|
||||||
|
onSuccess:(result)=> {
|
||||||
|
router.push(`/projects/${result.data.project_id}/`)
|
||||||
|
},
|
||||||
|
onError:(error)=> alert("خطا در ثبت پروژه")
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useProjectsQuery = () => {
|
||||||
|
return useQuery({queryKey: ['projects'], queryFn: getProjects})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useProjectQuery = (id:number) => {
|
||||||
|
return useQuery({queryKey: ['project', id], queryFn: ()=>getProject(id)})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUpdateProjectSellerMutation = () => {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
return useMutation({mutationFn: updateProjectSeller, onSuccess: async ()=>{
|
||||||
|
await queryClient.invalidateQueries({queryKey: ['project',]})
|
||||||
|
}})
|
||||||
|
}
|
||||||
Whitespace-only changes.
@@ -0,0 +1,20 @@
|
|||||||
|
import {useRouter} from "next/navigation";
|
||||||
|
import {useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import {registerUser} from "@/api/register";
|
||||||
|
|
||||||
|
export const useRegisterUserMutation = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
|
||||||
|
mutationFn: registerUser,
|
||||||
|
onSuccess:(result)=> {
|
||||||
|
router.push(`/login/`)
|
||||||
|
},
|
||||||
|
onError:(error)=> alert("خطا در عضویت")
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import {useRouter} from "next/navigation";
|
||||||
|
import {useMutation, useQuery, useQueryClient} from "@tanstack/react-query";
|
||||||
|
import {getUserProfile, updateUserProfile} from "@/api/userprofile";
|
||||||
|
import {getProject} from "@/api/project";
|
||||||
|
|
||||||
|
export const useGetUserProfile = () => {
|
||||||
|
return useQuery({queryKey: ['userprofile'], queryFn: getUserProfile })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUpdateUserProfile = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
|
||||||
|
mutationFn: updateUserProfile,
|
||||||
|
onSuccess:(result)=> {
|
||||||
|
queryClient.invalidateQueries({queryKey: ['userprofile']})
|
||||||
|
},
|
||||||
|
onError:(error)=> alert("خطا در بروز رسانی پروفایل")
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import {IPagination} from '@/types/pagination'
|
||||||
|
|
||||||
|
interface IProject {
|
||||||
|
id: number
|
||||||
|
factor_price: number
|
||||||
|
}
|
||||||
|
export interface IEmtiyazat {
|
||||||
|
id: number
|
||||||
|
project: IProject | null
|
||||||
|
created_at: string
|
||||||
|
updated_at: string
|
||||||
|
credit: number | null
|
||||||
|
debit: number | null
|
||||||
|
desc: string | null
|
||||||
|
cuser: number | null
|
||||||
|
operator: number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IEmtiyazatPagination extends IPagination {
|
||||||
|
results: Array<IEmtiyazat>
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import {IPagination} from '@/types/pagination'
|
||||||
|
|
||||||
|
interface Product {
|
||||||
|
id: number;
|
||||||
|
desc: string | null;
|
||||||
|
name: string;
|
||||||
|
price: number | null;
|
||||||
|
image: string | null;
|
||||||
|
}
|
||||||
|
export interface IOrder {
|
||||||
|
id: number;
|
||||||
|
product: Product;
|
||||||
|
status_text: string;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
name: string;
|
||||||
|
desc: string | null;
|
||||||
|
code: string | null;
|
||||||
|
price: number | null;
|
||||||
|
emtiyaz: number | null;
|
||||||
|
status: string | null;
|
||||||
|
cuser_mojri: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IOrderPagination extends IPagination {
|
||||||
|
data: IOrder[]
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export interface IPagination {
|
||||||
|
count: number
|
||||||
|
next: string | null
|
||||||
|
previous: string | null
|
||||||
|
results: Array<any>
|
||||||
|
}
|
||||||
+73
-13
@@ -1,20 +1,80 @@
|
|||||||
import type { Config } from "tailwindcss";
|
import type { Config } from "tailwindcss"
|
||||||
|
|
||||||
const config: Config = {
|
const config = {
|
||||||
|
darkMode: ["class"],
|
||||||
content: [
|
content: [
|
||||||
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
'./pages/**/*.{ts,tsx}',
|
||||||
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
'./components/**/*.{ts,tsx}',
|
||||||
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
'./app/**/*.{ts,tsx}',
|
||||||
],
|
'./src/**/*.{ts,tsx}',
|
||||||
|
],
|
||||||
|
prefix: "",
|
||||||
theme: {
|
theme: {
|
||||||
|
container: {
|
||||||
|
center: true,
|
||||||
|
padding: "2rem",
|
||||||
|
screens: {
|
||||||
|
"2xl": "1400px",
|
||||||
|
},
|
||||||
|
},
|
||||||
extend: {
|
extend: {
|
||||||
backgroundImage: {
|
colors: {
|
||||||
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
|
border: "hsl(var(--border))",
|
||||||
"gradient-conic":
|
input: "hsl(var(--input))",
|
||||||
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
|
ring: "hsl(var(--ring))",
|
||||||
|
background: "hsl(var(--background))",
|
||||||
|
foreground: "hsl(var(--foreground))",
|
||||||
|
primary: {
|
||||||
|
DEFAULT: "hsl(var(--primary))",
|
||||||
|
foreground: "hsl(var(--primary-foreground))",
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
DEFAULT: "hsl(var(--secondary))",
|
||||||
|
foreground: "hsl(var(--secondary-foreground))",
|
||||||
|
},
|
||||||
|
destructive: {
|
||||||
|
DEFAULT: "hsl(var(--destructive))",
|
||||||
|
foreground: "hsl(var(--destructive-foreground))",
|
||||||
|
},
|
||||||
|
muted: {
|
||||||
|
DEFAULT: "hsl(var(--muted))",
|
||||||
|
foreground: "hsl(var(--muted-foreground))",
|
||||||
|
},
|
||||||
|
accent: {
|
||||||
|
DEFAULT: "hsl(var(--accent))",
|
||||||
|
foreground: "hsl(var(--accent-foreground))",
|
||||||
|
},
|
||||||
|
popover: {
|
||||||
|
DEFAULT: "hsl(var(--popover))",
|
||||||
|
foreground: "hsl(var(--popover-foreground))",
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
DEFAULT: "hsl(var(--card))",
|
||||||
|
foreground: "hsl(var(--card-foreground))",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
borderRadius: {
|
||||||
|
lg: "var(--radius)",
|
||||||
|
md: "calc(var(--radius) - 2px)",
|
||||||
|
sm: "calc(var(--radius) - 4px)",
|
||||||
|
},
|
||||||
|
keyframes: {
|
||||||
|
"accordion-down": {
|
||||||
|
from: { height: "0" },
|
||||||
|
to: { height: "var(--radix-accordion-content-height)" },
|
||||||
|
},
|
||||||
|
"accordion-up": {
|
||||||
|
from: { height: "var(--radix-accordion-content-height)" },
|
||||||
|
to: { height: "0" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
"accordion-down": "accordion-down 0.2s ease-out",
|
||||||
|
"accordion-up": "accordion-up 0.2s ease-out",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [require("tailwindcss-animate")],
|
||||||
};
|
} satisfies Config
|
||||||
export default config;
|
|
||||||
|
export default config
|
||||||
Reference in new issue
Block a user