first commit
41
.gitignore
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/versions
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# env files (can opt-in for committing if needed)
|
||||||
|
.env*
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
40
README.md
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/pages/api-reference/create-next-app).
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
First, run the development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
# or
|
||||||
|
yarn dev
|
||||||
|
# or
|
||||||
|
pnpm dev
|
||||||
|
# or
|
||||||
|
bun dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||||
|
|
||||||
|
You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
|
||||||
|
|
||||||
|
[API routes](https://nextjs.org/docs/pages/building-your-application/routing/api-routes) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
|
||||||
|
|
||||||
|
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/pages/building-your-application/routing/api-routes) instead of React pages.
|
||||||
|
|
||||||
|
This project uses [`next/font`](https://nextjs.org/docs/pages/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
To learn more about Next.js, take a look at the following resources:
|
||||||
|
|
||||||
|
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||||
|
- [Learn Next.js](https://nextjs.org/learn-pages-router) - an interactive Next.js tutorial.
|
||||||
|
|
||||||
|
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||||
|
|
||||||
|
## Deploy on Vercel
|
||||||
|
|
||||||
|
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||||
|
|
||||||
|
Check out our [Next.js deployment documentation](https://nextjs.org/docs/pages/building-your-application/deploying) for more details.
|
||||||
18
eslint.config.mjs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { defineConfig, globalIgnores } from "eslint/config";
|
||||||
|
import nextVitals from "eslint-config-next/core-web-vitals";
|
||||||
|
import nextTs from "eslint-config-next/typescript";
|
||||||
|
|
||||||
|
const eslintConfig = defineConfig([
|
||||||
|
...nextVitals,
|
||||||
|
...nextTs,
|
||||||
|
// Override default ignores of eslint-config-next.
|
||||||
|
globalIgnores([
|
||||||
|
// Default ignores of eslint-config-next:
|
||||||
|
".next/**",
|
||||||
|
"out/**",
|
||||||
|
"build/**",
|
||||||
|
"next-env.d.ts",
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export default eslintConfig;
|
||||||
9
next.config.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
/* config options here */
|
||||||
|
reactCompiler: true,
|
||||||
|
reactStrictMode: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
36
package.json
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "wisatin",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "eslint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@ant-design/cssinjs": "^2.0.1",
|
||||||
|
"antd": "^6.0.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"lucide-react": "^0.556.0",
|
||||||
|
"next": "16.0.7",
|
||||||
|
"react": "19.2.0",
|
||||||
|
"react-dom": "19.2.0",
|
||||||
|
"react-slick": "^0.31.0",
|
||||||
|
"slick-carousel": "^1.8.1",
|
||||||
|
"swiper": "^8.4.7",
|
||||||
|
"tailwind-merge": "^3.4.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tailwindcss/postcss": "^4",
|
||||||
|
"@types/node": "^20",
|
||||||
|
"@types/react": "^19",
|
||||||
|
"@types/react-dom": "^19",
|
||||||
|
"@types/react-slick": "^0.23.13",
|
||||||
|
"babel-plugin-react-compiler": "1.0.0",
|
||||||
|
"eslint": "^9",
|
||||||
|
"eslint-config-next": "16.0.7",
|
||||||
|
"tailwindcss": "^4",
|
||||||
|
"typescript": "^5"
|
||||||
|
}
|
||||||
|
}
|
||||||
5009
pnpm-lock.yaml
generated
Normal file
7
postcss.config.mjs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
const config = {
|
||||||
|
plugins: {
|
||||||
|
"@tailwindcss/postcss": {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
BIN
public/favicon.ico
Normal file
|
After Width: | Height: | Size: 25 KiB |
1
public/file.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
||||||
|
After Width: | Height: | Size: 391 B |
1
public/globe.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
BIN
public/images/banners/arti.png
Normal file
|
After Width: | Height: | Size: 457 KiB |
BIN
public/images/banners/home-brilian.png
Normal file
|
After Width: | Height: | Size: 3.3 MiB |
BIN
public/images/banners/home-wellness-2025-2.png
Normal file
|
After Width: | Height: | Size: 6.2 MiB |
BIN
public/images/banners/ken2025-home.png
Normal file
|
After Width: | Height: | Size: 6.6 MiB |
1
public/next.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
1
public/vercel.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
||||||
|
After Width: | Height: | Size: 128 B |
1
public/window.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
||||||
|
After Width: | Height: | Size: 385 B |
13
src/Components/Atoms/Box.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import React, { PropsWithChildren, forwardRef } from 'react';
|
||||||
|
|
||||||
|
type BoxProps = React.HTMLAttributes<HTMLDivElement>;
|
||||||
|
|
||||||
|
const Box = forwardRef<HTMLDivElement, PropsWithChildren<BoxProps>>(({ children, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<div ref={ref} {...props}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Box;
|
||||||
23
src/Components/Atoms/Icons/AccountIcon.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { SvgIconProps } from '@/Types/Icon';
|
||||||
|
import { cn } from '@/Functions/cn';
|
||||||
|
|
||||||
|
const AccountIcon: React.FC<SvgIconProps> = ({ width = '24', height = '30', fill = 'black', className }) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 30 30"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
className={cn([className])}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M5.325 23.4375C6.9 22.3375 8.4625 21.4937 10.0125 20.9062C11.5625 20.3188 13.225 20.025 15 20.025C16.775 20.025 18.4438 20.3188 20.0062 20.9062C21.5687 21.4937 23.1375 22.3375 24.7125 23.4375C25.8125 22.0875 26.5938 20.725 27.0563 19.35C27.5188 17.975 27.75 16.525 27.75 15C27.75 11.375 26.5312 8.34375 24.0938 5.90625C21.6562 3.46875 18.625 2.25 15 2.25C11.375 2.25 8.34375 3.46875 5.90625 5.90625C3.46875 8.34375 2.25 11.375 2.25 15C2.25 16.525 2.4875 17.975 2.9625 19.35C3.4375 20.725 4.225 22.0875 5.325 23.4375ZM15 16.125C13.55 16.125 12.3313 15.6312 11.3438 14.6437C10.3562 13.6562 9.8625 12.4375 9.8625 10.9875C9.8625 9.5375 10.3562 8.31875 11.3438 7.33125C12.3313 6.34375 13.55 5.85 15 5.85C16.45 5.85 17.6688 6.34375 18.6562 7.33125C19.6437 8.31875 20.1375 9.5375 20.1375 10.9875C20.1375 12.4375 19.6437 13.6562 18.6562 14.6437C17.6688 15.6312 16.45 16.125 15 16.125ZM15 30C12.95 30 11.0125 29.6062 9.1875 28.8187C7.3625 28.0312 5.76875 26.9562 4.40625 25.5938C3.04375 24.2313 1.96875 22.6375 1.18125 20.8125C0.39375 18.9875 0 17.05 0 15C0 12.925 0.39375 10.9813 1.18125 9.16875C1.96875 7.35625 3.04375 5.76875 4.40625 4.40625C5.76875 3.04375 7.3625 1.96875 9.1875 1.18125C11.0125 0.39375 12.95 0 15 0C17.075 0 19.0187 0.39375 20.8312 1.18125C22.6437 1.96875 24.2313 3.04375 25.5938 4.40625C26.9562 5.76875 28.0312 7.35625 28.8187 9.16875C29.6062 10.9813 30 12.925 30 15C30 17.05 29.6062 18.9875 28.8187 20.8125C28.0312 22.6375 26.9562 24.2313 25.5938 25.5938C24.2313 26.9562 22.6437 28.0312 20.8312 28.8187C19.0187 29.6062 17.075 30 15 30ZM15 27.75C16.375 27.75 17.7188 27.55 19.0312 27.15C20.3438 26.75 21.6375 26.05 22.9125 25.05C21.6375 24.15 20.3375 23.4625 19.0125 22.9875C17.6875 22.5125 16.35 22.275 15 22.275C13.65 22.275 12.3125 22.5125 10.9875 22.9875C9.6625 23.4625 8.3625 24.15 7.0875 25.05C8.3625 26.05 9.65625 26.75 10.9688 27.15C12.2812 27.55 13.625 27.75 15 27.75ZM15 13.875C15.85 13.875 16.5437 13.6062 17.0812 13.0687C17.6187 12.5312 17.8875 11.8375 17.8875 10.9875C17.8875 10.1375 17.6187 9.44375 17.0812 8.90625C16.5437 8.36875 15.85 8.1 15 8.1C14.15 8.1 13.4563 8.36875 12.9188 8.90625C12.3813 9.44375 12.1125 10.1375 12.1125 10.9875C12.1125 11.8375 12.3813 12.5312 12.9188 13.0687C13.4563 13.6062 14.15 13.875 15 13.875Z"
|
||||||
|
fill={fill}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccountIcon;
|
||||||
37
src/Components/Atoms/SEO/index.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import Head from 'next/head';
|
||||||
|
|
||||||
|
export type SEOProps = {
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
img?: string;
|
||||||
|
imgAlt?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SEO = ({ title, description, img, imgAlt }: SEOProps) => {
|
||||||
|
return (
|
||||||
|
<Head>
|
||||||
|
{title ? (
|
||||||
|
<>
|
||||||
|
<title>{title}</title>
|
||||||
|
<meta key="og:title" property="og:title" content={title} />
|
||||||
|
<meta key="twitter:title" property="twitter:title" content={title} />
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
{description ? (
|
||||||
|
<>
|
||||||
|
<meta key="description" name="description" content={description} />
|
||||||
|
<meta key="og:description" name="og:description" content={description} />
|
||||||
|
<meta key="twitter:description" name="twitter:description" content={description} />
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
{img ? (
|
||||||
|
<>
|
||||||
|
<meta key="og:image" name="og:image" content={img} />
|
||||||
|
<meta key="og:image:alt" name="og:image:alt" content={imgAlt ?? ''} />
|
||||||
|
<meta key="twitter:image" name="twitter:image" content={img} />
|
||||||
|
<meta key="twitter:image:alt" name="twitter:image:alt" content={imgAlt ?? ''} />
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</Head>
|
||||||
|
);
|
||||||
|
};
|
||||||
40
src/Components/Molecules/AdminLayout/index.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import Box from "@/Components/Atoms/Box";
|
||||||
|
import { useBreakpoints } from "@/Contexts/BreakPointContext";
|
||||||
|
import useClient from "@/Hooks/useClient";
|
||||||
|
import { Calendar, Compass, Map, MapPin, Menu } from "lucide-react";
|
||||||
|
import { PropsWithChildren } from "react";
|
||||||
|
|
||||||
|
const AdminLayout: React.FC<PropsWithChildren> = ({ children }) => {
|
||||||
|
const { isDesktop } = useBreakpoints();
|
||||||
|
const isClient = useClient();
|
||||||
|
|
||||||
|
if (!isClient) return;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box className="min-h-screen bg-background">
|
||||||
|
<nav className="bg-white shadow-sm sticky top-0 z-50">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="flex justify-between items-center h-16">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<MapPin className="w-8 h-8 text-indigo-600" />
|
||||||
|
<span className="ml-2 text-2xl font-bold text-indigo-600">Wisatin - Admin</span>
|
||||||
|
</div>
|
||||||
|
<div className="hidden md:flex items-center space-x-4">
|
||||||
|
<button className="px-4 py-2 bg-indigo-600 text-white rounded-lg font-medium hover:bg-indigo-700 transition">
|
||||||
|
🇮🇩 ID
|
||||||
|
</button>
|
||||||
|
<button className="px-3 py-2 text-gray-600 hover:text-gray-800 transition">
|
||||||
|
EN 🇺🇸
|
||||||
|
</button>
|
||||||
|
<button className="px-6 py-2 border-2 border-gray-300 rounded-lg font-medium hover:border-indigo-600 hover:text-indigo-600 transition">
|
||||||
|
Logout
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AdminLayout;
|
||||||
99
src/Components/Molecules/DataTable/index.tsx
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import { Table, TableColumnsType } from 'antd';
|
||||||
|
import { TableRowSelection } from 'antd/es/table/interface';
|
||||||
|
import { TablePaginationConfig, TableProps } from 'antd/lib';
|
||||||
|
import React, { Key } from 'react';
|
||||||
|
|
||||||
|
type DataTableProps<T> = {
|
||||||
|
selectedRowKeys?: React.Key[];
|
||||||
|
columns: TableColumnsType<T>;
|
||||||
|
data: T[];
|
||||||
|
length?: number;
|
||||||
|
pagesize?: number;
|
||||||
|
selectedLength?: number;
|
||||||
|
tableLayout?: TableProps['tableLayout'];
|
||||||
|
toolbar?: React.ReactNode;
|
||||||
|
paginationPosition?: TablePaginationConfig['position'];
|
||||||
|
setSelectedRowKeys?: (keys: Key[]) => void;
|
||||||
|
onPageChange?: TableProps<T>['onChange'];
|
||||||
|
footer?: TableProps<T>['footer'];
|
||||||
|
} & Omit<TableProps<T>, 'columns'>;
|
||||||
|
|
||||||
|
// eslint-disable-next-line comma-spacing
|
||||||
|
const DataTable = <T,>({
|
||||||
|
selectedRowKeys,
|
||||||
|
columns,
|
||||||
|
data,
|
||||||
|
length,
|
||||||
|
pagesize,
|
||||||
|
selectedLength,
|
||||||
|
tableLayout = 'fixed',
|
||||||
|
toolbar,
|
||||||
|
setSelectedRowKeys,
|
||||||
|
onPageChange,
|
||||||
|
footer,
|
||||||
|
paginationPosition,
|
||||||
|
...rest
|
||||||
|
}: DataTableProps<T>) => {
|
||||||
|
const rowSelection: TableRowSelection<T> = {
|
||||||
|
selectedRowKeys,
|
||||||
|
checkStrictly: false,
|
||||||
|
onChange: (newSelectedRowKeys: React.Key[]) => {
|
||||||
|
if (!setSelectedRowKeys) return;
|
||||||
|
setSelectedRowKeys(newSelectedRowKeys);
|
||||||
|
},
|
||||||
|
selections: [
|
||||||
|
Table.SELECTION_ALL,
|
||||||
|
Table.SELECTION_INVERT,
|
||||||
|
Table.SELECTION_NONE,
|
||||||
|
{
|
||||||
|
key: 'odd',
|
||||||
|
text: 'Select Odd Row',
|
||||||
|
onSelect: (changeableRowKeys) => {
|
||||||
|
const newSelectedRowKeys = changeableRowKeys.filter((_, index) => {
|
||||||
|
if (index % 2 !== 0) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
if (newSelectedRowKeys && setSelectedRowKeys) setSelectedRowKeys(newSelectedRowKeys);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'even',
|
||||||
|
text: 'Select Even Row',
|
||||||
|
onSelect: (changeableRowKeys) => {
|
||||||
|
const newSelectedRowKeys = changeableRowKeys.filter((_, index) => {
|
||||||
|
if (index % 2 !== 0) return true;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
setSelectedRowKeys && setSelectedRowKeys(newSelectedRowKeys);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const canSelectRow = selectedRowKeys !== undefined && setSelectedRowKeys !== undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{toolbar}
|
||||||
|
<Table<T>
|
||||||
|
rowSelection={canSelectRow ? rowSelection : undefined}
|
||||||
|
columns={columns}
|
||||||
|
dataSource={data}
|
||||||
|
onChange={onPageChange}
|
||||||
|
pagination={{
|
||||||
|
showSizeChanger: true,
|
||||||
|
pageSize: pagesize,
|
||||||
|
...(paginationPosition && { position: paginationPosition }),
|
||||||
|
}}
|
||||||
|
tableLayout={tableLayout}
|
||||||
|
footer={
|
||||||
|
footer ? undefined : () => (selectedLength ? `${selectedLength} of ${length} row(s) selected` : '')
|
||||||
|
}
|
||||||
|
className="!overflow-x-auto"
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DataTable;
|
||||||
40
src/Components/Molecules/ProtectedLayout/index.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import Box from "@/Components/Atoms/Box";
|
||||||
|
import { useBreakpoints } from "@/Contexts/BreakPointContext";
|
||||||
|
import useClient from "@/Hooks/useClient";
|
||||||
|
import { Calendar, Compass, Map, MapPin, Menu } from "lucide-react";
|
||||||
|
import { PropsWithChildren } from "react";
|
||||||
|
|
||||||
|
const ProtectedLayout: React.FC<PropsWithChildren> = ({ children }) => {
|
||||||
|
const { isDesktop } = useBreakpoints();
|
||||||
|
const isClient = useClient();
|
||||||
|
|
||||||
|
if (!isClient) return;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box className="min-h-screen bg-background">
|
||||||
|
<nav className="bg-white shadow-sm sticky top-0 z-50">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="flex justify-between items-center h-16">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<MapPin className="w-8 h-8 text-indigo-600" />
|
||||||
|
<span className="ml-2 text-2xl font-bold text-indigo-600">Wisatin</span>
|
||||||
|
</div>
|
||||||
|
<div className="hidden md:flex items-center space-x-4">
|
||||||
|
<button className="px-4 py-2 bg-indigo-600 text-white rounded-lg font-medium hover:bg-indigo-700 transition">
|
||||||
|
🇮🇩 ID
|
||||||
|
</button>
|
||||||
|
<button className="px-3 py-2 text-gray-600 hover:text-gray-800 transition">
|
||||||
|
EN 🇺🇸
|
||||||
|
</button>
|
||||||
|
<button className="px-6 py-2 border-2 border-gray-300 rounded-lg font-medium hover:border-indigo-600 hover:text-indigo-600 transition">
|
||||||
|
Logout
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProtectedLayout;
|
||||||
65
src/Components/Molecules/PublicLayout/index.tsx
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import Box from "@/Components/Atoms/Box";
|
||||||
|
import { useBreakpoints } from "@/Contexts/BreakPointContext";
|
||||||
|
import useClient from "@/Hooks/useClient";
|
||||||
|
import { Button, Radio, Space } from "antd";
|
||||||
|
import { Calendar, Compass, Map, MapPin, Menu, Store } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { PropsWithChildren, useState } from "react";
|
||||||
|
|
||||||
|
const PublicLayout: React.FC<PropsWithChildren> = ({ children }) => {
|
||||||
|
const { isDesktop } = useBreakpoints();
|
||||||
|
const isClient = useClient();
|
||||||
|
|
||||||
|
const [position, setPosition] = useState<'ID' | 'EN'>('ID');
|
||||||
|
|
||||||
|
if (!isClient) return;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box className="min-h-screen bg-gray-50">
|
||||||
|
<nav className="bg-white shadow-sm sticky top-0 z-50">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="flex justify-between items-center h-16">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<MapPin className="w-8 h-8 text-primary" />
|
||||||
|
<span className="ml-2 text-2xl font-bold text-primary">Wisatin</span>
|
||||||
|
</div>
|
||||||
|
{/* Desktop Menu */}
|
||||||
|
<div className="items-center gap-6 flex">
|
||||||
|
<div className="hidden md:flex items-center gap-12">
|
||||||
|
<Link href="/" className="flex items-center text-gray-700 hover:text-primary transition">
|
||||||
|
<Store className="size-6 mr-2" />
|
||||||
|
<span className="font-semibold">Product</span>
|
||||||
|
</Link>
|
||||||
|
<Link href="/explore" className="flex items-center text-gray-700 hover:text-primary transition">
|
||||||
|
<Compass className="size-6 mr-2" />
|
||||||
|
<span className="font-semibold">Explore</span>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="hidden md:flex items-center space-x-6">
|
||||||
|
<Space>
|
||||||
|
<Radio.Group
|
||||||
|
value={position}
|
||||||
|
onChange={(e) => setPosition(e.target.value)}
|
||||||
|
>
|
||||||
|
<Radio.Button value="ID">🇮🇩 ID</Radio.Button>
|
||||||
|
<Radio.Button value="EN">EN 🇺🇸</Radio.Button>
|
||||||
|
</Radio.Group>
|
||||||
|
</Space>
|
||||||
|
<Button type="default" variant="outlined">
|
||||||
|
<Link href="/login">
|
||||||
|
Login
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div className="md:mt-[74px] mt-[56px]">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PublicLayout;
|
||||||
17
src/Components/Organisms/AdminPageLayout.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Box from '../Atoms/Box';
|
||||||
|
import { cn } from '@/Functions/cn';
|
||||||
|
import { LayoutProps } from '@/Types/Layout';
|
||||||
|
import AdminLayout from '../Molecules/AdminLayout';
|
||||||
|
|
||||||
|
const AdminPageLayout = ({ children, className = '' }: LayoutProps) => {
|
||||||
|
return (
|
||||||
|
<Box id="admin-layout" className={cn('relative', className)}>
|
||||||
|
<AdminLayout>
|
||||||
|
{children}
|
||||||
|
</AdminLayout>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AdminPageLayout;
|
||||||
17
src/Components/Organisms/ProtectedPageLayout.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Box from '../Atoms/Box';
|
||||||
|
import { cn } from '@/Functions/cn';
|
||||||
|
import { LayoutProps } from '@/Types/Layout';
|
||||||
|
import ProtectedLayout from '../Molecules/ProtectedLayout';
|
||||||
|
|
||||||
|
const ProtectedPageLayout = ({ children, className = '' }: LayoutProps) => {
|
||||||
|
return (
|
||||||
|
<Box id="protected-layout" className={cn('relative', className)}>
|
||||||
|
<ProtectedLayout>
|
||||||
|
{children}
|
||||||
|
</ProtectedLayout>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProtectedPageLayout;
|
||||||
17
src/Components/Organisms/PublicPageLayout.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Box from '../Atoms/Box';
|
||||||
|
import { cn } from '@/Functions/cn';
|
||||||
|
import { LayoutProps } from '@/Types/Layout';
|
||||||
|
import PublicLayout from '../Molecules/PublicLayout';
|
||||||
|
|
||||||
|
const PublicPageLayout = ({ children, className = '' }: LayoutProps) => {
|
||||||
|
return (
|
||||||
|
<Box id="public-layout" className={cn('relative', className)}>
|
||||||
|
<PublicLayout>
|
||||||
|
{children}
|
||||||
|
</PublicLayout>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PublicPageLayout;
|
||||||
3
src/Constants/AdminRoutes.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const adminRoutes = [
|
||||||
|
'/admin/dashboard',
|
||||||
|
];
|
||||||
3
src/Constants/ProtectedRoutes.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const protectedRoutes = [
|
||||||
|
'/account',
|
||||||
|
];
|
||||||
1
src/Constants/PublicRoutes.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const publicRoutes = ['/', '/login', 'register'];
|
||||||
48
src/Contexts/BreakPointContext.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import React, { createContext, useContext, useMemo, ReactNode, useEffect, useState } from 'react';
|
||||||
|
import { Grid } from 'antd';
|
||||||
|
import { BreakpointContextType } from '@/Types/BreakPoint';
|
||||||
|
|
||||||
|
const { useBreakpoint } = Grid;
|
||||||
|
|
||||||
|
const BreakpointContext = createContext<BreakpointContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
export const BreakpointProvider = ({ children }: { children: ReactNode }) => {
|
||||||
|
const screens = useBreakpoint();
|
||||||
|
|
||||||
|
const value = useMemo(
|
||||||
|
() => ({
|
||||||
|
isMobile: !!screens.xs,
|
||||||
|
isTablet: !!screens.sm || !!screens.md,
|
||||||
|
isDesktop: !!screens.lg || !!screens.xl || !!screens.xxl,
|
||||||
|
}),
|
||||||
|
[screens],
|
||||||
|
);
|
||||||
|
|
||||||
|
return <BreakpointContext.Provider value={value}>{children}</BreakpointContext.Provider>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useBreakpoints = (): BreakpointContextType => {
|
||||||
|
const context = useContext(BreakpointContext);
|
||||||
|
|
||||||
|
if (context === undefined) throw new Error('useBreakpoints must be used within a BreakpointProvider');
|
||||||
|
|
||||||
|
return context;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useScreenWidth() {
|
||||||
|
const [windowWidth, setWindowWidth] = useState<number | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
const handleResize = () => {
|
||||||
|
setWindowWidth(window.innerWidth);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
handleResize();
|
||||||
|
|
||||||
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return windowWidth;
|
||||||
|
}
|
||||||
23
src/Contexts/GroupContextProvider.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { ConfigProvider } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import { ToastProvider } from './ToastContext';
|
||||||
|
import { BreakpointProvider } from './BreakPointContext';
|
||||||
|
import theme from '@/theme/themeConfig';
|
||||||
|
|
||||||
|
type GroupContextProviderProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const GroupContextProvider = ({ children }: GroupContextProviderProps) => {
|
||||||
|
return (
|
||||||
|
<ConfigProvider theme={theme}>
|
||||||
|
<ToastProvider>
|
||||||
|
<BreakpointProvider>
|
||||||
|
{children}
|
||||||
|
</BreakpointProvider>
|
||||||
|
</ToastProvider>
|
||||||
|
</ConfigProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GroupContextProvider;
|
||||||
25
src/Contexts/ToastContext.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { message } from 'antd';
|
||||||
|
import React, { createContext } from 'react';
|
||||||
|
|
||||||
|
type GlobalStoresType = {
|
||||||
|
showToast: typeof message | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ToastStores: GlobalStoresType = {
|
||||||
|
showToast: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ToastContext = createContext(ToastStores);
|
||||||
|
|
||||||
|
message.config({
|
||||||
|
top: 0,
|
||||||
|
duration: 3,
|
||||||
|
maxCount: 3,
|
||||||
|
getContainer: () => document.body,
|
||||||
|
});
|
||||||
|
|
||||||
|
const ToastProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
|
return <ToastContext.Provider value={{ showToast: message }}>{children}</ToastContext.Provider>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { ToastContext, ToastProvider };
|
||||||
6
src/Functions/cn.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { type ClassValue, clsx } from 'clsx';
|
||||||
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs));
|
||||||
|
}
|
||||||
11
src/Hooks/useClient.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
const useClient = () => {
|
||||||
|
const [clientLoaded, setIsClient] = useState(false);
|
||||||
|
useEffect(() => {
|
||||||
|
setIsClient(true);
|
||||||
|
}, []);
|
||||||
|
return clientLoaded;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useClient;
|
||||||
9
src/LegacyPages/Admin/Dashboard/index.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Box } from "lucide-react";
|
||||||
|
|
||||||
|
export default function Dashboard(){
|
||||||
|
return (
|
||||||
|
<Box className="flex flex-col gap-6">
|
||||||
|
Dashboard
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
9
src/LegacyPages/Public/Account/index.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Box } from "lucide-react";
|
||||||
|
|
||||||
|
export default function Account(){
|
||||||
|
return (
|
||||||
|
<Box className="flex flex-col gap-6">
|
||||||
|
Account
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
9
src/LegacyPages/Public/Explore/index.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Box } from "lucide-react";
|
||||||
|
|
||||||
|
export default function Explore(){
|
||||||
|
return (
|
||||||
|
<Box className="flex flex-col gap-6">
|
||||||
|
Explore
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
76
src/LegacyPages/Public/Home/HomeBanner/index.tsx
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
|
||||||
|
import { Fragment, useRef, useState } from 'react';
|
||||||
|
import { Button } from 'antd';
|
||||||
|
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
||||||
|
import useClient from '@/Hooks/useClient';
|
||||||
|
import Slider from "react-slick";
|
||||||
|
|
||||||
|
import "slick-carousel/slick/slick.css";
|
||||||
|
import "slick-carousel/slick/slick-theme.css";
|
||||||
|
|
||||||
|
export default function HomeBanner() {
|
||||||
|
const isClient = useClient();
|
||||||
|
|
||||||
|
const [activeIndex, setActiveIndex] = useState<number>(0);
|
||||||
|
|
||||||
|
const sliderRef = useRef<Slider | null>(null);
|
||||||
|
|
||||||
|
const banners = [
|
||||||
|
"/images/banners/ken2025-home.png",
|
||||||
|
"/images/banners/home-wellness-2025-2.png",
|
||||||
|
"/images/banners/home-brilian.png",
|
||||||
|
"/images/banners/arti.png",
|
||||||
|
]
|
||||||
|
|
||||||
|
const onNext = () => {
|
||||||
|
sliderRef?.current?.slickNext()
|
||||||
|
}
|
||||||
|
|
||||||
|
const onPrevious = () => {
|
||||||
|
sliderRef?.current?.slickPrev()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isClient) return;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col relative'>
|
||||||
|
<Slider
|
||||||
|
ref={sliderRef}
|
||||||
|
className='center'
|
||||||
|
centerMode
|
||||||
|
infinite
|
||||||
|
centerPadding='60px'
|
||||||
|
beforeChange={(current, next) => setActiveIndex(next)}
|
||||||
|
slidesToShow={3}
|
||||||
|
speed={500}
|
||||||
|
>
|
||||||
|
{banners.map((item, index) => (
|
||||||
|
<Fragment key={index}>
|
||||||
|
<img
|
||||||
|
src={item}
|
||||||
|
className='w-full h-[400px] object-cover'
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</Slider>
|
||||||
|
<div className="flex justify-center items-center gap-2 mt-6">
|
||||||
|
<Button variant='text' type='text' onClick={onPrevious}>
|
||||||
|
<ChevronLeft/>
|
||||||
|
</Button>
|
||||||
|
{banners.map((_, index) => (
|
||||||
|
<button
|
||||||
|
key={index}
|
||||||
|
className={`transition-all duration-300 rounded-full ${index === activeIndex
|
||||||
|
? 'bg-primary w-8 h-3'
|
||||||
|
: 'bg-gray-300 hover:bg-gray-400 w-3 h-3'
|
||||||
|
}`}
|
||||||
|
aria-label={`Go to slide ${index + 1}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<Button variant='text' type='text' onClick={onNext}>
|
||||||
|
<ChevronRight/>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
10
src/LegacyPages/Public/Home/index.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { Box } from "lucide-react";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
|
||||||
|
export default function Home(){
|
||||||
|
return (
|
||||||
|
<Box className="flex flex-col gap-6">
|
||||||
|
Home
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
9
src/LegacyPages/Public/Login/index.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Box } from "lucide-react";
|
||||||
|
|
||||||
|
export default function Login(){
|
||||||
|
return (
|
||||||
|
<Box className="flex flex-col gap-6">
|
||||||
|
Login
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
9
src/LegacyPages/Public/Register/index.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Box } from "lucide-react";
|
||||||
|
|
||||||
|
export default function Register(){
|
||||||
|
return (
|
||||||
|
<Box className="flex flex-col gap-6">
|
||||||
|
REGISTER
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
35
src/Styles/globals.css
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--background: hwb(0 100% 0%);
|
||||||
|
--primary: #FF4D00;
|
||||||
|
--secondary: #1F2937;
|
||||||
|
--error: #FF6B6B;
|
||||||
|
--success: #00B894;
|
||||||
|
--info: #63B3ED;
|
||||||
|
--foreground: #171717;
|
||||||
|
}
|
||||||
|
|
||||||
|
@theme inline {
|
||||||
|
--color-background: var(--background);
|
||||||
|
--color-primary: var(--primary);
|
||||||
|
--color-secondary: var(--secondary);
|
||||||
|
--color-error: var(--error);
|
||||||
|
--color-success: var(--success);
|
||||||
|
--color-foreground: var(--foreground);
|
||||||
|
--font-sans: var(--font-geist-sans);
|
||||||
|
--font-mono: var(--font-geist-mono);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--background: #0a0a0a;
|
||||||
|
--foreground: #ededed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: var(--background);
|
||||||
|
color: var(--foreground);
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
}
|
||||||
5
src/Types/BreakPoint.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export interface BreakpointContextType {
|
||||||
|
isMobile: boolean;
|
||||||
|
isTablet: boolean;
|
||||||
|
isDesktop: boolean;
|
||||||
|
}
|
||||||
6
src/Types/Icon.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export interface SvgIconProps extends React.SVGProps<SVGSVGElement> {
|
||||||
|
width?: string;
|
||||||
|
height?: string;
|
||||||
|
fill?: string;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
6
src/Types/Layout.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
export interface LayoutProps {
|
||||||
|
children: ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
67
src/pages/_app.tsx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { ConfigProvider } from 'antd';
|
||||||
|
import type { AppProps } from 'next/app';
|
||||||
|
import theme from '@/theme/themeConfig';
|
||||||
|
import { Poppins } from 'next/font/google';
|
||||||
|
import Head from 'next/head';
|
||||||
|
import GroupContextProvider from '@/Contexts/GroupContextProvider';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import { publicRoutes } from '@/Constants/PublicRoutes';
|
||||||
|
import PublicPageLayout from '@/Components/Organisms/PublicPageLayout';
|
||||||
|
import { adminRoutes } from '@/Constants/AdminRoutes';
|
||||||
|
import AdminPageLayout from '@/Components/Organisms/AdminPageLayout';
|
||||||
|
import ProtectedPageLayout from '@/Components/Organisms/ProtectedPageLayout';
|
||||||
|
import '@/Styles/globals.css';
|
||||||
|
|
||||||
|
const poppins = Poppins({
|
||||||
|
subsets: ['latin'],
|
||||||
|
weight: ['400', '500', '600', '700'],
|
||||||
|
variable: '--font-poppins',
|
||||||
|
});
|
||||||
|
|
||||||
|
const AppLayout = ({ Component, ...pageProps }: AppProps) => {
|
||||||
|
const router = useRouter();
|
||||||
|
if (publicRoutes.includes(router.pathname))
|
||||||
|
return (
|
||||||
|
<PublicPageLayout className={poppins.variable}>
|
||||||
|
<Component {...pageProps} />
|
||||||
|
</PublicPageLayout>
|
||||||
|
);
|
||||||
|
else if(adminRoutes.includes(router.pathname))
|
||||||
|
<AdminPageLayout className={poppins.variable}>
|
||||||
|
<Component {...pageProps} />
|
||||||
|
</AdminPageLayout>
|
||||||
|
else
|
||||||
|
return (
|
||||||
|
<ProtectedPageLayout className={poppins.variable}>
|
||||||
|
<Component {...pageProps} />
|
||||||
|
</ProtectedPageLayout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const App = ({ Component, pageProps }: AppProps) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>Wisatin</title>
|
||||||
|
<meta charSet="utf-8" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<meta name="description" content="Generated by Wisatin" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:title" content="Wisatin" />
|
||||||
|
<meta name="og:description" content="Generated by Wisatin" />
|
||||||
|
<meta name="og:image:alt" content="Wisatin logo" />
|
||||||
|
<meta name="twitter:image:alt" content="Wisatin logo" />
|
||||||
|
<meta name="twitter:description" content="Generated by Wisatin" />
|
||||||
|
</Head>
|
||||||
|
|
||||||
|
<GroupContextProvider>
|
||||||
|
<AppLayout Component={Component} {...pageProps} />
|
||||||
|
</GroupContextProvider>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default App;
|
||||||
43
src/pages/_document.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { createCache, extractStyle, StyleProvider } from '@ant-design/cssinjs';
|
||||||
|
import Document, { Head, Html, Main, NextScript } from 'next/document';
|
||||||
|
import type { DocumentContext } from 'next/document';
|
||||||
|
|
||||||
|
const MyDocument = () => {
|
||||||
|
return (
|
||||||
|
<Html lang="en">
|
||||||
|
<Head />
|
||||||
|
<body className="antialiased">
|
||||||
|
<Main />
|
||||||
|
<NextScript />
|
||||||
|
</body>
|
||||||
|
</Html>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
MyDocument.getInitialProps = async (ctx: DocumentContext) => {
|
||||||
|
const cache = createCache();
|
||||||
|
const originalRenderPage = ctx.renderPage;
|
||||||
|
ctx.renderPage = () =>
|
||||||
|
originalRenderPage({
|
||||||
|
enhanceApp: (App) => (props) => (
|
||||||
|
<StyleProvider cache={cache}>
|
||||||
|
<App {...props} />
|
||||||
|
</StyleProvider>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
const initialProps = await Document.getInitialProps(ctx);
|
||||||
|
const style = extractStyle(cache, true);
|
||||||
|
return {
|
||||||
|
...initialProps,
|
||||||
|
styles: (
|
||||||
|
<>
|
||||||
|
{initialProps.styles}
|
||||||
|
<style dangerouslySetInnerHTML={{ __html: style }} />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MyDocument;
|
||||||
11
src/pages/account.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { SEO } from '@/Components/Atoms/SEO';
|
||||||
|
import Account from '@/LegacyPages/Public/Account';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SEO title="Wisatin" description="Generated by Wisatin" />
|
||||||
|
<Account />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
11
src/pages/admin/dashboard.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { SEO } from '@/Components/Atoms/SEO';
|
||||||
|
import Dashboard from '@/LegacyPages/Admin/Dashboard';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SEO title="Wisatin" description="Generated by Wisatin" />
|
||||||
|
<Dashboard />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
13
src/pages/api/hello.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||||
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
|
type Data = {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse<Data>,
|
||||||
|
) {
|
||||||
|
res.status(200).json({ name: "John Doe" });
|
||||||
|
}
|
||||||
11
src/pages/explore.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { SEO } from '@/Components/Atoms/SEO';
|
||||||
|
import Explore from '@/LegacyPages/Public/Explore';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SEO title="Wisatin" description="Generated by Wisatin" />
|
||||||
|
<Explore />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
11
src/pages/index.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { SEO } from '@/Components/Atoms/SEO';
|
||||||
|
import Home from '@/LegacyPages/Public/Home';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SEO title="Wisatin" description="Generated by Wisatin" />
|
||||||
|
<Home />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
11
src/pages/login.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { SEO } from '@/Components/Atoms/SEO';
|
||||||
|
import Login from '@/LegacyPages/Public/Login';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SEO title="Wisatin" description="Generated by Wisatin" />
|
||||||
|
<Login />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
11
src/pages/register.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { SEO } from '@/Components/Atoms/SEO';
|
||||||
|
import Register from '@/LegacyPages/Public/Register';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SEO title="Wisatin" description="Generated by Wisatin" />
|
||||||
|
<Register />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
14
src/theme/themeConfig.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// theme/themeConfig.ts
|
||||||
|
import type { ThemeConfig } from 'antd';
|
||||||
|
|
||||||
|
const theme: ThemeConfig = {
|
||||||
|
token: {
|
||||||
|
fontSize: 16,
|
||||||
|
colorPrimary: '#FF4D00',
|
||||||
|
colorError: '#FF6B6B',
|
||||||
|
colorSuccess: '#00B894',
|
||||||
|
colorInfo: '#63B3ED'
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default theme;
|
||||||
29
tsconfig.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2017",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"incremental": true,
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts",
|
||||||
|
".next/dev/types/**/*.ts",
|
||||||
|
"**/*.mts"
|
||||||
|
],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||