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.
- package/README.md +12 -2
- package/dist/index.js +1603 -66
- 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
|
|
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("
|
|
43
|
+
toast.success("Resource created successfully!");
|
|
44
44
|
router.refresh();
|
|
45
45
|
router.push("/manager");
|
|
46
46
|
}
|
|
47
|
-
else { toast.error("
|
|
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("
|
|
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("
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1390
|
+
const componentPath = path4.join(this.basePath, "Inputs/InputCustom");
|
|
713
1391
|
createDir(componentPath);
|
|
714
|
-
createFile(
|
|
715
|
-
if (this.options?.git) this.createCommit(`feat(
|
|
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(
|
|
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 =
|
|
1401
|
+
const detailDir = path4.join(this.basePath, `[${this.singular}Id]`);
|
|
724
1402
|
createDir(detailDir);
|
|
725
|
-
createFile(
|
|
726
|
-
const editDir =
|
|
1403
|
+
createFile(path4.join(detailDir, "page.tsx"), detailPageTemplate(capitalize(this.singular)));
|
|
1404
|
+
const editDir = path4.join(detailDir, "edit");
|
|
727
1405
|
createDir(editDir);
|
|
728
|
-
createFile(
|
|
729
|
-
const deleteDir =
|
|
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(
|
|
732
|
-
this.
|
|
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 =
|
|
1414
|
+
const dir = path4.join(this.basePath, "new");
|
|
737
1415
|
createDir(dir);
|
|
738
|
-
createFile(
|
|
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 =
|
|
743
|
-
const deletePath =
|
|
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(
|
|
748
|
-
createFile(
|
|
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 =
|
|
1429
|
+
const resourcePath = path4.join(this.basePath, this.resource);
|
|
752
1430
|
createDir(resourcePath);
|
|
753
1431
|
DependencyInstaller.getInstance().install();
|
|
754
|
-
const formNewPath =
|
|
1432
|
+
const formNewPath = path4.join(resourcePath, "FormNew");
|
|
755
1433
|
createDir(formNewPath);
|
|
756
|
-
createFile(
|
|
757
|
-
createFile(
|
|
758
|
-
const formEditPath =
|
|
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(
|
|
761
|
-
createFile(
|
|
762
|
-
createFile(
|
|
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.
|
|
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
|
-
|
|
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[
|
|
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(`
|
|
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[
|
|
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
|
|
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...");
|