Add website files
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
# bokhary-homepage
|
||||
|
||||
Homepage for bokhary.ir - coded in typescript with react.js and tailwind
|
||||
A cute homepage for bokhary.ir
|
||||
23
eslint.config.js
Normal file
23
eslint.config.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import tseslint from 'typescript-eslint'
|
||||
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
js.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
reactHooks.configs.flat.recommended,
|
||||
reactRefresh.configs.vite,
|
||||
],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
},
|
||||
])
|
||||
13
index.html
Normal file
13
index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/image.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Bokhary | بخاری</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
36
package.json
Normal file
36
package.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "bokhary-homepage",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/vazirmatn": "^5.2.8",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"@tsparticles/all": "^3.9.1",
|
||||
"@tsparticles/engine": "^3.9.1",
|
||||
"@tsparticles/react": "^3.0.0",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"tailwindcss": "^4.1.18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@types/node": "^24.10.1",
|
||||
"@types/react": "^19.2.5",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^5.1.1",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.4.24",
|
||||
"globals": "^16.5.0",
|
||||
"typescript": "~5.9.3",
|
||||
"typescript-eslint": "^8.46.4",
|
||||
"vite": "^7.2.4"
|
||||
}
|
||||
}
|
||||
3221
pnpm-lock.yaml
generated
Normal file
3221
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
3
pnpm-workspace.yaml
Normal file
3
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
onlyBuiltDependencies:
|
||||
- '@tsparticles/engine'
|
||||
- esbuild
|
||||
BIN
public/image.png
Normal file
BIN
public/image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 405 KiB |
16
src/components/Bokhary.tsx
Normal file
16
src/components/Bokhary.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
export default function Bokhary() {
|
||||
return (
|
||||
<div className="min-h-screen grid place-items-center">
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<img
|
||||
src="/image.png"
|
||||
alt="Bokhary"
|
||||
className="w-64 h-auto drop-shadow-[0_0_15px_rgba(255,255,255,0.8)]"
|
||||
/>
|
||||
<span className="text-4xl font-bold text-orange-200 -translate-y-18 font-vazirmatn [text-shadow:0_0_5px_rgba(0,0,0,1),0_0_10px_rgba(0,0,0,1),0_0_20px_rgba(0,0,0,0.9),0_0_30px_rgba(0,0,0,0.8)]">
|
||||
بـخــــاری
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
23
src/components/Button.tsx
Normal file
23
src/components/Button.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { MouseEventHandler } from "react";
|
||||
|
||||
type Props = {
|
||||
onClick?: MouseEventHandler<HTMLButtonElement>;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export default function ModalButton({ onClick, children }: Props) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className="relative px-5 py-1.5 rounded-lg overflow-hidden transition-all duration-300 mx-2 my-1.5
|
||||
bg-linear-to-br from-orange-300/60 to-orange-400/90
|
||||
hover:scale-105 active:scale-95
|
||||
text-white font-medium shadow-md font-vazirmatn
|
||||
before:absolute before:inset-0 before:bg-white/10 before:opacity-0 hover:before:opacity-100
|
||||
before:transition-opacity before:duration-300"
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
55
src/components/CodeBlock.tsx
Normal file
55
src/components/CodeBlock.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { useState } from "react";
|
||||
|
||||
type Props = {
|
||||
code: string;
|
||||
fileName: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export default function CodeBlock({ code, fileName, className }: Props) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const copyToClipboard = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(code);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 1000);
|
||||
} catch {
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
rounded-lg bg-gray-900 text-sm text-gray-100
|
||||
${className}
|
||||
`}
|
||||
>
|
||||
<div className="flex items-center justify-between border-b border-gray-800 px-4 py-2 bg-gray-800 rounded-t-lg">
|
||||
<span className="text-xs font-medium text-gray-300">
|
||||
{fileName}
|
||||
</span>
|
||||
|
||||
<button
|
||||
dir='rtl'
|
||||
onClick={copyToClipboard}
|
||||
className={`
|
||||
flex items-center gap-1 px-2.5 py-0.5 text-xs font-medium
|
||||
rounded transition-colors duration-200
|
||||
${
|
||||
copied
|
||||
? "bg-green-600 hover:bg-green-500 text-white"
|
||||
: "bg-gray-700 hover:bg-gray-600 text-gray-200"
|
||||
}
|
||||
`}
|
||||
>
|
||||
{copied ? "Copied" : "Copy"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<pre className="overflow-x-auto whitespace-pre-wrap wrap-break-word p-4">
|
||||
<code>{code}</code>
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
7
src/components/Element.tsx
Normal file
7
src/components/Element.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
export default function Bokhary() {
|
||||
return (
|
||||
<a href="https://element.bokhary.ir" target="_blank" rel="noreferrer" className="">
|
||||
Open in Element
|
||||
</a>
|
||||
);
|
||||
}
|
||||
30
src/components/FloatingButton.tsx
Normal file
30
src/components/FloatingButton.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import type { MouseEventHandler } from "react";
|
||||
|
||||
type Props = {
|
||||
onClick?: MouseEventHandler<HTMLButtonElement>;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
export default function Button({ onClick, text }: Props) {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className="group relative px-5 py-3 rounded-full overflow-hidden transition-all duration-300 hover:scale-105 active:scale-95 cursor-pointer mx-1"
|
||||
>
|
||||
<div className="absolute inset-0 bg-orange-500/10 backdrop-blur-xl border border-orange-400/30 rounded-full"></div>
|
||||
|
||||
<div className="absolute inset-0 bg-linear-to-br from-orange-400/20 via-orange-500/5 to-transparent rounded-full opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||
|
||||
<div className="absolute inset-0 bg-linear-to-r from-transparent via-orange-300/30 to-transparent -translate-x-full group-hover:translate-x-full transition-transform duration-1000 rounded-full"></div>
|
||||
|
||||
<div className="absolute inset-0 rounded-full opacity-0 group-hover:opacity-100 transition-opacity duration-300 blur-xl bg-orange-500/40"></div>
|
||||
|
||||
<div className="absolute inset-px rounded-full bg-linear-to-b from-orange-400/10 to-transparent"></div>
|
||||
|
||||
<span className="relative flex items-center gap-2 text-white font-medium text-lg font-vazirmatn">
|
||||
{text}
|
||||
<svg className="w-6 h-6 transition-transform group-hover:rotate-12 duration-300" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
124
src/components/Modal.tsx
Normal file
124
src/components/Modal.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
import { useEffect } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
|
||||
import Button from "./Button";
|
||||
import CodeBlock from "./CodeBlock";
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
title?: string;
|
||||
};
|
||||
|
||||
const hosts = `
|
||||
# Bokhary Hosts
|
||||
37.32.13.184 bokhary.ir
|
||||
37.32.13.184 cinny.bokhary.ir
|
||||
37.32.13.184 element.bokhary.ir
|
||||
`.trim();
|
||||
|
||||
export default function Modal({
|
||||
isOpen,
|
||||
onClose,
|
||||
title = "راهنمای استفاده",
|
||||
}: Props) {
|
||||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
const handler = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") onClose();
|
||||
};
|
||||
document.addEventListener("keydown", handler);
|
||||
return () => document.removeEventListener("keydown", handler);
|
||||
}, [isOpen, onClose]);
|
||||
|
||||
return createPortal(
|
||||
<div
|
||||
className={`
|
||||
fixed inset-0 z-50 flex items-center justify-center
|
||||
bg-black/30 backdrop-blur-sm transition-opacity duration-300
|
||||
${isOpen ? "opacity-100" : "opacity-0 pointer-events-none"}
|
||||
`}
|
||||
onClick={onClose}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
relative w-xl max-w-[90%] bg-linear-to-br from-orange-600/90 to-orange-300/80
|
||||
rounded-xl pb-6 text-center text-white shadow-xl
|
||||
transform transition-all duration-300
|
||||
${isOpen ? "scale-100" : "scale-95"}
|
||||
max-h-[80vh]
|
||||
`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
style={{
|
||||
scrollbarWidth: "none",
|
||||
msOverflowStyle: "none",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="flex items-center justify-between px-4 py-3 bg-black/20 rounded-t-xl"
|
||||
style={{ background: "inherit" }}
|
||||
>
|
||||
<p className="text-lg font-bold font-vazirmatn" dir="rtl">
|
||||
{title}
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
aria-label="Close"
|
||||
className="text-2xl hover:text-orange-200 transition-colors"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`
|
||||
overflow-y-auto overflow-x-hidden
|
||||
max-h-[calc(79vh-3rem)] p-8 pt-0
|
||||
`}
|
||||
>
|
||||
<style>{`
|
||||
div::-webkit-scrollbar { display: none; }
|
||||
`}</style>
|
||||
|
||||
<p className="mt-8 text-lg leading-relaxed font-vazirmatn text-center font-bold" dir="rtl">
|
||||
کلاینتهای تحت وب
|
||||
</p>
|
||||
|
||||
<Button onClick={() => open("https://element.bokhary.ir", "_blank")}>المنت وب</Button>
|
||||
<Button onClick={() => open("https://cinny.bokhary.ir", "_blank")}>سینی</Button>
|
||||
|
||||
<p className="mt-8 text-lg leading-relaxed font-vazirmatn text-center font-bold" dir="rtl">
|
||||
کلاینتهای اندروید
|
||||
</p>
|
||||
|
||||
<Button onClick={() => open("https://bokhary.ir/apps/Element.apk", "_blank")}>المنت</Button>
|
||||
<Button onClick={() => open("https://bokhary.ir/apps/elementx.apk", "_blank")}>المنت ایکس</Button>
|
||||
<Button onClick={() => open("https://bokhary.ir/apps/fluffy.apk", "_blank")}>فلافی چت</Button>
|
||||
|
||||
<p className="mt-8 text-md leading-relaxed font-vazirmatn text-center" dir="rtl">
|
||||
برای رفع اختلالات DNS مقادیر زیر را به فایل hosts سیستمعامل خود اضافه کنید.
|
||||
</p>
|
||||
|
||||
<CodeBlock fileName="Your Hosts file" code={hosts} className="mt-3 text-left" />
|
||||
|
||||
<p className="mt-8 text-md leading-relaxed font-vazirmatn text-center" dir="rtl">
|
||||
یا در اندروید از نسخه تغییر یافته AdAway استفاده کنید.
|
||||
</p>
|
||||
|
||||
<Button
|
||||
onClick={() =>
|
||||
open(
|
||||
"https://s34.picofile.com/file/8489250218/AdAway_6_1_4.apk.html",
|
||||
"_blank"
|
||||
)
|
||||
}
|
||||
>
|
||||
AdAway
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
);
|
||||
}
|
||||
57
src/components/Particles.tsx
Normal file
57
src/components/Particles.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import { useEffect, useState, memo } from "react";
|
||||
|
||||
import Particles, { initParticlesEngine } from "@tsparticles/react";
|
||||
import { loadAll } from "@tsparticles/all";
|
||||
import type { ISourceOptions } from "@tsparticles/engine";
|
||||
|
||||
function BackgroundParticles() {
|
||||
const [init, setInit] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
initParticlesEngine(async (engine) => {
|
||||
await loadAll(engine);
|
||||
}).then(() => setInit(true));
|
||||
}, []);
|
||||
|
||||
const options: ISourceOptions = {
|
||||
background: { color: { value: "#2b2b2b" } },
|
||||
fpsLimit: 60,
|
||||
particles: {
|
||||
number: { value: 150, density: { enable: true, width: 1920, height: 1080 } },
|
||||
color: { value: "#ff2e2e" },
|
||||
shape: { type: "circle" },
|
||||
opacity: { value: { min: 0.3, max: 0.8 }, animation: { enable: true, speed: 1 } },
|
||||
size: { value: { min: 2, max: 5 } },
|
||||
links: { enable: true, distance: 150, color: "#ffffff", opacity: 0.3, width: 1 },
|
||||
move: { enable: true, speed: 1, outModes: { default: "bounce" } },
|
||||
},
|
||||
interactivity: {
|
||||
events: {
|
||||
onHover: { enable: true, mode: "grab" },
|
||||
onClick: { enable: true, mode: "push" },
|
||||
},
|
||||
modes: {
|
||||
grab: { distance: 100, links: { opacity: 0.7 } },
|
||||
push: { quantity: 1 },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (!init) return null;
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: "fixed",
|
||||
inset: 0,
|
||||
width: "100dvw",
|
||||
height: "100dvh",
|
||||
zIndex: -1,
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
>
|
||||
<Particles id="tsparticles" options={options} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(BackgroundParticles);
|
||||
50
src/components/TimePassed.tsx
Normal file
50
src/components/TimePassed.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export default function TimePassed({ startDate = "2026-01-08T21:00:00" }) {
|
||||
|
||||
const OUTAGE_START_DATE = new Date(startDate);
|
||||
|
||||
const [formattedString, setFormattedString] = useState('');
|
||||
|
||||
const calculateTimePassed = () => {
|
||||
const now = new Date();
|
||||
const difference = now.getTime() - OUTAGE_START_DATE.getTime();
|
||||
|
||||
const days = Math.floor(difference / (1000 * 60 * 60 * 24));
|
||||
const hours = Math.floor((difference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
||||
const minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60));
|
||||
const seconds = Math.floor((difference % (1000 * 60)) / 1000);
|
||||
|
||||
return { days, hours, minutes, seconds };
|
||||
};
|
||||
|
||||
const generatePersianString = (time: Record<string, number>) => {
|
||||
const { days, hours, minutes, seconds } = time;
|
||||
|
||||
const parts = [];
|
||||
|
||||
if (days > 0) parts.push(`${days.toLocaleString('fa-IR')} روز`);
|
||||
if (hours > 0) parts.push(`${hours.toLocaleString('fa-IR')} ساعت`);
|
||||
if (minutes > 0) parts.push(`${minutes.toLocaleString('fa-IR')} دقیقه`);
|
||||
if (seconds > 0) parts.push(`${seconds.toLocaleString('fa-IR')} ثانیه`);
|
||||
|
||||
if (parts.length === 0) return "0 ثانیه";
|
||||
|
||||
return parts.join(" و ") + " " + "از قطعی سراسری اینترنت گذشته است...";
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const updateTimer = () => {
|
||||
const calculatedTime = calculateTimePassed();
|
||||
setFormattedString(generatePersianString(calculatedTime));
|
||||
};
|
||||
|
||||
updateTimer();
|
||||
|
||||
const intervalId = setInterval(updateTimer, 1000);
|
||||
|
||||
return () => clearInterval(intervalId);
|
||||
}, [startDate]);
|
||||
|
||||
return formattedString
|
||||
};
|
||||
27
src/components/Toast.tsx
Normal file
27
src/components/Toast.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import type { ReactNode } from "react";
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
onClose: () => void;
|
||||
};
|
||||
export default function Toast({ children, onClose }: Props) {
|
||||
return (
|
||||
<div className="group relative w-full px-5 py-2 rounded-lg overflow-hidden transition-all duration-300 hover:scale-105 active:scale-95 cursor-pointer">
|
||||
<div className="absolute inset-0 bg-orange-500/10 backdrop-blur-xl border border-orange-400/30 rounded-lg"></div>
|
||||
<div className="absolute inset-0 bg-linear-to-br from-orange-400/20 via-orange-500/5 to-transparent rounded-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||
<div className="absolute inset-0 bg-linear-to-r from-transparent via-orange-300/30 to-transparent -translate-x-full group-hover:translate-x-full transition-transform duration-1000 rounded-lg"></div>
|
||||
<div className="absolute inset-0 rounded-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300 blur-xl bg-orange-500/40"></div>
|
||||
<div className="absolute inset-px rounded-lg bg-linear-to-b from-orange-400/10 to-transparent"></div>
|
||||
|
||||
<span dir='rtl' className="relative flex items-center gap-2 text-white font-medium text-sm font-vazirmatn ml-4">
|
||||
{children}
|
||||
</span>
|
||||
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute left-3 top-1/2 transform -translate-y-1/2 text-white opacity-70 hover:opacity-100 transition-opacity duration-200"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
4
src/declarations.d.ts
vendored
Normal file
4
src/declarations.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
declare module '@fontsource/vazirmatn' {
|
||||
export const style: unknown; // Or more specific types if known, but unknown is generally safe for imports like this
|
||||
export default style;
|
||||
}
|
||||
42
src/index.css
Normal file
42
src/index.css
Normal file
@@ -0,0 +1,42 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@theme {
|
||||
--font-vazirmatn: 'Vazirmatn', sans-serif;
|
||||
--font-vazirmatn-sans: 'Vazirmatn', ui-sans-serif, system-ui, sans-serif;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: scale(0.95); }
|
||||
to { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
|
||||
.animate-fade-in {
|
||||
animation: fadeIn 0.3s forwards;
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.custom-scrollbar {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #ff9f1c #00000033;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background-color: #ff9f1c;
|
||||
border-radius: 4px;
|
||||
border: 2px solid transparent;
|
||||
background-clip: content-box;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #ff7f00;
|
||||
}
|
||||
}
|
||||
47
src/index.tsx
Normal file
47
src/index.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { StrictMode, useState } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
|
||||
import FloatingButton from "./components/FloatingButton";
|
||||
import Modal from "./components/Modal";
|
||||
import BackgroundParticles from "./components/Particles";
|
||||
import Bokhary from "./components/Bokhary";
|
||||
import Toast from "./components/Toast";
|
||||
import TimePassed from "./components/TimePassed"
|
||||
|
||||
import "@fontsource/vazirmatn";
|
||||
import "./index.css";
|
||||
|
||||
function App() {
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [toast, showToast] = useState(true);
|
||||
|
||||
|
||||
return (
|
||||
<StrictMode>
|
||||
|
||||
<BackgroundParticles />
|
||||
|
||||
<div className="relative min-h-dvh">
|
||||
<div className="fixed m-2 top-0 left-0 right-0">
|
||||
{toast && (
|
||||
<Toast onClose={() => showToast(false)}>
|
||||
<TimePassed></TimePassed>
|
||||
</Toast>
|
||||
)}
|
||||
</div>
|
||||
<div className="relative z-10">
|
||||
<Bokhary />
|
||||
</div>
|
||||
|
||||
<div className="fixed bottom-6 left-1/2 -translate-x-1/2 z-20">
|
||||
<FloatingButton text="اتصال" onClick={() => setModalOpen(true)} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal isOpen={modalOpen} onClose={() => setModalOpen(false)} />
|
||||
|
||||
</StrictMode>
|
||||
);
|
||||
}
|
||||
|
||||
createRoot(document.getElementById("root")!).render(<App />);
|
||||
6
src/vite-env.d.ts
vendored
Normal file
6
src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.css' {
|
||||
const classes: { [key: string]: string };
|
||||
export default classes;
|
||||
}
|
||||
28
tsconfig.app.json
Normal file
28
tsconfig.app.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2022",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"types": ["vite/client"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
7
tsconfig.json
Normal file
7
tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
26
tsconfig.node.json
Normal file
26
tsconfig.node.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2023",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"types": ["node"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
9
vite.config.ts
Normal file
9
vite.config.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react(), tailwindcss()],
|
||||
})
|
||||
Reference in New Issue
Block a user