next-accelerate 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of next-accelerate might be problematic. Click here for more details.

Files changed (3) hide show
  1. package/README.md +12 -2
  2. package/dist/index.js +1603 -66
  3. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/builders/resource-builder.ts
4
- import path from "path";
4
+ import path4 from "path";
5
5
  import pluralize from "pluralize";
6
6
 
7
7
  // src/templates/forms/create-form.ts
@@ -40,16 +40,16 @@ var formCreateTemplate = (resourceInSingular, resourceInPlural) => `
40
40
  const uploadResp = await fetch(\`\${process.env.NEXT_PUBLIC_FRONTEND_URL}/api/images/posts/\${post.postId}\`, { method: "POST", body: formData });
41
41
 
42
42
  if (uploadResp.ok) {
43
- toast.success("recurso criado com sucesso!");
43
+ toast.success("Resource created successfully!");
44
44
  router.refresh();
45
45
  router.push("/manager");
46
46
  }
47
- else { toast.error("Nenhuma foto foi selecionada."); }
47
+ else { toast.error("No photos were selected."); }
48
48
  } else {
49
49
  toast.error("Erro ao criar o recurso.");
50
50
  }
51
51
  } catch (err) {
52
- toast.error("Erro ao comunicar com o servidor.");
52
+ toast.error("Error communicating with the server.");
53
53
  }
54
54
  };
55
55
 
@@ -149,7 +149,7 @@ export const FormDeleteResource: React.FC<FormDeleteResourceProps> = ({ resource
149
149
  try {
150
150
  const response = await fetch(\`\${process.env.NEXT_PUBLIC_FRONTEND_URL}/api/delete/\${resource}/\${resourceId}\`, { method: "POST" });
151
151
  if (response.ok) {
152
- toast.success("Deletado com sucesso");
152
+ toast.success("Deleted successfully!");
153
153
  router.push("/manager");
154
154
  }
155
155
  else {
@@ -445,6 +445,54 @@ var newPageTemplate = (resourceInSingular, resourceInPlural) => `
445
445
  }
446
446
  `;
447
447
 
448
+ // src/templates/pages/manager-page.ts
449
+ var managerPageTemplate = () => `
450
+ import { authOptions } from "@/app/api/auth/[...nextauth]/route";
451
+ import { Session } from "@/utils/session";
452
+ import { getServerSession } from "next-auth";
453
+ import { redirect } from "next/navigation";
454
+
455
+ export default async function Manager() {
456
+ const session: Session | null = await getServerSession(authOptions);
457
+ if (!session) redirect("/");
458
+
459
+ return (
460
+ <div className="w-full min-h-screen relative bg-(--bg-section-100) transition-colors duration-500 p-6">
461
+ <h2>Bora major.catuca nesse freelas..</h2>
462
+ </div >
463
+ );
464
+ };
465
+ `;
466
+
467
+ // src/templates/routes/manager-context.ts
468
+ var managerContextTemplate = () => `
469
+ "use client";
470
+
471
+ import { createContext, useContext, useState, ReactNode } from "react";
472
+
473
+ type MenuContextType = {
474
+ menuActive: boolean;
475
+ setMenuActive: (value: boolean) => void;
476
+ };
477
+
478
+ const MenuContext = createContext<MenuContextType | undefined>(undefined);
479
+
480
+ export const MenuProvider = ({ children }: { children: ReactNode }) => {
481
+ const [menuActive, setMenuActive] = useState(true);
482
+ return (
483
+ <MenuContext.Provider value={{ menuActive, setMenuActive }}>
484
+ {children}
485
+ </MenuContext.Provider>
486
+ );
487
+ };
488
+
489
+ export const useMenu = () => {
490
+ const context = useContext(MenuContext);
491
+ if (!context) throw new Error("useMenu must be used within MenuProvider");
492
+ return context;
493
+ };
494
+ `;
495
+
448
496
  // src/templates/inputs/input-template.ts
449
497
  var inputTemplate = () => `
450
498
  "use client";
@@ -522,8 +570,598 @@ var inputTemplate = () => `
522
570
  };
523
571
  `;
524
572
 
573
+ // src/templates/routes/nextauth-route.ts
574
+ var nextConfigTemplate = () => `
575
+ import NextAuth, { NextAuthOptions } from "next-auth";
576
+ import CredentialsProvider from "next-auth/providers/credentials";
577
+ import { cookies } from "next/headers";
578
+ import { IAuthenticateUser, requestAuthenticationUser } from "../request-api";
579
+ import { decoderTokenToClaims } from "../decode-claims";
580
+
581
+ export type User = { id: string; name: string; email: string; image: string; userStatus?: string; accessToken: string; };
582
+
583
+ export const authOptions: NextAuthOptions = {
584
+ pages: {
585
+ signIn: "/",
586
+ },
587
+ providers: [
588
+ CredentialsProvider({
589
+ name: "Credentials",
590
+ credentials: {
591
+ email: { label: "Email", type: "email", placeholder: "example@gmail.com" },
592
+ password: { label: "Password", type: "password" },
593
+ },
594
+ async authorize(credentials, req): Promise<User | null> {
595
+ const user: IAuthenticateUser = {
596
+ email: credentials?.email,
597
+ password: credentials?.password,
598
+ clientType: "web",
599
+ };
600
+ const resposta = await requestAuthenticationUser(user);
601
+
602
+ if (resposta.accessToken && resposta.accessToken !== "User with credentials not found or invalid") {
603
+ (await cookies()).set("jwt_back", resposta.accessToken, { httpOnly: true, sameSite: "lax", path: "/", maxAge: 3600 });
604
+ (await cookies()).set("jwt_back_refresh", resposta.refreshToken, { httpOnly: true, sameSite: "lax", path: "/", maxAge: 3600 });
605
+
606
+ const decodedClaims = decoderTokenToClaims(resposta.accessToken);
607
+ return { id: decodedClaims?.id ?? "", name: decodedClaims?.username ?? "", email: decodedClaims?.email ?? "", image: decodedClaims?.imagem ?? "", userStatus: decodedClaims?.userStatus ?? "unknown", accessToken: resposta.accessToken } as User;
608
+ }
609
+ return null;
610
+ },
611
+ }),
612
+ ],
613
+
614
+ callbacks: {
615
+ async jwt({ token, user, trigger, session }) {
616
+ if (user) {
617
+ const u = user as any;
618
+ token.id = u.id ?? "";
619
+ token.name = u.name ?? "";
620
+ token.email = u.email ?? "";
621
+ token.image = u.image ?? "";
622
+ token.userStatus = u.userStatus ?? "";
623
+ if ("accessToken" in u) token.accessToken = u.accessToken ?? "";
624
+ }
625
+
626
+ if (trigger === "update" && session?.user) {
627
+ token.name = session.user.name ?? token.name;
628
+ token.email = session.user.email ?? token.email;
629
+ token.image = session.user.image ?? token.image;
630
+ token.userStatus = session.user.userStatus ?? token.userStatus;
631
+ }
632
+
633
+ return token;
634
+ },
635
+ async session({ session, token }) {
636
+ if (session.user) {
637
+ (session.user as any).id = (token as any).id;
638
+ session.user.name = (token as any).name;
639
+ session.user.email = (token as any).email;
640
+ const image = typeof (token as any).image === "string" ? (token as any).image : undefined;
641
+ session.user.image = image;
642
+ (session.user as any).userStatus = (token as any).userStatus;
643
+ }
644
+ (session as any).accessToken = (token as any).accessToken ?? null;
645
+ return session;
646
+ },
647
+ },
648
+ };
649
+
650
+ const handler = NextAuth(authOptions);
651
+ export { handler as GET, handler as POST };
652
+
653
+ `;
654
+
655
+ // src/templates/config/decode-claims.ts
656
+ var nextDecodeClaimsTemplate = () => `
657
+ import { jwtDecode } from 'jwt-decode';
658
+
659
+ export interface IObjectClaims { id: string; username: string; email: string; imagem: string; roles: string | string[]; userStatus?: string; };
660
+
661
+ export const decoderTokenToClaims = (token: string): Partial<IObjectClaims> | null => {
662
+ if (token) {
663
+ try {
664
+ const decodedToken = jwtDecode(token) as any;
665
+ return { id: decodedToken.userId, username: decodedToken.userName, email: decodedToken.email, imagem: decodedToken.image, roles: decodedToken.roles, userStatus: decodedToken.userStatus };
666
+ } catch (error) {
667
+ return null;
668
+ }
669
+ } else {
670
+ return null;
671
+ }
672
+ }
673
+ `;
674
+
675
+ // src/templates/config/request-api.ts
676
+ var nextRequestApiTemplate = () => `
677
+ export interface IAuthenticateUser { email: string | undefined; password: string | undefined; clientType: string; };
678
+
679
+ export const requestAuthenticationUser = async (user: IAuthenticateUser) => {
680
+ const resposta = await fetch(\`\${process.env.NEXT_BACKEND_URL}/auth/login\`, {
681
+ method: 'POST',
682
+ headers: {
683
+ "Content-Type": "application/json"
684
+ },
685
+ body: JSON.stringify(user)
686
+ });
687
+ if (resposta.ok) return await resposta.json();
688
+ return null;
689
+
690
+ };
691
+ `;
692
+
693
+ // src/templates/config/singout.ts
694
+ var nextSignOutTemplate = () => `
695
+ import { NextApiRequest, NextApiResponse } from 'next';
696
+ import { signOut } from 'next-auth/react';
697
+
698
+ export default async (req: NextApiRequest, res: NextApiResponse) => {
699
+ if (req.method === 'POST') {
700
+ try {
701
+ await signOut({ redirect: false });
702
+ } catch (error) {
703
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
704
+ res.status(500).json({ message: 'Erro ao realizar logout', error: errorMessage });
705
+ }
706
+ } else {
707
+ res.status(405).json({ message: 'Method not allowed' });
708
+ }
709
+ };
710
+ `;
711
+
712
+ // src/templates/config/session.ts
713
+ var nextSessionTypeTemplate = () => `
714
+ export interface Session {
715
+ user: { name: string; email: string; image: string; id: string; userStatus: string; };
716
+ accessToken: string;
717
+ }
718
+ `;
719
+
720
+ // src/templates/config/environment.ts
721
+ var environmentTemplate = () => `
722
+ NEXTAUTH_URL=http://localhost:3000
723
+ NEXTAUTH_SECRET=secret_key
724
+ `;
725
+
726
+ // src/templates/forms/forgot-form.ts
727
+ var formForgotTemplate = () => `
728
+ "use client";
729
+
730
+ import { sendEmailServerSideProps, Response } from "@/utils/actions";
731
+ import Link from "next/link";
732
+ import { redirect } from "next/navigation";
733
+ import { FormEvent, useEffect, useState } from "react";
734
+ import { toast } from "react-toastify";
735
+
736
+ export const FormForgot = () => {
737
+ const [data, setData] = useState<Response>({ statusCode: 0, message: '' });
738
+
739
+ useEffect(() => { }, [data]);
740
+
741
+ const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
742
+ e.preventDefault();
743
+ const email = (e.target as HTMLFormElement).email.value;
744
+ const data = await sendEmailServerSideProps(email);
745
+ setData(data);
746
+ if (data.statusCode != 200) {
747
+ toast.error(data.message);
748
+ } else {
749
+ redirect('/forgotredef');
750
+ }
751
+ }
752
+
753
+ return (
754
+ <div className="w-full max-w-md p-8 space-y-6 bg-white rounded shadow-md">
755
+ <h2 className="text-2xl font-bold text-center">Forgot Password</h2>
756
+ <form className="space-y-4" onSubmit={handleSubmit}>
757
+ <label htmlFor="email" className="block text-sm font-medium text-gray-700">Email</label>
758
+ <input id="email" type="email" className="w-full px-3 py-2 mt-1 border rounded shadow-sm focus:outline-none focus:ring focus:border-blue-300" />
759
+
760
+ <button type="submit" className="w-full px-4 py-2 font-bold text-white bg-blue-500 rounded hover:bg-blue-700">Send</button>
761
+ <div className="mt-3 flex justify-between">
762
+ <Link href="/" className="text-sm text-blue-500 hover:underline" > Back to login </Link>
763
+ <Link href="/cadastro" className="text-sm text-blue-500 hover:underline" > Create account </Link>
764
+ </div>
765
+ </form>
766
+ </div>
767
+ );
768
+ }
769
+ `;
770
+
771
+ // src/templates/forms/login-form.ts
772
+ var formLoginTemplate = () => `
773
+ "use client";
774
+
775
+ import { startTransition } from "react";
776
+ import { signIn, signOut } from "next-auth/react";
777
+ import { useId, useActionState, useEffect, useState } from "react";
778
+ import { useForm } from "react-hook-form";
779
+ import { toast } from "react-toastify";
780
+ import { usePathname } from "next/navigation";
781
+ import Link from "next/link";
782
+
783
+ import { hiddenPaths } from "./hidenpath";
784
+ import { ButtonGeneric } from "@/components/ButtonGeneric";
785
+
786
+ type TypeLoginData = { email: string; password: string; };
787
+
788
+ export const FormLogin: React.FC = () => {
789
+ const pathname = usePathname();
790
+ const [mounted, setMounted] = useState(false);
791
+ useEffect(() => setMounted(true), []);
792
+
793
+ const idEmail = useId();
794
+ const idPassword = useId();
795
+ const { register, handleSubmit, formState: { errors } } = useForm<TypeLoginData>();
796
+
797
+ const [state, setState, isPending] = useActionState(
798
+ async (prevState: any, data: TypeLoginData) => {
799
+ try {
800
+ const response = await signIn("credentials", { redirect: false, ...data, callbackUrl: "/manager" });
801
+
802
+ if (response?.status === 401) {
803
+ toast.error("invalid credentials");
804
+ return { success: false };
805
+ }
806
+
807
+ if (response?.url) {
808
+ window.location.href = response.url;
809
+ }
810
+
811
+ return { success: true };
812
+ } catch (error) {
813
+ toast.error(
814
+ error instanceof Error ? error.message : "error authenticating"
815
+ );
816
+ return { success: false };
817
+ }
818
+ },
819
+ { success: false }
820
+ );
821
+
822
+ const onSubmitForm = (data: TypeLoginData) => {
823
+ startTransition(() => { setState(data); });
824
+ };
825
+
826
+ const handleLogout = async () => {
827
+ await signOut({ callbackUrl: "/" });
828
+ };
829
+
830
+ if (!mounted) {
831
+ return <div style={{ height: 35 }} />;
832
+ }
833
+
834
+ const isHidden = hiddenPaths.some((path) => pathname.startsWith(path));
835
+ if (isHidden) { return <ButtonGeneric label="Sair" onClick={handleLogout} />; };
836
+
837
+ return (
838
+ <form onSubmit={handleSubmit(onSubmitForm)} className="w-full flex flex-col items-center gap-3 lg:gap-3">
839
+ {/* Email */}
840
+ <div className="flex flex-col lg:flex-row lg:items-center max-h-8.75 gap-2">
841
+ <label htmlFor={idEmail} className="text-sm font-semibold text-(--text-color)">e-mail:</label>
842
+ <input id={idEmail} type="text" {...register("email", { required: "Este campo \xE9 obrigat\xF3rio" })}
843
+ className="min-h-7.5 max-w-30 lg:max-w-30 text-(--text-color) px-3 border border-gray-300 rounded-full outline-none text-sm bg-(--bg-color) caret-(--text-color) text-center transition-all duration-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 w-full lg:w-auto"
844
+ />
845
+ {errors.email && (
846
+ <span className="text-red-600 text-sm mt-2">
847
+ {errors.email.message}
848
+ </span>
849
+ )}
850
+ </div>
851
+
852
+ {/* Password */}
853
+ <div className="flex flex-col lg:flex-row lg:items-center max-h-8.75 gap-2">
854
+ <label htmlFor={idPassword} className="text-sm font-semibold text-(--text-color)">password:</label>
855
+ <input id={idPassword} type="password" {...register("password", { required: "Este campo \xE9 obrigat\xF3rio" })}
856
+ className="min-h-7.5 max-w-30 lg:max-w-30 text-(--text-color) px-3 border border-gray-300 rounded-full outline-none text-sm bg-(--bg-color) caret-(--text-color) text-center transition-all duration-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 w-full lg:w-auto"
857
+ />
858
+ {errors.password && (
859
+ <span className="text-red-600 text-sm mt-2">
860
+ {errors.password.message}
861
+ </span>
862
+ )}
863
+ </div>
864
+
865
+ {/* Submit */}
866
+ <div className="mt-2 lg:mt-0">
867
+ <ButtonGeneric type="submit" label={isPending ? "Entrando..." : "Entrar"} disabled={isPending} className={\`w-full lg:w-auto justify-center \${isPending ? "opacity-70 cursor-not-allowed" : ""}\`} />
868
+ </div>
869
+
870
+ {/* Forgot password */}
871
+ <div className="h-full flex flex-col justify-center items-center mt-2 lg:mt-0 text-sm">
872
+ <Link href="/cadastro" className="text-blue-700 cursor-pointer">cadastre-se</Link>
873
+ <Link href="/forgotpassword" className="text-blue-700 cursor-pointer">esqueceu sua senha?</Link>
874
+ </div>
875
+ </form>
876
+ );
877
+ };
878
+ `;
879
+
880
+ // src/templates/forms/redef-form.ts
881
+ var formRedefTemplate = () => `
882
+ "use client";
883
+
884
+ import { useForm, FormProvider } from 'react-hook-form';
885
+ import { redirect } from 'next/navigation';
886
+ import { yupResolver } from '@hookform/resolvers/yup';
887
+ import { formSchema } from './formredef-scheme';
888
+ import { IFormInputRedem, sendRedemServerSideProps } from '../../../utils/actions';
889
+ import { InputCustom } from '@/components/Shared/Inputs';
890
+
891
+ export const FormRedef = () => {
892
+ const methods = useForm<IFormInputRedem>({ resolver: yupResolver(formSchema) , mode: 'onChange', defaultValues: { token: '', password: '', confirpassword: '' } });
893
+
894
+ const onSubmit = async (data: IFormInputRedem) => {
895
+ const response = await sendRedemServerSideProps(data);
896
+ if (response.statusCode === 200) {
897
+ redirect("/login");
898
+ }
899
+ };
900
+
901
+ return (
902
+ <FormProvider {...methods}>
903
+ <form onSubmit={methods.handleSubmit(onSubmit)} className="space-y-4 p-6 bg-white shadow-md rounded-md">
904
+ <InputCustom name="token" label="C\xF3digo" required={true} />
905
+ <InputCustom name="password" label="Senha" type="password" required={true} />
906
+ <InputCustom name="confirpassword" label="Confirme a senha" type="password" required={true} />
907
+
908
+ <button type="submit" className="w-full p-2 bg-blue-500 text-white rounded-md hover:bg-blue-600">Registrar</button>
909
+ </form>
910
+ </FormProvider>
911
+ );
912
+ }
913
+ `;
914
+
915
+ // src/templates/forms/update-user-form.ts
916
+ var formUpdateUserTemplate = () => `
917
+ "use client";
918
+
919
+ import { useForm, FormProvider, type Resolver } from "react-hook-form";
920
+ import { yupResolver } from "@hookform/resolvers/yup";
921
+ import { useRouter } from "next/navigation";
922
+ import { useState } from "react";
923
+ import { toast } from "react-toastify";
924
+ import { updateFormSchema, UpdateFormSchemaType } from "./formregister-scheme";
925
+ import { useSession } from "next-auth/react";
926
+ import { InputCustom } from "@/components/Shared/Inputs";
927
+ import { ButtonGeneric } from "@/components/Shared/Buttons/ButtonGeneric";
928
+ import { UpdateUserInput } from "@/utils/users";
929
+
930
+ export const FormUpdateUser = () => {
931
+ const [photoFile, setPhotoFile] = useState<File | null>(null);
932
+ const router = useRouter();
933
+ const { data: session, update } = useSession();
934
+
935
+ const methods = useForm<UpdateFormSchemaType>({
936
+ resolver: yupResolver(updateFormSchema) as Resolver<UpdateFormSchemaType>, mode: "onChange",
937
+ defaultValues: { photo: "", bio: "", skills: "", hourly_rate: null },
938
+ });
939
+
940
+ const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
941
+ const file = e.target.files?.[0] ?? null;
942
+ setPhotoFile(file);
943
+ };
944
+
945
+ const handleSubmitUpdate = async (data: UpdateFormSchemaType) => {
946
+ try {
947
+ const payload: UpdateUserInput = {
948
+ photo: photoFile?.name ?? undefined,
949
+ bio: data.bio ?? undefined,
950
+ skills: data.skills?.split(","),
951
+ hourly_rate: data.hourly_rate ?? undefined,
952
+ score: undefined,
953
+ checked: undefined,
954
+ userStatus: undefined,
955
+ };
956
+
957
+ const response = await fetch(\`\${process.env.NEXT_PUBLIC_URL}/api/users/update\`, {
958
+ method: "POST",
959
+ headers: { "Content-Type": "application/json" },
960
+ body: JSON.stringify(payload),
961
+ });
962
+
963
+ if (!response.ok) {
964
+ const errorData = await response.json();
965
+ toast.error(errorData.message || "Erro ao atualizar informa\xE7\xF5es do usu\xE1rio");
966
+ return;
967
+ }
968
+ const usersession = await response.json();
969
+ // \u{1F539} Upload da foto (caso exista)
970
+ if (photoFile && usersession) {
971
+ const formData = new FormData();
972
+ formData.append("photo", photoFile, photoFile.name);
973
+ const uploadResp = await fetch(\`\${process.env.NEXT_PUBLIC_URL}/api/users/photo\`, { method: "POST", body: formData });
974
+ if (uploadResp.ok) {
975
+ const userAtualizado = await uploadResp.json();
976
+ const newSession = await update({ user: { ...session?.user, image: userAtualizado.photo, } });
977
+ if (newSession) {
978
+ toast.success("Dados do usu\xE1rio atualizados com sucesso!");
979
+ }
980
+ } else {
981
+ toast.error("Erro ao enviar a foto");
982
+ }
983
+ }
984
+ router.refresh();
985
+ router.push("/manager");
986
+ } catch (error) {
987
+ console.error(error);
988
+ toast.error("Erro de comunica\xE7\xE3o com o servidor");
989
+ }
990
+ };
991
+
992
+ return (
993
+ <FormProvider {...methods}>
994
+ <form onSubmit={methods.handleSubmit(handleSubmitUpdate)}
995
+ className="flex flex-col gap-6 sm:p-6 lg:p-8 w-full bg-white/80 backdrop-blur-md rounded-3xl shadow-[0_8px_30px_rgb(0,0,0,0.12)] border border-gray-100 max-w-[90%] mx-auto"
996
+ >
997
+ {/* Foto */}
998
+ <div>
999
+ <label className="block text-sm font-medium text-gray-700">Foto do usu\xE1rio</label>
1000
+ <input type="file" accept="image/*" onChange={handleFileChange} className="mt-2 border border-gray-300 rounded-lg p-2 w-full" />
1001
+ {photoFile && <p className="text-sm text-gray-600 mt-1">{photoFile.name}</p>}
1002
+ </div>
1003
+
1004
+ {/* Bio */}
1005
+ <InputCustom name="bio" label="Biografia" type="text" />
1006
+
1007
+ {/* Skills */}
1008
+ <InputCustom name="skills" label="Habilidades (separadas por v\xEDrgula)" type="text" />
1009
+
1010
+ {/* Hourly Rate */}
1011
+ <InputCustom name="hourly_rate" label="Valor por hora" type="number" />
1012
+
1013
+ {/* Bot\xE3o */}
1014
+ <ButtonGeneric type="submit" label="Atualizar Usu\xE1rio" />
1015
+ </form>
1016
+ </FormProvider>
1017
+ );
1018
+ };
1019
+
1020
+ `;
1021
+
1022
+ // src/templates/forms/register-form.ts
1023
+ var formRegisterTemplate = () => `
1024
+ "use client";
1025
+
1026
+ import { useForm, FormProvider } from "react-hook-form";
1027
+ import { useRouter } from "next/navigation";
1028
+ import { yupResolver } from "@hookform/resolvers/yup";
1029
+ import { formSchema, FormSchemaType } from "./formregister-scheme";
1030
+ import { toast } from "react-toastify";
1031
+ import { InputCustom } from "@/components/Shared/Inputs";
1032
+ import { ButtonGeneric } from "@/components/Shared/Buttons/ButtonGeneric";
1033
+
1034
+ export const FormRegister = () => {
1035
+ const router = useRouter();
1036
+ const methods = useForm<FormSchemaType>({
1037
+ resolver: yupResolver(formSchema), mode: "onChange",
1038
+ defaultValues: { name: "", email: "", cpf: "", dateOfBirth: new Date(), password: "", repeatPassword: "", phone: "", country: "", state: "", city: "", address: "" },
1039
+ });
1040
+
1041
+ const handlesubmitRegister = async (data: FormSchemaType) => {
1042
+ try {
1043
+ const response = await fetch(\`http://localhost:3001/api/users\`, {
1044
+ method: "POST",
1045
+ headers: { "Content-Type": "application/json" },
1046
+ body: JSON.stringify(data),
1047
+ });
1048
+
1049
+ if (response && response.status === 201) {
1050
+ toast.success("Usu\xE1rio criado com sucesso, 1 seg e ser\xE1 redirecionado!");
1051
+ router.push("/");
1052
+ } else {
1053
+ toast.error(response.status === 400 ? "Email j\xE1 existe na aplica\xE7\xE3o, prossiga para redefini\xE7\xE3o de senha" : "Erro ao registrar");
1054
+ }
1055
+ } catch (error) {
1056
+ throw new Error("Ocorreu um erro de comunica\xE7\xE3o no Next...");
1057
+ }
1058
+ };
1059
+
1060
+ return (
1061
+ <FormProvider {...methods}>
1062
+ <form onSubmit={methods.handleSubmit(handlesubmitRegister)} className="flex flex-col gap-4 p-6 bg-white rounded-md w-full max-w-[80%] mx-auto">
1063
+ {/* Linha 1 */}
1064
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
1065
+ <InputCustom name="name" label="Nome completo" required />
1066
+ <InputCustom name="email" label="Email" type="email" required />
1067
+ </div>
1068
+
1069
+ {/* Linha 2 */}
1070
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
1071
+ <InputCustom name="cpf" label="CPF" required />
1072
+ <InputCustom name="dateOfBirth" label="Data de nascimento" type="date" asDate required />
1073
+ <InputCustom name="phone" label="Telefone" required />
1074
+ </div>
1075
+
1076
+ {/* Linha 3 */}
1077
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
1078
+ <InputCustom name="country" label="Pa\xEDs" required />
1079
+ <InputCustom name="state" label="Estado" required />
1080
+ <InputCustom name="city" label="Cidade" required />
1081
+ </div>
1082
+
1083
+ {/* Linha 4 */}
1084
+ <InputCustom name="address" label="Endere\xE7o" required />
1085
+
1086
+ {/* Linha 5 - Senhas */}
1087
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
1088
+ <InputCustom name="password" label="Senha" type="password" required />
1089
+ <InputCustom name="repeatPassword" label="Confirme sua senha" type="password" required />
1090
+ </div>
1091
+
1092
+ {/* Bot\xE3o */}
1093
+ <div className="flex justify-center mt-4">
1094
+ <ButtonGeneric label="Registrar" />
1095
+ </div>
1096
+ </form>
1097
+ </FormProvider>
1098
+ );
1099
+ };
1100
+
1101
+ `;
1102
+
1103
+ // src/templates/forms/login-wrapper-form.ts
1104
+ var formLoginWrapperTemplate = () => `
1105
+ "use client";
1106
+
1107
+ import { useState } from "react";
1108
+ import { usePathname } from "next/navigation";
1109
+ import { motion, AnimatePresence } from "framer-motion";
1110
+ import { FormLogin } from ".";
1111
+ import { XIcon } from "lucide-react";
1112
+ import { hiddenPaths } from "./hidenpath";
1113
+ import { ButtonGeneric } from "@/components/ButtonGeneric";
1114
+
1115
+ export const FormLoginWrapper = () => {
1116
+ const pathname = usePathname();
1117
+ const [open, setOpen] = useState(false);
1118
+
1119
+ const isHidden = hiddenPaths.some((path) => pathname.startsWith(path));
1120
+
1121
+ // If the route is protected \u2192 only the FormLogin itself is rendered (which displays the "Log Out" button).
1122
+ if (isHidden) {
1123
+ return <FormLogin />;
1124
+ }
1125
+
1126
+ return (
1127
+ <>
1128
+ {/* Mobile & Tablets (<lg): displays normal form */}
1129
+ <div className="bg-(--bg-color) block md:hidden">
1130
+ <FormLogin />
1131
+ </div>
1132
+
1133
+ {/* Desktop (lg:): button + modal */}
1134
+ <div className="hidden md:flex">
1135
+ <ButtonGeneric label="Login" onClick={() => setOpen(true)} />
1136
+ </div>
1137
+
1138
+ {/* MODAL */}
1139
+ <AnimatePresence>
1140
+ {open && (
1141
+ <motion.div className="fixed inset-0 bg-black/40 backdrop-blur-sm flex items-center justify-center z-999" initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}>
1142
+ <motion.div className="bg-(--bg-color) rounded-2xl p-6 w-105 shadow-xl relative" initial={{ scale: 0.8, opacity: 0 }} animate={{ scale: 1, opacity: 1 }} exit={{ scale: 0.8, opacity: 0 }} transition={{ type: "spring", stiffness: 120 }}>
1143
+ {/* Bot\xE3o fechar */}
1144
+ <button onClick={() => setOpen(false)} className="absolute right-4 top-4 text-gray-600 hover:text-black text-xl">
1145
+ <XIcon />
1146
+ </button>
1147
+
1148
+ <h2 className="text-lg font-semibold mb-4">Login</h2>
1149
+
1150
+ {/* Form within the modal */}
1151
+ <FormLogin />
1152
+ </motion.div>
1153
+ </motion.div>
1154
+ )}
1155
+ </AnimatePresence>
1156
+ </>
1157
+ );
1158
+ };
1159
+
1160
+ `;
1161
+
525
1162
  // src/utils/fs.ts
526
1163
  import fs from "fs";
1164
+ import path from "path";
527
1165
  var createDir = (dirPath) => {
528
1166
  if (!fs.existsSync(dirPath)) {
529
1167
  fs.mkdirSync(dirPath, { recursive: true });
@@ -538,6 +1176,15 @@ var createFile = (filePath, content) => {
538
1176
  function pathExists(p) {
539
1177
  return fs.existsSync(p);
540
1178
  }
1179
+ var moveFile = (fromPath, toPath) => {
1180
+ if (fs.existsSync(fromPath)) {
1181
+ const toDir = path.dirname(toPath);
1182
+ if (!fs.existsSync(toDir)) {
1183
+ fs.mkdirSync(toDir, { recursive: true });
1184
+ }
1185
+ fs.renameSync(fromPath, toPath);
1186
+ }
1187
+ };
541
1188
 
542
1189
  // src/utils/string.ts
543
1190
  var capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
@@ -643,6 +1290,42 @@ function gitCommit(message) {
643
1290
 
644
1291
  // src/utils/services/install-dependences.service.ts
645
1292
  import { execSync as execSync2 } from "child_process";
1293
+
1294
+ // src/utils/guards/next-verify.guard.ts
1295
+ import fs2 from "fs";
1296
+ import path2 from "path";
1297
+ function nextProjectGuardSimple() {
1298
+ const cwd = process.cwd();
1299
+ const pkgPath = path2.join(cwd, "package.json");
1300
+ if (!fs2.existsSync(pkgPath)) {
1301
+ console.error("\x1B[31m \u2716 Erro \x1B[0mNenhum package.json encontrado neste diret\xF3rio.");
1302
+ process.exit(1);
1303
+ }
1304
+ const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
1305
+ const hasNext = pkg.dependencies?.next || pkg.devDependencies?.next;
1306
+ if (!hasNext) {
1307
+ console.error("\x1B[31m \u2716 Erro \x1B[0mExecute dentro de um projeto Next.js");
1308
+ process.exit(1);
1309
+ }
1310
+ }
1311
+
1312
+ // src/utils/guards/dependency.guard.ts
1313
+ import fs3 from "fs";
1314
+ import path3 from "path";
1315
+ function hasDependency(pkgName) {
1316
+ const cwd = process.cwd();
1317
+ const pkgPath = path3.join(cwd, "package.json");
1318
+ if (!fs3.existsSync(pkgPath)) {
1319
+ console.error("\x1B[31m \u2716 Erro \x1B[0mNenhum package.json encontrado.");
1320
+ process.exit(1);
1321
+ }
1322
+ const pkg = JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
1323
+ return Boolean(
1324
+ pkg.dependencies?.[pkgName] || pkg.devDependencies?.[pkgName] || pkg.peerDependencies?.[pkgName]
1325
+ );
1326
+ }
1327
+
1328
+ // src/utils/services/install-dependences.service.ts
646
1329
  var DependencyInstaller = class _DependencyInstaller {
647
1330
  constructor() {
648
1331
  this.hasInstalled = false;
@@ -654,11 +1337,11 @@ var DependencyInstaller = class _DependencyInstaller {
654
1337
  return _DependencyInstaller.instance;
655
1338
  }
656
1339
  async install() {
657
- if (this.hasInstalled) return;
1340
+ if (this.hasInstalled || hasDependency("react-hook-form")) return;
658
1341
  this.hasInstalled = true;
659
1342
  try {
660
1343
  execSync2(`
661
- npm i lucide-react &&
1344
+ npm install lucide-react &&
662
1345
  npm install next-auth &&
663
1346
  npm install jwt-decode &&
664
1347
  npm install --save-dev @types/jwt-decode &&
@@ -680,7 +1363,6 @@ var DependencyInstaller = class _DependencyInstaller {
680
1363
  var NextResourceBuilder = class {
681
1364
  constructor(inputName, options) {
682
1365
  this.inputName = inputName;
683
- this.commitQueue = [];
684
1366
  this.options = options;
685
1367
  this.resource = pluralize(inputName.toLowerCase());
686
1368
  this.singular = pluralize.singular(this.resource);
@@ -689,132 +1371,983 @@ var NextResourceBuilder = class {
689
1371
  if (!this.options?.git) return;
690
1372
  gitCommit(message);
691
1373
  }
692
- registerCommit(message) {
693
- if (!this.options?.git) return;
694
- this.commitQueue.push(message);
695
- }
696
1374
  setBasePath() {
697
- this.basePath = path.join(process.cwd(), "src/app/(privates)", this.resource);
1375
+ this.basePath = path4.join(process.cwd(), "src/app/(privates)", this.resource);
698
1376
  createDir(this.basePath);
699
1377
  return this;
700
1378
  }
701
1379
  setBasePathForForm() {
702
- this.basePath = path.join(process.cwd(), "src/forms");
1380
+ this.basePath = path4.join(process.cwd(), "src/forms");
703
1381
  createDir(this.basePath);
704
1382
  return this;
705
1383
  }
706
1384
  setBasePathForComponents() {
707
- this.basePath = path.join(process.cwd(), "src/components");
1385
+ this.basePath = path4.join(process.cwd(), "src/components");
708
1386
  createDir(this.basePath);
709
1387
  return this;
710
1388
  }
711
1389
  createComponentInputCustom() {
712
- const componentPath = path.join(this.basePath, "Inputs/InputCustom");
1390
+ const componentPath = path4.join(this.basePath, "Inputs/InputCustom");
713
1391
  createDir(componentPath);
714
- createFile(path.join(componentPath, "index.tsx"), inputTemplate());
715
- if (this.options?.git) this.createCommit(`feat(${this.resource}): add input custom component`);
1392
+ createFile(path4.join(componentPath, "index.tsx"), inputTemplate());
1393
+ if (this.options?.git) this.createCommit(`feat(input): add input custom component`);
716
1394
  return this;
717
1395
  }
718
1396
  createListPage() {
719
- createFile(path.join(this.basePath, "page.tsx"), listPageTemplate(capitalize(this.singular), capitalize(this.resource)));
1397
+ createFile(path4.join(this.basePath, "page.tsx"), listPageTemplate(capitalize(this.singular), capitalize(this.resource)));
720
1398
  return this;
721
1399
  }
722
1400
  createDetailPage() {
723
- const detailDir = path.join(this.basePath, `[${this.singular}Id]`);
1401
+ const detailDir = path4.join(this.basePath, `[${this.singular}Id]`);
724
1402
  createDir(detailDir);
725
- createFile(path.join(detailDir, "page.tsx"), detailPageTemplate(capitalize(this.singular)));
726
- const editDir = path.join(detailDir, "edit");
1403
+ createFile(path4.join(detailDir, "page.tsx"), detailPageTemplate(capitalize(this.singular)));
1404
+ const editDir = path4.join(detailDir, "edit");
727
1405
  createDir(editDir);
728
- createFile(path.join(editDir, "page.tsx"), updatePageTemplate(capitalize(this.singular), capitalize(this.resource)));
729
- const deleteDir = path.join(detailDir, "delete");
1406
+ createFile(path4.join(editDir, "page.tsx"), updatePageTemplate(capitalize(this.singular), capitalize(this.resource)));
1407
+ const deleteDir = path4.join(detailDir, "delete");
730
1408
  createDir(deleteDir);
731
- createFile(path.join(deleteDir, "page.tsx"), deletePageTemplate(capitalize(this.singular)));
732
- this.registerCommit(`feat(${this.resource}): add all pages for detail view`);
1409
+ createFile(path4.join(deleteDir, "page.tsx"), deletePageTemplate(capitalize(this.singular)));
1410
+ this.createCommit(`feat(${this.resource}): add all pages for detail view`);
733
1411
  return this;
734
1412
  }
735
1413
  createNewPage() {
736
- const dir = path.join(this.basePath, "new");
1414
+ const dir = path4.join(this.basePath, "new");
737
1415
  createDir(dir);
738
- createFile(path.join(dir, "page.tsx"), newPageTemplate(capitalize(this.singular), capitalize(this.resource)));
1416
+ createFile(path4.join(dir, "page.tsx"), newPageTemplate(capitalize(this.singular), capitalize(this.resource)));
739
1417
  return this;
740
1418
  }
741
1419
  createCrudForm() {
742
- const sharedPath = path.join(this.basePath, "shared");
743
- const deletePath = path.join(sharedPath, "FormDelete");
1420
+ const sharedPath = path4.join(this.basePath, "shared");
1421
+ const deletePath = path4.join(sharedPath, "FormDelete");
744
1422
  if (!pathExists(deletePath)) {
745
1423
  createDir(sharedPath);
746
1424
  createDir(deletePath);
747
- createFile(path.join(deletePath, "index.tsx"), formDeleteTemplate(capitalize(this.singular), capitalize(this.resource)));
748
- createFile(path.join(sharedPath, "index.ts"), `export { FormDeleteResource } from "./FormDelete";
1425
+ createFile(path4.join(deletePath, "index.tsx"), formDeleteTemplate(capitalize(this.singular), capitalize(this.resource)));
1426
+ createFile(path4.join(sharedPath, "index.ts"), `export { FormDeleteResource } from "./FormDelete";
749
1427
  `);
750
1428
  }
751
- const resourcePath = path.join(this.basePath, this.resource);
1429
+ const resourcePath = path4.join(this.basePath, this.resource);
752
1430
  createDir(resourcePath);
753
1431
  DependencyInstaller.getInstance().install();
754
- const formNewPath = path.join(resourcePath, "FormNew");
1432
+ const formNewPath = path4.join(resourcePath, "FormNew");
755
1433
  createDir(formNewPath);
756
- createFile(path.join(formNewPath, "index.tsx"), formCreateTemplate(capitalize(this.singular), capitalize(this.resource)));
757
- createFile(path.join(formNewPath, "form-scheme.ts"), formSchemeCreateTemplate());
758
- const formEditPath = path.join(resourcePath, "FormEdit");
1434
+ createFile(path4.join(formNewPath, "index.tsx"), formCreateTemplate(capitalize(this.singular), capitalize(this.resource)));
1435
+ createFile(path4.join(formNewPath, "form-scheme.ts"), formSchemeCreateTemplate());
1436
+ const formEditPath = path4.join(resourcePath, "FormEdit");
759
1437
  createDir(formEditPath);
760
- createFile(path.join(formEditPath, "index.tsx"), formUpdateTemplate(capitalize(this.singular), capitalize(this.resource)));
761
- createFile(path.join(formEditPath, "form-scheme.ts"), formSchemeUpdateTemplate(capitalize(this.singular), capitalize(this.resource)));
762
- createFile(path.join(resourcePath, "index.ts"), `
1438
+ createFile(path4.join(formEditPath, "index.tsx"), formUpdateTemplate(capitalize(this.singular), capitalize(this.resource)));
1439
+ createFile(path4.join(formEditPath, "form-scheme.ts"), formSchemeUpdateTemplate(capitalize(this.singular), capitalize(this.resource)));
1440
+ createFile(path4.join(resourcePath, "index.ts"), `
763
1441
  export { FormNew${capitalize(this.singular)} } from "./FormNew";
764
1442
 
765
1443
  export { FormEdit${capitalize(this.singular)} } from "./FormEdit";
766
1444
  `);
767
- this.registerCommit(`feat(${this.resource}): created crud form components`);
1445
+ this.createCommit(`feat(${this.resource}): created crud form components`);
768
1446
  return this;
769
1447
  }
770
1448
  build() {
771
1449
  if (!this.options?.git) return;
772
- for (const message of this.commitQueue) {
773
- gitCommit(message);
774
- }
1450
+ console.log("commits made successfully \u2728");
775
1451
  }
776
1452
  };
777
1453
 
778
- // src/utils/guards/next-verify.guard.ts
779
- import fs2 from "fs";
780
- import path2 from "path";
781
- function nextProjectGuardSimple() {
782
- const cwd = process.cwd();
783
- const pkgPath = path2.join(cwd, "package.json");
784
- if (!fs2.existsSync(pkgPath)) {
785
- console.error("\x1B[31m \u2716 Erro \x1B[0mNenhum package.json encontrado neste diret\xF3rio.");
786
- process.exit(1);
787
- }
788
- const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
789
- const hasNext = pkg.dependencies?.next || pkg.devDependencies?.next;
790
- if (!hasNext) {
791
- console.error("\x1B[31m \u2716 Erro \x1B[0mExecute dentro de um projeto Next.js");
792
- process.exit(1);
793
- }
794
- }
795
-
796
1454
  // src/commands/create-resource.ts
797
1455
  function createResource(inputName, options) {
798
1456
  nextProjectGuardSimple();
799
1457
  if (!inputName) {
800
- console.error("\x1B[31m \u2716 Erro \x1B[0mInforme o nome do recurso");
1458
+ console.error("\x1B[31m \u2716 Erro \x1B[0mPlease provide the name of the resource.");
801
1459
  process.exit(1);
802
1460
  }
803
1461
  const builder = new NextResourceBuilder(inputName, options);
804
1462
  builder.setBasePath().createListPage().createDetailPage().createNewPage().build();
805
- console.log(`Recurso "${inputName}" criado \x1B[32m\u2714 Sucesso\x1B[0m`);
1463
+ console.log(`Resource "${inputName}" created \x1B[32m\u2714 Success\x1B[0m`);
806
1464
  }
807
1465
 
808
1466
  // src/commands/create-form-resource.ts
809
1467
  function createFormForResource(inputName, options) {
810
1468
  nextProjectGuardSimple();
811
1469
  if (!inputName) {
812
- console.error("\x1B[31m \u2716 Erro \x1B[0mInforme o nome do recurso");
1470
+ console.error("\x1B[31m \u2716 Erro \x1B[0mPlease provide the resource name.");
813
1471
  process.exit(1);
814
1472
  }
815
1473
  const builder = new NextResourceBuilder(inputName, options);
816
1474
  builder.setBasePathForComponents().createComponentInputCustom().setBasePathForForm().createCrudForm().build();
817
- console.log(`Form para o recurso "${inputName}" criado \x1B[32m\u2714 Sucesso\x1B[0m`);
1475
+ console.log(`Form for the resource "${inputName}" created \x1B[32m\u2714 Success\x1B[0m`);
1476
+ }
1477
+
1478
+ // src/builders/next-auth-builder.ts
1479
+ import path5 from "path";
1480
+
1481
+ // src/templates/forms/schems/login-form-scheme.ts
1482
+ var formSchemeLoginTemplate = () => `
1483
+ import * as yup from 'yup';
1484
+
1485
+ export const formSchema = yup.object().shape({
1486
+ email: yup.string().required('O email \xE9 obrigat\xF3rio').email('Digite um email v\xE1lido'),
1487
+ password: yup.string().required('A senha \xE9 obrigat\xF3ria').min(6, 'A senha deve ter pelo menos 6 caracteres'),
1488
+ });
1489
+
1490
+ export type FormSchemaType = yup.InferType<typeof formSchema>;
1491
+
1492
+
1493
+ `;
1494
+
1495
+ // src/templates/forms/schems/redef-form-scheme.ts
1496
+ var formSchemeRedefTemplate = () => `
1497
+ import * as yup from 'yup';
1498
+
1499
+ export const formSchema = yup.object().shape({
1500
+ token: yup.string().required('O c\xF3digo \xE9 obrigat\xF3rio'),
1501
+ password: yup.string().required('A senha \xE9 obrigat\xF3ria').min(8, 'A senha deve ter pelo menos 8 caracteres'),
1502
+ confirpassword: yup.string().required('A confirma\xE7\xE3o de senha \xE9 obrigat\xF3ria').oneOf([yup.ref('password')], 'As senhas devem ser iguais'),
1503
+ });
1504
+
1505
+ export type FormSchemaType = yup.InferType<typeof formSchema>;
1506
+
1507
+ `;
1508
+
1509
+ // src/templates/forms/schems/register-form-scheme.ts
1510
+ var formSchemeRegisterTemplate = () => `
1511
+ import * as yup from "yup";
1512
+
1513
+ export const formSchema = yup.object({
1514
+ name: yup.string().required("O nome \xE9 obrigat\xF3rio").min(3, "O nome deve ter pelo menos 3 caracteres"),
1515
+ email: yup.string().required("O email \xE9 obrigat\xF3rio").email("Digite um email v\xE1lido"),
1516
+ cpf: yup.string().required("O CPF \xE9 obrigat\xF3rio").matches(/^d{11}$/, "O CPF deve conter 11 d\xEDgitos"),
1517
+ dateOfBirth: yup.date().required("A data \xE9 obrigat\xF3ria").max(new Date(), "A data de aniversario \xE9 obrigatoria"),
1518
+ password: yup.string().required("A senha \xE9 obrigat\xF3ria").min(6, "A senha deve ter pelo menos 6 caracteres"),
1519
+ repeatPassword: yup.string().required("A confirma\xE7\xE3o de senha \xE9 obrigat\xF3ria").oneOf([yup.ref("password")], "As senhas devem ser iguais"),
1520
+ phone: yup.string().required("O telefone \xE9 obrigat\xF3rio").matches(/^+?d{10,15}$/, "N\xFAmero de telefone inv\xE1lido"),
1521
+ country: yup.string().required("O pa\xEDs \xE9 obrigat\xF3rio"),
1522
+ state: yup.string().required("O estado \xE9 obrigat\xF3rio"),
1523
+ city: yup.string().required("A cidade \xE9 obrigat\xF3ria"),
1524
+ address: yup.string().required("O endere\xE7o \xE9 obrigat\xF3rio"),
1525
+ });
1526
+
1527
+ export type FormSchemaType = yup.InferType<typeof formSchema>;
1528
+
1529
+ `;
1530
+
1531
+ // src/templates/forms/schems/updateuser-form-scheme.ts
1532
+ var formSchemeUpdateUserTemplate = () => `
1533
+ import * as yup from "yup";
1534
+
1535
+ export const updateFormSchema = yup.object({
1536
+ photo: yup.string().url("Digite uma URL v\xE1lida para a foto").nullable().optional(),
1537
+ bio: yup.string().max(500, "A bio deve ter no m\xE1ximo 500 caracteres").nullable().optional(),
1538
+ skills: yup.string().max(100, "").nullable().optional(),
1539
+ hourly_rate: yup.number().min(0, "O valor deve ser maior ou igual a 0").nullable().optional(),
1540
+ });
1541
+
1542
+ export type UpdateFormSchemaType = yup.InferType<typeof updateFormSchema>;
1543
+
1544
+ `;
1545
+
1546
+ // src/templates/config/hiddenpaths.ts
1547
+ var hiddenPathsTemplate = () => `
1548
+ export const hiddenPaths = ["/manager", "/posts/new", "/posts", "/categories", "/categories/new", "/settings"];
1549
+ `;
1550
+
1551
+ // src/templates/config/proxy.ts
1552
+ var proxyTemplate = () => `
1553
+ import { NextRequest, NextResponse } from "next/server";
1554
+ import { decoderTokenToClaims } from "./app/api/auth/decode-claims";
1555
+
1556
+ type Role = "ADMIN" | "AUTHOR" | "COMMENTATOR";
1557
+
1558
+ const adminRoutes = ["/admin", "/users", "/dashboard/admin", "/settings", "/desk/view"];
1559
+ const authorRoutes = ["/desk/view"];
1560
+ const commntatorRoutes = ["/community", "/messages", "/", "/announced"];
1561
+
1562
+ export const config = {
1563
+ matcher: [
1564
+ "/admin/:path*", "/users/:path*", "/settings", "/settings/:path*", "/desk/view", "/desk/view/:path*", "/messages", "/messages/:path*"
1565
+ ]
1566
+ };
1567
+
1568
+ function canAccessRoute(path: string, role: Role) {
1569
+ switch (role) {
1570
+ case "ADMIN":
1571
+ return (adminRoutes.some(r => path.startsWith(r)) || authorRoutes.some(r => path.startsWith(r)) || commntatorRoutes.some(r => path.startsWith(r)));
1572
+
1573
+ case "AUTHOR":
1574
+ return authorRoutes.some(r => path.startsWith(r));
1575
+
1576
+ case "COMMENTATOR":
1577
+ return commntatorRoutes.some(r => path.startsWith(r));
1578
+
1579
+ default:
1580
+ return false;
1581
+ };
1582
+ };
1583
+
1584
+ export function proxy(request: NextRequest) {
1585
+ const path = request.nextUrl.pathname;
1586
+
1587
+ const token = request.cookies.get("jwt_back")?.value;
1588
+ if (!token) {
1589
+ return NextResponse.redirect(new URL("/manager?error=not_has_token", request.url));
1590
+ }
1591
+
1592
+ const claims = decoderTokenToClaims(token);
1593
+ if (!claims) {
1594
+ return NextResponse.redirect(new URL("/manager?error=invalid_token", request.url));
1595
+ }
1596
+
1597
+ const role = claims.roles as Role;
1598
+ const hasAccess = canAccessRoute(path, role);
1599
+
1600
+ if (!hasAccess) {
1601
+ return NextResponse.redirect(new URL("/manager?error=invalid_role", request.url));
1602
+ }
1603
+
1604
+ return NextResponse.next();
1605
+ }
1606
+ `;
1607
+
1608
+ // src/templates/layouts/capture-error-layout.ts
1609
+ var captureErrorLayoutTemplate = () => `
1610
+ "use client";
1611
+
1612
+ import { useSearchParams } from "next/navigation";
1613
+ import { type ReactNode } from "react";
1614
+ import { toast } from "react-toastify";
1615
+ import { useEffect } from "react";
1616
+
1617
+ type LayoutCaptureErrorProps = { children: ReactNode; };
1618
+
1619
+ export const LayoutCaptureError: React.FC<LayoutCaptureErrorProps> = ({ children }) => {
1620
+ const searchParams = useSearchParams();
1621
+ const error = searchParams.get("error");
1622
+
1623
+ useEffect(() => {
1624
+ if (error) {
1625
+ switch (error) {
1626
+ case "not_has_token":
1627
+ toast.error("Voc\xEA n\xE3o deveria nem estar aqui bixo, est\xE1 sem token de acesso.");
1628
+ break;
1629
+ case "invalid_token":
1630
+ toast.error("Token inv\xE1lido, \xE9 de 1500 A.C");
1631
+ break;
1632
+ case "invalid_role":
1633
+ toast.error("Voc\xEA n\xE3o \xE9 um usuario do tipo que pode mecher nisso a\xED major...");
1634
+ break;
1635
+ default:
1636
+ toast.error("Ocorreu um erro desconhecido.");
1637
+ }
1638
+ }
1639
+ }, [error]);
1640
+ return (
1641
+ <div style={{ width: "100%", height: "100%" }}>
1642
+ {children}
1643
+ </div>
1644
+ );
1645
+ };
1646
+ `;
1647
+
1648
+ // src/templates/layouts/main-layout.ts
1649
+ var mainLayoutTemplate = () => `
1650
+ import type { ReactNode } from "react";
1651
+
1652
+ type TypeProps = { children: ReactNode[] };
1653
+
1654
+ export const MainLayout = ({ children }: TypeProps) => {
1655
+ const childrenArray = Array.isArray(children) ? children : [children];
1656
+
1657
+ if (childrenArray.length > 4)
1658
+ throw new Error(
1659
+ "MainLayout s\xF3 aceita no m\xE1ximo 4 elementos filhos: header, toast, main e footer."
1660
+ );
1661
+
1662
+ return (
1663
+ <div className="grid min-h-screen grid-rows-[auto_auto_1fr_auto]">
1664
+ <header className="max-h-50">{childrenArray[0]}</header>
1665
+ <div className="min-h-2.5 overflow-hidden bg-(--bg-section-100)">{childrenArray[1]}</div>
1666
+ <main className="min-h-[60vh]">{childrenArray[2]}</main>
1667
+ <footer className="h-87.5">{childrenArray[3]}</footer>
1668
+ </div>
1669
+ );
1670
+ };
1671
+ `;
1672
+
1673
+ // src/templates/layouts/manager-layout.ts
1674
+ var managerLayoutTemplate = () => `
1675
+ "use client";
1676
+
1677
+ import clsx from "clsx";
1678
+ import styles from "./grid.module.css";
1679
+ import { useMenu } from "@/contexts/manager-context";
1680
+ import { ReactNode } from "react";
1681
+
1682
+ type ManagerLayoutProps = {
1683
+ /** [0] header [1] toast,
1684
+ * [2] sidebar, [3] conte\xFAdo,
1685
+ * [4] footer
1686
+ **/
1687
+ children: [ReactNode, ReactNode, ReactNode, ReactNode, ReactNode];
1688
+ };
1689
+
1690
+ export const ManagerLayout: React.FC<ManagerLayoutProps> = ({ children }) => {
1691
+ const [header, toastify, sidebar, content, footer] = children;
1692
+ const { menuActive } = useMenu();
1693
+
1694
+ return (
1695
+ <div className={clsx(styles.layout)} style={{ ["--aside-width" as any]: menuActive ? "80px" : "220px" }}>
1696
+ <header className={clsx(styles.header)}>{header}</header>
1697
+ <div className={clsx(styles.toastify)}>{toastify}</div>
1698
+
1699
+ <aside className={clsx(styles.side)}>{sidebar}</aside>
1700
+ <main className={clsx(styles.content)}>{content}</main>
1701
+
1702
+ <footer className={clsx(styles.footer)}>{footer}</footer>
1703
+ </div>
1704
+ );
1705
+ };
1706
+
1707
+ `;
1708
+
1709
+ // src/templates/layouts/manager-layout-css.ts
1710
+ var managerLayoutCssTemplate = () => `
1711
+ .layout {
1712
+ width: 100%;
1713
+ display: grid;
1714
+ grid-template-columns: var(--aside-width, 220px) 1fr;
1715
+ grid-template-rows: auto auto auto auto;
1716
+ grid-template-areas:
1717
+ "header header"
1718
+ "toast toast"
1719
+ "side content"
1720
+ "footer footer";
1721
+ transition: grid-template-columns 0.3s ease;
1722
+ }
1723
+
1724
+ .header {
1725
+ grid-area: header;
1726
+ }
1727
+
1728
+ .toastify {
1729
+ grid-area: toast;
1730
+ min-height: 2px;
1731
+ }
1732
+
1733
+ .content {
1734
+ grid-area: content;
1735
+ min-height: 0;
1736
+ overflow-y: auto;
1737
+ }
1738
+
1739
+ .side {
1740
+ grid-area: side;
1741
+ min-width: 80px;
1742
+ overflow: hidden;
1743
+ }
1744
+
1745
+ .footer {
1746
+ grid-area: footer;
1747
+ }
1748
+
1749
+ @media (max-width: 768px) {
1750
+ .layout {
1751
+ grid-template-columns: 60px 1fr;
1752
+ }
1753
+ }`;
1754
+
1755
+ // src/templates/layouts/private-next-layout.ts
1756
+ var privateNextLayoutTemplate = () => `
1757
+ import { Footer } from "@/components/Footer";
1758
+ import { Header } from "@/components/Header";
1759
+ import { LayoutCaptureError } from "@/components/Layouts/LayoutCaptureError";
1760
+ import { ManagerLayout } from "@/components/Layouts/ManagerLayout";
1761
+ import { MenuAside } from "@/components/MenuAside";
1762
+ import { MenuProvider } from "@/contexts/manager-context";
1763
+ import { ToastContainer } from "react-toastify";
1764
+
1765
+ export default function RootLayout({ children }: Readonly<{ children: React.ReactNode; }>) {
1766
+ return (
1767
+ <div data-testid="root-layout-private">
1768
+ <MenuProvider>
1769
+ <ManagerLayout>
1770
+ <Header />
1771
+ <ToastContainer position="top-center" />
1772
+ <MenuAside />
1773
+ <LayoutCaptureError>
1774
+ {children}
1775
+ </LayoutCaptureError>
1776
+ <Footer />
1777
+ </ManagerLayout>
1778
+ </MenuProvider>
1779
+ </div>
1780
+ );
1781
+ }
1782
+ `;
1783
+
1784
+ // src/templates/layouts/root-next-layout.ts
1785
+ var rootLayoutMinimalTemplate = () => `
1786
+ import type { Metadata } from "next";
1787
+ import { Geist, Geist_Mono } from "next/font/google";
1788
+ import "./globals.css";
1789
+
1790
+ const geistSans = Geist({
1791
+ variable: "--font-geist-sans",
1792
+ subsets: ["latin"],
1793
+ });
1794
+
1795
+ const geistMono = Geist_Mono({
1796
+ variable: "--font-geist-mono",
1797
+ subsets: ["latin"],
1798
+ });
1799
+
1800
+ export const metadata: Metadata = {
1801
+ title: "Create Next App",
1802
+ description: "Generated by create next app",
1803
+ };
1804
+
1805
+ export default function RootLayout({ children }: Readonly<{ children: React.ReactNode; }>) {
1806
+ return (
1807
+ <html lang="en">
1808
+ <body className={\`\${geistSans.variable} \${geistMono.variable} antialiased\`} data-testid="root-layout">
1809
+ {children}
1810
+ </body>
1811
+ </html>
1812
+ );
1813
+ }
1814
+ `;
1815
+
1816
+ // src/templates/layouts/public-next-layout.ts
1817
+ var publicLayoutTemplate = () => `
1818
+ import { Footer } from "@/components/Footer";
1819
+ import { Header } from "@/components/Header";
1820
+ import { MainLayout } from "@/components/Layouts/MainLayout";
1821
+ import { ToastContainer } from "react-toastify";
1822
+
1823
+ export default function RootLayout({ children }: Readonly<{ children: React.ReactNode; }>) {
1824
+ return (
1825
+ <div data-testid="root-layout-public">
1826
+ <MainLayout>
1827
+ <Header />
1828
+ <ToastContainer position="top-center" />
1829
+ {children}
1830
+ <Footer />
1831
+ </MainLayout>
1832
+ </div>
1833
+ );
1834
+ }
1835
+
1836
+ `;
1837
+
1838
+ // src/templates/components/footer-comp.ts
1839
+ var footerTemplate = () => `
1840
+ export const Footer: React.FC = () => {
1841
+ return (
1842
+ <footer className="bg-(--brand-footer) font-sans">
1843
+ <div className="container px-6 py-12 mx-auto">
1844
+ <div className="grid grid-cols-1 gap-6 sm:grid-cols-2 sm:gap-y-10 lg:grid-cols-4">
1845
+ <div className="sm:col-span-2">
1846
+ <h1 className="max-w-lg text-xl font-semibold tracking-tight text-gray-800 xl:text-2xl dark:text-white">Subscribe our newsletter to get an update.</h1>
1847
+
1848
+ <div className="flex flex-col mx-auto mt-6 space-y-3 md:space-y-0 md:flex-row">
1849
+ <input id="email" type="text" className="px-4 py-2 text-gray-700 border rounded-md bg-(--brand-footer) dark:text-gray-300 dark:border-gray-600 focus:border-blue-400 dark:focus:border-blue-300 focus:outline-none focus:ring focus:ring-opacity-40 focus:ring-blue-300" placeholder="Email Address" />
1850
+
1851
+ <button className="w-full px-6 py-2.5 text-sm font-medium tracking-wider text-white transition-colors duration-300 transform md:w-auto md:mx-4 focus:outline-none bg-gray-800 rounded-lg hover:bg-gray-700 focus:ring focus:ring-gray-300 focus:ring-opacity-80">
1852
+ Subscribe
1853
+ </button>
1854
+ </div>
1855
+ </div>
1856
+
1857
+ <div>
1858
+ <p className="font-semibold text-gray-800 dark:text-white">Quick Link</p>
1859
+
1860
+ <div className="flex flex-col items-start mt-5 space-y-2">
1861
+ <p className="text-gray-600 transition-colors duration-300 dark:text-gray-300 dark:hover:text-blue-400 hover:underline hover:cursor-pointer hover:text-blue-500">Home</p>
1862
+ <p className="text-gray-600 transition-colors duration-300 dark:text-gray-300 dark:hover:text-blue-400 hover:underline hover:cursor-pointer hover:text-blue-500">Who We Are</p>
1863
+ <p className="text-gray-600 transition-colors duration-300 dark:text-gray-300 dark:hover:text-blue-400 hover:underline hover:cursor-pointer hover:text-blue-500">Our Philosophy</p>
1864
+ </div>
1865
+ </div>
1866
+
1867
+ <div>
1868
+ <p className="font-semibold text-gray-800 dark:text-white">Industries</p>
1869
+
1870
+ <div className="flex flex-col items-start mt-5 space-y-2">
1871
+ <p className="text-gray-600 transition-colors duration-300 dark:text-gray-300 dark:hover:text-blue-400 hover:underline hover:cursor-pointer hover:text-blue-500">Retail & E-Commerce</p>
1872
+ <p className="text-gray-600 transition-colors duration-300 dark:text-gray-300 dark:hover:text-blue-400 hover:underline hover:cursor-pointer hover:text-blue-500">Information Technology</p>
1873
+ <p className="text-gray-600 transition-colors duration-300 dark:text-gray-300 dark:hover:text-blue-400 hover:underline hover:cursor-pointer hover:text-blue-500">Finance & Insurance</p>
1874
+ </div>
1875
+ </div>
1876
+ </div>
1877
+
1878
+ <hr className="my-6 border-gray-200 md:my-8 dark:border-gray-700 h-2" />
1879
+
1880
+ <div className="sm:flex sm:items-center sm:justify-between">
1881
+ <div className="flex flex-1 gap-2 hover:cursor-pointer">
1882
+ <img src="https://www.svgrepo.com/show/303139/google-play-badge-logo.svg" width="130" height="110" alt="" />
1883
+ <img src="https://www.svgrepo.com/show/303128/download-on-the-app-store-apple-logo.svg" width="130" height="110" alt="" />
1884
+ </div>
1885
+
1886
+ <div className="flex gap-2 hover:cursor-pointer">
1887
+ <img src="https://www.svgrepo.com/show/303114/facebook-3-logo.svg" width="30" height="30" alt="fb" />
1888
+ <img src="https://www.svgrepo.com/show/303145/instagram-2-1-logo.svg" width="30" height="30" alt="inst" />
1889
+ <img src="https://www.svgrepo.com/show/94698/github.svg" className="" width="30" height="30" alt="gt" />
1890
+ <img src="https://www.svgrepo.com/show/22037/path.svg" width="30" height="30" alt="pn" />
1891
+ <img src="https://www.svgrepo.com/show/28145/linkedin.svg" width="30" height="30" alt="in" />
1892
+ </div>
1893
+ </div>
1894
+ <p className="font-sans p-8 text-start md:text-center md:text-lg md:p-4">
1895
+ \xA9 2025 Template Deloped by <a href="https://github.com/brito-response" target="_blank" rel="noopener noreferrer">Neto.</a></p>
1896
+ </div>
1897
+ </footer>
1898
+ );
1899
+ };
1900
+
1901
+ `;
1902
+
1903
+ // src/templates/components/menu-aside-comp.ts
1904
+ var menuAsideTemplate = () => `
1905
+ "use client";
1906
+
1907
+ import { AlignJustifyIcon, FileEditIcon, FolderTreeIcon, HouseIcon, MessageCircleIcon, SettingsIcon, UserIcon, WalletIcon } from "lucide-react";
1908
+ import Link from "next/link";
1909
+ import { ReactElement, useState } from "react";
1910
+ import { useMenu } from "@/contexts/manager-context";
1911
+
1912
+ type NavItem = { icon: ReactElement; title: string; url: string; };
1913
+
1914
+ export const MenuAside: React.FC = () => {
1915
+ const { menuActive, setMenuActive } = useMenu();
1916
+ const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
1917
+
1918
+ const navItems: NavItem[] = [
1919
+ { icon: <HouseIcon size={24} />, title: "Manager", url: "/manager" },
1920
+ { icon: <FileEditIcon size={24} />, title: "Posts", url: "/posts" },
1921
+ { icon: <FolderTreeIcon size={24} />, title: "Categorias", url: "/categories" },
1922
+ { icon: <MessageCircleIcon size={24} />, title: "Mensagens", url: "/messages" },
1923
+ { icon: <UserIcon size={24} />, title: "Portifolio", url: "/portifolios" },
1924
+ { icon: <SettingsIcon size={24} />, title: "Setting", url: "/settings" },
1925
+ ];
1926
+
1927
+ return (
1928
+ <aside className={\`\${menuActive ? "w-20" : "w-72"} h-full bg-[#202020] border-r-8 border-[#0489a1] flex flex-col transition-all duration-100 overflow-hidden z-10\`}>
1929
+ {/* Bot\xE3o toggle */}
1930
+ <button type="button" onClick={() => setMenuActive(!menuActive)} className="w-14 h-14 flex justify-center items-center text-white hover:bg-[#0489a1] transition-colors duration-300 mt-3 rounded-lg">
1931
+ <AlignJustifyIcon size={28} />
1932
+ </button>
1933
+
1934
+ {/* Navega\xE7\xE3o */}
1935
+ <ul className="flex flex-col mt-4 gap-2 flex-1">
1936
+ {navItems.map((item: NavItem, index) => (
1937
+ <li key={index} onMouseEnter={() => setHoveredIndex(index)} onMouseLeave={() => setHoveredIndex(null)} className={\`relative rounded-l-3xl transition-all duration-300 \${hoveredIndex === index ? "hover:bg-[#0489a1]" : ""}\`}>
1938
+ <Link href={item.url} className={\`flex items-center text-white py-3 transition-colors duration-200 \${hoveredIndex === index ? "text-[#0489a1] " : "text-white"}\`}>
1939
+ <span className="flex justify-center items-center min-w-15 h-15">
1940
+ {item.icon}
1941
+ </span>
1942
+ <span className={\`whitespace-nowrap text-base font-medium transition-opacity duration-300 \${menuActive ? "opacity-0 pointer-events-none" : "opacity-100"}\`}>
1943
+ {item.title}
1944
+ </span>
1945
+ </Link>
1946
+ </li>
1947
+ ))}
1948
+ </ul>
1949
+ </aside>
1950
+ );
1951
+ };`;
1952
+
1953
+ // src/templates/components/header-comp.ts
1954
+ var headerTemplate = () => `
1955
+ "use client";
1956
+
1957
+ import { useEffect, useRef, useState } from "react";
1958
+ import { Menu, X } from "lucide-react";
1959
+ // import { ToggleButtonTheme } from "../../Buttons/ToggleButtonTheme";
1960
+ // import { NavBar } from "../../Navbar";
1961
+ // import { Logo } from "../../Logo";
1962
+ import { FormLoginWrapper } from "@/forms/users/FormLogin/formwraper";
1963
+
1964
+ export const Header: React.FC = () => {
1965
+ const [open, setOpen] = useState(false);
1966
+ const [isMobile, setIsMobile] = useState(false);
1967
+ const panelRef = useRef<HTMLDivElement | null>(null);
1968
+
1969
+ useEffect(() => {
1970
+ const checkSize = () => setIsMobile(window.innerWidth < 1024);
1971
+ checkSize();
1972
+ window.addEventListener("resize", checkSize);
1973
+ return () => window.removeEventListener("resize", checkSize);
1974
+ }, []);
1975
+
1976
+ useEffect(() => {
1977
+ const onKey = (e: KeyboardEvent) => {
1978
+ if (e.key === "Escape") setOpen(false);
1979
+ };
1980
+ document.addEventListener("keydown", onKey);
1981
+ return () => document.removeEventListener("keydown", onKey);
1982
+ }, []);
1983
+
1984
+ useEffect(() => {
1985
+ const onClick = (e: MouseEvent) => {
1986
+ if (!open) return;
1987
+ if (panelRef.current && !panelRef.current.contains(e.target as Node)) {
1988
+ setOpen(false);
1989
+ }
1990
+ };
1991
+ document.addEventListener("mousedown", onClick);
1992
+ return () => document.removeEventListener("mousedown", onClick);
1993
+ }, [open]);
1994
+
1995
+ return (
1996
+ <header className="w-full px-5 py-1.5 bg-(--bg-header-color) text-(--text-color) shadow-[rgba(0,0,0,0.25)_0px_54px_55px,rgba(0,0,0,0.12)_0px_-12px_30px,rgba(0,0,0,0.12)_0px_4px_6px,rgba(0,0,0,0.17)_0px_12px_13px,rgba(0,0,0,0.09)_0px_-3px_5px] relative z-40">
1997
+ <div className="flex items-center justify-between lg:py-2">
1998
+ {/* Logo */}
1999
+ <div className="flex items-center px-[5%] mr-auto">
2000
+ {/* <Logo /> */}
2001
+ </div>
2002
+
2003
+ <div className="flex items-center justify-between gap-5">
2004
+ {/* Menu Desktop */}
2005
+ <nav className="hidden lg:block">
2006
+ {/* <NavBar onLinkClick={() => setOpen(false)} /> */}
2007
+ </nav>
2008
+
2009
+ {/* \xC1rea Desktop: tema + login */}
2010
+ <div className="hidden lg:flex items-center gap-3">
2011
+ {/* <ToggleButtonTheme /> */}
2012
+ <FormLoginWrapper />
2013
+ </div>
2014
+
2015
+ {/* Bot\xE3o Mobile */}
2016
+ {isMobile && (
2017
+ <button className="inline-flex items-center justify-center p-1.5 rounded-lg bg-transparent border-none cursor-pointer"
2018
+ onClick={() => setOpen((s) => !s)} aria-expanded={open} aria-label={open ? "Fechar menu" : "Abrir menu"}>
2019
+ {open ? <X size={22} /> : <Menu size={22} />}
2020
+ </button>
2021
+ )}
2022
+ </div>
2023
+ </div>
2024
+
2025
+ {/* Backdrop Mobile */}
2026
+ {open && isMobile && (
2027
+ <div className="fixed inset-0 bg-black/35 z-49" onClick={() => setOpen(false)}/>
2028
+ )}
2029
+
2030
+ {/* Painel Mobile */}
2031
+ {isMobile && (
2032
+ <aside className={\`fixed top-0 right-0 h-screen w-[85%] max-w-90 bg-(--bg-color) shadow-[-8px_0_30px_rgba(0,0,0,0.15)] z-50 overflow-y-auto transition-all duration-300 ease-in-out \${open ? "translate-x-0" : "translate-x-full"}\`}
2033
+ role="dialog" aria-hidden={!open} ref={panelRef}>
2034
+ <div className="flex flex-col gap-5 p-5 min-h-screen">
2035
+ {/* <NavBar isMobile onLinkClick={() => setOpen(false)} /> */}
2036
+ <div className="mt-[10%] flex flex-col justify-center items-center">
2037
+ {/* <ToggleButtonTheme /> */}
2038
+ <FormLoginWrapper />
2039
+ </div>
2040
+ </div>
2041
+ </aside>
2042
+ )}
2043
+ </header>
2044
+ );
2045
+ };
2046
+
2047
+
2048
+
2049
+ `;
2050
+
2051
+ // src/templates/components/button-comp.ts
2052
+ var buttonGenericTemplate = () => `
2053
+ "use client";
2054
+
2055
+ import React from "react";
2056
+
2057
+ type ButtonGenericProps = {
2058
+ label: string;
2059
+ type?: "reset" | "submit" | "button";
2060
+ } & React.ComponentProps<"button">;
2061
+
2062
+ export const ButtonGeneric: React.FC<ButtonGenericProps> = ({
2063
+ label,
2064
+ type = "submit",
2065
+ className = "",
2066
+ ...props
2067
+ }) => {
2068
+ return (
2069
+ <button
2070
+ type={type}
2071
+ className={\`max-h-7 px-6 py-1 bg-(--brand-300) text-white font-bold text-md rounded-md flex items-center justify-center cursor-pointer transition-colors duration-200 ease-in-out hover:bg-(--brand-400) lg:w-auto w-full lg:mt-0 \${className}\`}
2072
+ {...props}
2073
+ >
2074
+ {label}
2075
+ </button>
2076
+ );
2077
+ };
2078
+ `;
2079
+
2080
+ // src/utils/services/install-frame-motion.service.ts
2081
+ import { execSync as execSync3 } from "child_process";
2082
+ var DependencyFramemotionInstaller = class _DependencyFramemotionInstaller {
2083
+ constructor() {
2084
+ this.hasInstalled = false;
2085
+ }
2086
+ static getInstance() {
2087
+ if (!_DependencyFramemotionInstaller.instance) {
2088
+ _DependencyFramemotionInstaller.instance = new _DependencyFramemotionInstaller();
2089
+ }
2090
+ return _DependencyFramemotionInstaller.instance;
2091
+ }
2092
+ async install() {
2093
+ if (this.hasInstalled || hasDependency("framer-motion")) return;
2094
+ this.hasInstalled = true;
2095
+ try {
2096
+ execSync3(`npm install framer-motion`, { stdio: "inherit" });
2097
+ console.log("Depend\xEAncias instaladas com sucesso!");
2098
+ } catch (error) {
2099
+ console.error("Falha ao instalar depend\xEAncias:", error);
2100
+ }
2101
+ ;
2102
+ }
2103
+ };
2104
+
2105
+ // src/builders/next-auth-builder.ts
2106
+ var NextAuthBuilder = class {
2107
+ constructor(options) {
2108
+ this.commitQueue = [];
2109
+ this.options = options;
2110
+ }
2111
+ createCommit(message) {
2112
+ if (!this.options?.git) return;
2113
+ gitCommit(message);
2114
+ }
2115
+ registerCommit(message) {
2116
+ if (!this.options?.git) return;
2117
+ this.commitQueue.push(message);
2118
+ }
2119
+ setBasePathAndCreateConfig() {
2120
+ this.basePath = path5.join(process.cwd(), "src/app/api/auth/[...nextauth]");
2121
+ createDir(this.basePath);
2122
+ if (!pathExists(this.basePath)) {
2123
+ console.error("\x1B[31m \u2716 Erro \x1B[0m creating NextAuth config directory.");
2124
+ process.exit(1);
2125
+ }
2126
+ ;
2127
+ createFile(path5.join(this.basePath, "route.ts"), nextConfigTemplate());
2128
+ if (this.options?.git) this.createCommit("feat(next-auth): create config route.");
2129
+ return this;
2130
+ }
2131
+ createNextAuthAuxOptions() {
2132
+ this.basePath = path5.join(process.cwd(), "src/app/api/auth/");
2133
+ createFile(path5.join(this.basePath, "decode-claims.ts"), nextDecodeClaimsTemplate());
2134
+ createFile(path5.join(this.basePath, "request-api.ts"), nextRequestApiTemplate());
2135
+ createFile(path5.join(this.basePath, "singout.ts"), nextSignOutTemplate());
2136
+ this.basePath = path5.join(process.cwd(), "src/utils");
2137
+ createDir(this.basePath);
2138
+ if (!pathExists(this.basePath)) {
2139
+ throw new Error(`Failed to create directory at ${this.basePath}`);
2140
+ }
2141
+ ;
2142
+ createFile(path5.join(this.basePath, "route.ts"), nextSessionTypeTemplate());
2143
+ if (this.options?.git) this.createCommit("feat(next-auth): create aux options for login sessions.");
2144
+ return this;
2145
+ }
2146
+ createNextAuthForms() {
2147
+ const forms = [
2148
+ { name: "FormForgot", template: formForgotTemplate, scheme: () => "" },
2149
+ { name: "FormLogin", template: formLoginTemplate, scheme: formSchemeLoginTemplate },
2150
+ { name: "FormRedef", template: formRedefTemplate, scheme: formSchemeRedefTemplate },
2151
+ { name: "FormRegister", template: formRegisterTemplate, scheme: formSchemeRegisterTemplate },
2152
+ { name: "FormUpdateUser", template: formUpdateUserTemplate, scheme: formSchemeUpdateUserTemplate }
2153
+ ];
2154
+ const formsRoot = path5.join(process.cwd(), "src/forms");
2155
+ const usersPath = path5.join(formsRoot, "users");
2156
+ let created = false;
2157
+ if (!pathExists(formsRoot)) {
2158
+ createDir(formsRoot);
2159
+ created = true;
2160
+ }
2161
+ ;
2162
+ if (!pathExists(usersPath)) {
2163
+ createDir(usersPath);
2164
+ created = true;
2165
+ }
2166
+ ;
2167
+ forms.forEach(({ name, template, scheme }) => {
2168
+ const formDir = path5.join(usersPath, name);
2169
+ if (!pathExists(formDir)) {
2170
+ createDir(formDir);
2171
+ created = true;
2172
+ }
2173
+ ;
2174
+ const filePath = path5.join(formDir, "index.tsx");
2175
+ if (!pathExists(filePath)) {
2176
+ createFile(filePath, template());
2177
+ created = true;
2178
+ }
2179
+ ;
2180
+ const schemePath = path5.join(formDir, `${name.toLocaleLowerCase() + "-scheme"}.tsx`);
2181
+ if (!pathExists(schemePath)) {
2182
+ createFile(schemePath, scheme());
2183
+ created = true;
2184
+ }
2185
+ ;
2186
+ });
2187
+ this.basePath = path5.join(process.cwd(), "src/forms/users/FormLogin");
2188
+ createFile(path5.join(this.basePath, "formwraper.tsx"), formLoginWrapperTemplate());
2189
+ createFile(path5.join(this.basePath, "hidenpath.ts"), hiddenPathsTemplate());
2190
+ const indexPath = path5.join(usersPath, "index.ts");
2191
+ if (!pathExists(indexPath)) {
2192
+ const exports = forms.map(({ name }) => `export { ${name} } from "./${name}";`).join("\n");
2193
+ createFile(indexPath, exports + "\n");
2194
+ created = true;
2195
+ }
2196
+ if (created && this.options?.git) {
2197
+ this.registerCommit("feat(auth): create next-auth user forms");
2198
+ }
2199
+ return this;
2200
+ }
2201
+ createNextAutorizationSystem() {
2202
+ this.basePath = path5.join(process.cwd(), "src");
2203
+ createFile(path5.join(this.basePath, "proxy.ts"), proxyTemplate());
2204
+ if (this.options?.git) this.createCommit("feat(next-auth): create proxy for autorize users in frontend.");
2205
+ return this;
2206
+ }
2207
+ createNextLayouts() {
2208
+ const layoutsRoot = path5.join(process.cwd(), "src/components/Layouts");
2209
+ let created = false;
2210
+ if (!pathExists(layoutsRoot)) {
2211
+ createDir(layoutsRoot);
2212
+ created = true;
2213
+ }
2214
+ const layouts = [
2215
+ { name: "LayoutCaptureError", files: [{ name: "index.tsx", content: captureErrorLayoutTemplate() }] },
2216
+ { name: "MainLayout", files: [{ name: "index.tsx", content: mainLayoutTemplate() }] },
2217
+ { name: "ManagerLayout", files: [{ name: "index.tsx", content: managerLayoutTemplate() }, { name: "grid.module.css", content: managerLayoutCssTemplate() }] }
2218
+ ];
2219
+ layouts.forEach((layout) => {
2220
+ const layoutDir = path5.join(layoutsRoot, layout.name);
2221
+ if (!pathExists(layoutDir)) {
2222
+ createDir(layoutDir);
2223
+ created = true;
2224
+ }
2225
+ ;
2226
+ layout.files.forEach((file) => {
2227
+ const filePath = path5.join(layoutDir, file.name);
2228
+ if (!pathExists(filePath)) {
2229
+ createFile(filePath, file.content);
2230
+ created = true;
2231
+ }
2232
+ });
2233
+ });
2234
+ if (created && this.options?.git) this.createCommit("feat(next-auth): compoenets for authentication system.");
2235
+ return this;
2236
+ }
2237
+ setLayouts() {
2238
+ const appRoot = path5.join(process.cwd(), "src/app");
2239
+ const publicsDir = path5.join(appRoot, "(publics)");
2240
+ const privatesDir = path5.join(appRoot, "(privates)");
2241
+ const managerDir = path5.join(privatesDir, "manager");
2242
+ let created = false;
2243
+ if (!pathExists(publicsDir)) {
2244
+ createDir(publicsDir);
2245
+ created = true;
2246
+ }
2247
+ if (!pathExists(privatesDir)) {
2248
+ createDir(privatesDir);
2249
+ created = true;
2250
+ }
2251
+ if (!pathExists(managerDir)) {
2252
+ createDir(managerDir);
2253
+ created = true;
2254
+ }
2255
+ const rootLayout = path5.join(appRoot, "layout.tsx");
2256
+ if (!pathExists(rootLayout)) {
2257
+ createFile(rootLayout, rootLayoutMinimalTemplate());
2258
+ created = true;
2259
+ }
2260
+ const rootPage = path5.join(appRoot, "page.tsx");
2261
+ const publicsPage = path5.join(publicsDir, "page.tsx");
2262
+ if (pathExists(rootPage) && !pathExists(publicsPage)) {
2263
+ moveFile(rootPage, publicsPage);
2264
+ created = true;
2265
+ }
2266
+ const publicsLayout = path5.join(publicsDir, "layout.tsx");
2267
+ if (!pathExists(publicsLayout)) {
2268
+ createFile(publicsLayout, publicLayoutTemplate());
2269
+ created = true;
2270
+ }
2271
+ const privateLayoutPath = path5.join(privatesDir, "layout.tsx");
2272
+ if (!pathExists(privateLayoutPath)) {
2273
+ createFile(privateLayoutPath, privateNextLayoutTemplate());
2274
+ created = true;
2275
+ }
2276
+ const managerPagePath = path5.join(managerDir, "page.tsx");
2277
+ if (!pathExists(managerPagePath)) {
2278
+ createFile(managerPagePath, managerPageTemplate());
2279
+ created = true;
2280
+ }
2281
+ if (created && this.options?.git) {
2282
+ this.createCommit("feat(app-router): created private and publics route groups");
2283
+ }
2284
+ ;
2285
+ return this;
2286
+ }
2287
+ createComponentsAux() {
2288
+ const root = process.cwd();
2289
+ const contextsDir = path5.join(root, "src/contexts");
2290
+ const componentsDir = path5.join(root, "src/components");
2291
+ const headerDir = path5.join(componentsDir, "Header");
2292
+ const footerDir = path5.join(componentsDir, "Footer");
2293
+ const menuAsideDir = path5.join(componentsDir, "MenuAside");
2294
+ const buttonGenericDir = path5.join(componentsDir, "ButtonGeneric");
2295
+ DependencyFramemotionInstaller.getInstance().install();
2296
+ let created = false;
2297
+ if (!pathExists(contextsDir)) {
2298
+ createDir(contextsDir);
2299
+ created = true;
2300
+ }
2301
+ ;
2302
+ const managerContextPath = path5.join(contextsDir, "manager-context.tsx");
2303
+ if (!pathExists(managerContextPath)) {
2304
+ createFile(managerContextPath, managerContextTemplate());
2305
+ created = true;
2306
+ }
2307
+ if (!pathExists(componentsDir)) {
2308
+ createDir(componentsDir);
2309
+ created = true;
2310
+ }
2311
+ const components = [
2312
+ { dir: headerDir, template: headerTemplate() },
2313
+ { dir: footerDir, template: footerTemplate() },
2314
+ { dir: menuAsideDir, template: menuAsideTemplate() },
2315
+ { dir: buttonGenericDir, template: buttonGenericTemplate() }
2316
+ ];
2317
+ components.forEach((component) => {
2318
+ if (!pathExists(component.dir)) {
2319
+ createDir(component.dir);
2320
+ created = true;
2321
+ }
2322
+ const indexPath = path5.join(component.dir, "index.tsx");
2323
+ if (!pathExists(indexPath)) {
2324
+ createFile(indexPath, component.template);
2325
+ created = true;
2326
+ }
2327
+ });
2328
+ if (created && this.options?.git) {
2329
+ this.createCommit("feat(core): create manager context and essential layout components");
2330
+ }
2331
+ ;
2332
+ return this;
2333
+ }
2334
+ setEnvironmentVariable() {
2335
+ this.basePath = path5.join(process.cwd());
2336
+ createFile(path5.join(this.basePath, ".env.local"), environmentTemplate());
2337
+ return this;
2338
+ }
2339
+ build() {
2340
+ if (!this.options?.git) return;
2341
+ console.log("commits made successfully \u2728");
2342
+ }
2343
+ };
2344
+
2345
+ // src/commands/create-nextauth-resource.ts
2346
+ function createNextAutResource(options) {
2347
+ nextProjectGuardSimple();
2348
+ const builder = new NextAuthBuilder(options);
2349
+ builder.setBasePathAndCreateConfig().createNextAuthAuxOptions().createNextAuthForms().createNextLayouts().setLayouts().createComponentsAux().createNextAutorizationSystem().setEnvironmentVariable().build();
2350
+ console.log(`next auth configured \x1B[32m\u2714 success\x1B[0m`);
818
2351
  }
819
2352
 
820
2353
  // src/utils/interceptors/args.interceptor.ts
@@ -867,10 +2400,14 @@ function main(args) {
867
2400
  case "create:form":
868
2401
  createFormForResource(resource, { git: useGit });
869
2402
  break;
2403
+ case "config:next-auth":
2404
+ createNextAutResource({ git: useGit });
2405
+ break;
870
2406
  case "-help":
871
2407
  console.log("commands available in the cli:");
872
2408
  console.log(" create <resource-name> - creates all folders for a new resource.");
873
2409
  console.log(" create:form <resource-name> - creates a new form for the resource");
2410
+ console.log(" config:next-auth - creates a new form for the resource");
874
2411
  break;
875
2412
  default:
876
2413
  console.log("command unavailable in the cli...");