first push
24
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
9
.prettierrc
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"singleQuote": true,
|
||||
"semi": true,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"jsxSingleQuote": false,
|
||||
"jsxBracketSameLine": false,
|
||||
"arrowParens": "always"
|
||||
}
|
||||
50
README.md
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# React + TypeScript + Vite
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
|
||||
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
|
||||
|
||||
- Configure the top-level `parserOptions` property like this:
|
||||
|
||||
```js
|
||||
export default tseslint.config({
|
||||
languageOptions: {
|
||||
// other options...
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
|
||||
- Optionally add `...tseslint.configs.stylisticTypeChecked`
|
||||
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:
|
||||
|
||||
```js
|
||||
// eslint.config.js
|
||||
import react from 'eslint-plugin-react';
|
||||
|
||||
export default tseslint.config({
|
||||
// Set the react version
|
||||
settings: { react: { version: '18.3' } },
|
||||
plugins: {
|
||||
// Add the react plugin
|
||||
react,
|
||||
},
|
||||
rules: {
|
||||
// other rules...
|
||||
// Enable its recommended rules
|
||||
...react.configs.recommended.rules,
|
||||
...react.configs['jsx-runtime'].rules,
|
||||
},
|
||||
});
|
||||
```
|
||||
4842
bundle-analysis.html
Normal file
28
eslint.config.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
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';
|
||||
|
||||
export default tseslint.config(
|
||||
{ ignores: ['dist'] },
|
||||
{
|
||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
plugins: {
|
||||
'react-hooks': reactHooks,
|
||||
'react-refresh': reactRefresh,
|
||||
},
|
||||
rules: {
|
||||
...reactHooks.configs.recommended.rules,
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
},
|
||||
);
|
||||
19
index.html
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<!doctype html>
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/webp"
|
||||
href="/src/assets/core/Logo-192x192.webp"
|
||||
/>
|
||||
<meta name="description" content=" description of your web app" />
|
||||
<title>Website</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
44
package.json
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"name": "website",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "vite --port=3000",
|
||||
"build": "vite build",
|
||||
"format": "prettier --write .",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "^5.53.1",
|
||||
"antd": "^5.21.1",
|
||||
"axios": "^1.7.5",
|
||||
"formik": "^2.4.6",
|
||||
"framer-motion": "^11.5.0",
|
||||
"i18next": "^23.14.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-i18next": "^14.1.3",
|
||||
"react-icons": "^5.3.0",
|
||||
"react-player": "^2.16.0",
|
||||
"react-router-dom": "^6.26.1",
|
||||
"react-transition-group": "^4.4.5",
|
||||
"swiper": "^11.1.14",
|
||||
"zustand": "^4.5.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.5.1",
|
||||
"@types/react": "^18.3.5",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"eslint": "^8.57.0",
|
||||
"prettier": "^3.3.3",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"sass": "^1.77.8",
|
||||
"typescript": "^5.5.4",
|
||||
"vite": "^5.4.2",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-env-compatible": "^2.0.1"
|
||||
}
|
||||
}
|
||||
3467
pnpm-lock.yaml
Normal file
BIN
public/Download/1.png
Normal file
|
After Width: | Height: | Size: 209 KiB |
BIN
public/Features/1.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
public/Features/2.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
public/Features/3.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
public/Features/4.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
public/Features/5.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
public/Features/6.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
public/Features/main.png
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
public/HomeImage.png
Normal file
|
After Width: | Height: | Size: 202 KiB |
BIN
public/HowItWorks/1.png
Normal file
|
After Width: | Height: | Size: 763 B |
BIN
public/HowItWorks/2.png
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
public/HowItWorks/3.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/HowItWorks/4.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
public/HowItWorks/main.png
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
public/LOGO.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
public/Note/1.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
public/Note/2.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
public/Slider.png
Normal file
|
After Width: | Height: | Size: 197 KiB |
BIN
public/SwipeScreenShoot/1.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
public/SwipeScreenShoot/2.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
public/SwipeScreenShoot/3.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
public/SwipeScreenShoot/4.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
public/SwipeScreenShoot/5.png
Normal file
|
After Width: | Height: | Size: 97 KiB |
BIN
public/Video/1.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
public/Video/2.png
Normal file
|
After Width: | Height: | Size: 896 B |
BIN
public/Video/3.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
public/Video/4.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
public/Video/Play.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
public/Video/video.png
Normal file
|
After Width: | Height: | Size: 396 KiB |
BIN
public/circle.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
public/contact/1.png
Normal file
|
After Width: | Height: | Size: 342 B |
BIN
public/contact/2.png
Normal file
|
After Width: | Height: | Size: 480 B |
BIN
public/contact/3.png
Normal file
|
After Width: | Height: | Size: 626 B |
3
public/shape.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="1440" height="476" viewBox="0 0 1440 476" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 316.38L717.5 0.5L1440 316.38V476H0V316.38Z" fill="#F5F7FB"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 182 B |
1
public/vite.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
3
robots.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
26
site.webmanifest
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"name": "My Web App",
|
||||
"short_name": "WebApp",
|
||||
"description": "A description of your web app.",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#000000",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/src/assets/core/Logo-192x192.webp",
|
||||
"sizes": "192x192",
|
||||
"type": "image/webp"
|
||||
},
|
||||
{
|
||||
"src": "/src/assets/core/Logo-512x512.webp",
|
||||
"sizes": "512x512",
|
||||
"type": "image/webp"
|
||||
},
|
||||
{
|
||||
"src": "/src/assets/core/Logo-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
||||
31
src/App.tsx
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import Layout from './components/layout/Layout';
|
||||
import DownloadPage from './pages/DownloadPage';
|
||||
import FeaturesPage from './pages/FeaturesPage';
|
||||
import HomePage from './pages/HomePage';
|
||||
import HowItWork from './pages/HowItWork';
|
||||
import NotePage from './pages/NotePage';
|
||||
import ScreenShoot from './pages/ScreenShoot';
|
||||
import VideoPage from './pages/VideoPage';
|
||||
import ProviderContainer from './ProviderContainer';
|
||||
import ContactPage from './pages/ContactPage';
|
||||
import './styles/App/index.scss';
|
||||
const App = () => {
|
||||
|
||||
return (
|
||||
<ProviderContainer>
|
||||
{/* <Routes /> */}
|
||||
<Layout>
|
||||
<HomePage />
|
||||
<FeaturesPage/>
|
||||
<HowItWork/>
|
||||
<VideoPage/>
|
||||
<ScreenShoot/>
|
||||
<NotePage/>
|
||||
<DownloadPage/>
|
||||
<ContactPage/>
|
||||
</Layout>
|
||||
</ProviderContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
13
src/ProviderContainer.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { BrowserRouter } from 'react-router-dom';
|
||||
import QueryProvider from './lib/ReactQueryProvider';
|
||||
import I18nProvider from './lib/I18nProvider';
|
||||
|
||||
function ProviderContainer({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<QueryProvider>
|
||||
<I18nProvider>{children}</I18nProvider>
|
||||
</QueryProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProviderContainer;
|
||||
33
src/Routes.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import { lazy, Suspense } from 'react';
|
||||
import { Route, Routes as RoutesContainer } from 'react-router-dom';
|
||||
import './styles/App/index.scss';
|
||||
import SpinContainer from './components/layout/SpinContainer';
|
||||
import HomePage from './pages/HomePage';
|
||||
import Layout from './components/layout/Layout';
|
||||
import { RoutesEnums } from './enums/RoutesEnums';
|
||||
const Page404 = lazy(() => import('./components/layout/NotFoundPage'));
|
||||
|
||||
const Routes = () => {
|
||||
return (
|
||||
<RoutesContainer>
|
||||
<Route
|
||||
path={RoutesEnums.HOME}
|
||||
element={
|
||||
<Layout>
|
||||
<HomePage />
|
||||
</Layout>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={'/*'}
|
||||
element={
|
||||
<Suspense fallback={<SpinContainer />}>
|
||||
<Page404 />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
</RoutesContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default Routes;
|
||||
BIN
src/apis/Logo-192x192.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
22
src/apis/example.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import {
|
||||
useAddMutation,
|
||||
useDeleteMutation,
|
||||
useGetQuery,
|
||||
useUpdateMutation,
|
||||
} from './helpers';
|
||||
|
||||
const API = {
|
||||
GET: '/example',
|
||||
ADD: '/example',
|
||||
DELETE: '/example',
|
||||
UPDATE: '/example',
|
||||
};
|
||||
|
||||
const KEY = 'example';
|
||||
|
||||
export const useGetExample = (params?: any, options?: any) =>
|
||||
useGetQuery(KEY, API.GET, params, options);
|
||||
export const useAddExample = () => useAddMutation(KEY, API.ADD);
|
||||
export const useUpdateExample = () => useUpdateMutation(KEY, API.ADD);
|
||||
|
||||
export const useDeleteExample = () => useDeleteMutation(KEY, API.ADD);
|
||||
41
src/apis/helpers/AxiosBuilder.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import axios, { AxiosInstance, AxiosRequestConfig, ResponseType } from 'axios';
|
||||
|
||||
class AxiosBuilder {
|
||||
private baseURL: string = '';
|
||||
private headers: Record<string, any> = {};
|
||||
private timeout: number = 60000;
|
||||
private responseType: ResponseType = 'json';
|
||||
|
||||
withBaseURL(baseURL: string): AxiosBuilder {
|
||||
this.baseURL = baseURL;
|
||||
return this;
|
||||
}
|
||||
|
||||
withHeaders(headers: Record<string, any>): AxiosBuilder {
|
||||
this.headers = { ...this.headers, ...headers };
|
||||
return this;
|
||||
}
|
||||
|
||||
withTimeout(timeout: number): AxiosBuilder {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
withResponseType(responseType: ResponseType): AxiosBuilder {
|
||||
this.responseType = responseType;
|
||||
return this;
|
||||
}
|
||||
|
||||
build(): AxiosInstance {
|
||||
const config: AxiosRequestConfig = {
|
||||
baseURL: this.baseURL,
|
||||
headers: this.headers,
|
||||
timeout: this.timeout,
|
||||
responseType: this.responseType,
|
||||
};
|
||||
|
||||
return axios.create(config);
|
||||
}
|
||||
}
|
||||
|
||||
export default AxiosBuilder;
|
||||
5
src/apis/helpers/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export { default as useDeleteMutation } from './useDeleteMutation';
|
||||
export { default as useUpdateMutation } from './useUpdateMutation';
|
||||
export { default as useAddMutation } from './useAddMutation';
|
||||
export { default as useGetQuery } from './useGetQuery';
|
||||
export { default as useGetQueryPagination } from './useGetQueryPagination';
|
||||
30
src/apis/helpers/useAddMutation.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { useMutation, UseMutationResult } from '@tanstack/react-query';
|
||||
import useAxios from './useAxios';
|
||||
import { AxiosEnum } from '../../enums/Axios';
|
||||
|
||||
interface MutationData {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
function useAddMutation<TResponse = any>(
|
||||
key: string,
|
||||
url: string,
|
||||
message: string = '',
|
||||
): UseMutationResult<TResponse, unknown, MutationData, unknown> {
|
||||
const axios = useAxios();
|
||||
return useMutation<TResponse, unknown, MutationData, unknown>({
|
||||
mutationFn: async (dataToSend: MutationData) => {
|
||||
const response = await axios.post<TResponse>(url, dataToSend, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
[AxiosEnum.HEADER_KEY]: key,
|
||||
[AxiosEnum.HEADER_CUSTOM_MESSAGE]: message,
|
||||
},
|
||||
});
|
||||
|
||||
return response.data;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export default useAddMutation;
|
||||
15
src/apis/helpers/useAxios.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import AxiosBuilder from './AxiosBuilder';
|
||||
import { AxiosEnum } from '../../enums/Axios';
|
||||
|
||||
function useAxios() {
|
||||
const buildAxios = new AxiosBuilder()
|
||||
.withBaseURL(AxiosEnum?.BASEURL as string)
|
||||
.withResponseType(AxiosEnum.RESPONSE_TYPE)
|
||||
.withTimeout(AxiosEnum.TIMEOUT)
|
||||
.withHeaders({ Accept: 'application/json' });
|
||||
const axios = buildAxios.build();
|
||||
|
||||
return axios;
|
||||
}
|
||||
|
||||
export default useAxios;
|
||||
33
src/apis/helpers/useDeleteMutation.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import { useMutation, UseMutationResult } from '@tanstack/react-query';
|
||||
import useAxios from './useAxios';
|
||||
import { AxiosEnum } from '../../enums/Axios';
|
||||
import { AxiosResponse } from '../../types/axios';
|
||||
|
||||
type DataToSend = {
|
||||
id: number | string;
|
||||
};
|
||||
|
||||
function useDeleteMutation<TResponse = any>(
|
||||
key: string,
|
||||
url: string,
|
||||
message: string = '',
|
||||
): UseMutationResult<AxiosResponse<TResponse>, unknown, DataToSend, unknown> {
|
||||
const axios = useAxios();
|
||||
|
||||
return useMutation<AxiosResponse<TResponse>, unknown, DataToSend, unknown>({
|
||||
mutationFn: async (dataToSend: DataToSend) => {
|
||||
const { data } = await axios.delete<AxiosResponse<TResponse>>(
|
||||
`${url}/${dataToSend.id}`,
|
||||
{
|
||||
headers: {
|
||||
[AxiosEnum.HEADER_KEY]: key,
|
||||
[AxiosEnum.HEADER_CUSTOM_MESSAGE]: message,
|
||||
},
|
||||
},
|
||||
);
|
||||
return data;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export default useDeleteMutation;
|
||||
23
src/apis/helpers/useGetQuery.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
import useAxios from './useAxios';
|
||||
|
||||
function useGetQuery(
|
||||
KEY: string | string[],
|
||||
url: string,
|
||||
params: Record<string, any> = {},
|
||||
options: Record<string, any> = {},
|
||||
) {
|
||||
const axios = useAxios();
|
||||
|
||||
const KEYS = typeof KEY === 'string' ? [KEY, params] : [...KEY, params];
|
||||
return useQuery({
|
||||
queryKey: KEYS,
|
||||
queryFn: async () => {
|
||||
const response = await axios.get(url, { params });
|
||||
return response?.data ?? [];
|
||||
},
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export default useGetQuery;
|
||||
41
src/apis/helpers/useGetQueryPagination.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
import useAxios from './useAxios';
|
||||
import { QueryPaginationEnum } from '../../enums/TankStackQueryEnum';
|
||||
|
||||
function useGetQueryPagination(
|
||||
KEY: string | string[],
|
||||
url: string,
|
||||
params: Record<string, any> = {},
|
||||
options: Record<string, any> = {},
|
||||
) {
|
||||
const axios = useAxios();
|
||||
|
||||
const queryString = window.location.search;
|
||||
const urlParams = new URLSearchParams(queryString);
|
||||
|
||||
const page = urlParams.get('page') || QueryPaginationEnum.DEFAULT_PAGE;
|
||||
const per_page =
|
||||
urlParams.get('per_page') || QueryPaginationEnum.DEFAULT_PER_PAGE;
|
||||
|
||||
const KEYS =
|
||||
typeof KEY === 'string'
|
||||
? [KEY, params, page, per_page]
|
||||
: [...KEY, params, page, per_page];
|
||||
|
||||
return useQuery({
|
||||
queryKey: KEYS,
|
||||
queryFn: async () => {
|
||||
const response = await axios.get(url, {
|
||||
params: {
|
||||
...params,
|
||||
page,
|
||||
per_page,
|
||||
},
|
||||
});
|
||||
return (response?.data as any) ?? [];
|
||||
},
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export default useGetQueryPagination;
|
||||
48
src/apis/helpers/useUpdateMutation.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import { useMutation, UseMutationResult } from '@tanstack/react-query';
|
||||
import useAxios from './useAxios'; // Adjust the import path as necessary
|
||||
import { AxiosEnum } from '../../enums/Axios';
|
||||
|
||||
type DataToSend = {
|
||||
id: number | string;
|
||||
newData: any; // Define the type for updated data
|
||||
};
|
||||
|
||||
interface AxiosResponse<T = any> {
|
||||
data: T;
|
||||
status: number;
|
||||
statusText: string;
|
||||
headers: Record<string, any>;
|
||||
config: Record<string, any>;
|
||||
request?: any;
|
||||
}
|
||||
|
||||
function useUpdateMutation<TResponse = any>(
|
||||
key: string,
|
||||
url: string,
|
||||
message: string = '',
|
||||
): UseMutationResult<AxiosResponse<TResponse>, unknown, DataToSend, unknown> {
|
||||
const axios = useAxios();
|
||||
|
||||
return useMutation<AxiosResponse<TResponse>, unknown, DataToSend, unknown>({
|
||||
mutationFn: async (dataToSend: DataToSend) => {
|
||||
const { id, newData } = dataToSend;
|
||||
const newUrl = `${url}/${id}`;
|
||||
const MutateData =
|
||||
newData instanceof FormData ? newData : { ...newData, _method: 'PUT' };
|
||||
const { data } = await axios.post<AxiosResponse<TResponse>>(
|
||||
newUrl,
|
||||
MutateData,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
[AxiosEnum.HEADER_KEY]: key,
|
||||
[AxiosEnum.HEADER_CUSTOM_MESSAGE]: message,
|
||||
},
|
||||
},
|
||||
);
|
||||
return data;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export default useUpdateMutation;
|
||||
BIN
src/assets/core/Logo-192x192.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
src/assets/core/Logo-192x192.webp
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/assets/core/Logo-512x512.webp
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
1
src/assets/core/logo.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg fill="none" height="28" viewBox="0 0 115 28" width="115" xmlns="http://www.w3.org/2000/svg"><g fill="#fff"><path clip-rule="evenodd" d="m54.834 0h5.3192v4.75959h-4.207c-1.1108 0-1.6677.55492-1.6677 1.6662v.95192h5.8745v-.00103h5.5963v20.38432h-5.5963v-15.6643h-5.8745v15.6465h-5.2518l.0151.0179h-6.6809l-7.8498-9.4236v9.4236h-5.589l-.0014-.0015v-27.483864h5.589v16.491164l7.8512-9.39018h6.3214v-1.78548c0-1.79807.5163-3.17885 1.5474-4.14381 1.0325-.964954 2.5668-1.44743 4.6043-1.44743zm-6.1517 7.80026-8.2777 9.75194 8.2777 9.7839z" fill-rule="evenodd"/><path d="m27.269 27.7436v-11.0651h.0015c0-3.4107-.7686-5.8564-2.3029-7.33716-1.5343-1.48075-3.4934-2.22113-5.8746-2.22113-1.4821 0-2.7263.24341-3.7313.73313s-1.8649 1.1707-2.5799 2.04292l-.5162-2.53844h-4.88276v20.38728h5.59616v-10.4306c0-1.7719.3974-3.1469 1.1907-4.1249.7946-.978 1.9185-1.4677 3.3745-1.4677 2.7524 0 4.1286 1.7575 4.1286 5.2739v10.7478z"/><path d="m5.59623 27.7418v-20.38437h-5.59623v20.38437z"/><path d="m73.4106 23.1607c.7932.555 1.8127.8331 3.0569.8331 1.2443 0 2.1304-.1912 2.8177-.5752.6874-.3839 1.0326-.9316 1.0326-1.6459 0-.5288-.1581-.9258-.4757-1.1895-.3176-.2652-.7672-.4564-1.3501-.5752-.5815-.1188-1.5343-.2579-2.8583-.4159-1.7997-.2376-3.2948-.5418-4.4854-.9128-1.1906-.3694-2.1433-.9519-2.8583-1.7444-.7149-.7926-1.0717-1.8502-1.0717-3.1731 0-1.3228.364-2.492 1.092-3.5092.728-1.01854 1.7533-1.80382 3.0758-2.36019 1.3226-.55493 2.8453-.83312 4.5652-.83312 2.7785.02608 5.0219.62157 6.7288 1.78502 1.7068 1.16349 2.6262 2.77609 2.7582 4.83779h-5.3192c-.0798-.7664-.4829-1.3952-1.2109-1.8835-.728-.4897-1.6605-.7331-2.7988-.7331-1.0587 0-1.9114.1985-2.5596.5955-.6482.3969-.9731.9258-.9731 1.5865 0 .4752.1726.8331.5163 1.0707s.7932.41 1.3501.5158c.5554.1058 1.4821.2246 2.7785.3564 2.7524.3173 4.8625.9056 6.3315 1.7648s2.2028 2.3327 2.2028 4.422c0 1.3213-.3901 2.4848-1.1703 3.4903-.7802 1.0041-1.8649 1.7778-3.2556 2.3197-1.3893.5419-2.9975.8128-4.8233.8128-2.8307 0-5.1336-.6274-6.9072-1.8835-1.7735-1.2562-2.7118-2.9674-2.8177-5.1363h5.3193c.0797.8983.5162 1.6256 1.3095 2.1805z"/><path d="m106.754 27.762v-11.0651c0-3.4106-.773-5.8564-2.321-7.33712-1.548-1.48075-3.42-2.22114-5.9344-2.22114-2.5147 0-4.526.9128-6.0342 2.73694v-9.87558h-5.5963v27.7606h5.5963v-10.4696c0-1.7445.4031-3.1064 1.2109-4.0844.8077-.978 1.978-1.4677 3.3542-1.4677s2.4087.4433 3.0965 1.3286c.688.8867 1.032 2.1747 1.032 3.867v10.8275z"/><g clip-rule="evenodd" fill-rule="evenodd"><path d="m112.545 22.6713c.274.2304.412.5376.412.923 0 .2419-.061.4564-.182.6418-.12.1855-.297.3347-.527.4448l.758 1.3997h-1.038l-.643-1.1867h-.445v1.1867h-.94v-3.7555h1.517c.451 0 .814.1159 1.088.3462zm-1.139 1.433c.176 0 .317-.0464.421-.1406.104-.0927.157-.2173.157-.3709 0-.1651-.05-.2912-.148-.3796-.099-.0884-.242-.1318-.43-.1318h-.527v1.0214h.527z"/><path d="m109.494 21.1312c.561-.313 1.17-.4695 1.83-.4695s1.286.1565 1.846.4695c.561.3129 1.006.7418 1.336 1.2851.329.5434.494 1.1389.494 1.788s-.165 1.2605-.494 1.8038c-.33.5434-.775.9722-1.336 1.2852-.561.3129-1.176.4694-1.846.4694s-1.271-.1565-1.83-.4694c-.562-.313-1.007-.7418-1.336-1.2852-.329-.5433-.494-1.1388-.494-1.7879s.165-1.2605.494-1.8039c.329-.5433.774-.9722 1.336-1.2851zm3.223.6998c-.423-.242-.887-.3622-1.393-.3622s-.965.1202-1.376.3622c-.412.242-.74.5737-.982.9968s-.363.8925-.363 1.4083.121.9635.363 1.375.568.739.982.9809c.413.242.871.3622 1.376.3622.504 0 .97-.1202 1.393-.3622.424-.2419.756-.5708.998-.9881s.363-.878.363-1.3837-.121-.9693-.363-1.3924-.574-.7548-.998-.9968z"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
1
src/assets/language/ar.svg
Normal file
|
After Width: | Height: | Size: 10 KiB |
1
src/assets/language/en.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="42.67" height="32" viewBox="0 0 640 480"><defs><clipPath id="flagUm4x30"><path fill-opacity=".7" d="M0 0h682.7v512H0z"/></clipPath></defs><g fill-rule="evenodd" clip-path="url(#flagUm4x30)" transform="scale(.9375)"><g stroke-width="1pt"><path fill="#bd3d44" d="M0 0h972.8v39.4H0zm0 78.8h972.8v39.4H0zm0 78.7h972.8V197H0zm0 78.8h972.8v39.4H0zm0 78.8h972.8v39.4H0zm0 78.7h972.8v39.4H0zm0 78.8h972.8V512H0z"/><path fill="#fff" d="M0 39.4h972.8v39.4H0zm0 78.8h972.8v39.3H0zm0 78.7h972.8v39.4H0zm0 78.8h972.8v39.4H0zm0 78.8h972.8v39.4H0zm0 78.7h972.8v39.4H0z"/></g><path fill="#192f5d" d="M0 0h389.1v275.7H0z"/><path fill="#fff" d="M32.4 11.8L36 22.7h11.4l-9.2 6.7l3.5 11l-9.3-6.8l-9.2 6.7l3.5-10.9l-9.3-6.7H29zm64.9 0l3.5 10.9h11.5l-9.3 6.7l3.5 11l-9.2-6.8l-9.3 6.7l3.5-10.9l-9.2-6.7h11.4zm64.8 0l3.6 10.9H177l-9.2 6.7l3.5 11l-9.3-6.8l-9.2 6.7l3.5-10.9l-9.3-6.7h11.5zm64.9 0l3.5 10.9H242l-9.3 6.7l3.6 11l-9.3-6.8l-9.3 6.7l3.6-10.9l-9.3-6.7h11.4zm64.8 0l3.6 10.9h11.4l-9.2 6.7l3.5 11l-9.3-6.8l-9.2 6.7l3.5-10.9l-9.2-6.7h11.4zm64.9 0l3.5 10.9h11.5l-9.3 6.7l3.6 11l-9.3-6.8l-9.3 6.7l3.6-10.9l-9.3-6.7h11.5zM64.9 39.4l3.5 10.9h11.5L70.6 57L74 67.9l-9-6.7l-9.3 6.7L59 57l-9-6.7h11.4zm64.8 0l3.6 10.9h11.4l-9.3 6.7l3.6 10.9l-9.3-6.7l-9.3 6.7L124 57l-9.3-6.7h11.5zm64.9 0l3.5 10.9h11.5l-9.3 6.7l3.5 10.9l-9.2-6.7l-9.3 6.7l3.5-10.9l-9.2-6.7H191zm64.8 0l3.6 10.9h11.4l-9.3 6.7l3.6 10.9l-9.3-6.7l-9.2 6.7l3.5-10.9l-9.3-6.7H256zm64.9 0l3.5 10.9h11.5L330 57l3.5 10.9l-9.2-6.7l-9.3 6.7l3.5-10.9l-9.2-6.7h11.4zM32.4 66.9L36 78h11.4l-9.2 6.7l3.5 10.9l-9.3-6.8l-9.2 6.8l3.5-11l-9.3-6.7H29zm64.9 0l3.5 11h11.5l-9.3 6.7l3.5 10.9l-9.2-6.8l-9.3 6.8l3.5-11l-9.2-6.7h11.4zm64.8 0l3.6 11H177l-9.2 6.7l3.5 10.9l-9.3-6.8l-9.2 6.8l3.5-11l-9.3-6.7h11.5zm64.9 0l3.5 11H242l-9.3 6.7l3.6 10.9l-9.3-6.8l-9.3 6.8l3.6-11l-9.3-6.7h11.4zm64.8 0l3.6 11h11.4l-9.2 6.7l3.5 10.9l-9.3-6.8l-9.2 6.8l3.5-11l-9.2-6.7h11.4zm64.9 0l3.5 11h11.5l-9.3 6.7l3.6 10.9l-9.3-6.8l-9.3 6.8l3.6-11l-9.3-6.7h11.5zM64.9 94.5l3.5 10.9h11.5l-9.3 6.7l3.5 11l-9.2-6.8l-9.3 6.7l3.5-10.9l-9.2-6.7h11.4zm64.8 0l3.6 10.9h11.4l-9.3 6.7l3.6 11l-9.3-6.8l-9.3 6.7l3.6-10.9l-9.3-6.7h11.5zm64.9 0l3.5 10.9h11.5l-9.3 6.7l3.5 11l-9.2-6.8l-9.3 6.7l3.5-10.9l-9.2-6.7H191zm64.8 0l3.6 10.9h11.4l-9.2 6.7l3.5 11l-9.3-6.8l-9.2 6.7l3.5-10.9l-9.3-6.7H256zm64.9 0l3.5 10.9h11.5l-9.3 6.7l3.5 11l-9.2-6.8l-9.3 6.7l3.5-10.9l-9.2-6.7h11.4zM32.4 122.1L36 133h11.4l-9.2 6.7l3.5 11l-9.3-6.8l-9.2 6.7l3.5-10.9l-9.3-6.7H29zm64.9 0l3.5 10.9h11.5l-9.3 6.7l3.5 10.9l-9.2-6.7l-9.3 6.7l3.5-10.9l-9.2-6.7h11.4zm64.8 0l3.6 10.9H177l-9.2 6.7l3.5 11l-9.3-6.8l-9.2 6.7l3.5-10.9l-9.3-6.7h11.5zm64.9 0l3.5 10.9H242l-9.3 6.7l3.6 11l-9.3-6.8l-9.3 6.7l3.6-10.9l-9.3-6.7h11.4zm64.8 0l3.6 10.9h11.4l-9.2 6.7l3.5 11l-9.3-6.8l-9.2 6.7l3.5-10.9l-9.2-6.7h11.4zm64.9 0l3.5 10.9h11.5l-9.3 6.7l3.6 11l-9.3-6.8l-9.3 6.7l3.6-10.9l-9.3-6.7h11.5zM64.9 149.7l3.5 10.9h11.5l-9.3 6.7l3.5 10.9l-9.2-6.8l-9.3 6.8l3.5-11l-9.2-6.7h11.4zm64.8 0l3.6 10.9h11.4l-9.3 6.7l3.6 10.9l-9.3-6.8l-9.3 6.8l3.6-11l-9.3-6.7h11.5zm64.9 0l3.5 10.9h11.5l-9.3 6.7l3.5 10.9l-9.2-6.8l-9.3 6.8l3.5-11l-9.2-6.7H191zm64.8 0l3.6 10.9h11.4l-9.2 6.7l3.5 10.9l-9.3-6.8l-9.2 6.8l3.5-11l-9.3-6.7H256zm64.9 0l3.5 10.9h11.5l-9.3 6.7l3.5 10.9l-9.2-6.8l-9.3 6.8l3.5-11l-9.2-6.7h11.4zM32.4 177.2l3.6 11h11.4l-9.2 6.7l3.5 10.8l-9.3-6.7l-9.2 6.7l3.5-10.9l-9.3-6.7H29zm64.9 0l3.5 11h11.5l-9.3 6.7l3.6 10.8l-9.3-6.7l-9.3 6.7l3.6-10.9l-9.3-6.7h11.4zm64.8 0l3.6 11H177l-9.2 6.7l3.5 10.8l-9.3-6.7l-9.2 6.7l3.5-10.9l-9.3-6.7h11.5zm64.9 0l3.5 11H242l-9.3 6.7l3.6 10.8l-9.3-6.7l-9.3 6.7l3.6-10.9l-9.3-6.7h11.4zm64.8 0l3.6 11h11.4l-9.2 6.7l3.5 10.8l-9.3-6.7l-9.2 6.7l3.5-10.9l-9.2-6.7h11.4zm64.9 0l3.5 11h11.5l-9.3 6.7l3.6 10.8l-9.3-6.7l-9.3 6.7l3.6-10.9l-9.3-6.7h11.5zM64.9 204.8l3.5 10.9h11.5l-9.3 6.7l3.5 11l-9.2-6.8l-9.3 6.7l3.5-10.9l-9.2-6.7h11.4zm64.8 0l3.6 10.9h11.4l-9.3 6.7l3.6 11l-9.3-6.8l-9.3 6.7l3.6-10.9l-9.3-6.7h11.5zm64.9 0l3.5 10.9h11.5l-9.3 6.7l3.5 11l-9.2-6.8l-9.3 6.7l3.5-10.9l-9.2-6.7H191zm64.8 0l3.6 10.9h11.4l-9.2 6.7l3.5 11l-9.3-6.8l-9.2 6.7l3.5-10.9l-9.3-6.7H256zm64.9 0l3.5 10.9h11.5l-9.3 6.7l3.5 11l-9.2-6.8l-9.3 6.7l3.5-10.9l-9.2-6.7h11.4zM32.4 232.4l3.6 10.9h11.4l-9.2 6.7l3.5 10.9l-9.3-6.7l-9.2 6.7l3.5-11l-9.3-6.7H29zm64.9 0l3.5 10.9h11.5L103 250l3.6 10.9l-9.3-6.7l-9.3 6.7l3.6-11l-9.3-6.7h11.4zm64.8 0l3.6 10.9H177l-9 6.7l3.5 10.9l-9.3-6.7l-9.2 6.7l3.5-11l-9.3-6.7h11.5zm64.9 0l3.5 10.9H242l-9.3 6.7l3.6 10.9l-9.3-6.7l-9.3 6.7l3.6-11l-9.3-6.7h11.4zm64.8 0l3.6 10.9h11.4l-9.2 6.7l3.5 10.9l-9.3-6.7l-9.2 6.7l3.5-11l-9.2-6.7h11.4zm64.9 0l3.5 10.9h11.5l-9.3 6.7l3.6 10.9l-9.3-6.7l-9.3 6.7l3.6-11l-9.3-6.7h11.5z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
14
src/assets/static/shapes.svg
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<svg width="520" height="81" viewBox="0 0 520 81" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M455 16.0003H390V32.0006H455V16.0003Z" fill="red"/>
|
||||
<path d="M325 16.0003H260V32.0006H325V16.0003Z" fill="red"/>
|
||||
<path d="M195 16.0003H130V32.0006H195V16.0003Z" fill="red"/>
|
||||
<path d="M64.9997 16.0003H0V32.0006H64.9997V16.0003Z" fill="red"/>
|
||||
<path d="M520 31.9998L455 32.0006L455 48H520V31.9998Z" fill="red"/>
|
||||
<path d="M260.001 0H195.001V16.0002L260 16.0003L260.001 0Z" fill="red"/>
|
||||
<path d="M130 0H64.9999L64.9997 16.0003H130L130 0Z" fill="red"/>
|
||||
<path d="M260.001 31.9998H195.001V48H260.001V31.9998Z" fill="red"/>
|
||||
<path d="M195 64.5H260V81H195V64.5Z" fill="red"/>
|
||||
<path d="M325 64.5H390V81H325V64.5Z" fill="red"/>
|
||||
<path d="M455 64.5H520V81H455V64.5Z" fill="red"/>
|
||||
<path d="M390 48H455L455 64.5H390L390 48Z" fill="red"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 835 B |
46
src/components/Contact/Left.tsx
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import React from 'react'
|
||||
|
||||
const Left = () => {
|
||||
const Left = [
|
||||
{
|
||||
title: "المكتب الرئيسي",
|
||||
image: "/contact/1.png",
|
||||
value: "121 King St , ملبورن VIC 3000, أستراليا"
|
||||
},
|
||||
{
|
||||
title: "المكتب الرئيسي",
|
||||
image: "/contact/2.png",
|
||||
value: "121 King St , ملبورن VIC 3000, أستراليا"
|
||||
},
|
||||
{
|
||||
title: "المكتب الرئيسي",
|
||||
image: "/contact/3.png",
|
||||
value: "121 King St , ملبورن VIC 3000, أستراليا"
|
||||
}
|
||||
]
|
||||
return (
|
||||
<div className='Left'>
|
||||
<h1> تواصل معنا </h1>
|
||||
<p>
|
||||
من السهل جدا الاتصال بنا. ما عليك سوى استخدام نموذج الاتصال أو زيارتنا في المكتب. ابتكار التكنولوجيا التنافسية ديناميكيا بعد مجموعة موسعة من القيادة.
|
||||
</p>
|
||||
<div>
|
||||
{Left?.map((item, index) => {
|
||||
return (
|
||||
<span key={index}>
|
||||
<div>
|
||||
<img src={item.image} alt="" />
|
||||
<h6> {item.title} </h6>
|
||||
</div>
|
||||
<p> {item.value} </p>
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default Left
|
||||
32
src/components/Contact/Right.tsx
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { Form, Formik } from 'formik'
|
||||
import React from 'react'
|
||||
import InputField from '../Field/InputField'
|
||||
import TextAreaField from '../Field/TextAreaField'
|
||||
|
||||
const Right = () => {
|
||||
const handleSubmit = ()=>{
|
||||
|
||||
}
|
||||
return (
|
||||
<div className='Right'>
|
||||
<h1>تواصل معنا بسرعة</h1>
|
||||
<Formik initialValues={handleSubmit} onSubmit={handleSubmit} >
|
||||
<Form>
|
||||
<div className='Forms'>
|
||||
<InputField name='name' placeholder='أدخل الاسم' />
|
||||
<InputField name='email' placeholder='أدخل البريد الإلكتروني' />
|
||||
<InputField name='number' placeholder='هاتفك' />
|
||||
<InputField name='company' placeholder='شركتك' />
|
||||
<TextAreaField name='message' placeholder='رسالة' />
|
||||
</div>
|
||||
<button className='scale' >
|
||||
ارسل رسالة
|
||||
</button>
|
||||
</Form>
|
||||
</Formik>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Right
|
||||
25
src/components/Field/InputField.tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { Input, InputProps } from "antd";
|
||||
import React from "react";
|
||||
import { Field } from "formik";
|
||||
|
||||
const InputField = ({
|
||||
name,
|
||||
...props
|
||||
}: InputProps) => {
|
||||
|
||||
return (
|
||||
<div className="ValidationField w-100">
|
||||
|
||||
<Field
|
||||
as={Input}
|
||||
name={name}
|
||||
id={name}
|
||||
size="large"
|
||||
styles={{width:"100%"}}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(InputField);
|
||||
27
src/components/Field/TextAreaField.tsx
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { InputProps } from "antd";
|
||||
import React from "react";
|
||||
import { Field } from "formik";
|
||||
import TextArea from "antd/es/input/TextArea";
|
||||
|
||||
const TextAreaField = ({
|
||||
name,
|
||||
...props
|
||||
}: InputProps) => {
|
||||
|
||||
return (
|
||||
<div className="ValidationField TextAreaField">
|
||||
|
||||
<Field
|
||||
as={TextArea}
|
||||
name={name}
|
||||
id={name}
|
||||
size="large"
|
||||
styles={{width:"100%"}}
|
||||
autoSize={{ minRows: 4, maxRows: 10 }}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(TextAreaField);
|
||||
67
src/components/SwiperScreenShoot.tsx
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import React from 'react';
|
||||
// Import Swiper React components
|
||||
import { Pagination, A11y ,Autoplay} from 'swiper/modules';
|
||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||
import 'swiper/css';
|
||||
import 'swiper/css/navigation';
|
||||
import 'swiper/css/pagination';
|
||||
import 'swiper/css/scrollbar';
|
||||
|
||||
const SwipeScreenShoot = () => {
|
||||
const SwipeScreenShoot = [
|
||||
{ image: "/SwipeScreenShoot/1.png" },
|
||||
{ image: "/SwipeScreenShoot/2.png" },
|
||||
{ image: "/SwipeScreenShoot/3.png" },
|
||||
{ image: "/SwipeScreenShoot/4.png" },
|
||||
{ image: "/SwipeScreenShoot/1.png" },
|
||||
{ image: "/SwipeScreenShoot/2.png" },
|
||||
{ image: "/SwipeScreenShoot/3.png" },
|
||||
{ image: "/SwipeScreenShoot/1.png" },
|
||||
{ image: "/SwipeScreenShoot/2.png" },
|
||||
{ image: "/SwipeScreenShoot/3.png" },
|
||||
{ image: "/SwipeScreenShoot/4.png" },
|
||||
{ image: "/SwipeScreenShoot/1.png" },
|
||||
{ image: "/SwipeScreenShoot/2.png" },
|
||||
{ image: "/SwipeScreenShoot/3.png" },
|
||||
|
||||
];
|
||||
|
||||
return (
|
||||
<div className='SwipeScreenShoot'>
|
||||
<Swiper
|
||||
modules={[Pagination, A11y, Autoplay]}
|
||||
spaceBetween={10}
|
||||
autoplay={{ delay: 3000 }} // Configure autoplay options
|
||||
|
||||
pagination={{ clickable: true }}
|
||||
onSwiper={(swiper) => console.log(swiper)}
|
||||
onSlideChange={() => console.log('slide change')}
|
||||
breakpoints={{
|
||||
400: {
|
||||
slidesPerView: 1,
|
||||
},
|
||||
800: {
|
||||
slidesPerView: 2,
|
||||
},
|
||||
1200: {
|
||||
slidesPerView: 3,
|
||||
},
|
||||
1500: {
|
||||
slidesPerView: 5,
|
||||
},
|
||||
2000: {
|
||||
slidesPerView: 7,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{SwipeScreenShoot.map((item, index) => (
|
||||
<SwiperSlide key={index}>
|
||||
<img src={item.image} alt={`Screenshot ${index + 1}`} />
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SwipeScreenShoot;
|
||||
33
src/components/layout/Footer.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import { Input } from "antd";
|
||||
import { FaFacebook, FaInstagram, FaTelegram, FaTwitter } from "react-icons/fa";
|
||||
|
||||
const Footer = () => {
|
||||
return (
|
||||
<div className="Footer">
|
||||
<img src="/LOGO.png" alt="logo" />
|
||||
<div>
|
||||
<FaTelegram/>
|
||||
<FaFacebook/>
|
||||
<FaInstagram/>
|
||||
<FaTwitter/>
|
||||
</div>
|
||||
<p>
|
||||
© زاكر 2024.جميع الحقوق محفوظة
|
||||
</p>
|
||||
<span className="FooterHeader">
|
||||
<span>
|
||||
<h1>اشترك في نشرتنا الإخبارية</h1>
|
||||
<p>نحن فريق من غير المتشائمين الذين يهتمون حقا بعملنا.</p>
|
||||
</span>
|
||||
<div>
|
||||
<Input placeholder="أدخل بريدك الإلكتروني" />
|
||||
<div>
|
||||
<FaTelegram/>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
15
src/components/layout/Layout.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import React from 'react';
|
||||
import NavBar from './NavBar';
|
||||
import Footer from './Footer';
|
||||
|
||||
const Layout = ({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<div className="Layout">
|
||||
<NavBar />
|
||||
{children}
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Layout;
|
||||
87
src/components/layout/NavBar.tsx
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
// NavBar.tsx
|
||||
import React, { useState } from 'react';
|
||||
import { RoutesEnums } from '../../enums/RoutesEnums';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { MdLanguage } from "react-icons/md";
|
||||
import { FaEllipsis } from 'react-icons/fa6';
|
||||
import { Popover } from 'antd';
|
||||
|
||||
// Define an enum for the routes
|
||||
|
||||
|
||||
// Define a type for the link objects
|
||||
interface NavLink {
|
||||
path: RoutesEnums;
|
||||
label: string;
|
||||
}
|
||||
|
||||
const NavBar: React.FC = () => {
|
||||
// Define an array of link objects using the RoutesEnums
|
||||
const [t] = useTranslation()
|
||||
const links: NavLink[] = [
|
||||
{ path: RoutesEnums.HOME, label: t('home') },
|
||||
{ path: RoutesEnums.FEATURES, label: t('features') },
|
||||
{ path: RoutesEnums.HOW_IT_WORK, label: t('how_it_work') },
|
||||
{ path: RoutesEnums.SCREEN_SHOOT, label: t('screen_shoot') },
|
||||
{ path: RoutesEnums.NOTE, label: t('note') },
|
||||
{ path: RoutesEnums.CONTACT, label: t('contact_us') },
|
||||
];
|
||||
const [Open, setOpen] = useState(false)
|
||||
const handleToggle = ()=>{
|
||||
setOpen(!Open)
|
||||
}
|
||||
|
||||
|
||||
const NavBarContent = (
|
||||
<div className='NavBarContent'>
|
||||
<ul className='NavBarLinks'>
|
||||
{links.map((link) =>{
|
||||
return (
|
||||
<li onClick={handleToggle} key={link.path} className={`${"activeLink"}`}>
|
||||
<a href={link.path}>{link.label}</a>
|
||||
</li>
|
||||
)
|
||||
|
||||
})}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
const [Active, setActive] = useState(location.hash)
|
||||
console.log(Active);
|
||||
|
||||
return (
|
||||
<nav className='NavBar'>
|
||||
<img src="/LOGO.png" className='scale' alt="" />
|
||||
<ul className='NavBarLinks'>
|
||||
{links.map((link) =>{
|
||||
|
||||
const handleClick =()=>{
|
||||
setActive(link.path)
|
||||
}
|
||||
const isActive = Active === "" && link.path === "#" ? true : Active === link.path;
|
||||
return (
|
||||
<li key={link.path} onClick={handleClick} className={`${isActive ? "activeLink" : ""}`}>
|
||||
<a href={link.path}>{link.label}</a>
|
||||
</li>
|
||||
)
|
||||
|
||||
})}
|
||||
</ul>
|
||||
|
||||
<div>
|
||||
<MdLanguage />
|
||||
<article>
|
||||
<Popover open={Open} content={NavBarContent} >
|
||||
<FaEllipsis onClick={handleToggle} />
|
||||
</Popover>
|
||||
</article>
|
||||
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
export default NavBar;
|
||||
21
src/components/layout/NotFoundPage.tsx
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
function NotFoundPage() {
|
||||
const navigate = useNavigate();
|
||||
const [t] = useTranslation();
|
||||
const handleNavigate = () => {
|
||||
navigate('/');
|
||||
};
|
||||
return (
|
||||
<div className="notFoundPage">
|
||||
<div className="containerNotFound">
|
||||
<p>404 | {t('This_page_could_not_be_found')}</p>
|
||||
<div>
|
||||
<button onClick={handleNavigate}>{t('home')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default NotFoundPage;
|
||||
47
src/components/layout/PageTransition.tsx
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import React, { useState, useEffect, ReactNode } from 'react';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
|
||||
interface PageTransitionProps {
|
||||
to: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const PageTransition: React.FC<PageTransitionProps> = ({ to, children }) => {
|
||||
const [transitioning, setTransitioning] = useState(false);
|
||||
const [reversed, setReversed] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
const handleClick = () => {
|
||||
if (location.pathname === to) {
|
||||
return; // Prevent navigation to the same route
|
||||
}
|
||||
setTransitioning(true);
|
||||
setTimeout(() => {
|
||||
navigate(to);
|
||||
setReversed(true); // Set reversed for the entrance animation
|
||||
}, 1000); // Match this timing with the exit animation duration
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (reversed) {
|
||||
setTimeout(() => {
|
||||
setReversed(false); // Clear the reversed state after the animation
|
||||
setTransitioning(false);
|
||||
}, 1000); // Match this timing with the entrance animation duration
|
||||
}
|
||||
}, [reversed]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`transition-overlay ${transitioning ? (reversed ? 'enter' : 'exit') : ''}`}
|
||||
/>
|
||||
<div onClick={handleClick}>
|
||||
{children}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PageTransition;
|
||||
12
src/components/layout/SpinContainer.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import React from 'react';
|
||||
|
||||
const SpinContainer: React.FC = () => {
|
||||
return (
|
||||
<div className='full-screen-center'>
|
||||
<div className="loader"></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default SpinContainer;
|
||||
59
src/design-system/FirstLoading.tsx
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { Logo } from '../utils';
|
||||
|
||||
interface FirstLoadingProps {
|
||||
seconds: number;
|
||||
children: React.ReactNode; // Add children prop
|
||||
}
|
||||
const FirstLoading: React.FC<FirstLoadingProps> = ({ seconds,children }) => {
|
||||
const [currentNumber, setCurrentNumber] = useState(1);
|
||||
const [animate, setAnimate] = useState(false);
|
||||
const [pageUp, setPageUp] = useState(false); // New state for page movement
|
||||
const totalNumbers = 100;
|
||||
|
||||
useEffect(() => {
|
||||
const intervalTime = (seconds * 1000) / totalNumbers;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
setCurrentNumber((prev) => {
|
||||
if (prev < totalNumbers) {
|
||||
setAnimate(true);
|
||||
return prev + 1;
|
||||
} else {
|
||||
setPageUp(true); // Trigger page movement
|
||||
clearInterval(interval);
|
||||
return prev;
|
||||
}
|
||||
});
|
||||
}, intervalTime);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [seconds]);
|
||||
|
||||
useEffect(() => {
|
||||
if (animate) {
|
||||
const timer = setTimeout(() => setAnimate(false), 500);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [animate]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={`FirstLoading ${pageUp ? 'PageUp' : ''}`}>
|
||||
<div>
|
||||
<img src={Logo} className='Logo' alt="" />
|
||||
<div className={`Info`}>
|
||||
[{currentNumber} %]
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
pageUp && children
|
||||
}
|
||||
|
||||
</>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
export default FirstLoading;
|
||||
28
src/enums/Axios.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
export enum AxiosQueryEnum {
|
||||
GET = 'get',
|
||||
POST = 'post',
|
||||
DELETE = 'delete',
|
||||
}
|
||||
|
||||
export enum AxiosStatusEnum {
|
||||
VALIDATION = 422,
|
||||
AUTHENTICATED = 401,
|
||||
}
|
||||
|
||||
export enum AxiosEnum {
|
||||
BASEURL = import.meta.env.REACT_APP_BASE_URL,
|
||||
IMAGE_BASE_URL = 'http://localhost:8000',
|
||||
|
||||
HEADER_KEY = 'X-Custom-Query-Key',
|
||||
HEADER_CUSTOM_MESSAGE = 'X-Custom-message',
|
||||
RESPONSE_TYPE = 'json',
|
||||
TIMEOUT = 120000,
|
||||
BEARER = 'Bearer ',
|
||||
}
|
||||
|
||||
export enum AxiosQueryStatusEnum {
|
||||
ERROR = 'error',
|
||||
IDLE = 'idle',
|
||||
PENDING = 'pending',
|
||||
SUCCESS = 'success',
|
||||
}
|
||||
8
src/enums/LocalStorage.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export enum LocalStorageEnum {
|
||||
PROJECT_NAME = 'NERD',
|
||||
LANGUAGE_KEY = LocalStorageEnum.PROJECT_NAME + '_L',
|
||||
THEME_KEY = LocalStorageEnum.PROJECT_NAME + '_T',
|
||||
TOKEN_KEY = LocalStorageEnum.PROJECT_NAME + '_TK',
|
||||
USER_KEY = LocalStorageEnum.PROJECT_NAME + '_UK',
|
||||
ABILITIES_KEY = LocalStorageEnum.PROJECT_NAME + '_AK',
|
||||
}
|
||||
8
src/enums/RoutesEnums.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export enum RoutesEnums {
|
||||
HOME = '#',
|
||||
FEATURES = '#features',
|
||||
HOW_IT_WORK = '#how_it_work',
|
||||
SCREEN_SHOOT = '#screen_shoot',
|
||||
NOTE = '#note',
|
||||
CONTACT = '#contact_us',
|
||||
}
|
||||
11
src/enums/TankStackQueryEnum.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
export enum QueryPaginationEnum {
|
||||
DEFAULT_PER_PAGE = 10,
|
||||
DEFAULT_PAGE = 1,
|
||||
}
|
||||
|
||||
export enum QueryStatusEnum {
|
||||
ERROR = 'error',
|
||||
IDLE = 'idle',
|
||||
PENDING = 'pending',
|
||||
SUCCESS = 'success',
|
||||
}
|
||||
52
src/hooks/useChangeLanguage.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import { useState, useCallback, useEffect } from 'react';
|
||||
import i18n from 'i18next';
|
||||
import { setLanguageDirection } from '../utils/document/setLanguageDirection';
|
||||
import { LocalStorageEnum } from '../enums/LocalStorage';
|
||||
import en from '../../../assets/language/en.svg';
|
||||
import ar from '../../../assets/language/ar.svg';
|
||||
import { LocalStorageManager } from '../utils/class/LocalStorageManager';
|
||||
const validLanguages = ['en', 'ar'];
|
||||
|
||||
export const useChangeLanguage = () => {
|
||||
const getInitialLanguage = () => {
|
||||
const storedLanguage = LocalStorageManager.getItem(
|
||||
LocalStorageEnum.LANGUAGE_KEY,
|
||||
) as string;
|
||||
return validLanguages.includes(storedLanguage) ? storedLanguage : 'ar';
|
||||
};
|
||||
|
||||
const [currentLanguage, setCurrentLanguage] =
|
||||
useState<string>(getInitialLanguage());
|
||||
|
||||
const changeLanguage = useCallback((newLanguage: string) => {
|
||||
if (validLanguages.includes(newLanguage)) {
|
||||
setCurrentLanguage(newLanguage);
|
||||
} else {
|
||||
console.error(`Invalid language code: ${newLanguage}`);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const applyLanguage = async () => {
|
||||
try {
|
||||
await i18n.changeLanguage(currentLanguage);
|
||||
setLanguageDirection(currentLanguage);
|
||||
LocalStorageManager.setItem(
|
||||
LocalStorageEnum.LANGUAGE_KEY,
|
||||
currentLanguage,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error changing language:', error);
|
||||
}
|
||||
};
|
||||
|
||||
applyLanguage();
|
||||
}, [currentLanguage]);
|
||||
|
||||
return { currentLanguage, changeLanguage };
|
||||
};
|
||||
|
||||
export const languageOptions = [
|
||||
{ code: 'ar', icon: ar, label: 'Arabic' },
|
||||
{ code: 'en', icon: en, label: 'English' },
|
||||
];
|
||||
29
src/hooks/useTheme.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import { LocalStorageEnum } from '../enums/LocalStorage';
|
||||
import { LocalStorageManager } from '../utils/class/LocalStorageManager';
|
||||
|
||||
function useTheme(): [string, () => void] {
|
||||
const [theme, setTheme] = useState(() => {
|
||||
const storedTheme = LocalStorageManager.getItem(
|
||||
LocalStorageEnum.THEME_KEY,
|
||||
) as string;
|
||||
return storedTheme ? storedTheme : 'light';
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// Update the body class when the theme changes
|
||||
document.body.classList.remove(theme === 'light' ? 'dark' : 'light');
|
||||
document.body.classList.add(theme);
|
||||
|
||||
// Save the new theme to local storage
|
||||
LocalStorageManager.setItem(LocalStorageEnum.THEME_KEY, theme);
|
||||
}, [theme]);
|
||||
|
||||
const changeTheme = () => {
|
||||
setTheme(theme === 'light' ? 'dark' : 'light');
|
||||
};
|
||||
|
||||
return [theme, changeTheme];
|
||||
}
|
||||
|
||||
export default useTheme;
|
||||
23
src/lib/I18nProvider.tsx
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import React, { ReactNode, useEffect } from 'react';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import i18n from './i18n'; // Import the pre-initialized i18n instance
|
||||
import { LocalStorageEnum } from '../enums/LocalStorage';
|
||||
import { LocalStorageManager } from '../utils/class/LocalStorageManager';
|
||||
|
||||
interface I18nProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const I18nProvider: React.FC<I18nProviderProps> = ({ children }) => {
|
||||
useEffect(() => {
|
||||
const currentLanguage =(LocalStorageManager.getItem(LocalStorageEnum.LANGUAGE_KEY) as string) ===
|
||||
'en'
|
||||
? 'en'
|
||||
: 'ar';
|
||||
i18n.changeLanguage(currentLanguage);
|
||||
}, []);
|
||||
|
||||
return <I18nextProvider i18n={i18n}>{children}</I18nextProvider>;
|
||||
};
|
||||
|
||||
export default I18nProvider;
|
||||
17
src/lib/ReactQueryProvider.tsx
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
|
||||
function QueryProvider({ children }: { children: React.ReactNode }) {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default QueryProvider;
|
||||
22
src/lib/i18n.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// i18n.ts
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import translationEN from '../translate/en.json';
|
||||
import translationAR from '../translate/ar.json';
|
||||
|
||||
i18n.use(initReactI18next).init({
|
||||
resources: {
|
||||
en: {
|
||||
translation: translationEN,
|
||||
},
|
||||
ar: {
|
||||
translation: translationAR,
|
||||
},
|
||||
},
|
||||
fallbackLng: 'en',
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
0
src/lib/index.ts
Normal file
4
src/main.tsx
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import { createRoot } from 'react-dom/client';
|
||||
import App from './App.tsx';
|
||||
|
||||
createRoot(document.getElementById('root')!).render(<App />);
|
||||
9
src/pages/AboutUs.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import React from 'react'
|
||||
|
||||
const AboutUs = () => {
|
||||
return (
|
||||
<div className='AboutUs'>AboutUs</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AboutUs
|
||||
15
src/pages/ContactPage.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import React from 'react'
|
||||
import Left from '../components/Contact/Left'
|
||||
import Right from '../components/Contact/Right'
|
||||
|
||||
const ContactPage = () => {
|
||||
return (
|
||||
<div className='ContactPage' id='contact_us'>
|
||||
<Left/>
|
||||
<Right/>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ContactPage
|
||||
47
src/pages/DownloadPage.tsx
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { BsGooglePlay } from 'react-icons/bs'
|
||||
import { FaApple } from 'react-icons/fa'
|
||||
|
||||
const DownloadPage = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='DownloadPage' >
|
||||
<main>
|
||||
<h1>متوفر الآن على</h1>
|
||||
<p>ابدأ العمل مع ذلك الذي يمكن أن يوفر كل ما تحتاجه لتوليد الوعي وزيادة حركة المرور والتواصل. تحويل القيمة الدقيقة بكفاءة من خلال المحتوى الذي يركز على العميل.</p>
|
||||
<div>
|
||||
<button>
|
||||
<div>
|
||||
<BsGooglePlay />
|
||||
</div>
|
||||
<div>
|
||||
<h6>{t('download_on')}</h6>
|
||||
<h5>{t('play_store')}</h5>
|
||||
</div>
|
||||
</button>
|
||||
<button>
|
||||
<div>
|
||||
<FaApple />
|
||||
</div>
|
||||
<div>
|
||||
<h6>{t('available_on')}</h6>
|
||||
<h5>{t('google_play')}</h5>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div>
|
||||
<img className='circle' src="/circle.png" alt="" />
|
||||
|
||||
</div>
|
||||
|
||||
<img className='main scale' src="/Download/1.png" alt="" />
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default DownloadPage
|
||||
68
src/pages/FeaturesPage.tsx
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const FeaturesPage = () => {
|
||||
|
||||
const features = [
|
||||
{
|
||||
"img": "1",
|
||||
"title": "Friendly_Online_Support",
|
||||
"description": "Providing_round_the_clock_technical_support_to_assist_you_with_any_inquiries"
|
||||
},
|
||||
{
|
||||
"img": "2",
|
||||
"title": "Unlimited_Features",
|
||||
"description": "Enjoy_limitless_possibilities_to_customize_your_experience"
|
||||
},
|
||||
{
|
||||
"img": "3",
|
||||
"title": "Modular_and_Switchable_Component",
|
||||
"description": "Easily_switch_between_layouts_and_even_demos"
|
||||
},
|
||||
{
|
||||
"img": "4",
|
||||
"title": "High_Resolution",
|
||||
"description": "Get_high_quality_images_and_videos"
|
||||
},
|
||||
{
|
||||
"img": "5",
|
||||
"title": "Social_Sharing",
|
||||
"description": "Easily_share_content_across_social_media_platforms"
|
||||
},
|
||||
{
|
||||
"img": "6",
|
||||
"title": "Use_on_Any_Device",
|
||||
"description": "Compatibility_with_all_devices_for_a_seamless_experience"
|
||||
}
|
||||
]
|
||||
const [t] = useTranslation()
|
||||
return (
|
||||
<div className='FeaturesPage' id='features'>
|
||||
<h1> {t("Application_Features")} </h1>
|
||||
<p> {t("Providing_professional_value_objectively_with_varied_web_readiness_Collaborative_wireless_customer_service_without_targeted_incentives_for_change_Collaboration")} </p>
|
||||
<div>
|
||||
<span>
|
||||
{features.map((item, index) => {
|
||||
return (
|
||||
<article key={index}>
|
||||
<img className='scale' src={`/Features/${item.img}.png`} alt={`${index}`} />
|
||||
<div>
|
||||
<h4>
|
||||
{t(item.title)}
|
||||
</h4>
|
||||
<p>
|
||||
{t(item.description)}
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
)
|
||||
})}
|
||||
|
||||
</span>
|
||||
<img className='scale' src="/Features/main.png" alt="" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default FeaturesPage
|
||||
41
src/pages/HomePage.tsx
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { BsGooglePlay } from 'react-icons/bs'
|
||||
import { FaApple } from 'react-icons/fa'
|
||||
|
||||
const HomePage = () => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className='HomePage' id='#'>
|
||||
<main>
|
||||
<h1>{t('get_free_download')} <h2>{t('quiz_app')}</h2></h1>
|
||||
<p>{t('reader_distracted')}</p>
|
||||
<div>
|
||||
<button>
|
||||
<div>
|
||||
<BsGooglePlay />
|
||||
</div>
|
||||
<div>
|
||||
<h6>{t('download_on')}</h6>
|
||||
<h5>{t('play_store')}</h5>
|
||||
</div>
|
||||
</button>
|
||||
<button>
|
||||
<div>
|
||||
<FaApple />
|
||||
</div>
|
||||
<div>
|
||||
<h6>{t('available_on')}</h6>
|
||||
<h5>{t('google_play')}</h5>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</main>
|
||||
<img className='circle' src="/circle.png" alt="" />
|
||||
<img className='shape' src="/shape.svg" alt="" />
|
||||
<img className='slider' src="/Slider.png" alt="" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default HomePage
|
||||
77
src/pages/HowItWork.tsx
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const HowItWork = () => {
|
||||
|
||||
const HowItWorks = [
|
||||
{
|
||||
"img": "1",
|
||||
"title": "Friendly_Online_Support",
|
||||
"description": "Providing_round_the_clock_technical_support_to_assist_you_with_any_inquiries"
|
||||
},
|
||||
{
|
||||
"img": "2",
|
||||
"title": "Unlimited_Features",
|
||||
"description": "Enjoy_limitless_possibilities_to_customize_your_experience"
|
||||
},
|
||||
{
|
||||
"img": "3",
|
||||
"title": "Modular_and_Switchable_Component",
|
||||
"description": "Easily_switch_between_layouts_and_even_demos"
|
||||
},
|
||||
{
|
||||
"img": "4",
|
||||
"title": "High_Resolution",
|
||||
"description": "Get_high_quality_images_and_videos"
|
||||
},
|
||||
]
|
||||
const [t] = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='HowItWork' id='how_it_work'>
|
||||
<h1> كيف يعمل؟ </h1>
|
||||
<p>
|
||||
استضافة احترافية بسعر مناسب. تلخيص الكفاءات الأساسية التي تركز على المبدأ بشكل مميز من خلال الكفاءات الأساسية التي تركز على العميل.
|
||||
</p>
|
||||
<div>
|
||||
<span>
|
||||
{HowItWorks.slice(0,2).map((item, index) => {
|
||||
return (
|
||||
<article key={index}>
|
||||
<img className='scale' src={`/HowItWorks/${item.img}.png`} alt={`${index}`} />
|
||||
<div>
|
||||
<h4>
|
||||
{t(item.title)}
|
||||
</h4>
|
||||
<p>
|
||||
{t(item.description)}
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
)
|
||||
})}
|
||||
</span>
|
||||
<img className='scale' src="/HowItWorks/main.png" alt="" />
|
||||
<span>
|
||||
{HowItWorks.slice(2,4).map((item, index) => {
|
||||
return (
|
||||
<article key={index}>
|
||||
<img src={`/HowItWorks/${item.img}.png`} alt={`${index}`} />
|
||||
<div>
|
||||
<h4>
|
||||
{t(item.title)}
|
||||
</h4>
|
||||
<p>
|
||||
{t(item.description)}
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
)
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default HowItWork
|
||||
58
src/pages/NotePage.tsx
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import React from 'react'
|
||||
import { Rate } from 'antd';
|
||||
|
||||
const NotePage = () => {
|
||||
const Note = [
|
||||
{
|
||||
description:"تسهيل الضرورات الوظيفية بشكل جوهري بدون خدمات وصفية من الجيل التالي. إحداث ثورة مقنعة في المستخدمين في جميع أنحاء العالم مقابل أفضل ممارسات المؤسسة",
|
||||
rate:4,
|
||||
img:"/Note/1.png",
|
||||
name:"إليسا اسكندر"
|
||||
|
||||
},
|
||||
{
|
||||
description:"تسهيل الضرورات الوظيفية بشكل جوهري بدون خدمات وصفية من الجيل التالي. إحداث ثورة مقنعة في المستخدمين في جميع أنحاء العالم مقابل أفضل ممارسات المؤسسة",
|
||||
rate:4,
|
||||
img:"/Note/1.png",
|
||||
name:"إليسا اسكندر"
|
||||
|
||||
},
|
||||
{
|
||||
description:"تسهيل الضرورات الوظيفية بشكل جوهري بدون خدمات وصفية من الجيل التالي. إحداث ثورة مقنعة في المستخدمين في جميع أنحاء العالم مقابل أفضل ممارسات المؤسسة",
|
||||
rate:4,
|
||||
img:"/Note/1.png",
|
||||
name:"إليسا اسكندر"
|
||||
|
||||
},
|
||||
|
||||
]
|
||||
return (
|
||||
<div className='NotePage' id='note' >
|
||||
<h1> ماذا يقول عملاؤنا عن ذاكر </h1>
|
||||
<p>
|
||||
استضافة احترافية بسعر مناسب. تلخيص الكفاءات الأساسية التي تركز على المبدأ بشكل مميز من خلال الكفاءات الأساسية التي تركز على العميل.
|
||||
</p>
|
||||
<div>
|
||||
{Note.map((item,index:number)=>{
|
||||
return (
|
||||
<article key={index}>
|
||||
<div>
|
||||
<p>
|
||||
{item.description}
|
||||
</p>
|
||||
<Rate value={item.rate} />
|
||||
</div>
|
||||
<span>
|
||||
<img src={item.img} alt="" />
|
||||
<h5> {item.name} </h5>
|
||||
</span>
|
||||
</article>
|
||||
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default NotePage
|
||||
18
src/pages/ScreenShoot.tsx
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import React from 'react'
|
||||
import SwipeScreenShoot from '../components/SwiperScreenShoot'
|
||||
|
||||
const ScreenShoot = () => {
|
||||
return (
|
||||
<div className='ScreenShoot' id='screen_shoot'>
|
||||
|
||||
<h1> لقطات شاشة التطبيق </h1>
|
||||
<p>
|
||||
استضافة احترافية بسعر مناسب. تلخيص الكفاءات الأساسية التي تركز على المبدأ بشكل مميز من خلال الكفاءات الأساسية التي تركز على العميل.
|
||||
</p>
|
||||
|
||||
<SwipeScreenShoot/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ScreenShoot
|
||||
67
src/pages/VideoPage.tsx
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaPlayCircle } from 'react-icons/fa';
|
||||
import { PiPlayCircle } from 'react-icons/pi';
|
||||
import ReactPlayer from 'react-player';
|
||||
|
||||
const VideoPage = () => {
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [t] = useTranslation();
|
||||
|
||||
const details = [
|
||||
{
|
||||
"img": "1",
|
||||
"title": "Friendly_Online_Support",
|
||||
"number": "1111"
|
||||
},
|
||||
{
|
||||
"img": "2",
|
||||
"title": "Unlimited_Features",
|
||||
"number": "2222"
|
||||
},
|
||||
{
|
||||
"img": "3",
|
||||
"title": "Modular_and_Switchable_Component",
|
||||
"number": "3333"
|
||||
},
|
||||
{
|
||||
"img": "4",
|
||||
"title": "High_Resolution",
|
||||
"number": "4444"
|
||||
},
|
||||
];
|
||||
|
||||
const handlePlayPause = () => {
|
||||
setIsPlaying(!isPlaying);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='VideoPage'>
|
||||
|
||||
<img src="/Video/video.png" alt="" />
|
||||
|
||||
<main>
|
||||
<h1>منصة التطبيق الأكثر استخداما</h1>
|
||||
<p>
|
||||
ابدأ العمل مع ذلك الذي يمكن أن يوفر كل ما تحتاجه لتوليد الوعي وزيادة حركة المرور والتواصل. تحويل القيمة الدقيقة بكفاءة من خلال المحتوى الذي يركز على العميل.
|
||||
</p>
|
||||
<img src="/Video/Play.png" alt="" />
|
||||
</main>
|
||||
|
||||
<span>
|
||||
{details.map((item, index) => (
|
||||
<article key={index}>
|
||||
<img src={`/Video/${item.img}.png`} alt={`${index}`} />
|
||||
|
||||
<h4>{t(item.number)}</h4>
|
||||
<p>{t(item.title)}</p>
|
||||
|
||||
</article>
|
||||
))}
|
||||
</span>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VideoPage;
|
||||
16
src/states/ObjectToEditState.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { create } from 'zustand';
|
||||
|
||||
// Define the type of the object you are editing
|
||||
interface EditableObject {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface ModelState {
|
||||
objectToEdit: {};
|
||||
setObjectToEdit: (data: EditableObject) => void;
|
||||
}
|
||||
|
||||
export const useObjectToEdit = create<ModelState>((set) => ({
|
||||
objectToEdit: {},
|
||||
setObjectToEdit: (data) => set({ objectToEdit: data }),
|
||||
}));
|
||||
82
src/styles/App/App.scss
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
|
||||
|
||||
|
||||
html,
|
||||
body {
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
overflow-x: hidden;
|
||||
font-family: 'Noto Sans Arabic';
|
||||
|
||||
@include Scrollbar();
|
||||
direction: rtl;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
// font-size: 16px;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button,
|
||||
svg {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
i {
|
||||
@include Flex;
|
||||
}
|
||||
|
||||
input:-webkit-autofill,
|
||||
input:-webkit-autofill:hover,
|
||||
input:-webkit-autofill:focus,
|
||||
input:-webkit-autofill:active {
|
||||
-webkit-box-shadow: 0 0 0 30px var(--bg) inset !important;
|
||||
}
|
||||
|
||||
input:-webkit-autofill,
|
||||
input:-webkit-autofill:hover,
|
||||
input:-webkit-autofill:focus,
|
||||
input:-webkit-autofill:active {
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: var(--text);
|
||||
transition: background-color 5000s ease-in-out 0s;
|
||||
box-shadow: inset 0 0 20px 20px #23232329;
|
||||
}
|
||||
|
||||
|
||||
button{
|
||||
cursor: pointer;
|
||||
transition: .5s ease-in-out;
|
||||
&:hover{
|
||||
scale: 1.02;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
svg,.scale{
|
||||
cursor: pointer;
|
||||
transition: .5s ease-in-out;
|
||||
&:hover{
|
||||
scale: 1.1;
|
||||
}
|
||||
}
|
||||
224
src/styles/App/Mixing.scss
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
@mixin Shadow {
|
||||
box-shadow: 2px 2px 2px 3px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
@mixin Flex {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@mixin Hover {
|
||||
&:hover {
|
||||
color: var(--secondary);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin Hover_outline_characters {
|
||||
&:hover {
|
||||
text-shadow: 2px 2px var(--secondary);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin underLineText($color1, $color2) {
|
||||
background-image: linear-gradient(90deg, $color1, $color2);
|
||||
background-size: 0% 3px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: left bottom;
|
||||
transition: background-size 500ms ease-in-out;
|
||||
&:hover {
|
||||
background-size: 100% 3px;
|
||||
}
|
||||
}
|
||||
.underLineText {
|
||||
background-image: linear-gradient(90deg, var(--primary), var(--secondary));
|
||||
background-size: 0% 3px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: left bottom;
|
||||
transition: background-size 500ms ease-in-out;
|
||||
&:hover {
|
||||
background-size: 100% 3px;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin Typewriter {
|
||||
// border-right: 5px solid var(--secondary);
|
||||
width: 100%;
|
||||
word-spacing: nowrap;
|
||||
text-wrap: nowrap;
|
||||
overflow: hidden;
|
||||
animation: typing 6s;
|
||||
}
|
||||
|
||||
@keyframes typing {
|
||||
from {
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin Glitch {
|
||||
letter-spacing: 5px;
|
||||
animation: shift 8s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
@keyframes shift {
|
||||
0%,
|
||||
40%,
|
||||
44%,
|
||||
58%,
|
||||
61%,
|
||||
65%,
|
||||
69%,
|
||||
73%,
|
||||
100% {
|
||||
transform: skewX(0deg);
|
||||
}
|
||||
|
||||
41% {
|
||||
transform: skewX(10deg);
|
||||
}
|
||||
|
||||
42% {
|
||||
transform: skewX(-10deg);
|
||||
}
|
||||
|
||||
59% {
|
||||
transform: skewX(40deg) skewY(10deg);
|
||||
}
|
||||
|
||||
60% {
|
||||
transform: skewX(-40deg) skewY(-10deg);
|
||||
}
|
||||
|
||||
63% {
|
||||
transform: skewX(10deg) skewY(-5deg);
|
||||
}
|
||||
|
||||
70% {
|
||||
transform: skewX(-50deg) skewY(-20deg);
|
||||
}
|
||||
|
||||
71% {
|
||||
transform: skewX(10deg) skewY(-10deg);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin Scrollbar() {
|
||||
scroll-behavior: smooth;
|
||||
scroll-padding: 10rem;
|
||||
&::-webkit-scrollbar {
|
||||
width: 0.4vw;
|
||||
max-height: 10px;
|
||||
}
|
||||
|
||||
/* Handle */
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: var(--primary);
|
||||
border-radius: 5px; /* Adjust border-radius as needed */
|
||||
}
|
||||
|
||||
/* Track */
|
||||
&::-webkit-scrollbar-track {
|
||||
border-radius: 5px; /* Adjust border-radius as needed */
|
||||
background-color: #d3d5d7; /* Set to desired background color */
|
||||
}
|
||||
}
|
||||
|
||||
@mixin CustomScrollbar($color) {
|
||||
scroll-behavior: smooth;
|
||||
scroll-padding: 10rem;
|
||||
&::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
/* Handle */
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: $color;
|
||||
border-radius: 5px; /* Adjust border-radius as needed */
|
||||
}
|
||||
|
||||
/* Track */
|
||||
&::-webkit-scrollbar-track {
|
||||
border-radius: 5px; /* Adjust border-radius as needed */
|
||||
background-color: transparent; /* Set to desired background color */
|
||||
}
|
||||
}
|
||||
|
||||
@mixin ScrollbarHover($color) {
|
||||
scroll-behavior: smooth;
|
||||
scroll-padding: 10rem;
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
/* Handle */
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: transparent;
|
||||
border-radius: 3px; /* Adjust border-radius as needed */
|
||||
}
|
||||
&:hover {
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: $color;
|
||||
|
||||
border-radius: 3px; /* Adjust border-radius as needed */
|
||||
}
|
||||
}
|
||||
|
||||
/* Track */
|
||||
&::-webkit-scrollbar-track {
|
||||
background-color: transparent; /* Set to desired background color */
|
||||
}
|
||||
}
|
||||
.maxWidth150 {
|
||||
white-space: nowrap; /* Prevents text from wrapping */
|
||||
overflow: hidden; /* Hides any content that overflows the container */
|
||||
text-overflow: ellipsis;
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
.maxWidth200 {
|
||||
white-space: nowrap; /* Prevents text from wrapping */
|
||||
overflow: hidden; /* Hides any content that overflows the container */
|
||||
text-overflow: ellipsis;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
// background-color: rgb(
|
||||
// /* Random value between 0 and 255 for red */
|
||||
// calc(255 * random()),
|
||||
// /* Random value between 0 and 255 for green */
|
||||
// calc(255 * random()),
|
||||
// /* Random value between 0 and 255 for blue */
|
||||
// calc(255 * random())
|
||||
// );
|
||||
|
||||
@mixin colorIndicator($color) {
|
||||
color: var(--secondary);
|
||||
position: relative;
|
||||
text-align: center;
|
||||
width: 3vw;
|
||||
&::after {
|
||||
background-color: $color;
|
||||
position: absolute;
|
||||
right: -1.4vw;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
content: '';
|
||||
width: 0.7vw;
|
||||
height: 0.7vw;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Apply the mixin using CSS variables */
|
||||
h6[data-color='Red'] {
|
||||
@include colorIndicator(red);
|
||||
}
|
||||
|
||||
h6[data-color='Green'] {
|
||||
@include colorIndicator(green);
|
||||
}
|
||||
|
||||
h6[data-color='Orange'] {
|
||||
@include colorIndicator(orange);
|
||||
}
|
||||