diff --git a/.env.example b/.env.example deleted file mode 100644 index 49d9f6e..0000000 --- a/.env.example +++ /dev/null @@ -1 +0,0 @@ -VITE_API_URL=https://api.example.com diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index 84ebe3e..c37a607 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -24,5 +24,14 @@ jobs: - name: Install dependencies run: npm install + - name: Set env vars + run: | + echo "CMS_SECRET=${{ secrets.CMS_SECRET }}" >> $GITHUB_ENV + echo "CMS_STORAGE_REGION=${{ secrets.CMS_STORAGE_REGION }}" >> $GITHUB_ENV + echo "CMS_STORAGE_ENDPOINT=${{ secrets.CMS_STORAGE_ENDPOINT }}" >> $GITHUB_ENV + echo "CMS_STORAGE_ACCESS_KEY_ID=${{ secrets.CMS_STORAGE_ACCESS_KEY_ID }}" >> $GITHUB_ENV + echo "CMS_STORAGE_SECRET_ACCESS_KEY=${{ secrets.CMS_STORAGE_SECRET_ACCESS_KEY }}" >> $GITHUB_ENV + echo "CMS_POSTGRES_URL=${{ secrets.CMS_POSTGRES_URL }}" >> $GITHUB_ENV + - name: Nx Build run: npx nx run-many --target=build --all diff --git a/.gitignore b/.gitignore index 412f51a..075e8e7 100644 --- a/.gitignore +++ b/.gitignore @@ -57,4 +57,8 @@ storybook-static .data .nuxt .nitro -.cache \ No newline at end of file +.cache + +# Next.js +.next +out diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 462e29b..a90dde4 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,6 +2,7 @@ "recommendations": [ "nrwl.angular-console", "esbenp.prettier-vscode", - "dbaeumer.vscode-eslint" + "dbaeumer.vscode-eslint", + "ms-playwright.playwright" ] } diff --git a/README.md b/README.md index a924890..effee35 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,10 @@ This repository is a **monorepo** for all frontend services of IMPHNEN. The monorepo includes three main applications: -1. **Gacha** - Application for gacha website. -2. **Backoffice** - Application for internal management. -3. **Dimentorin** - Application for mentoring services. +1. **Gacha** - Application for Gacha Website. +2. **Backoffice** - Application for Internal Management Website. +3. **Dimentorin** - Application for Mentoring Service. +4. **Landing Page** - Application for Landing Page. ## How to install @@ -24,6 +25,15 @@ This repository is a **monorepo** for all frontend services of IMPHNEN. The mono ## How to run +### Setup Environment Variables + +Before running the applications, you need to set up the environment variables. You can do this by copying the `.env.example` file to `.env` and modifying the values according to your needs. + +```sh +cd apps/{appname} +cp .env.example .env +``` + ### Development Use the following commands to run in development mode: @@ -40,6 +50,10 @@ Use the following commands to run in development mode: ```sh npm run dimentorin:dev ``` +- **Landing Page**: + ```sh + npm run landing:dev + ``` ### Build @@ -57,6 +71,10 @@ Use the following commands to build the applications: ```sh npm run dimentorin:build ``` +- **Landing Page**: + ```sh + npm run landing:build + ``` ### Production @@ -74,19 +92,22 @@ Use the following commands to run the applications in production mode: ```sh npm run dimentorin:prod ``` +- **Landing Page**: + ```sh + npm run landing:prod + ``` ### Storybook This repository uses Storybook to develop, test, and document UI components in an isolated and interactive environment. Below are the commands to work with Storybook: - **Run Storybook** - + This command starts Storybook in development mode, allowing you to view and test UI components interactively. ```sh npm run ui:storybook ``` - - **Run Unit Test** diff --git a/apps/backoffice/.env.example b/apps/backoffice/.env.example new file mode 100644 index 0000000..292a14c --- /dev/null +++ b/apps/backoffice/.env.example @@ -0,0 +1 @@ +VITE_API_URL= diff --git a/apps/backoffice/src/app/accounts/_components/modal-edit-account.tsx b/apps/backoffice/src/app/(protected)/accounts/_components/modal-edit-account.tsx similarity index 100% rename from apps/backoffice/src/app/accounts/_components/modal-edit-account.tsx rename to apps/backoffice/src/app/(protected)/accounts/_components/modal-edit-account.tsx diff --git a/apps/backoffice/src/app/accounts/page.tsx b/apps/backoffice/src/app/(protected)/accounts/page.tsx similarity index 97% rename from apps/backoffice/src/app/accounts/page.tsx rename to apps/backoffice/src/app/(protected)/accounts/page.tsx index e762610..dd246b4 100644 --- a/apps/backoffice/src/app/accounts/page.tsx +++ b/apps/backoffice/src/app/(protected)/accounts/page.tsx @@ -18,7 +18,7 @@ import { RowSelectionState, } from '@tanstack/react-table'; import ModalEditAccount from './_components/modal-edit-account'; -import { useQueryState } from '../../hook/use-query-state'; +import { useQueryState } from '@imphnen-frontend-service/utils'; interface Account { id: number; @@ -166,7 +166,7 @@ export const Components: FC = (): ReactElement => { {showFilter && (
- setShowFilter(false)} /> + setShowFilter(false)} options={[]} />
)} diff --git a/apps/backoffice/src/app/dashboard/_components/modal-add-item.tsx b/apps/backoffice/src/app/(protected)/dashboard/_components/modal-add-item.tsx similarity index 100% rename from apps/backoffice/src/app/dashboard/_components/modal-add-item.tsx rename to apps/backoffice/src/app/(protected)/dashboard/_components/modal-add-item.tsx diff --git a/apps/backoffice/src/app/dashboard/_components/modal-delete-item.tsx b/apps/backoffice/src/app/(protected)/dashboard/_components/modal-delete-item.tsx similarity index 100% rename from apps/backoffice/src/app/dashboard/_components/modal-delete-item.tsx rename to apps/backoffice/src/app/(protected)/dashboard/_components/modal-delete-item.tsx diff --git a/apps/backoffice/src/app/dashboard/_components/modal-edit-item.tsx b/apps/backoffice/src/app/(protected)/dashboard/_components/modal-edit-item.tsx similarity index 100% rename from apps/backoffice/src/app/dashboard/_components/modal-edit-item.tsx rename to apps/backoffice/src/app/(protected)/dashboard/_components/modal-edit-item.tsx diff --git a/apps/backoffice/src/app/dashboard/_hook/use-item.ts b/apps/backoffice/src/app/(protected)/dashboard/_hook/use-item.ts similarity index 100% rename from apps/backoffice/src/app/dashboard/_hook/use-item.ts rename to apps/backoffice/src/app/(protected)/dashboard/_hook/use-item.ts diff --git a/apps/backoffice/src/app/dashboard/page.tsx b/apps/backoffice/src/app/(protected)/dashboard/page.tsx similarity index 99% rename from apps/backoffice/src/app/dashboard/page.tsx rename to apps/backoffice/src/app/(protected)/dashboard/page.tsx index be30690..f456c48 100644 --- a/apps/backoffice/src/app/dashboard/page.tsx +++ b/apps/backoffice/src/app/(protected)/dashboard/page.tsx @@ -10,7 +10,7 @@ import { FC, Fragment, ReactElement, useState } from 'react'; import ModalAddItem from './_components/modal-add-item'; import ModalEditItem from './_components/modal-edit-item'; import ModalDeleteItem from './_components/modal-delete-item'; -import { useQueryState } from '../../hook/use-query-state'; +import { useQueryState } from '@imphnen-frontend-service/utils'; export const Components: FC = (): ReactElement => { const [showModalAddItem, setShowModalAddItem] = useState(false); diff --git a/apps/backoffice/src/app/gacha-roll/_components/modal-add-item.tsx b/apps/backoffice/src/app/(protected)/gacha-roll/_components/modal-add-item.tsx similarity index 100% rename from apps/backoffice/src/app/gacha-roll/_components/modal-add-item.tsx rename to apps/backoffice/src/app/(protected)/gacha-roll/_components/modal-add-item.tsx diff --git a/apps/backoffice/src/app/gacha-roll/_components/modal-delete-item.tsx b/apps/backoffice/src/app/(protected)/gacha-roll/_components/modal-delete-item.tsx similarity index 100% rename from apps/backoffice/src/app/gacha-roll/_components/modal-delete-item.tsx rename to apps/backoffice/src/app/(protected)/gacha-roll/_components/modal-delete-item.tsx diff --git a/apps/backoffice/src/app/gacha-roll/_components/modal-update-item.tsx b/apps/backoffice/src/app/(protected)/gacha-roll/_components/modal-update-item.tsx similarity index 100% rename from apps/backoffice/src/app/gacha-roll/_components/modal-update-item.tsx rename to apps/backoffice/src/app/(protected)/gacha-roll/_components/modal-update-item.tsx diff --git a/apps/backoffice/src/app/gacha-roll/_hook/use-item.ts b/apps/backoffice/src/app/(protected)/gacha-roll/_hook/use-item.ts similarity index 100% rename from apps/backoffice/src/app/gacha-roll/_hook/use-item.ts rename to apps/backoffice/src/app/(protected)/gacha-roll/_hook/use-item.ts diff --git a/apps/backoffice/src/app/gacha-roll/page.tsx b/apps/backoffice/src/app/(protected)/gacha-roll/page.tsx similarity index 100% rename from apps/backoffice/src/app/gacha-roll/page.tsx rename to apps/backoffice/src/app/(protected)/gacha-roll/page.tsx diff --git a/apps/backoffice/src/app/accounts/layout.tsx b/apps/backoffice/src/app/(protected)/layout.tsx similarity index 100% rename from apps/backoffice/src/app/accounts/layout.tsx rename to apps/backoffice/src/app/(protected)/layout.tsx diff --git a/apps/backoffice/src/app/permissions/_components/modal-add-permission.tsx b/apps/backoffice/src/app/(protected)/permissions/_components/modal-add-permission.tsx similarity index 100% rename from apps/backoffice/src/app/permissions/_components/modal-add-permission.tsx rename to apps/backoffice/src/app/(protected)/permissions/_components/modal-add-permission.tsx diff --git a/apps/backoffice/src/app/permissions/_components/modal-delete-permission.tsx b/apps/backoffice/src/app/(protected)/permissions/_components/modal-delete-permission.tsx similarity index 100% rename from apps/backoffice/src/app/permissions/_components/modal-delete-permission.tsx rename to apps/backoffice/src/app/(protected)/permissions/_components/modal-delete-permission.tsx diff --git a/apps/backoffice/src/app/permissions/_components/modal-update-permission.tsx b/apps/backoffice/src/app/(protected)/permissions/_components/modal-update-permission.tsx similarity index 100% rename from apps/backoffice/src/app/permissions/_components/modal-update-permission.tsx rename to apps/backoffice/src/app/(protected)/permissions/_components/modal-update-permission.tsx diff --git a/apps/backoffice/src/app/permissions/_hook/use-item.ts b/apps/backoffice/src/app/(protected)/permissions/_hook/use-item.ts similarity index 100% rename from apps/backoffice/src/app/permissions/_hook/use-item.ts rename to apps/backoffice/src/app/(protected)/permissions/_hook/use-item.ts diff --git a/apps/backoffice/src/app/permissions/page.tsx b/apps/backoffice/src/app/(protected)/permissions/page.tsx similarity index 100% rename from apps/backoffice/src/app/permissions/page.tsx rename to apps/backoffice/src/app/(protected)/permissions/page.tsx diff --git a/apps/backoffice/src/app/prizes/_components/modal-process-item.tsx b/apps/backoffice/src/app/(protected)/prizes/_components/modal-process-item.tsx similarity index 100% rename from apps/backoffice/src/app/prizes/_components/modal-process-item.tsx rename to apps/backoffice/src/app/(protected)/prizes/_components/modal-process-item.tsx diff --git a/apps/backoffice/src/app/prizes/page.tsx b/apps/backoffice/src/app/(protected)/prizes/page.tsx similarity index 100% rename from apps/backoffice/src/app/prizes/page.tsx rename to apps/backoffice/src/app/(protected)/prizes/page.tsx diff --git a/apps/backoffice/src/app/roles/_components/modal-add-role.tsx b/apps/backoffice/src/app/(protected)/roles/_components/modal-add-role.tsx similarity index 100% rename from apps/backoffice/src/app/roles/_components/modal-add-role.tsx rename to apps/backoffice/src/app/(protected)/roles/_components/modal-add-role.tsx diff --git a/apps/backoffice/src/app/roles/_components/modal-delete-role.tsx b/apps/backoffice/src/app/(protected)/roles/_components/modal-delete-role.tsx similarity index 100% rename from apps/backoffice/src/app/roles/_components/modal-delete-role.tsx rename to apps/backoffice/src/app/(protected)/roles/_components/modal-delete-role.tsx diff --git a/apps/backoffice/src/app/roles/_components/modal-update-role.tsx b/apps/backoffice/src/app/(protected)/roles/_components/modal-update-role.tsx similarity index 100% rename from apps/backoffice/src/app/roles/_components/modal-update-role.tsx rename to apps/backoffice/src/app/(protected)/roles/_components/modal-update-role.tsx diff --git a/apps/backoffice/src/app/roles/_hook/use-item.ts b/apps/backoffice/src/app/(protected)/roles/_hook/use-item.ts similarity index 100% rename from apps/backoffice/src/app/roles/_hook/use-item.ts rename to apps/backoffice/src/app/(protected)/roles/_hook/use-item.ts diff --git a/apps/backoffice/src/app/roles/page.tsx b/apps/backoffice/src/app/(protected)/roles/page.tsx similarity index 100% rename from apps/backoffice/src/app/roles/page.tsx rename to apps/backoffice/src/app/(protected)/roles/page.tsx diff --git a/apps/backoffice/src/app/transactions/_components/modal-validate.tsx b/apps/backoffice/src/app/(protected)/transactions/_components/modal-validate.tsx similarity index 100% rename from apps/backoffice/src/app/transactions/_components/modal-validate.tsx rename to apps/backoffice/src/app/(protected)/transactions/_components/modal-validate.tsx diff --git a/apps/backoffice/src/app/transactions/page.tsx b/apps/backoffice/src/app/(protected)/transactions/page.tsx similarity index 100% rename from apps/backoffice/src/app/transactions/page.tsx rename to apps/backoffice/src/app/(protected)/transactions/page.tsx diff --git a/apps/backoffice/src/app/_hooks/use-login.ts b/apps/backoffice/src/app/(public)/auth/login/_hooks/use-login.ts similarity index 56% rename from apps/backoffice/src/app/_hooks/use-login.ts rename to apps/backoffice/src/app/(public)/auth/login/_hooks/use-login.ts index 3c031d3..e0323ce 100644 --- a/apps/backoffice/src/app/_hooks/use-login.ts +++ b/apps/backoffice/src/app/(public)/auth/login/_hooks/use-login.ts @@ -2,24 +2,19 @@ import { useForm } from 'react-hook-form'; import { authLoginSchema, TLoginRequest, - usePostLogin, } from '@imphnen-frontend-service/service'; import { zodResolver } from '@hookform/resolvers/zod'; -import { toast } from 'sonner'; +import { useSession } from '@imphnen-frontend-service/utils'; export const useLogin = () => { - const postLogin = usePostLogin(); const form = useForm({ resolver: zodResolver(authLoginSchema), mode: 'all', }); - const onSubmit = form.handleSubmit((data) => { - postLogin.mutate(data, { - onSuccess: () => toast.success("Login sukses"), - onError: (error) => toast.error(error.message), - }); - }); + const { signIn } = useSession(); + + const onSubmit = form.handleSubmit((data) => signIn(data)); return { form, diff --git a/apps/backoffice/src/app/page.tsx b/apps/backoffice/src/app/(public)/auth/login/page.tsx similarity index 83% rename from apps/backoffice/src/app/page.tsx rename to apps/backoffice/src/app/(public)/auth/login/page.tsx index 99b0c4e..bb0a020 100644 --- a/apps/backoffice/src/app/page.tsx +++ b/apps/backoffice/src/app/(public)/auth/login/page.tsx @@ -32,7 +32,16 @@ export const Components: FC = (): ReactElement => { size="lg" className="w-full" /> - diff --git a/apps/backoffice/src/app/layout.tsx b/apps/backoffice/src/app/(public)/layout.tsx similarity index 100% rename from apps/backoffice/src/app/layout.tsx rename to apps/backoffice/src/app/(public)/layout.tsx diff --git a/apps/backoffice/src/app/dashboard/layout.tsx b/apps/backoffice/src/app/dashboard/layout.tsx deleted file mode 100644 index a770c99..0000000 --- a/apps/backoffice/src/app/dashboard/layout.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { FC, ReactElement } from 'react'; -import { Outlet } from 'react-router-dom'; -import { BackofficeSidebar } from '@imphnen-frontend-service/ui/organisms'; - -export const AppLayout: FC = (): ReactElement => { - return ( -
-
- -
- -
-
-
- ); -}; - -export default AppLayout; diff --git a/apps/backoffice/src/app/gacha-roll/layout.tsx b/apps/backoffice/src/app/gacha-roll/layout.tsx deleted file mode 100644 index a770c99..0000000 --- a/apps/backoffice/src/app/gacha-roll/layout.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { FC, ReactElement } from 'react'; -import { Outlet } from 'react-router-dom'; -import { BackofficeSidebar } from '@imphnen-frontend-service/ui/organisms'; - -export const AppLayout: FC = (): ReactElement => { - return ( -
-
- -
- -
-
-
- ); -}; - -export default AppLayout; diff --git a/apps/backoffice/src/app/permissions/layout.tsx b/apps/backoffice/src/app/permissions/layout.tsx deleted file mode 100644 index a770c99..0000000 --- a/apps/backoffice/src/app/permissions/layout.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { FC, ReactElement } from 'react'; -import { Outlet } from 'react-router-dom'; -import { BackofficeSidebar } from '@imphnen-frontend-service/ui/organisms'; - -export const AppLayout: FC = (): ReactElement => { - return ( -
-
- -
- -
-
-
- ); -}; - -export default AppLayout; diff --git a/apps/backoffice/src/app/prizes/layout.tsx b/apps/backoffice/src/app/prizes/layout.tsx deleted file mode 100644 index a770c99..0000000 --- a/apps/backoffice/src/app/prizes/layout.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { FC, ReactElement } from 'react'; -import { Outlet } from 'react-router-dom'; -import { BackofficeSidebar } from '@imphnen-frontend-service/ui/organisms'; - -export const AppLayout: FC = (): ReactElement => { - return ( -
-
- -
- -
-
-
- ); -}; - -export default AppLayout; diff --git a/apps/backoffice/src/app/roles/layout.tsx b/apps/backoffice/src/app/roles/layout.tsx deleted file mode 100644 index a770c99..0000000 --- a/apps/backoffice/src/app/roles/layout.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { FC, ReactElement } from 'react'; -import { Outlet } from 'react-router-dom'; -import { BackofficeSidebar } from '@imphnen-frontend-service/ui/organisms'; - -export const AppLayout: FC = (): ReactElement => { - return ( -
-
- -
- -
-
-
- ); -}; - -export default AppLayout; diff --git a/apps/backoffice/src/app/transactions/layout.tsx b/apps/backoffice/src/app/transactions/layout.tsx deleted file mode 100644 index a770c99..0000000 --- a/apps/backoffice/src/app/transactions/layout.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { FC, ReactElement } from 'react'; -import { Outlet } from 'react-router-dom'; -import { BackofficeSidebar } from '@imphnen-frontend-service/ui/organisms'; - -export const AppLayout: FC = (): ReactElement => { - return ( -
-
- -
- -
-
-
- ); -}; - -export default AppLayout; diff --git a/apps/backoffice/src/middleware.ts b/apps/backoffice/src/middleware.ts index f32b107..f47b40b 100644 --- a/apps/backoffice/src/middleware.ts +++ b/apps/backoffice/src/middleware.ts @@ -1,11 +1,108 @@ -import { LoaderFunctionArgs } from 'react-router-dom'; +import { + PERMISSIONS, + SessionToken, + SessionUser, +} from '@imphnen-frontend-service/utils'; +import { LoaderFunctionArgs, redirect } from 'react-router'; + +const mappingPublicRoutes = [ + '/auth/login', + '/auth/forgot', + '/auth/new-password', +]; + +const mappingRoutePermissions = [ + { + path: '/dashboard', + permissions: [], + }, + { + path: '/users', + permissions: [PERMISSIONS.USERS.READ_LIST], + }, + { + path: '/users/create', + permissions: [PERMISSIONS.USERS.CREATE], + }, + { + path: '/users/update', + permissions: [PERMISSIONS.USERS.UPDATE], + }, + { + path: '/users/detail', + permissions: [PERMISSIONS.USERS.READ_DETAIL], + }, + { + path: '/roles', + permissions: [PERMISSIONS.ROLES.READ_LIST], + }, + { + path: '/roles/create', + permissions: [PERMISSIONS.ROLES.CREATE], + }, + { + path: '/roles/update', + permissions: [PERMISSIONS.ROLES.UPDATE], + }, + { + path: '/roles/detail', + permissions: [PERMISSIONS.ROLES.READ_DETAIL], + }, + { + path: '/permissions', + permissions: [PERMISSIONS.PERMISSIONS.READ_LIST], + }, + { + path: '/permissions/create', + permissions: [PERMISSIONS.PERMISSIONS.CREATE], + }, + { + path: '/permissions/update', + permissions: [PERMISSIONS.PERMISSIONS.UPDATE], + }, + { + path: '/permissions/detail', + permissions: [PERMISSIONS.PERMISSIONS.READ_DETAIL], + }, +]; + +//TODO : Fix this later +// const redirectToFirstAccessibleRoute = (userPermissions: string[]) => { +// const fallback = mappingRoutePermissions.find((route) => +// route.permissions.some((perm) => userPermissions.includes(perm)) +// ); +// return redirect(fallback?.path ?? '/auth/login'); +// }; export const middleware = async ({ request }: LoaderFunctionArgs) => { const url = new URL(request.url); - const pathname = url.pathname; + const session = SessionUser.get(); + const session_token = SessionToken.get(); + const token = session_token?.token?.access_token; + const userPermissions = + session?.role?.permissions?.map?.((perm) => perm?.name) ?? []; - if (pathname) return null; + if (mappingPublicRoutes.includes(pathname)) { + if (token) return redirect('/dashboard'); + return null; + } + + if (!session) return redirect('/auth/login'); + + const matchedRoute = mappingRoutePermissions.find( + (route) => route.path === pathname + ); + + if (matchedRoute) { + const hasPermission = + !matchedRoute.permissions || + matchedRoute.permissions.some((perm) => userPermissions.includes(perm)); + + if (!hasPermission) { + return '/dashboard'; + } + } return null; }; diff --git a/apps/dimentorin/.env.example b/apps/dimentorin/.env.example new file mode 100644 index 0000000..3452b5c --- /dev/null +++ b/apps/dimentorin/.env.example @@ -0,0 +1 @@ +VITE_API_URL= \ No newline at end of file diff --git a/apps/dimentorin/src/app/_hooks/use-login.ts b/apps/dimentorin/src/app/_hooks/use-login.ts new file mode 100644 index 0000000..e0323ce --- /dev/null +++ b/apps/dimentorin/src/app/_hooks/use-login.ts @@ -0,0 +1,23 @@ +import { useForm } from 'react-hook-form'; +import { + authLoginSchema, + TLoginRequest, +} from '@imphnen-frontend-service/service'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useSession } from '@imphnen-frontend-service/utils'; + +export const useLogin = () => { + const form = useForm({ + resolver: zodResolver(authLoginSchema), + mode: 'all', + }); + + const { signIn } = useSession(); + + const onSubmit = form.handleSubmit((data) => signIn(data)); + + return { + form, + onSubmit, + }; +}; diff --git a/apps/dimentorin/src/app/auth/login/page.tsx b/apps/dimentorin/src/app/auth/login/page.tsx index 95440b3..3859aba 100644 --- a/apps/dimentorin/src/app/auth/login/page.tsx +++ b/apps/dimentorin/src/app/auth/login/page.tsx @@ -1,46 +1,50 @@ import { FC, ReactElement } from 'react'; -import { LoginBanner } from '@imphnen-frontend-service/ui/organisms'; +import { ControlledInputField, LoginBanner } from '@imphnen-frontend-service/ui/organisms'; import { Button } from '@imphnen-frontend-service/ui/atoms'; -import { useSearchParams } from 'react-router-dom'; -import { InputField } from '@imphnen-frontend-service/ui/molecules'; +import { useLogin } from '../../_hooks/use-login'; +import { ArrowLeftOutlined } from '@ant-design/icons'; export const Components: FC = (): ReactElement => { - const [searchParams] = useSearchParams(); - const error = searchParams.get('error'); // Ambil nilai ?error= + const { form, onSubmit } = useLogin(); return (
-
+
-
-
+
+

Hallo Minna-san

-
+
Welcome to Dimentorin by IMPHNEN
- - - - +
+ + + + + +
diff --git a/apps/dimentorin/src/app/auth/register/page.tsx b/apps/dimentorin/src/app/auth/register/page.tsx index a3933ce..79bcadf 100644 --- a/apps/dimentorin/src/app/auth/register/page.tsx +++ b/apps/dimentorin/src/app/auth/register/page.tsx @@ -37,7 +37,6 @@ export const Components: FC = (): ReactElement => {

OTP Code

+ diff --git a/apps/dimentorin/src/middleware.ts b/apps/dimentorin/src/middleware.ts index f32b107..3d4d24a 100644 --- a/apps/dimentorin/src/middleware.ts +++ b/apps/dimentorin/src/middleware.ts @@ -1,11 +1,109 @@ -import { LoaderFunctionArgs } from 'react-router-dom'; +import { + PERMISSIONS, + SessionToken, + SessionUser, +} from '@imphnen-frontend-service/utils'; +import { LoaderFunctionArgs, redirect } from 'react-router'; + +const mappingPublicRoutes = [ + '/', + '/auth/login', + '/auth/forgot', + '/auth/new-password', +]; + +const mappingRoutePermissions = [ + { + path: '/dashboard', + permissions: [], + }, + { + path: '/users', + permissions: [PERMISSIONS.USERS.READ_LIST], + }, + { + path: '/users/create', + permissions: [PERMISSIONS.USERS.CREATE], + }, + { + path: '/users/update', + permissions: [PERMISSIONS.USERS.UPDATE], + }, + { + path: '/users/detail', + permissions: [PERMISSIONS.USERS.READ_DETAIL], + }, + { + path: '/roles', + permissions: [PERMISSIONS.ROLES.READ_LIST], + }, + { + path: '/roles/create', + permissions: [PERMISSIONS.ROLES.CREATE], + }, + { + path: '/roles/update', + permissions: [PERMISSIONS.ROLES.UPDATE], + }, + { + path: '/roles/detail', + permissions: [PERMISSIONS.ROLES.READ_DETAIL], + }, + { + path: '/permissions', + permissions: [PERMISSIONS.PERMISSIONS.READ_LIST], + }, + { + path: '/permissions/create', + permissions: [PERMISSIONS.PERMISSIONS.CREATE], + }, + { + path: '/permissions/update', + permissions: [PERMISSIONS.PERMISSIONS.UPDATE], + }, + { + path: '/permissions/detail', + permissions: [PERMISSIONS.PERMISSIONS.READ_DETAIL], + }, +]; + +//TODO : Fix this later +// const redirectToFirstAccessibleRoute = (userPermissions: string[]) => { +// const fallback = mappingRoutePermissions.find((route) => +// route.permissions.some((perm) => userPermissions.includes(perm)) +// ); +// return redirect(fallback?.path ?? '/auth/login'); +// }; export const middleware = async ({ request }: LoaderFunctionArgs) => { const url = new URL(request.url); - const pathname = url.pathname; + const session = SessionUser.get(); + const session_token = SessionToken.get(); + const token = session_token?.token?.access_token; + const userPermissions = + session?.role?.permissions?.map?.((perm) => perm?.name) ?? []; - if (pathname) return null; + if (mappingPublicRoutes.includes(pathname)) { + if (token) return redirect('/dashboard'); + return null; + } + + if (!session) return redirect('/auth/login'); + + const matchedRoute = mappingRoutePermissions.find( + (route) => route.path === pathname + ); + + if (matchedRoute) { + const hasPermission = + !matchedRoute.permissions || + matchedRoute.permissions.some((perm) => userPermissions.includes(perm)); + + if (!hasPermission) { + return '/dashboard'; + } + } return null; }; diff --git a/apps/gacha/.env.example b/apps/gacha/.env.example new file mode 100644 index 0000000..292a14c --- /dev/null +++ b/apps/gacha/.env.example @@ -0,0 +1 @@ +VITE_API_URL= diff --git a/apps/gacha/src/app/_hooks/use-login.ts b/apps/gacha/src/app/_hooks/use-login.ts index 272b2ba..e0323ce 100644 --- a/apps/gacha/src/app/_hooks/use-login.ts +++ b/apps/gacha/src/app/_hooks/use-login.ts @@ -2,22 +2,19 @@ import { useForm } from 'react-hook-form'; import { authLoginSchema, TLoginRequest, - usePostLogin, } from '@imphnen-frontend-service/service'; import { zodResolver } from '@hookform/resolvers/zod'; +import { useSession } from '@imphnen-frontend-service/utils'; export const useLogin = () => { - const postLogin = usePostLogin(); const form = useForm({ resolver: zodResolver(authLoginSchema), mode: 'all', }); - const onSubmit = form.handleSubmit((data) => { - postLogin.mutate(data, { - onSuccess: () => console.log('Success Login'), - }); - }); + const { signIn } = useSession(); + + const onSubmit = form.handleSubmit((data) => signIn(data)); return { form, diff --git a/apps/gacha/src/app/_hooks/use-register.ts b/apps/gacha/src/app/_hooks/use-register.ts index 5817c30..d7c5531 100644 --- a/apps/gacha/src/app/_hooks/use-register.ts +++ b/apps/gacha/src/app/_hooks/use-register.ts @@ -1,14 +1,11 @@ import { useForm } from 'react-hook-form'; import { authRegisterSchema, - stepOneRegisterSchema, TRegisterRequest, usePostRegister, } from '@imphnen-frontend-service/service'; -import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; import { toast } from 'sonner'; -import { useEffect, useState } from 'react'; export const useRegister = () => { const postRegister = usePostRegister(); @@ -21,31 +18,12 @@ export const useRegister = () => { password: '', confirm_password: '', phone_number: '', - referral_code: '', - referred_by: '', - student_type: '', }, }); - const [stepValid, setStepValid] = useState(false); - - useEffect(() => { - const subscription = form.watch(() => { - const { fullname, email, password, confirm_password } = form.getValues(); - const valid = stepOneRegisterSchema.safeParse({ - fullname, - email, - password, - confirm_password, - }).success; - setStepValid(valid); - }); - return () => subscription.unsubscribe(); - }, [form]); - const onSubmit = form.handleSubmit((data) => { postRegister.mutate(data, { - onSuccess: (data) => toast.success(data.message), + onSuccess: () => toast.success('Registrasi sukses'), onError: (error) => toast.error(error.message), }); }); @@ -53,6 +31,5 @@ export const useRegister = () => { return { form, onSubmit, - isStepOneValid: stepValid, }; }; diff --git a/apps/gacha/src/middleware.ts b/apps/gacha/src/middleware.ts index f32b107..3d4d24a 100644 --- a/apps/gacha/src/middleware.ts +++ b/apps/gacha/src/middleware.ts @@ -1,11 +1,109 @@ -import { LoaderFunctionArgs } from 'react-router-dom'; +import { + PERMISSIONS, + SessionToken, + SessionUser, +} from '@imphnen-frontend-service/utils'; +import { LoaderFunctionArgs, redirect } from 'react-router'; + +const mappingPublicRoutes = [ + '/', + '/auth/login', + '/auth/forgot', + '/auth/new-password', +]; + +const mappingRoutePermissions = [ + { + path: '/dashboard', + permissions: [], + }, + { + path: '/users', + permissions: [PERMISSIONS.USERS.READ_LIST], + }, + { + path: '/users/create', + permissions: [PERMISSIONS.USERS.CREATE], + }, + { + path: '/users/update', + permissions: [PERMISSIONS.USERS.UPDATE], + }, + { + path: '/users/detail', + permissions: [PERMISSIONS.USERS.READ_DETAIL], + }, + { + path: '/roles', + permissions: [PERMISSIONS.ROLES.READ_LIST], + }, + { + path: '/roles/create', + permissions: [PERMISSIONS.ROLES.CREATE], + }, + { + path: '/roles/update', + permissions: [PERMISSIONS.ROLES.UPDATE], + }, + { + path: '/roles/detail', + permissions: [PERMISSIONS.ROLES.READ_DETAIL], + }, + { + path: '/permissions', + permissions: [PERMISSIONS.PERMISSIONS.READ_LIST], + }, + { + path: '/permissions/create', + permissions: [PERMISSIONS.PERMISSIONS.CREATE], + }, + { + path: '/permissions/update', + permissions: [PERMISSIONS.PERMISSIONS.UPDATE], + }, + { + path: '/permissions/detail', + permissions: [PERMISSIONS.PERMISSIONS.READ_DETAIL], + }, +]; + +//TODO : Fix this later +// const redirectToFirstAccessibleRoute = (userPermissions: string[]) => { +// const fallback = mappingRoutePermissions.find((route) => +// route.permissions.some((perm) => userPermissions.includes(perm)) +// ); +// return redirect(fallback?.path ?? '/auth/login'); +// }; export const middleware = async ({ request }: LoaderFunctionArgs) => { const url = new URL(request.url); - const pathname = url.pathname; + const session = SessionUser.get(); + const session_token = SessionToken.get(); + const token = session_token?.token?.access_token; + const userPermissions = + session?.role?.permissions?.map?.((perm) => perm?.name) ?? []; - if (pathname) return null; + if (mappingPublicRoutes.includes(pathname)) { + if (token) return redirect('/dashboard'); + return null; + } + + if (!session) return redirect('/auth/login'); + + const matchedRoute = mappingRoutePermissions.find( + (route) => route.path === pathname + ); + + if (matchedRoute) { + const hasPermission = + !matchedRoute.permissions || + matchedRoute.permissions.some((perm) => userPermissions.includes(perm)); + + if (!hasPermission) { + return '/dashboard'; + } + } return null; }; diff --git a/apps/landing/.env.example b/apps/landing/.env.example new file mode 100644 index 0000000..cd9c155 --- /dev/null +++ b/apps/landing/.env.example @@ -0,0 +1,6 @@ +CMS_SECRET=your-very-long-secret-key +CMS_POSTGRES_URL=postgres://user:password@host:port/database +CMS_STORAGE_ENDPOINT=https://your-s3-endpoint.com +CMS_STORAGE_ACCESS_KEY_ID=your-access-key-id +CMS_STORAGE_SECRET_ACCESS_KEY=your-secret-access-key +CMS_STORAGE_REGION=ap-southeast-1 diff --git a/apps/landing/.npmrc b/apps/landing/.npmrc deleted file mode 100644 index cf04042..0000000 --- a/apps/landing/.npmrc +++ /dev/null @@ -1,2 +0,0 @@ -shamefully-hoist=true -strict-peer-dependencies=false diff --git a/apps/landing/.swcrc b/apps/landing/.swcrc new file mode 100644 index 0000000..ac23c93 --- /dev/null +++ b/apps/landing/.swcrc @@ -0,0 +1,30 @@ +{ + "jsc": { + "target": "es2017", + "parser": { + "syntax": "typescript", + "decorators": true, + "dynamicImport": true + }, + "transform": { + "decoratorMetadata": true, + "legacyDecorator": true + }, + "keepClassNames": true, + "externalHelpers": true, + "loose": true + }, + "module": { + "type": "commonjs" + }, + "sourceMaps": true, + "exclude": [ + "jest.config.ts", + ".*\\.spec.tsx?$", + ".*\\.test.tsx?$", + "./src/jest-setup.ts$", + "./**/jest-setup.ts$", + ".*.js$", + ".*.d.ts$" + ] +} diff --git a/apps/landing/eslint.config.mjs b/apps/landing/eslint.config.mjs index 05e4cb7..0551fe2 100644 --- a/apps/landing/eslint.config.mjs +++ b/apps/landing/eslint.config.mjs @@ -2,6 +2,8 @@ import { FlatCompat } from '@eslint/eslintrc'; import { dirname } from 'path'; import { fileURLToPath } from 'url'; import js from '@eslint/js'; +import { fixupConfigRules } from '@eslint/compat'; +import nx from '@nx/eslint-plugin'; import baseConfig from '../../eslint.config.mjs'; const compat = new FlatCompat({ baseDirectory: dirname(fileURLToPath(import.meta.url)), @@ -9,22 +11,11 @@ const compat = new FlatCompat({ }); export default [ + ...fixupConfigRules(compat.extends('next')), + ...fixupConfigRules(compat.extends('next/core-web-vitals')), ...baseConfig, + ...nx.configs['flat/react-typescript'], { - files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.vue'], - // Override or add rules here - rules: {}, - }, - ...compat.extends('@nuxt/eslint-config'), - { - files: ['**/*.vue'], - languageOptions: { - parserOptions: { - parser: await import('@typescript-eslint/parser'), - }, - }, - }, - { - ignores: ['.nuxt/**', '.output/**', 'node_modules'], + ignores: ['.next/**/*'], }, ]; diff --git a/apps/landing/index.d.ts b/apps/landing/index.d.ts new file mode 100644 index 0000000..7ba08fa --- /dev/null +++ b/apps/landing/index.d.ts @@ -0,0 +1,6 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +declare module '*.svg' { + const content: any; + export const ReactComponent: any; + export default content; +} diff --git a/apps/landing/next-env.d.ts b/apps/landing/next-env.d.ts new file mode 100644 index 0000000..1b3be08 --- /dev/null +++ b/apps/landing/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/landing/next.config.js b/apps/landing/next.config.js new file mode 100644 index 0000000..e6eccb9 --- /dev/null +++ b/apps/landing/next.config.js @@ -0,0 +1,15 @@ +const { composePlugins, withNx } = require('@nx/next'); +const { withPayload } = require('@payloadcms/next/withPayload'); + +/** @type {import('@nx/next/plugins/with-nx').WithNxOptions} */ +const nextConfig = { + nx: { svgr: false }, + sassOptions: { + quietDeps: true, + logger: { + warn: () => {}, + }, + }, +}; + +module.exports = composePlugins(withNx, withPayload)(nextConfig); diff --git a/apps/landing/nuxt.config.ts b/apps/landing/nuxt.config.ts deleted file mode 100644 index 6464ecd..0000000 --- a/apps/landing/nuxt.config.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; -import { defineNuxtConfig } from 'nuxt/config'; - -// https://nuxt.com/docs/api/configuration/nuxt-config -export default defineNuxtConfig({ - workspaceDir: '../../', - srcDir: 'src', - devtools: { enabled: true }, - devServer: { - host: 'localhost', - port: 3000, - }, - typescript: { - typeCheck: true, - tsConfig: { - extends: '../tsconfig.app.json', // Nuxt copies this string as-is to the `./.nuxt/tsconfig.json`, therefore it needs to be relative to that directory - }, - }, - imports: { - autoImport: true, - }, - vite: { - plugins: [nxViteTsPaths()], - }, -}); diff --git a/apps/landing/postcss.config.mjs b/apps/landing/postcss.config.mjs new file mode 100644 index 0000000..297374d --- /dev/null +++ b/apps/landing/postcss.config.mjs @@ -0,0 +1,7 @@ +const config = { + plugins: { + '@tailwindcss/postcss': {}, + }, +}; + +export default config; diff --git a/apps/landing/project.json b/apps/landing/project.json index 97f5751..88a66fe 100644 --- a/apps/landing/project.json +++ b/apps/landing/project.json @@ -1,8 +1,24 @@ { "name": "landing", "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "apps/landing", "projectType": "application", - "sourceRoot": "apps/landing/src", + "tags": [], "// targets": "to see all targets run: nx show project landing --web", - "targets": {} + "targets": { + "serve": { + "executor": "@nx/next:server", + "options": { + "buildTarget": "landing:build", + "dev": true + } + }, + "build": { + "executor": "@nx/next:build", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/apps/landing" + } + } + } } diff --git a/apps/landing/src/public/.gitkeep b/apps/landing/public/.gitkeep similarity index 100% rename from apps/landing/src/public/.gitkeep rename to apps/landing/public/.gitkeep diff --git a/apps/landing/public/Anita Ratna.jpg b/apps/landing/public/Anita Ratna.jpg new file mode 100644 index 0000000..bb24e3c Binary files /dev/null and b/apps/landing/public/Anita Ratna.jpg differ diff --git a/apps/landing/public/Budi Santoso.jpg b/apps/landing/public/Budi Santoso.jpg new file mode 100644 index 0000000..6824edf Binary files /dev/null and b/apps/landing/public/Budi Santoso.jpg differ diff --git a/apps/landing/public/Dedi Permana.jpg b/apps/landing/public/Dedi Permana.jpg new file mode 100644 index 0000000..2c58733 Binary files /dev/null and b/apps/landing/public/Dedi Permana.jpg differ diff --git a/apps/landing/public/logo.png b/apps/landing/public/logo.png new file mode 100644 index 0000000..d6e435e Binary files /dev/null and b/apps/landing/public/logo.png differ diff --git a/apps/landing/src/app.vue b/apps/landing/src/app.vue deleted file mode 100644 index d96c753..0000000 --- a/apps/landing/src/app.vue +++ /dev/null @@ -1,44 +0,0 @@ - - - - - diff --git a/apps/landing/src/app/(frontend)/_components/call-to-action.tsx b/apps/landing/src/app/(frontend)/_components/call-to-action.tsx new file mode 100644 index 0000000..a6ee70c --- /dev/null +++ b/apps/landing/src/app/(frontend)/_components/call-to-action.tsx @@ -0,0 +1,78 @@ +'use client'; + +import { Button } from '@imphnen-frontend-service/shadcn-ui/atoms'; +import { CallToActionSection } from 'apps/landing/src/payload-types'; +import { motion, useInView } from 'framer-motion'; +import { useRef } from 'react'; + +export function CallToAction(props: CallToActionSection) { + const { + title, + highlightedTitle, + subtitle, + primaryButtonLabel, + primaryButtonLink, + secondaryButtonLabel, + secondaryButtonLink, + } = props; + + const ref = useRef(null); + const isInView = useInView(ref, { once: true, amount: 0.2 }); + + return ( +
+ {/* Background Elements */} +
+
+
+
+ +
+ +
+
+ +
+

+ {title}{' '} + + {highlightedTitle} + +

+

+ {subtitle} +

+ +
+ + +
+
+ + {/* Decorative Elements */} +
+
+
+ +
+
+ ); +} diff --git a/apps/landing/src/app/(frontend)/_components/community.tsx b/apps/landing/src/app/(frontend)/_components/community.tsx new file mode 100644 index 0000000..a0d72c7 --- /dev/null +++ b/apps/landing/src/app/(frontend)/_components/community.tsx @@ -0,0 +1,110 @@ +'use client'; + +import { Icon } from '@iconify/react'; +import { Button } from '@imphnen-frontend-service/shadcn-ui/atoms'; +import { CommunitiesSection } from 'apps/landing/src/payload-types'; +import { motion, useInView } from 'framer-motion'; +import { useRef } from 'react'; + +export function Community(props: CommunitiesSection) { + const { items, stats } = props; + const ref = useRef(null); + const isInView = useInView(ref, { once: true, amount: 0.2 }); + + const containerVariants = { + hidden: { opacity: 0 }, + visible: { + opacity: 1, + transition: { + staggerChildren: 0.1, + }, + }, + }; + + const itemVariants = { + hidden: { y: 20, opacity: 0 }, + visible: { + y: 0, + opacity: 1, + transition: { duration: 0.5 }, + }, + }; + + return ( +
+
+
+
+
+
+ +

+ Komunitas{' '} + + Kami + +

+

+ Bergabunglah dengan ribuan programmer Indonesia yang saling membantu + dan berbagi pengalaman. +

+
+ + {items?.map((c, i) => ( + +
+
+
+ +
+

{c.title}

+

{c.description}

+ +
+
+ + ))} + + + {stats?.map((s, i) => ( +
+
+ {s.value} +
+
{s.label}
+
+ ))} +
+
+
+ ); +} diff --git a/apps/landing/src/app/(frontend)/_components/features.tsx b/apps/landing/src/app/(frontend)/_components/features.tsx new file mode 100644 index 0000000..35a970a --- /dev/null +++ b/apps/landing/src/app/(frontend)/_components/features.tsx @@ -0,0 +1,95 @@ +'use client'; + +import { Icon } from '@iconify/react'; +import { FeaturesSection } from 'apps/landing/src/payload-types'; +import { motion, useInView } from 'framer-motion'; +import { useRef } from 'react'; + +export function Features(props: FeaturesSection) { + const { heading, subheading, features } = props; + + const ref = useRef(null); + const isInView = useInView(ref, { once: true, amount: 0.2 }); + + const containerVariants = { + hidden: { opacity: 0 }, + visible: { + opacity: 1, + transition: { + staggerChildren: 0.1, + }, + }, + }; + + const itemVariants = { + hidden: { y: 20, opacity: 0 }, + visible: { + y: 0, + opacity: 1, + transition: { duration: 0.5 }, + }, + }; + + return ( +
+
+
+
+
+ +
+
+ +
+ Fitur Unggulan +
+

+ {heading} +

+

+ {subheading} +

+
+
+ + + {features?.map((feature, index) => ( + +
+ +
+
+ +
+

{feature.title}

+

{feature.description}

+
+ +
+ + ))} + +
+
+ ); +} diff --git a/apps/landing/src/app/(frontend)/_components/footer.tsx b/apps/landing/src/app/(frontend)/_components/footer.tsx new file mode 100644 index 0000000..1711850 --- /dev/null +++ b/apps/landing/src/app/(frontend)/_components/footer.tsx @@ -0,0 +1,208 @@ +import Link from 'next/link'; + +export default function Footer() { + return ( +
+
+
+
+
+ + IMPHNEN + +
+

+ Ingin Menjadi Programmer Handal merupakan komunitas Programmer + Indonesia. +

+
+ + + + + + + + + + + + + + + + + + + + +
+
+
+

Tautan Cepat

+
    +
  • + + Fitur + +
  • +
  • + + Komunitas + +
  • +
  • + + Sumber Belajar + +
  • +
  • + + Testimoni + +
  • +
+
+
+

Sumber Belajar

+
    +
  • + + Video Tutorial + +
  • +
  • + + Artikel + +
  • +
  • + + Tantangan Koding + +
  • +
  • + + Sharing Session + +
  • +
+
+
+

Bahasa Pemrograman

+
+ + PHP + + + JavaScript + + + Python + + + C# + + + Java + + + Go + + + Rust + + + HTML + +
+ +
+

Newsletter

+

+ Dapatkan update terbaru dari kami +

+
+ + +
+
+
+
+
+

+ © {new Date().getFullYear()} IMPHNEN - Ingin Menjadi Programmer + Handal, Namun Enggan Ngoding. All rights reserved. +

+
+
+
+ ); +} diff --git a/apps/landing/src/app/(frontend)/_components/header.tsx b/apps/landing/src/app/(frontend)/_components/header.tsx new file mode 100644 index 0000000..b27daa0 --- /dev/null +++ b/apps/landing/src/app/(frontend)/_components/header.tsx @@ -0,0 +1,159 @@ +'use client'; + +import { + Button, + MenuIcon, + XIcon, +} from '@imphnen-frontend-service/shadcn-ui/atoms'; +import { cn } from '@imphnen-frontend-service/utils'; +import Image from 'next/image'; +import Link from 'next/link'; +import { useEffect, useState } from 'react'; +import { SimpleThemeToggle } from './simple-theme-toggle'; + +export function Header() { + const [isScrolled, setIsScrolled] = useState(false); + const [mobileMenuOpen, setMobileMenuOpen] = useState(false); + + useEffect(() => { + const handleScroll = () => { + setIsScrolled(window.scrollY > 10); + }; + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + return ( +
+
+
+
+ + IMPHNEN + +
+
+ + {/* Desktop Navigation */} + + +
+ + + + + {/* Mobile Menu Button */} + +
+
+ + {/* Mobile Menu */} + {mobileMenuOpen && ( +
+ )} +
+ ); +} diff --git a/apps/landing/src/app/(frontend)/_components/hero.tsx b/apps/landing/src/app/(frontend)/_components/hero.tsx new file mode 100644 index 0000000..f88594e --- /dev/null +++ b/apps/landing/src/app/(frontend)/_components/hero.tsx @@ -0,0 +1,154 @@ +'use client'; + +import { + Button, + CodeIcon, + SparklesIcon, + UsersIcon, +} from '@imphnen-frontend-service/shadcn-ui/atoms'; +import { formatCMSImageDataToMedia } from 'apps/landing/src/lib/format'; +import { HeroSection } from 'apps/landing/src/payload-types'; +import { motion } from 'framer-motion'; +import Image from 'next/image'; +import { Fragment, useEffect, useState } from 'react'; + +export function Hero(props: HeroSection) { + const { + badgeText, + buttons, + description, + highlight, + title, + stats, + heroImage, + } = props; + + const [scrollY, setScrollY] = useState(0); + + useEffect(() => { + const handleScroll = () => { + setScrollY(window.scrollY); + }; + + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + return ( +
+
+
+
+
+
+
+ +
+
+ +
+ + {badgeText} +
+ +
+

+ {title}
+ + {highlight} + +

+

+ {description} +

+
+ +
+ + +
+ +
+ {stats?.map(({ value, label }, i) => ( + +
+
+ {value} +
+
{label}
+
+ {i < stats.length - 1 && ( +
+ )} + + ))} +
+ + + +
+
+ +
+
+ +
+
+
+ {(() => { + const media = formatCMSImageDataToMedia(heroImage); + if (!media) return null; + return ( + {media.alt} + ); + })()} +
+
+ +
+
+
+ ); +} diff --git a/apps/landing/src/app/(frontend)/_components/learning-resources.tsx b/apps/landing/src/app/(frontend)/_components/learning-resources.tsx new file mode 100644 index 0000000..8f7b2b1 --- /dev/null +++ b/apps/landing/src/app/(frontend)/_components/learning-resources.tsx @@ -0,0 +1,144 @@ +'use client'; + +import { Icon } from '@iconify/react'; +import { Button } from '@imphnen-frontend-service/shadcn-ui/atoms'; +import { LearningResourcesSection } from 'apps/landing/src/payload-types'; +import { motion, useInView } from 'framer-motion'; +import { useRef } from 'react'; + +export function LearningResources(props: LearningResourcesSection) { + const { resources, featured, title, description } = props; + + const ref = useRef(null); + const isInView = useInView(ref, { once: true, amount: 0.2 }); + + const containerVariants = { + hidden: { opacity: 0 }, + visible: { + opacity: 1, + transition: { + staggerChildren: 0.1, + }, + }, + }; + + const itemVariants = { + hidden: { y: 20, opacity: 0 }, + visible: { + y: 0, + opacity: 1, + transition: { duration: 0.5 }, + }, + }; + + return ( +
+
+
+
+
+
+
+
+ +

+ {title} +

+

+ {description} +

+
+
+ + {resources.map((r, i) => ( + +
+
+
+ +
+

{r.title}

+

{r.description}

+ + +
+
+ + ))} + + +
+
+
+ {featured.label} +
+

+ {featured.title} +

+

+ {featured.description} +

+
+ + +
+
+
+
+
+
+
+
+
+
+
+ +
+
+ ); +} diff --git a/apps/landing/src/app/(frontend)/_components/simple-theme-toggle.tsx b/apps/landing/src/app/(frontend)/_components/simple-theme-toggle.tsx new file mode 100644 index 0000000..a9fc723 --- /dev/null +++ b/apps/landing/src/app/(frontend)/_components/simple-theme-toggle.tsx @@ -0,0 +1,42 @@ +'use client'; + +import { + Button, + MoonIcon, + SunIcon, +} from '@imphnen-frontend-service/shadcn-ui/atoms'; +import { useTheme } from 'next-themes'; +import { useEffect, useState } from 'react'; + +export function SimpleThemeToggle() { + const { theme, setTheme } = useTheme(); + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + }, []); + + if (!mounted) { + return null; + } + + const toggleTheme = () => { + setTheme(theme === 'dark' ? 'light' : 'dark'); + }; + + return ( + + ); +} diff --git a/apps/landing/src/app/(frontend)/_components/testimonials.tsx b/apps/landing/src/app/(frontend)/_components/testimonials.tsx new file mode 100644 index 0000000..36ee5b5 --- /dev/null +++ b/apps/landing/src/app/(frontend)/_components/testimonials.tsx @@ -0,0 +1,137 @@ +'use client'; + +import { QuoteIcon } from '@imphnen-frontend-service/shadcn-ui/atoms'; +import { formatCMSImageDataToMedia } from 'apps/landing/src/lib/format'; +import { TestimonialsSection } from 'apps/landing/src/payload-types'; +import { motion, useInView } from 'framer-motion'; +import Image from 'next/image'; +import { useRef } from 'react'; + +export function Testimonials(props: TestimonialsSection) { + const { title, subtitle, items, stats, joinTitle, joinText } = props; + + const ref = useRef(null); + const isInView = useInView(ref, { once: true, amount: 0.2 }); + + const containerVariants = { + hidden: { opacity: 0 }, + visible: { opacity: 1, transition: { staggerChildren: 0.1 } }, + }; + + const itemVariants = { + hidden: { y: 20, opacity: 0 }, + visible: { y: 0, opacity: 1, transition: { duration: 0.5 } }, + }; + + return ( +
+
+
+
+
+ +
+ +

+ {title.split(' ').map((word, i) => + i === title.split(' ').length - 1 ? ( + + {word} + + ) : ( + {word} + ) + )} +

+

+ {subtitle} +

+
+ + + {items?.map((t, idx) => ( + +
+ +
+
+

+ “{t.quote}” +

+
+
+ {(() => { + const media = formatCMSImageDataToMedia(t.avatar); + if (!media) return null; + return ( + {media.alt} + ); + })()} +
+
+

{t.name}

+

{t.role}

+
+
+
+
+ + ))} + + + +
+
+

+ {joinTitle} +

+

{joinText}

+
+
+ {stats?.map((s, idx) => ( +
+
+ {s.value} +
+
{s.label}
+
+ ))} +
+
+
+
+
+ ); +} diff --git a/apps/landing/src/app/(frontend)/_components/theme-provider.tsx b/apps/landing/src/app/(frontend)/_components/theme-provider.tsx new file mode 100644 index 0000000..4db6c3c --- /dev/null +++ b/apps/landing/src/app/(frontend)/_components/theme-provider.tsx @@ -0,0 +1,8 @@ +'use client'; + +import type { ThemeProviderProps } from 'next-themes'; +import { ThemeProvider as NextThemesProvider } from 'next-themes'; + +export function ThemeProvider({ children, ...props }: ThemeProviderProps) { + return {children}; +} diff --git a/apps/landing/src/app/(frontend)/layout.tsx b/apps/landing/src/app/(frontend)/layout.tsx new file mode 100644 index 0000000..8cbe8a6 --- /dev/null +++ b/apps/landing/src/app/(frontend)/layout.tsx @@ -0,0 +1,38 @@ +import { type Metadata } from 'next'; +import { Inter } from 'next/font/google'; +import '../../styles/globals.css'; +import Footer from './_components/footer'; +import { Header } from './_components/header'; +import { ThemeProvider } from './_components/theme-provider'; + +const inter = Inter({ subsets: ['latin'] }); + +export const metadata: Metadata = { + title: 'IMPHNEN - Ingin Menjadi Programmer Handal?', + description: 'Komunitas belajar programming untuk semua level', +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + +
+
+
{children}
+
+
+
+ + + ); +} diff --git a/apps/landing/src/app/(frontend)/page.tsx b/apps/landing/src/app/(frontend)/page.tsx new file mode 100644 index 0000000..e50e601 --- /dev/null +++ b/apps/landing/src/app/(frontend)/page.tsx @@ -0,0 +1,43 @@ +import { getGlobalsCallToActionSection } from '../../services/get-globals-call-to-action-section'; +import { getGlobalsCommunitiesSection } from '../../services/get-globals-communitues-section'; +import { getGlobalsFeaturesSection } from '../../services/get-globals-features-section'; +import { getGlobalsHeroSection } from '../../services/get-globals-hero-section'; +import { getGlobalsLearningResourcesSection } from '../../services/get-globals-learning-resources-section'; +import { getGlobalsTestimonialsSection } from '../../services/get-globals-testimonials-section'; +import { CallToAction } from './_components/call-to-action'; +import { Community } from './_components/community'; +import { Features } from './_components/features'; +import { Hero } from './_components/hero'; +import { LearningResources } from './_components/learning-resources'; +import { Testimonials } from './_components/testimonials'; + +export const revalidate = 10; // Seconds + +export default async function Page() { + const [ + heroData, + featuresData, + communitiesData, + learningResourcesData, + testimonialsData, + callToActionData, + ] = await Promise.all([ + getGlobalsHeroSection(), + getGlobalsFeaturesSection(), + getGlobalsCommunitiesSection(), + getGlobalsLearningResourcesSection(), + getGlobalsTestimonialsSection(), + getGlobalsCallToActionSection(), + ]); + + return ( + <> + + + + + + + + ); +} diff --git a/apps/landing/src/app/(payload)/admin/[[...segments]]/not-found.tsx b/apps/landing/src/app/(payload)/admin/[[...segments]]/not-found.tsx new file mode 100644 index 0000000..94d4e63 --- /dev/null +++ b/apps/landing/src/app/(payload)/admin/[[...segments]]/not-found.tsx @@ -0,0 +1,27 @@ +/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ +/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ +import type { Metadata } from 'next'; + +import { NotFoundPage, generatePageMetadata } from '@payloadcms/next/views'; +import config from '../../../../payload.config'; +import { importMap } from '../importMap'; + +type Args = { + params: Promise<{ + segments: string[]; + }>; + searchParams: Promise<{ + [key: string]: string | string[]; + }>; +}; + +export const generateMetadata = ({ + params, + searchParams, +}: Args): Promise => + generatePageMetadata({ config, params, searchParams }); + +const NotFound = ({ params, searchParams }: Args) => + NotFoundPage({ config, params, searchParams, importMap }); + +export default NotFound; diff --git a/apps/landing/src/app/(payload)/admin/[[...segments]]/page.tsx b/apps/landing/src/app/(payload)/admin/[[...segments]]/page.tsx new file mode 100644 index 0000000..cf3fdd0 --- /dev/null +++ b/apps/landing/src/app/(payload)/admin/[[...segments]]/page.tsx @@ -0,0 +1,27 @@ +/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ +/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ +import type { Metadata } from 'next'; + +import { RootPage, generatePageMetadata } from '@payloadcms/next/views'; +import config from '../../../../payload.config'; +import { importMap } from '../importMap'; + +type Args = { + params: Promise<{ + segments: string[]; + }>; + searchParams: Promise<{ + [key: string]: string | string[]; + }>; +}; + +export const generateMetadata = ({ + params, + searchParams, +}: Args): Promise => + generatePageMetadata({ config, params, searchParams }); + +const Page = ({ params, searchParams }: Args) => + RootPage({ config, params, searchParams, importMap }); + +export default Page; diff --git a/apps/landing/src/app/(payload)/admin/importMap.js b/apps/landing/src/app/(payload)/admin/importMap.js new file mode 100644 index 0000000..bd56203 --- /dev/null +++ b/apps/landing/src/app/(payload)/admin/importMap.js @@ -0,0 +1,5 @@ +import { S3ClientUploadHandler as S3ClientUploadHandler_f97aa6c64367fa259c5bc0567239ef24 } from '@payloadcms/storage-s3/client' + +export const importMap = { + "@payloadcms/storage-s3/client#S3ClientUploadHandler": S3ClientUploadHandler_f97aa6c64367fa259c5bc0567239ef24 +} diff --git a/apps/landing/src/app/(payload)/api/[...slug]/route.ts b/apps/landing/src/app/(payload)/api/[...slug]/route.ts new file mode 100644 index 0000000..7177895 --- /dev/null +++ b/apps/landing/src/app/(payload)/api/[...slug]/route.ts @@ -0,0 +1,19 @@ +/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ +/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ +import '@payloadcms/next/css'; +import { + REST_DELETE, + REST_GET, + REST_OPTIONS, + REST_PATCH, + REST_POST, + REST_PUT, +} from '@payloadcms/next/routes'; +import config from '../../../../payload.config'; + +export const GET = REST_GET(config); +export const POST = REST_POST(config); +export const DELETE = REST_DELETE(config); +export const PATCH = REST_PATCH(config); +export const PUT = REST_PUT(config); +export const OPTIONS = REST_OPTIONS(config); diff --git a/apps/landing/src/app/(payload)/api/graphql-playground/route.ts b/apps/landing/src/app/(payload)/api/graphql-playground/route.ts new file mode 100644 index 0000000..9c2a713 --- /dev/null +++ b/apps/landing/src/app/(payload)/api/graphql-playground/route.ts @@ -0,0 +1,7 @@ +/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ +/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ +import '@payloadcms/next/css'; +import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes'; +import config from '../../../../payload.config'; + +export const GET = GRAPHQL_PLAYGROUND_GET(config); diff --git a/apps/landing/src/app/(payload)/api/graphql/route.ts b/apps/landing/src/app/(payload)/api/graphql/route.ts new file mode 100644 index 0000000..e6ab622 --- /dev/null +++ b/apps/landing/src/app/(payload)/api/graphql/route.ts @@ -0,0 +1,8 @@ +/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ +/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ +import { GRAPHQL_POST, REST_OPTIONS } from '@payloadcms/next/routes'; +import config from '../../../../payload.config'; + +export const POST = GRAPHQL_POST(config); + +export const OPTIONS = REST_OPTIONS(config); diff --git a/apps/landing/src/app/(payload)/custom.scss b/apps/landing/src/app/(payload)/custom.scss new file mode 100644 index 0000000..e69de29 diff --git a/apps/landing/src/app/(payload)/layout.tsx b/apps/landing/src/app/(payload)/layout.tsx new file mode 100644 index 0000000..e32d647 --- /dev/null +++ b/apps/landing/src/app/(payload)/layout.tsx @@ -0,0 +1,35 @@ +/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */ +/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */ +import '@payloadcms/next/css'; +import { handleServerFunctions, RootLayout } from '@payloadcms/next/layouts'; +import type { ServerFunctionClient } from 'payload'; +import React from 'react'; +import config from '../../payload.config'; + +import { importMap } from './admin/importMap.js'; +import './custom.scss'; + +type Args = { + children: React.ReactNode; +}; + +const serverFunction: ServerFunctionClient = async function (args) { + 'use server'; + return handleServerFunctions({ + ...args, + config, + importMap, + }); +}; + +const Layout = ({ children }: Args) => ( + + {children} + +); + +export default Layout; diff --git a/apps/landing/src/collections/CallToActionSection.ts b/apps/landing/src/collections/CallToActionSection.ts new file mode 100644 index 0000000..4357b7c --- /dev/null +++ b/apps/landing/src/collections/CallToActionSection.ts @@ -0,0 +1,51 @@ +import { GlobalConfig } from 'payload'; + +export const CallToActionSection: GlobalConfig = { + slug: 'call-to-action-section', + label: 'Call To Action Section', + fields: [ + { + name: 'title', + type: 'text', + required: true, + defaultValue: 'Siap Menjadi', + }, + { + name: 'highlightedTitle', + type: 'text', + required: true, + defaultValue: 'Programmer Handal?', + }, + { + name: 'subtitle', + type: 'textarea', + required: true, + defaultValue: + 'Bergabunglah dengan komunitas IMPHNEN sekarang dan mulai perjalanan programming mu dengan cara yang menyenangkan!', + }, + { + name: 'primaryButtonLabel', + type: 'text', + required: true, + defaultValue: 'Gabung Discord', + }, + { + name: 'primaryButtonLink', + type: 'text', + required: true, + defaultValue: 'https://discord.gg/imphnen', + }, + { + name: 'secondaryButtonLabel', + type: 'text', + required: true, + defaultValue: 'Join Facebook Group', + }, + { + name: 'secondaryButtonLink', + type: 'text', + required: true, + defaultValue: 'https://facebook.com/groups/programmerhandal', + }, + ], +}; diff --git a/apps/landing/src/collections/CommunitiesSection.ts b/apps/landing/src/collections/CommunitiesSection.ts new file mode 100644 index 0000000..1c04430 --- /dev/null +++ b/apps/landing/src/collections/CommunitiesSection.ts @@ -0,0 +1,65 @@ +import { GlobalConfig } from 'payload'; + +export const CommunitiesSection: GlobalConfig = { + slug: 'communities-section', + label: 'Communities Section', + fields: [ + { + name: 'items', + type: 'array', + required: true, + defaultValue: [ + { + iconName: 'tabler:brand-facebook', + title: 'Facebook Group', + description: + 'Bergabunglah dengan grup Facebook kami untuk diskusi santai dan berbagi artikel menarik.', + buttonText: 'Gabung Sekarang', + buttonLink: 'https://facebook.com/groups/programmerhandal', + }, + { + iconName: 'tabler:brand-instagram', + title: 'Instagram', + description: + 'Ikuti kami di Instagram untuk tips programming, konten inspiratif, dan info event terbaru.', + buttonText: 'Follow Kami', + buttonLink: 'https://www.instagram.com/imphnen.dev', + }, + { + iconName: 'tabler:brand-discord-filled', + title: 'Discord Server', + description: + 'Diskusikan langsung dengan sesama programmer dan dapatkan bantuan langsung dari para ahli.', + buttonText: 'Join Server', + buttonLink: 'https://discord.com/invite/imphnen', + }, + ], + fields: [ + { + name: 'iconName', + type: 'text', + required: true, + }, + { name: 'title', type: 'text', required: true }, + { name: 'description', type: 'textarea', required: true }, + { name: 'buttonText', type: 'text', required: true }, + { name: 'buttonLink', type: 'text', required: true }, + ], + }, + { + name: 'stats', + type: 'array', + required: true, + defaultValue: [ + { value: '100K+', label: 'Member Aktif' }, + { value: '50+', label: 'Event Bulanan' }, + { value: '100+', label: 'Mentor Profesional' }, + { value: '5K+', label: 'Diskusi Mingguan' }, + ], + fields: [ + { name: 'value', type: 'text', required: true }, + { name: 'label', type: 'text', required: true }, + ], + }, + ], +}; diff --git a/apps/landing/src/collections/FeaturesSection.ts b/apps/landing/src/collections/FeaturesSection.ts new file mode 100644 index 0000000..4ae5dc2 --- /dev/null +++ b/apps/landing/src/collections/FeaturesSection.ts @@ -0,0 +1,65 @@ +import { GlobalConfig } from 'payload'; + +export const FeaturesSection: GlobalConfig = { + slug: 'features-section', + label: 'Features Section', + fields: [ + { + name: 'heading', + type: 'text', + defaultValue: 'Belajar programming dengan cara yang lebih baik', + }, + { + name: 'subheading', + type: 'text', + defaultValue: + 'IMPHNEN hadir dengan berbagai fitur untuk membantu kamu menjadi programmer handal tanpa harus pusing dengan coding.', + }, + { + name: 'features', + type: 'array', + defaultValue: [ + { + iconName: 'tabler:device-laptop', + title: 'Belajar Tanpa Koding', + description: + 'Pelajari konsep programming dengan cara yang mudah dipahami tanpa harus menulis kode yang rumit.', + }, + { + iconName: 'tabler:users', + title: 'Komunitas Supportif', + description: + 'Bergabunglah dengan komunitas programmer Indonesia yang siap membantu dan berbagi pengalaman.', + }, + { + iconName: 'tabler:book', + title: 'Tutorial Interaktif', + description: + 'Akses tutorial interaktif yang membuat konsep programming lebih mudah untuk dipahami.', + }, + { + iconName: 'tabler:code', + title: 'Proyek Praktis', + description: + 'Terapkan pengetahuan Anda dalam proyek nyata dengan panduan langkah demi langkah.', + }, + ], + fields: [ + { + name: 'iconName', + type: 'text', + required: true, + }, + { + name: 'title', + type: 'text', + required: true, + }, + { + name: 'description', + type: 'textarea', + }, + ], + }, + ], +}; diff --git a/apps/landing/src/collections/HeroSection.ts b/apps/landing/src/collections/HeroSection.ts new file mode 100644 index 0000000..da1df61 --- /dev/null +++ b/apps/landing/src/collections/HeroSection.ts @@ -0,0 +1,104 @@ +import type { GlobalConfig } from 'payload'; + +export const HeroSection: GlobalConfig = { + slug: 'hero-section', + label: 'Hero Section', + fields: [ + { + name: 'heroImage', + label: 'Hero Image', + type: 'upload', + relationTo: 'media', + required: true, + }, + { + name: 'badgeText', + label: 'Badge Text', + type: 'text', + required: true, + defaultValue: 'Komunitas Programmer Indonesia', + }, + { + name: 'title', + label: 'Main Title', + type: 'text', + required: true, + defaultValue: 'Programmer Handal,', + }, + { + name: 'highlight', + label: 'Highlight Text', + type: 'text', + required: true, + defaultValue: 'Tanpa Ribet', + }, + { + name: 'description', + label: 'Description', + type: 'textarea', + required: true, + defaultValue: + 'Temukan potensi programming Anda bersama komunitas yang mendukung, tutorial interaktif, dan sumber daya berkualitas tinggi.', + }, + { + type: 'group', + name: 'buttons', + label: 'Buttons', + fields: [ + { + name: 'primaryLabel', + label: 'Primary Button Label', + type: 'text', + required: true, + defaultValue: 'Mulai Belajar', + }, + { + name: 'primaryUrl', + label: 'Primary Button URL', + type: 'text', + required: true, + defaultValue: 'https://facebook.com/groups/programmerhandal', + }, + { + name: 'secondaryLabel', + label: 'Secondary Button Label', + type: 'text', + required: true, + defaultValue: 'Gabung Discord', + }, + { + name: 'secondaryUrl', + label: 'Secondary Button URL', + type: 'text', + required: true, + defaultValue: 'https://discord.com/invite/imphnen', + }, + ], + }, + { + name: 'stats', + label: 'Statistics', + type: 'array', + minRows: 1, + fields: [ + { + name: 'value', + label: 'Value', + type: 'text', + required: true, + }, + { + name: 'label', + label: 'Label', + type: 'text', + required: true, + }, + ], + defaultValue: [ + { value: '180K+', label: 'Member' }, + { value: '500+', label: 'Tutorial' }, + { value: '24/7', label: 'Yapping' }, + ], + }, + ], +}; diff --git a/apps/landing/src/collections/LearningResourcesSection.ts b/apps/landing/src/collections/LearningResourcesSection.ts new file mode 100644 index 0000000..ddc8505 --- /dev/null +++ b/apps/landing/src/collections/LearningResourcesSection.ts @@ -0,0 +1,117 @@ +// payload config +import { GlobalConfig } from 'payload'; + +export const LearningResourcesSection: GlobalConfig = { + slug: 'learning-resources-section', + label: 'Learning Resources Section', + fields: [ + { + name: 'title', + type: 'text', + required: true, + defaultValue: 'Sumber Belajar', + }, + { + name: 'description', + type: 'textarea', + required: true, + defaultValue: + 'Akses berbagai materi belajar yang akan membantu kamu menguasai konsep programming dengan cara yang menyenangkan.', + }, + { + name: 'resources', + type: 'array', + required: true, + fields: [ + { name: 'icon', type: 'text', required: true }, + { name: 'title', type: 'text', required: true }, + { name: 'description', type: 'textarea', required: true }, + { name: 'buttonText', type: 'text', required: true }, + { name: 'buttonLink', type: 'text', required: true }, + ], + defaultValue: [ + { + icon: 'tabler:video', + title: 'Video Tutorial', + description: + 'Belajar melalui tutorial video dari langkah awal hingga mahir.', + buttonText: 'Lihat Semua Video', + buttonLink: '#', + }, + { + icon: 'tabler:article', + title: 'Artikel & Tutorial', + description: + 'Pelajari konsep programming melalui artikel yang disusun secara terstruktur.', + buttonText: 'Baca Artikel', + buttonLink: '#', + }, + { + icon: 'tabler:brand-vscode', + title: 'Tantangan Koding', + description: + 'Uji kemampuan koding kamu dengan tantangan yang menyenangkan dan menantang.', + buttonText: 'Mulai Tantangan', + buttonLink: '#', + }, + { + icon: 'tabler:device-desktop-share', + title: 'Sharing Session', + description: + 'Ikuti sesi berbagi pengalaman dari programmer berpengalaman dan belajar dari pengalaman mereka.', + buttonText: 'Jadwal Session', + buttonLink: '#', + }, + ], + }, + { + name: 'featured', + type: 'group', + fields: [ + { + name: 'label', + type: 'text', + required: true, + defaultValue: 'Rekomendasi Terbaik', + }, + { + name: 'title', + type: 'text', + required: true, + defaultValue: 'Kursus Lengkap Web Development', + }, + { + name: 'description', + type: 'textarea', + required: true, + defaultValue: + 'Pelajari HTML, CSS, JavaScript, React, dan Node.js dalam satu kursus komprehensif yang dirancang untuk pemula hingga tingkat menengah.', + }, + { + name: 'primaryButtonText', + type: 'text', + required: true, + defaultValue: 'Mulai Kursus', + }, + { + name: 'primaryButtonLink', + type: 'text', + required: true, + defaultValue: '#', + }, + { + name: 'secondaryButtonText', + type: 'text', + required: true, + defaultValue: 'Lihat Silabus', + }, + { + name: 'secondaryButtonLink', + type: 'text', + required: true, + defaultValue: '#', + }, + ], + }, + ], +}; diff --git a/apps/landing/src/collections/Media.ts b/apps/landing/src/collections/Media.ts new file mode 100644 index 0000000..bb11256 --- /dev/null +++ b/apps/landing/src/collections/Media.ts @@ -0,0 +1,16 @@ +import type { CollectionConfig } from 'payload'; + +export const Media: CollectionConfig = { + slug: 'media', + access: { + read: () => true, + }, + fields: [ + { + name: 'alt', + type: 'text', + required: true, + }, + ], + upload: true, +}; diff --git a/apps/landing/src/collections/TestimonialsSection.ts b/apps/landing/src/collections/TestimonialsSection.ts new file mode 100644 index 0000000..92926b4 --- /dev/null +++ b/apps/landing/src/collections/TestimonialsSection.ts @@ -0,0 +1,93 @@ +import { GlobalConfig } from 'payload'; + +export const TestimonialsSection: GlobalConfig = { + slug: 'testimonials-section', + label: 'Testimonials Section', + fields: [ + { + name: 'title', + label: 'Section Title', + type: 'text', + defaultValue: 'Testimoni Member', + required: true, + }, + { + name: 'subtitle', + label: 'Section Subtitle', + type: 'textarea', + defaultValue: + 'Apa kata mereka yang telah bergabung dengan komunitas IMPHNEN?', + required: true, + }, + { + name: 'items', + label: 'Testimonials Items', + type: 'array', + fields: [ + { name: 'quote', type: 'textarea', label: 'Quote', required: true }, + { name: 'name', type: 'text', label: 'Name', required: true }, + { name: 'role', type: 'text', label: 'Role', required: true }, + { + name: 'avatar', + type: 'upload', + label: 'Avatar', + relationTo: 'media', + required: true, + }, + ], + defaultValue: [ + { + quote: + 'Saya yang tadinya tidak mengerti apa-apa tentang programming, sekarang bisa membuat website sendiri dengan mudah. Terima kasih IMPHNEN!', + name: 'Budi Santoso', + role: 'Web Developer Pemula', + avatar: { id: '', relationTo: 'media' }, + }, + { + quote: + 'Komunitas yang sangat supportif! Setiap pertanyaan selalu dijawab dengan cepat dan jelas. Diskusi programnya mudah dipahami.', + name: 'Anita Ratna', + role: 'Mobile App Developer', + avatar: { id: '', relationTo: 'media' }, + }, + { + quote: + 'Server Discord IMPHNEN adalah tempat belajar terbaik untuk programmer pemula seperti saya. Materinya lengkap dan komunitasnya sangat membantu!', + name: 'Dedi Permana', + role: 'Data Scientist', + avatar: { id: '', relationTo: 'media' }, + }, + ], + }, + { + name: 'stats', + label: 'Statistics', + type: 'array', + fields: [ + { name: 'value', type: 'text', label: 'Value', required: true }, + { name: 'label', type: 'text', label: 'Label', required: true }, + ], + defaultValue: [ + { value: '98%', label: 'Tingkat Kemalasan' }, + { value: '4.9/5', label: 'Rating Drama' }, + { value: '85%', label: 'Mendapat Pekerjaan' }, + { value: '24/7', label: 'Yapping' }, + ], + }, + { + name: 'joinTitle', + label: 'Join Section Title', + type: 'text', + defaultValue: 'Bergabunglah dengan 10,000+ programmer Indonesia lainnya', + required: true, + }, + { + name: 'joinText', + label: 'Join Section Text', + type: 'textarea', + defaultValue: + 'Komunitas kami terus berkembang dengan programmer dari berbagai latar belakang dan tingkat keahlian. Bersama-sama, kita belajar, berbagi, dan tumbuh sebagai profesional.', + required: true, + }, + ], +}; diff --git a/apps/landing/src/collections/Users.ts b/apps/landing/src/collections/Users.ts new file mode 100644 index 0000000..c167856 --- /dev/null +++ b/apps/landing/src/collections/Users.ts @@ -0,0 +1,13 @@ +import type { CollectionConfig } from 'payload'; + +export const Users: CollectionConfig = { + slug: 'users', + admin: { + useAsTitle: 'email', + }, + auth: true, + fields: [ + // Email added by default + // Add more fields as needed + ], +}; diff --git a/apps/landing/src/components/NxWelcome.vue b/apps/landing/src/components/NxWelcome.vue deleted file mode 100644 index d9b62ac..0000000 --- a/apps/landing/src/components/NxWelcome.vue +++ /dev/null @@ -1,814 +0,0 @@ - - - - - diff --git a/apps/landing/src/lib/format.ts b/apps/landing/src/lib/format.ts new file mode 100644 index 0000000..5c0dd9e --- /dev/null +++ b/apps/landing/src/lib/format.ts @@ -0,0 +1,10 @@ +import { Media } from '../payload-types'; + +export function formatCMSImageDataToMedia( + image: number | Media | null | undefined +): Media | null { + if (image && typeof image === 'object' && 'url' in image) { + return image as Media; + } + return null; +} diff --git a/apps/landing/src/lib/payload.ts b/apps/landing/src/lib/payload.ts new file mode 100644 index 0000000..2151d1a --- /dev/null +++ b/apps/landing/src/lib/payload.ts @@ -0,0 +1,4 @@ +import { getPayload } from 'payload'; +import config from '../payload.config'; + +export const payload = await getPayload({ config }); diff --git a/apps/landing/src/pages/about.vue b/apps/landing/src/pages/about.vue deleted file mode 100644 index e5f1bc9..0000000 --- a/apps/landing/src/pages/about.vue +++ /dev/null @@ -1,16 +0,0 @@ - - - diff --git a/apps/landing/src/pages/index.vue b/apps/landing/src/pages/index.vue deleted file mode 100644 index 675eb0f..0000000 --- a/apps/landing/src/pages/index.vue +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/apps/landing/src/payload-types.ts b/apps/landing/src/payload-types.ts new file mode 100644 index 0000000..4e4d9f6 --- /dev/null +++ b/apps/landing/src/payload-types.ts @@ -0,0 +1,591 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * This file was automatically generated by Payload. + * DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config, + * and re-run `payload generate:types` to regenerate this file. + */ + +/** + * Supported timezones in IANA format. + * + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "supportedTimezones". + */ +export type SupportedTimezones = + | 'Pacific/Midway' + | 'Pacific/Niue' + | 'Pacific/Honolulu' + | 'Pacific/Rarotonga' + | 'America/Anchorage' + | 'Pacific/Gambier' + | 'America/Los_Angeles' + | 'America/Tijuana' + | 'America/Denver' + | 'America/Phoenix' + | 'America/Chicago' + | 'America/Guatemala' + | 'America/New_York' + | 'America/Bogota' + | 'America/Caracas' + | 'America/Santiago' + | 'America/Buenos_Aires' + | 'America/Sao_Paulo' + | 'Atlantic/South_Georgia' + | 'Atlantic/Azores' + | 'Atlantic/Cape_Verde' + | 'Europe/London' + | 'Europe/Berlin' + | 'Africa/Lagos' + | 'Europe/Athens' + | 'Africa/Cairo' + | 'Europe/Moscow' + | 'Asia/Riyadh' + | 'Asia/Dubai' + | 'Asia/Baku' + | 'Asia/Karachi' + | 'Asia/Tashkent' + | 'Asia/Calcutta' + | 'Asia/Dhaka' + | 'Asia/Almaty' + | 'Asia/Jakarta' + | 'Asia/Bangkok' + | 'Asia/Shanghai' + | 'Asia/Singapore' + | 'Asia/Tokyo' + | 'Asia/Seoul' + | 'Australia/Brisbane' + | 'Australia/Sydney' + | 'Pacific/Guam' + | 'Pacific/Noumea' + | 'Pacific/Auckland' + | 'Pacific/Fiji'; + +export interface Config { + auth: { + users: UserAuthOperations; + }; + blocks: {}; + collections: { + users: User; + media: Media; + 'payload-locked-documents': PayloadLockedDocument; + 'payload-preferences': PayloadPreference; + 'payload-migrations': PayloadMigration; + }; + collectionsJoins: {}; + collectionsSelect: { + users: UsersSelect | UsersSelect; + media: MediaSelect | MediaSelect; + 'payload-locked-documents': PayloadLockedDocumentsSelect | PayloadLockedDocumentsSelect; + 'payload-preferences': PayloadPreferencesSelect | PayloadPreferencesSelect; + 'payload-migrations': PayloadMigrationsSelect | PayloadMigrationsSelect; + }; + db: { + defaultIDType: number; + }; + globals: { + 'hero-section': HeroSection; + 'features-section': FeaturesSection; + 'communities-section': CommunitiesSection; + 'learning-resources-section': LearningResourcesSection; + 'testimonials-section': TestimonialsSection; + 'call-to-action-section': CallToActionSection; + }; + globalsSelect: { + 'hero-section': HeroSectionSelect | HeroSectionSelect; + 'features-section': FeaturesSectionSelect | FeaturesSectionSelect; + 'communities-section': CommunitiesSectionSelect | CommunitiesSectionSelect; + 'learning-resources-section': LearningResourcesSectionSelect | LearningResourcesSectionSelect; + 'testimonials-section': TestimonialsSectionSelect | TestimonialsSectionSelect; + 'call-to-action-section': CallToActionSectionSelect | CallToActionSectionSelect; + }; + locale: null; + user: User & { + collection: 'users'; + }; + jobs: { + tasks: unknown; + workflows: unknown; + }; +} +export interface UserAuthOperations { + forgotPassword: { + email: string; + password: string; + }; + login: { + email: string; + password: string; + }; + registerFirstUser: { + email: string; + password: string; + }; + unlock: { + email: string; + password: string; + }; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "users". + */ +export interface User { + id: number; + updatedAt: string; + createdAt: string; + email: string; + resetPasswordToken?: string | null; + resetPasswordExpiration?: string | null; + salt?: string | null; + hash?: string | null; + loginAttempts?: number | null; + lockUntil?: string | null; + password?: string | null; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "media". + */ +export interface Media { + id: number; + alt: string; + updatedAt: string; + createdAt: string; + url?: string | null; + thumbnailURL?: string | null; + filename?: string | null; + mimeType?: string | null; + filesize?: number | null; + width?: number | null; + height?: number | null; + focalX?: number | null; + focalY?: number | null; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "payload-locked-documents". + */ +export interface PayloadLockedDocument { + id: number; + document?: + | ({ + relationTo: 'users'; + value: number | User; + } | null) + | ({ + relationTo: 'media'; + value: number | Media; + } | null); + globalSlug?: string | null; + user: { + relationTo: 'users'; + value: number | User; + }; + updatedAt: string; + createdAt: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "payload-preferences". + */ +export interface PayloadPreference { + id: number; + user: { + relationTo: 'users'; + value: number | User; + }; + key?: string | null; + value?: + | { + [k: string]: unknown; + } + | unknown[] + | string + | number + | boolean + | null; + updatedAt: string; + createdAt: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "payload-migrations". + */ +export interface PayloadMigration { + id: number; + name?: string | null; + batch?: number | null; + updatedAt: string; + createdAt: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "users_select". + */ +export interface UsersSelect { + updatedAt?: T; + createdAt?: T; + email?: T; + resetPasswordToken?: T; + resetPasswordExpiration?: T; + salt?: T; + hash?: T; + loginAttempts?: T; + lockUntil?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "media_select". + */ +export interface MediaSelect { + alt?: T; + updatedAt?: T; + createdAt?: T; + url?: T; + thumbnailURL?: T; + filename?: T; + mimeType?: T; + filesize?: T; + width?: T; + height?: T; + focalX?: T; + focalY?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "payload-locked-documents_select". + */ +export interface PayloadLockedDocumentsSelect { + document?: T; + globalSlug?: T; + user?: T; + updatedAt?: T; + createdAt?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "payload-preferences_select". + */ +export interface PayloadPreferencesSelect { + user?: T; + key?: T; + value?: T; + updatedAt?: T; + createdAt?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "payload-migrations_select". + */ +export interface PayloadMigrationsSelect { + name?: T; + batch?: T; + updatedAt?: T; + createdAt?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "hero-section". + */ +export interface HeroSection { + id: number; + heroImage: number | Media; + badgeText: string; + title: string; + highlight: string; + description: string; + buttons: { + primaryLabel: string; + primaryUrl: string; + secondaryLabel: string; + secondaryUrl: string; + }; + stats?: + | { + value: string; + label: string; + id?: string | null; + }[] + | null; + updatedAt?: string | null; + createdAt?: string | null; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "features-section". + */ +export interface FeaturesSection { + id: number; + heading?: string | null; + subheading?: string | null; + features?: + | { + iconName: string; + title: string; + description?: string | null; + id?: string | null; + }[] + | null; + updatedAt?: string | null; + createdAt?: string | null; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "communities-section". + */ +export interface CommunitiesSection { + id: number; + items: { + iconName: string; + title: string; + description: string; + buttonText: string; + buttonLink: string; + id?: string | null; + }[]; + stats: { + value: string; + label: string; + id?: string | null; + }[]; + updatedAt?: string | null; + createdAt?: string | null; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "learning-resources-section". + */ +export interface LearningResourcesSection { + id: number; + title: string; + description: string; + resources: { + icon: string; + title: string; + description: string; + buttonText: string; + buttonLink: string; + id?: string | null; + }[]; + featured: { + label: string; + title: string; + description: string; + primaryButtonText: string; + primaryButtonLink: string; + secondaryButtonText: string; + secondaryButtonLink: string; + }; + updatedAt?: string | null; + createdAt?: string | null; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "testimonials-section". + */ +export interface TestimonialsSection { + id: number; + title: string; + subtitle: string; + items?: + | { + quote: string; + name: string; + role: string; + avatar: number | Media; + id?: string | null; + }[] + | null; + stats?: + | { + value: string; + label: string; + id?: string | null; + }[] + | null; + joinTitle: string; + joinText: string; + updatedAt?: string | null; + createdAt?: string | null; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "call-to-action-section". + */ +export interface CallToActionSection { + id: number; + title: string; + highlightedTitle: string; + subtitle: string; + primaryButtonLabel: string; + primaryButtonLink: string; + secondaryButtonLabel: string; + secondaryButtonLink: string; + updatedAt?: string | null; + createdAt?: string | null; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "hero-section_select". + */ +export interface HeroSectionSelect { + heroImage?: T; + badgeText?: T; + title?: T; + highlight?: T; + description?: T; + buttons?: + | T + | { + primaryLabel?: T; + primaryUrl?: T; + secondaryLabel?: T; + secondaryUrl?: T; + }; + stats?: + | T + | { + value?: T; + label?: T; + id?: T; + }; + updatedAt?: T; + createdAt?: T; + globalType?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "features-section_select". + */ +export interface FeaturesSectionSelect { + heading?: T; + subheading?: T; + features?: + | T + | { + iconName?: T; + title?: T; + description?: T; + id?: T; + }; + updatedAt?: T; + createdAt?: T; + globalType?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "communities-section_select". + */ +export interface CommunitiesSectionSelect { + items?: + | T + | { + iconName?: T; + title?: T; + description?: T; + buttonText?: T; + buttonLink?: T; + id?: T; + }; + stats?: + | T + | { + value?: T; + label?: T; + id?: T; + }; + updatedAt?: T; + createdAt?: T; + globalType?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "learning-resources-section_select". + */ +export interface LearningResourcesSectionSelect { + title?: T; + description?: T; + resources?: + | T + | { + icon?: T; + title?: T; + description?: T; + buttonText?: T; + buttonLink?: T; + id?: T; + }; + featured?: + | T + | { + label?: T; + title?: T; + description?: T; + primaryButtonText?: T; + primaryButtonLink?: T; + secondaryButtonText?: T; + secondaryButtonLink?: T; + }; + updatedAt?: T; + createdAt?: T; + globalType?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "testimonials-section_select". + */ +export interface TestimonialsSectionSelect { + title?: T; + subtitle?: T; + items?: + | T + | { + quote?: T; + name?: T; + role?: T; + avatar?: T; + id?: T; + }; + stats?: + | T + | { + value?: T; + label?: T; + id?: T; + }; + joinTitle?: T; + joinText?: T; + updatedAt?: T; + createdAt?: T; + globalType?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "call-to-action-section_select". + */ +export interface CallToActionSectionSelect { + title?: T; + highlightedTitle?: T; + subtitle?: T; + primaryButtonLabel?: T; + primaryButtonLink?: T; + secondaryButtonLabel?: T; + secondaryButtonLink?: T; + updatedAt?: T; + createdAt?: T; + globalType?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "auth". + */ +export interface Auth { + [k: string]: unknown; +} + + +declare module 'payload' { + export interface GeneratedTypes extends Config {} +} \ No newline at end of file diff --git a/apps/landing/src/payload.config.ts b/apps/landing/src/payload.config.ts new file mode 100644 index 0000000..7ccc7e1 --- /dev/null +++ b/apps/landing/src/payload.config.ts @@ -0,0 +1,78 @@ +import { postgresAdapter } from '@payloadcms/db-postgres'; +import { lexicalEditor } from '@payloadcms/richtext-lexical'; +import { s3Storage } from '@payloadcms/storage-s3'; +import { NodeHttpHandler } from '@smithy/node-http-handler'; +import https from 'https'; +import path from 'path'; +import { buildConfig } from 'payload'; +import sharp from 'sharp'; +import { fileURLToPath } from 'url'; + +import { CallToActionSection } from './collections/CallToActionSection'; +import { CommunitiesSection } from './collections/CommunitiesSection'; +import { FeaturesSection } from './collections/FeaturesSection'; +import { HeroSection } from './collections/HeroSection'; +import { LearningResourcesSection } from './collections/LearningResourcesSection'; +import { Media } from './collections/Media'; +import { TestimonialsSection } from './collections/TestimonialsSection'; +import { Users } from './collections/Users'; + +const filename = fileURLToPath(import.meta.url); +const dirname = path.dirname(filename); + +export default buildConfig({ + admin: { + user: Users.slug, + importMap: { + importMapFile: path.resolve( + __dirname, + 'app', + '(payload)', + 'admin', + 'importMap.js' + ), + }, + }, + collections: [Users, Media], + globals: [ + HeroSection, + FeaturesSection, + CommunitiesSection, + LearningResourcesSection, + TestimonialsSection, + CallToActionSection, + ], + editor: lexicalEditor(), + secret: process.env.CMS_SECRET || '', + typescript: { + outputFile: path.resolve(dirname, 'payload-types.ts'), + }, + db: postgresAdapter({ + pool: { + connectionString: process.env.CMS_POSTGRES_URL || '', + }, + }), + sharp, + plugins: [ + s3Storage({ + collections: { + media: { + disableLocalStorage: true, + }, + }, + bucket: 'landing-cms', + config: { + endpoint: process.env.CMS_STORAGE_ENDPOINT, + credentials: { + accessKeyId: process.env.CMS_STORAGE_ACCESS_KEY_ID!, + secretAccessKey: process.env.CMS_STORAGE_SECRET_ACCESS_KEY!, + }, + region: process.env.CMS_STORAGE_REGION!, + forcePathStyle: true, + requestHandler: new NodeHttpHandler({ + httpsAgent: new https.Agent({ rejectUnauthorized: false }), + }), + }, + }), + ], +}); diff --git a/apps/landing/src/public/favicon.ico b/apps/landing/src/public/favicon.ico deleted file mode 100644 index 3146711..0000000 Binary files a/apps/landing/src/public/favicon.ico and /dev/null differ diff --git a/apps/landing/src/server/api/greet.ts b/apps/landing/src/server/api/greet.ts deleted file mode 100644 index 9109551..0000000 --- a/apps/landing/src/server/api/greet.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { defineEventHandler, getQuery } from 'h3'; - -export default defineEventHandler((event) => { - const q = getQuery(event); - const projectName = q.name || 'World'; - - return { - message: `Hello ${projectName}`, - }; -}); diff --git a/apps/landing/src/server/tsconfig.json b/apps/landing/src/server/tsconfig.json deleted file mode 100644 index 3c6e968..0000000 --- a/apps/landing/src/server/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../.nuxt/tsconfig.server.json" -} diff --git a/apps/landing/src/services/get-globals-call-to-action-section.ts b/apps/landing/src/services/get-globals-call-to-action-section.ts new file mode 100644 index 0000000..c742304 --- /dev/null +++ b/apps/landing/src/services/get-globals-call-to-action-section.ts @@ -0,0 +1,7 @@ +import { payload } from '../lib/payload'; + +export async function getGlobalsCallToActionSection() { + return await payload.findGlobal({ + slug: 'call-to-action-section', + }); +} diff --git a/apps/landing/src/services/get-globals-communitues-section.ts b/apps/landing/src/services/get-globals-communitues-section.ts new file mode 100644 index 0000000..7b11ce7 --- /dev/null +++ b/apps/landing/src/services/get-globals-communitues-section.ts @@ -0,0 +1,7 @@ +import { payload } from '../lib/payload'; + +export async function getGlobalsCommunitiesSection() { + return await payload.findGlobal({ + slug: 'communities-section', + }); +} diff --git a/apps/landing/src/services/get-globals-features-section.ts b/apps/landing/src/services/get-globals-features-section.ts new file mode 100644 index 0000000..d22574d --- /dev/null +++ b/apps/landing/src/services/get-globals-features-section.ts @@ -0,0 +1,7 @@ +import { payload } from '../lib/payload'; + +export async function getGlobalsFeaturesSection() { + return await payload.findGlobal({ + slug: 'features-section', + }); +} diff --git a/apps/landing/src/services/get-globals-hero-section.ts b/apps/landing/src/services/get-globals-hero-section.ts new file mode 100644 index 0000000..cd8e3c0 --- /dev/null +++ b/apps/landing/src/services/get-globals-hero-section.ts @@ -0,0 +1,7 @@ +import { payload } from '../lib/payload'; + +export async function getGlobalsHeroSection() { + return await payload.findGlobal({ + slug: 'hero-section', + }); +} diff --git a/apps/landing/src/services/get-globals-learning-resources-section.ts b/apps/landing/src/services/get-globals-learning-resources-section.ts new file mode 100644 index 0000000..9a42e8a --- /dev/null +++ b/apps/landing/src/services/get-globals-learning-resources-section.ts @@ -0,0 +1,7 @@ +import { payload } from '../lib/payload'; + +export async function getGlobalsLearningResourcesSection() { + return await payload.findGlobal({ + slug: 'learning-resources-section', + }); +} diff --git a/apps/landing/src/services/get-globals-testimonials-section.ts b/apps/landing/src/services/get-globals-testimonials-section.ts new file mode 100644 index 0000000..2f9480d --- /dev/null +++ b/apps/landing/src/services/get-globals-testimonials-section.ts @@ -0,0 +1,7 @@ +import { payload } from '../lib/payload'; + +export async function getGlobalsTestimonialsSection() { + return await payload.findGlobal({ + slug: 'testimonials-section', + }); +} diff --git a/apps/landing/src/styles/globals.css b/apps/landing/src/styles/globals.css new file mode 100644 index 0000000..6756676 --- /dev/null +++ b/apps/landing/src/styles/globals.css @@ -0,0 +1,3 @@ +@import 'tailwindcss'; +@import "../../../../libs/shadcn-ui/src/index.css"; +@source "../../../../libs/shadcn-ui/src/atoms/**/*.{ts,tsx}"; diff --git a/apps/landing/tsconfig.app.json b/apps/landing/tsconfig.app.json deleted file mode 100644 index c3a330d..0000000 --- a/apps/landing/tsconfig.app.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "composite": true - }, - "include": [".nuxt/nuxt.d.ts", "src/**/*"], - "exclude": [ - "vite.config.ts", - "vite.config.mts", - "vitest.config.ts", - "vitest.config.mts", - "src/**/*.test.ts", - "src/**/*.spec.ts", - "src/**/*.test.tsx", - "src/**/*.spec.tsx", - "src/**/*.test.js", - "src/**/*.spec.js", - "src/**/*.test.jsx", - "src/**/*.spec.jsx" - ] -} diff --git a/apps/landing/tsconfig.json b/apps/landing/tsconfig.json index dc6be23..2a48629 100644 --- a/apps/landing/tsconfig.json +++ b/apps/landing/tsconfig.json @@ -1,13 +1,45 @@ { - "files": [], - "include": [".nuxt/nuxt.d.ts"], - "references": [ - { - "path": "./tsconfig.app.json" - }, - { - "path": "./tsconfig.spec.json" - } + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "jsx": "preserve", + "strict": true, + "noEmit": true, + "emitDeclarationOnly": false, + "esModuleInterop": true, + "target": "ES2022", + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "incremental": true, + "plugins": [ + { + "name": "next" + } + ] + }, + "include": [ + "**/*.js", + "**/*.jsx", + "**/*.ts", + "**/*.tsx", + "../../apps/landing/.next/types/**/*.ts", + "../../dist/apps/landing/.next/types/**/*.ts", + "next-env.d.ts", + ".next/types/**/*.ts" ], - "extends": "../../tsconfig.base.json" + "exclude": [ + "node_modules", + "jest.config.ts", + "**/*.spec.ts", + "**/*.test.ts" + ] } diff --git a/apps/landing/tsconfig.spec.json b/apps/landing/tsconfig.spec.json deleted file mode 100644 index 1436c0a..0000000 --- a/apps/landing/tsconfig.spec.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "../../dist/out-tsc", - "types": [ - "vitest/globals", - "vitest/importMeta", - "vite/client", - "node", - "vitest" - ], - "composite": true - }, - "include": [ - ".nuxt/nuxt.d.ts", - "vite.config.ts", - "vite.config.mts", - "vitest.config.ts", - "vitest.config.mts", - "src/**/*.test.ts", - "src/**/*.spec.ts", - "src/**/*.test.tsx", - "src/**/*.spec.tsx", - "src/**/*.test.js", - "src/**/*.spec.js", - "src/**/*.test.jsx", - "src/**/*.spec.jsx", - "src/**/*.d.ts" - ] -} diff --git a/apps/landing/vitest.config.ts b/apps/landing/vitest.config.ts deleted file mode 100644 index 68561bc..0000000 --- a/apps/landing/vitest.config.ts +++ /dev/null @@ -1,26 +0,0 @@ -/// -import { defineConfig } from 'vite'; -import vue from '@vitejs/plugin-vue'; -import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; -import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; - -export default defineConfig(() => ({ - root: __dirname, - cacheDir: '../../node_modules/.vite/apps/landing', - plugins: [vue(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], - // Uncomment this if you are using workers. - // worker: { - // plugins: [ nxViteTsPaths() ], - // }, - test: { - watch: false, - globals: true, - environment: 'jsdom', - include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - reporters: ['default'], - coverage: { - reportsDirectory: '../../coverage/apps/landing', - provider: 'v8' as const, - }, - }, -})); diff --git a/libs/service/src/hooks/auth/index.ts b/libs/service/src/hooks/auth/index.ts index d4fd99e..4c2540b 100644 --- a/libs/service/src/hooks/auth/index.ts +++ b/libs/service/src/hooks/auth/index.ts @@ -6,7 +6,6 @@ import { TRegisterRequest, TVerifyEmailRequest, } from '../../types/auth'; -import { SessionToken, SessionUser } from '@imphnen-frontend-service/utils'; import { TResponseError, TResponseMessage } from '../../types/common'; @@ -19,11 +18,6 @@ export const usePostLogin = (): UseMutationResult< return useMutation({ mutationKey: ['post-login'], mutationFn: async (payload) => await postLogin(payload), - onSuccess: (res) => { - SessionUser.set(res.data.user); - SessionToken.set(res.data.token); - window.location.reload(); - }, }); }; diff --git a/libs/service/src/schemas/auth/index.ts b/libs/service/src/schemas/auth/index.ts index d406f97..3f99256 100644 --- a/libs/service/src/schemas/auth/index.ts +++ b/libs/service/src/schemas/auth/index.ts @@ -16,7 +16,7 @@ export const authLoginSchema = z.object({ .min(1, 'Password tidak boleh kosong'), }); -export const stepOneRegisterSchema = z +export const authRegisterSchema = z .object({ email: z .string({ @@ -25,6 +25,12 @@ export const stepOneRegisterSchema = z }) .min(1, 'Email tidak boleh kosong') .email('Email harus valid'), + phone_number: z + .string({ + required_error: 'Nomor telepon tidak boleh kosong', + }) + .min(1, 'Nomor telepon tidak boleh kosong') + .max(15, 'Nomor telepon tidak boleh lebih dari 15 digit'), fullname: z .string({ required_error: 'Nama tidak boleh kosong', @@ -51,28 +57,3 @@ export const stepOneRegisterSchema = z message: 'Password dan Konfirmasi Password harus sama', path: ['confirm_password'], }); - -const stepTwoRegisterSchema = z.object({ - phone_number: z - .string({ - required_error: 'Nomor telepon tidak boleh kosong', - invalid_type_error: 'Nomor telepon harus berupa string', - }) - .min(1, 'Nomor telepon tidak boleh kosong') - .max(15, 'Nomor telepon tidak boleh lebih dari 15 karakter'), - referral_code: z - .string() - .min(1, 'Kode referral tidak boleh kosong') - .max(4, 'Kode referral tidak boleh lebih dari 4 karakter') - .optional(), - referred_by: z - .string() - .min(1, 'Kode referral tidak boleh kosong') - .max(50, 'Kode referral tidak boleh lebih dari 50 karakter') - .optional(), - student_type: z.string(), -}); - -export const authRegisterSchema = stepOneRegisterSchema.and( - stepTwoRegisterSchema -); diff --git a/libs/service/src/types/auth/index.ts b/libs/service/src/types/auth/index.ts index 8bd2d8d..874ad0f 100644 --- a/libs/service/src/types/auth/index.ts +++ b/libs/service/src/types/auth/index.ts @@ -1,29 +1,26 @@ +import { z } from 'zod'; +import { authLoginSchema, authRegisterSchema } from '../../schemas'; +import { TResponseDetail, TResponseMessage } from '../common'; import { TUserItem } from '../users'; -export type TLoginRequest = { - email: string; - password: string; +export type TTokenItem = { + access_token: string; + refresh_token: string; }; -export type TLoginResponse = { - data: { - token: { - access_token: string; - refresh_token: string; - }; - user: TUserItem; - }; +export type TLoginItem = { + token?: TTokenItem; + user?: TUserItem; }; -export type TRegisterRequest = { - email: string; - fullname: string; - password: string; - phone_number: string; - referral_code?: string; - referred_by?: string; - student_type: string; - confirm_password: string; +export type TLoginRequest = z.infer; +export type TLoginResponse = TResponseDetail; + +export type TRegisterRequest = z.infer; +export type TRegisterResponse = TResponseDetail; + +export type TVerifyOtpRequest = { + otp: number; }; export type TVerifyEmailRequest = { diff --git a/libs/service/src/types/common/index.ts b/libs/service/src/types/common/index.ts index b57f062..e6c509a 100644 --- a/libs/service/src/types/common/index.ts +++ b/libs/service/src/types/common/index.ts @@ -1,7 +1,19 @@ import { AxiosError } from 'axios'; -export type TResponse = { +export type TMetaResponse = { + page: number; + per_page: number; + total: number; +}; + +export type TResponseDetail = { data: T; + message: string; +}; + +export type TResponseList = { + data: T[]; + meta: TMetaResponse; }; export type TResponseMessage = { diff --git a/libs/service/src/types/roles/index.ts b/libs/service/src/types/roles/index.ts index 9a51667..5e6b8a5 100644 --- a/libs/service/src/types/roles/index.ts +++ b/libs/service/src/types/roles/index.ts @@ -1,9 +1,17 @@ import { TPermissionItem } from '../permissions'; -export type TRoleItem = { +export type TRoleDetailItem = { id: string; name: string; + permissions: TPermissionItem[]; + created_at: string; + updated_at: string; +}; + +export type TRolesListItem = { + id: string; + name: string; + permissions_count: number; created_at: string; updated_at: string; - permissions: TPermissionItem[]; }; diff --git a/libs/service/src/types/users/index.ts b/libs/service/src/types/users/index.ts index 8b6abae..854aba6 100644 --- a/libs/service/src/types/users/index.ts +++ b/libs/service/src/types/users/index.ts @@ -1,4 +1,4 @@ -import { TRoleItem } from '../roles'; +import { TRoleDetailItem } from '../roles'; export type TUserItem = { id: string; @@ -7,13 +7,7 @@ export type TUserItem = { email: string; fullname: string; gender: string; - identity_number: string; is_active: boolean; - is_profile_completed: boolean; phone_number: string; - referral_code: string; - referred_by: string; - religion: string; - student_type: string; - role: TRoleItem; + role: TRoleDetailItem; }; diff --git a/libs/shadcn-ui/.babelrc b/libs/shadcn-ui/.babelrc new file mode 100644 index 0000000..1ea870e --- /dev/null +++ b/libs/shadcn-ui/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ + [ + "@nx/react/babel", + { + "runtime": "automatic", + "useBuiltIns": "usage" + } + ] + ], + "plugins": [] +} diff --git a/libs/shadcn-ui/README.md b/libs/shadcn-ui/README.md new file mode 100644 index 0000000..898cda4 --- /dev/null +++ b/libs/shadcn-ui/README.md @@ -0,0 +1,7 @@ +# shadcn-ui + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test shadcn-ui` to execute the unit tests via [Vitest](https://vitest.dev/). diff --git a/libs/shadcn-ui/components.json b/libs/shadcn-ui/components.json new file mode 100644 index 0000000..9c7376e --- /dev/null +++ b/libs/shadcn-ui/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/index.css", + "baseColor": "zinc", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@imphnen-frontend-service/service/components", + "utils": "@imphnen-frontend-service/service/lib/utils", + "ui": "@imphnen-frontend-service/service/components/ui", + "lib": "@imphnen-frontend-service/service/lib", + "hooks": "@imphnen-frontend-service/service/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/libs/shadcn-ui/package-lock.json b/libs/shadcn-ui/package-lock.json new file mode 100644 index 0000000..652dc97 --- /dev/null +++ b/libs/shadcn-ui/package-lock.json @@ -0,0 +1,1654 @@ +{ + "name": "@imphnen-frontend-service/shadcn-ui", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@imphnen-frontend-service/shadcn-ui", + "version": "0.0.1", + "dependencies": { + "@radix-ui/react-slot": "^1.2.0", + "@tailwindcss/vite": "^4.1.4", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.503.0", + "react-icons": "^5.5.0", + "tailwind-merge": "^3.2.0", + "tailwindcss": "^4.1.4" + }, + "devDependencies": { + "tw-animate-css": "^1.2.8" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.3.tgz", + "integrity": "sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.3.tgz", + "integrity": "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.3.tgz", + "integrity": "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.3.tgz", + "integrity": "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.3.tgz", + "integrity": "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.3.tgz", + "integrity": "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.3.tgz", + "integrity": "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.3.tgz", + "integrity": "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.3.tgz", + "integrity": "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.3.tgz", + "integrity": "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.3.tgz", + "integrity": "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.3.tgz", + "integrity": "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.3.tgz", + "integrity": "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.3.tgz", + "integrity": "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.3.tgz", + "integrity": "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.3.tgz", + "integrity": "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.3.tgz", + "integrity": "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.3.tgz", + "integrity": "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.3.tgz", + "integrity": "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.3.tgz", + "integrity": "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.3.tgz", + "integrity": "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.3.tgz", + "integrity": "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.3.tgz", + "integrity": "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.3.tgz", + "integrity": "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.3.tgz", + "integrity": "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz", + "integrity": "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", + "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz", + "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz", + "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz", + "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz", + "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz", + "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz", + "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz", + "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz", + "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz", + "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz", + "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz", + "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz", + "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz", + "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz", + "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", + "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", + "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz", + "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz", + "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz", + "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.4.tgz", + "integrity": "sha512-MT5118zaiO6x6hNA04OWInuAiP1YISXql8Z+/Y8iisV5nuhM8VXlyhRuqc2PEviPszcXI66W44bCIk500Oolhw==", + "license": "MIT", + "dependencies": { + "enhanced-resolve": "^5.18.1", + "jiti": "^2.4.2", + "lightningcss": "1.29.2", + "tailwindcss": "4.1.4" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.4.tgz", + "integrity": "sha512-p5wOpXyOJx7mKh5MXh5oKk+kqcz8T+bA3z/5VWWeQwFrmuBItGwz8Y2CHk/sJ+dNb9B0nYFfn0rj/cKHZyjahQ==", + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.4", + "@tailwindcss/oxide-darwin-arm64": "4.1.4", + "@tailwindcss/oxide-darwin-x64": "4.1.4", + "@tailwindcss/oxide-freebsd-x64": "4.1.4", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.4", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.4", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.4", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.4", + "@tailwindcss/oxide-linux-x64-musl": "4.1.4", + "@tailwindcss/oxide-wasm32-wasi": "4.1.4", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.4", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.4" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.4.tgz", + "integrity": "sha512-xMMAe/SaCN/vHfQYui3fqaBDEXMu22BVwQ33veLc8ep+DNy7CWN52L+TTG9y1K397w9nkzv+Mw+mZWISiqhmlA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.4.tgz", + "integrity": "sha512-JGRj0SYFuDuAGilWFBlshcexev2hOKfNkoX+0QTksKYq2zgF9VY/vVMq9m8IObYnLna0Xlg+ytCi2FN2rOL0Sg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.4.tgz", + "integrity": "sha512-sdDeLNvs3cYeWsEJ4H1DvjOzaGios4QbBTNLVLVs0XQ0V95bffT3+scptzYGPMjm7xv4+qMhCDrkHwhnUySEzA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.4.tgz", + "integrity": "sha512-VHxAqxqdghM83HslPhRsNhHo91McsxRJaEnShJOMu8mHmEj9Ig7ToHJtDukkuLWLzLboh2XSjq/0zO6wgvykNA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.4.tgz", + "integrity": "sha512-OTU/m/eV4gQKxy9r5acuesqaymyeSCnsx1cFto/I1WhPmi5HDxX1nkzb8KYBiwkHIGg7CTfo/AcGzoXAJBxLfg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.4.tgz", + "integrity": "sha512-hKlLNvbmUC6z5g/J4H+Zx7f7w15whSVImokLPmP6ff1QqTVE+TxUM9PGuNsjHvkvlHUtGTdDnOvGNSEUiXI1Ww==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.4.tgz", + "integrity": "sha512-X3As2xhtgPTY/m5edUtddmZ8rCruvBvtxYLMw9OsZdH01L2gS2icsHRwxdU0dMItNfVmrBezueXZCHxVeeb7Aw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.4.tgz", + "integrity": "sha512-2VG4DqhGaDSmYIu6C4ua2vSLXnJsb/C9liej7TuSO04NK+JJJgJucDUgmX6sn7Gw3Cs5ZJ9ZLrnI0QRDOjLfNQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.4.tgz", + "integrity": "sha512-v+mxVgH2kmur/X5Mdrz9m7TsoVjbdYQT0b4Z+dr+I4RvreCNXyCFELZL/DO0M1RsidZTrm6O1eMnV6zlgEzTMQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.4.tgz", + "integrity": "sha512-2TLe9ir+9esCf6Wm+lLWTMbgklIjiF0pbmDnwmhR9MksVOq+e8aP3TSsXySnBDDvTTVd/vKu1aNttEGj3P6l8Q==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.0", + "@emnapi/runtime": "^1.4.0", + "@emnapi/wasi-threads": "^1.0.1", + "@napi-rs/wasm-runtime": "^0.2.8", + "@tybys/wasm-util": "^0.9.0", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.4.tgz", + "integrity": "sha512-VlnhfilPlO0ltxW9/BgfLI5547PYzqBMPIzRrk4W7uupgCt8z6Trw/tAj6QUtF2om+1MH281Pg+HHUJoLesmng==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.4.tgz", + "integrity": "sha512-+7S63t5zhYjslUGb8NcgLpFXD+Kq1F/zt5Xv5qTv7HaFTG/DHyHD9GA6ieNAxhgyA4IcKa/zy7Xx4Oad2/wuhw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.4.tgz", + "integrity": "sha512-4UQeMrONbvrsXKXXp/uxmdEN5JIJ9RkH7YVzs6AMxC/KC1+Np7WZBaNIco7TEjlkthqxZbt8pU/ipD+hKjm80A==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.1.4", + "@tailwindcss/oxide": "4.1.4", + "tailwindcss": "4.1.4" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "license": "MIT", + "peer": true + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", + "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/esbuild": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz", + "integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==", + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.3", + "@esbuild/android-arm": "0.25.3", + "@esbuild/android-arm64": "0.25.3", + "@esbuild/android-x64": "0.25.3", + "@esbuild/darwin-arm64": "0.25.3", + "@esbuild/darwin-x64": "0.25.3", + "@esbuild/freebsd-arm64": "0.25.3", + "@esbuild/freebsd-x64": "0.25.3", + "@esbuild/linux-arm": "0.25.3", + "@esbuild/linux-arm64": "0.25.3", + "@esbuild/linux-ia32": "0.25.3", + "@esbuild/linux-loong64": "0.25.3", + "@esbuild/linux-mips64el": "0.25.3", + "@esbuild/linux-ppc64": "0.25.3", + "@esbuild/linux-riscv64": "0.25.3", + "@esbuild/linux-s390x": "0.25.3", + "@esbuild/linux-x64": "0.25.3", + "@esbuild/netbsd-arm64": "0.25.3", + "@esbuild/netbsd-x64": "0.25.3", + "@esbuild/openbsd-arm64": "0.25.3", + "@esbuild/openbsd-x64": "0.25.3", + "@esbuild/sunos-x64": "0.25.3", + "@esbuild/win32-arm64": "0.25.3", + "@esbuild/win32-ia32": "0.25.3", + "@esbuild/win32-x64": "0.25.3" + } + }, + "node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/lightningcss": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz", + "integrity": "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.29.2", + "lightningcss-darwin-x64": "1.29.2", + "lightningcss-freebsd-x64": "1.29.2", + "lightningcss-linux-arm-gnueabihf": "1.29.2", + "lightningcss-linux-arm64-gnu": "1.29.2", + "lightningcss-linux-arm64-musl": "1.29.2", + "lightningcss-linux-x64-gnu": "1.29.2", + "lightningcss-linux-x64-musl": "1.29.2", + "lightningcss-win32-arm64-msvc": "1.29.2", + "lightningcss-win32-x64-msvc": "1.29.2" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.29.2.tgz", + "integrity": "sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.29.2.tgz", + "integrity": "sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.29.2.tgz", + "integrity": "sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.29.2.tgz", + "integrity": "sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.29.2.tgz", + "integrity": "sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.29.2.tgz", + "integrity": "sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.29.2.tgz", + "integrity": "sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.29.2.tgz", + "integrity": "sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.29.2.tgz", + "integrity": "sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.29.2.tgz", + "integrity": "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lucide-react": { + "version": "0.503.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.503.0.tgz", + "integrity": "sha512-HGGkdlPWQ0vTF8jJ5TdIqhQXZi6uh3LnNgfZ8MHiuxFfX3RZeA79r2MW2tHAZKlAVfoNE8esm3p+O6VkIvpj6w==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC", + "peer": true + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, + "node_modules/rollup": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz", + "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.40.0", + "@rollup/rollup-android-arm64": "4.40.0", + "@rollup/rollup-darwin-arm64": "4.40.0", + "@rollup/rollup-darwin-x64": "4.40.0", + "@rollup/rollup-freebsd-arm64": "4.40.0", + "@rollup/rollup-freebsd-x64": "4.40.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", + "@rollup/rollup-linux-arm-musleabihf": "4.40.0", + "@rollup/rollup-linux-arm64-gnu": "4.40.0", + "@rollup/rollup-linux-arm64-musl": "4.40.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-musl": "4.40.0", + "@rollup/rollup-linux-s390x-gnu": "4.40.0", + "@rollup/rollup-linux-x64-gnu": "4.40.0", + "@rollup/rollup-linux-x64-musl": "4.40.0", + "@rollup/rollup-win32-arm64-msvc": "4.40.0", + "@rollup/rollup-win32-ia32-msvc": "4.40.0", + "@rollup/rollup-win32-x64-msvc": "4.40.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tailwind-merge": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.2.0.tgz", + "integrity": "sha512-FQT/OVqCD+7edmmJpsgCsY820RTD5AkBryuG5IUqR5YQZSdj5xlH5nLgH7YPths7WsLPSpSBNneJdM8aS8aeFA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.4.tgz", + "integrity": "sha512-1ZIUqtPITFbv/DxRmDr5/agPqJwF69d24m9qmM1939TJehgY539CtzeZRjbLt5G6fSy/7YqqYsfvoTEw9xUI2A==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", + "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "license": "MIT", + "peer": true, + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tw-animate-css": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.2.8.tgz", + "integrity": "sha512-AxSnYRvyFnAiZCUndS3zQZhNfV/B77ZhJ+O7d3K6wfg/jKJY+yv6ahuyXwnyaYA9UdLqnpCwhTRv9pPTBnPR2g==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Wombosvideo" + } + }, + "node_modules/vite": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.3.tgz", + "integrity": "sha512-5nXH+QsELbFKhsEfWLkHrvgRpTdGJzqOZ+utSdmPTvwHmvU6ITTm3xx+mRusihkcI8GeC7lCDyn3kDtiki9scw==", + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + } + } +} diff --git a/libs/shadcn-ui/package.json b/libs/shadcn-ui/package.json new file mode 100644 index 0000000..374ee3c --- /dev/null +++ b/libs/shadcn-ui/package.json @@ -0,0 +1,25 @@ +{ + "name": "@imphnen-frontend-service/shadcn-ui", + "version": "0.0.1", + "main": "./index.js", + "types": "./index.d.ts", + "exports": { + ".": { + "import": "./index.mjs", + "require": "./index.js" + } + }, + "dependencies": { + "@radix-ui/react-slot": "^1.2.0", + "@tailwindcss/vite": "^4.1.4", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.503.0", + "react-icons": "^5.5.0", + "tailwind-merge": "^3.2.0", + "tailwindcss": "^4.1.4" + }, + "devDependencies": { + "tw-animate-css": "^1.2.8" + } +} diff --git a/libs/shadcn-ui/project.json b/libs/shadcn-ui/project.json new file mode 100644 index 0000000..25ff5a6 --- /dev/null +++ b/libs/shadcn-ui/project.json @@ -0,0 +1,9 @@ +{ + "name": "shadcn-ui", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/shadcn-ui/src", + "projectType": "library", + "tags": [], + "// targets": "to see all targets run: nx show project shadcn-ui --web", + "targets": {} +} diff --git a/libs/shadcn-ui/src/atoms/button.tsx b/libs/shadcn-ui/src/atoms/button.tsx new file mode 100644 index 0000000..0ebab5c --- /dev/null +++ b/libs/shadcn-ui/src/atoms/button.tsx @@ -0,0 +1,58 @@ +'use client'; + +import { cn } from '@imphnen-frontend-service/utils'; +import { Slot } from '@radix-ui/react-slot'; +import { cva, type VariantProps } from 'class-variance-authority'; +import * as React from 'react'; + +const buttonVariants = cva( + 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', + { + variants: { + variant: { + default: + 'bg-primary text-primary-foreground shadow hover:bg-primary/90', + destructive: + 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90', + outline: + 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground', + secondary: + 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80', + ghost: 'hover:bg-accent hover:text-accent-foreground', + link: 'text-primary underline-offset-4 hover:underline', + }, + size: { + default: 'h-9 px-4 py-2', + sm: 'h-8 rounded-md px-3 text-xs', + lg: 'h-10 rounded-md px-8', + icon: 'h-9 w-9', + }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, + } +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : 'button'; + return ( + + ); + } +); +Button.displayName = 'Button'; + +export { Button, buttonVariants }; diff --git a/libs/shadcn-ui/src/atoms/icons.tsx b/libs/shadcn-ui/src/atoms/icons.tsx new file mode 100644 index 0000000..9843457 --- /dev/null +++ b/libs/shadcn-ui/src/atoms/icons.tsx @@ -0,0 +1,37 @@ +import { + LuArrowDown as ArrowDownIcon, + LuBookOpen as BookOpenIcon, + LuCode as CodeIcon, + LuFacebook as FacebookIcon, + LuInstagram as InstagramIcon, + LuLaptop as LaptopIcon, + LuMenu as MenuIcon, + LuMessageCircle as MessageCircleIcon, + LuMoon as MoonIcon, + LuQuote as QuoteIcon, + LuShare2 as Share2Icon, + LuSparkles as SparklesIcon, + LuSun as SunIcon, + LuUsers as UsersIcon, + LuVideo as VideoIcon, + LuX as XIcon, +} from 'react-icons/lu'; + +export { + ArrowDownIcon, + BookOpenIcon, + CodeIcon, + FacebookIcon, + InstagramIcon, + LaptopIcon, + MenuIcon, + MessageCircleIcon, + MoonIcon, + QuoteIcon, + Share2Icon, + SparklesIcon, + SunIcon, + UsersIcon, + VideoIcon, + XIcon, +}; diff --git a/libs/shadcn-ui/src/atoms/index.ts b/libs/shadcn-ui/src/atoms/index.ts new file mode 100644 index 0000000..e54f955 --- /dev/null +++ b/libs/shadcn-ui/src/atoms/index.ts @@ -0,0 +1,2 @@ +export * from './button'; +export * from './icons'; diff --git a/libs/shadcn-ui/src/index.css b/libs/shadcn-ui/src/index.css new file mode 100644 index 0000000..d3e908b --- /dev/null +++ b/libs/shadcn-ui/src/index.css @@ -0,0 +1,134 @@ +@import 'tailwindcss'; +@import "tw-animate-css"; + +@custom-variant dark (&:is(.dark *)); + +@theme inline { + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); +} + +:root { + --radius: 1rem; + --background: oklch(1 0 0); + --foreground: oklch(0.141 0.005 285.823); + --card: oklch(1 0 0); + --card-foreground: oklch(0.141 0.005 285.823); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.141 0.005 285.823); + --primary: oklch(0.623 0.214 259.815); + --primary-foreground: oklch(0.97 0.014 254.604); + --secondary: oklch(0.967 0.001 286.375); + --secondary-foreground: oklch(0.21 0.006 285.885); + --muted: oklch(0.967 0.001 286.375); + --muted-foreground: oklch(0.552 0.016 285.938); + --accent: oklch(0.967 0.001 286.375); + --accent-foreground: oklch(0.21 0.006 285.885); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.92 0.004 286.32); + --input: oklch(0.92 0.004 286.32); + --ring: oklch(0.623 0.214 259.815); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.141 0.005 285.823); + --sidebar-primary: oklch(0.623 0.214 259.815); + --sidebar-primary-foreground: oklch(0.97 0.014 254.604); + --sidebar-accent: oklch(0.967 0.001 286.375); + --sidebar-accent-foreground: oklch(0.21 0.006 285.885); + --sidebar-border: oklch(0.92 0.004 286.32); + --sidebar-ring: oklch(0.623 0.214 259.815); +} + +.dark { + --background: oklch(0.141 0.005 285.823); + --foreground: oklch(0.985 0 0); + --card: oklch(0.21 0.006 285.885); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.21 0.006 285.885); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.546 0.245 262.881); + --primary-foreground: oklch(0.379 0.146 265.522); + --secondary: oklch(0.274 0.006 286.033); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.274 0.006 286.033); + --muted-foreground: oklch(0.705 0.015 286.067); + --accent: oklch(0.274 0.006 286.033); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.488 0.243 264.376); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.21 0.006 285.885); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.546 0.245 262.881); + --sidebar-primary-foreground: oklch(0.379 0.146 265.522); + --sidebar-accent: oklch(0.274 0.006 286.033); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.488 0.243 264.376); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} + +/* https://github.com/tailwindlabs/tailwindcss/issues/13129 */ +.container { + margin-left: auto; + margin-right: auto; + padding-left: 2rem; + padding-right: 2rem; + width: 100%; +} +@media (min-width: 1536px) { + .container { + max-width: 1400px; + } +} diff --git a/libs/shadcn-ui/src/index.ts b/libs/shadcn-ui/src/index.ts new file mode 100644 index 0000000..7b99b77 --- /dev/null +++ b/libs/shadcn-ui/src/index.ts @@ -0,0 +1 @@ +export * from './atoms/index'; diff --git a/libs/shadcn-ui/tsconfig.json b/libs/shadcn-ui/tsconfig.json new file mode 100644 index 0000000..6734c59 --- /dev/null +++ b/libs/shadcn-ui/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": false, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "types": ["vite/client"] + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "extends": "../../tsconfig.base.json" +} diff --git a/libs/shadcn-ui/tsconfig.lib.json b/libs/shadcn-ui/tsconfig.lib.json new file mode 100644 index 0000000..0b53580 --- /dev/null +++ b/libs/shadcn-ui/tsconfig.lib.json @@ -0,0 +1,25 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": [ + "node", + "@nx/react/typings/cssmodule.d.ts", + "@nx/react/typings/image.d.ts", + "vite/client", + "next", + "@nx/next/typings/image.d.ts" + ] + }, + "exclude": [ + "**/*.spec.ts", + "**/*.test.ts", + "**/*.spec.tsx", + "**/*.test.tsx", + "**/*.spec.js", + "**/*.test.js", + "**/*.spec.jsx", + "**/*.test.jsx" + ], + "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"] +} diff --git a/libs/shadcn-ui/vite.config.ts b/libs/shadcn-ui/vite.config.ts new file mode 100644 index 0000000..87a8ee8 --- /dev/null +++ b/libs/shadcn-ui/vite.config.ts @@ -0,0 +1,48 @@ +/// +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import dts from 'vite-plugin-dts'; +import * as path from 'path'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/libs/shadcn-ui', + plugins: [ + react(), + nxViteTsPaths(), + nxCopyAssetsPlugin(['*.md']), + dts({ + entryRoot: 'src', + tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'), + }), + ], + // Uncomment this if you are using workers. + // worker: { + // plugins: [ nxViteTsPaths() ], + // }, + // Configuration for building your library. + // See: https://vitejs.dev/guide/build.html#library-mode + build: { + outDir: '../../dist/libs/shadcn-ui', + emptyOutDir: true, + reportCompressedSize: true, + commonjsOptions: { + transformMixedEsModules: true, + }, + lib: { + // Could also be a dictionary or array of multiple entry points. + entry: 'src/index.ts', + name: 'shadcn-ui', + fileName: 'index', + // Change this to the formats you want to support. + // Don't forget to update your package.json as well. + formats: ['es' as const], + }, + rollupOptions: { + // External packages that should not be bundled into your library. + external: ['react', 'react-dom', 'react/jsx-runtime'], + }, + }, +})); diff --git a/libs/ui/src/atoms/input/input.tsx b/libs/ui/src/atoms/input/input.tsx index d8a6bf3..664169a 100644 --- a/libs/ui/src/atoms/input/input.tsx +++ b/libs/ui/src/atoms/input/input.tsx @@ -11,6 +11,7 @@ import { Button } from '../button'; type TInputType = 'text' | 'email' | 'number' | 'password' | 'file'; type TInputSize = 'sm' | 'md' | 'lg'; +type Width = 'standard' | 'custom' type TInputProps = Omit< DetailedHTMLProps, HTMLInputElement>, @@ -18,6 +19,7 @@ type TInputProps = Omit< > & { type?: TInputType; size?: TInputSize; + widthform?: Width; disabled?: boolean; }; @@ -34,6 +36,7 @@ export const Input: FC = ({ type = 'text', size = 'md', placeholder = 'Placeholder', + widthform='standard', disabled, className, ...rest @@ -46,7 +49,7 @@ export const Input: FC = ({ }; const mergedClassName = cn( - 'px-[12px] py-[8px] text-neutral-800 bg-white placeholder:text-neutral-300 border border-neutral-200 hover:border-blue-300 focus:outline-1 focus:outline-blue-500 rounded-md font-bai-jamjuree min-w-70', + `px-[12px] py-[8px] text-neutral-800 bg-white placeholder:text-neutral-300 border border-neutral-200 hover:border-blue-300 focus:outline-1 focus:outline-blue-500 rounded-md font-bai-jamjuree ${widthform === "standard" ? "min-w-70" : ""}`, sizeClasses[size].textSize, disabled && disabledClass, className diff --git a/libs/ui/src/organisms/auth-banner/auth-banner.tsx b/libs/ui/src/organisms/auth-banner/auth-banner.tsx index fbd5927..8b230b4 100644 --- a/libs/ui/src/organisms/auth-banner/auth-banner.tsx +++ b/libs/ui/src/organisms/auth-banner/auth-banner.tsx @@ -5,7 +5,7 @@ import { FC, ReactElement } from "react"; function AuthBanner({text, href}: {text: string, href: string}){ return ( -
+
Banner