feat: initial release of NASG
This commit is contained in:
+41
@@ -0,0 +1,41 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/versions
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# env files (can opt-in for committing if needed)
|
||||||
|
.env*
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import Footer from "@/components/Footer";
|
||||||
|
import GithubLink from "@/components/GithubLink";
|
||||||
|
import { metadata } from "@/metadata.config";
|
||||||
|
import "./globals.css";
|
||||||
|
|
||||||
|
export { metadata };
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<html lang="en" className="scroll-smooth">
|
||||||
|
<body className="min-h-screen bg-neutral-950 text-white antialiased">
|
||||||
|
<GithubLink />
|
||||||
|
{children}
|
||||||
|
<Footer />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import Actions from "@/components/Actions";
|
||||||
|
import Background from "@/components/Background";
|
||||||
|
import Header from "@/components/Header";
|
||||||
|
import SecretCard from "@/components/SecretCard";
|
||||||
|
import SecurityNotice from "@/components/SecurityNotice";
|
||||||
|
import { copyToClipboard } from "@/lib/copyToClipboard";
|
||||||
|
import { generateSecret } from "@/lib/generateSecret";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Toaster, toast } from "react-hot-toast";
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
const [secret, setSecret] = useState("");
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
const [copiedEnv, setCopiedEnv] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => setSecret(generateSecret()), []);
|
||||||
|
|
||||||
|
const handleGenerate = () => {
|
||||||
|
const s = generateSecret();
|
||||||
|
setSecret(s);
|
||||||
|
setCopied(false);
|
||||||
|
setCopiedEnv(false);
|
||||||
|
toast.success("New secret generated!", { id: "generate", duration: 1200 });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="relative min-h-screen flex items-center justify-center p-6 text-white">
|
||||||
|
<Background />
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
className="w-full max-w-3xl"
|
||||||
|
initial={{ opacity: 0, y: 30 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6, ease: "easeOut" }}
|
||||||
|
>
|
||||||
|
<div className="relative mx-auto rounded-3xl border border-white/10 bg-white/5 p-8 shadow-[0_10px_30px_-10px_rgba(0,0,0,.5)] backdrop-blur-xl">
|
||||||
|
<div className="pointer-events-none absolute inset-x-8 -top-0.5 h-px bg-gradient-to-r from-transparent via-sky-400/60 to-transparent" />
|
||||||
|
|
||||||
|
<Header />
|
||||||
|
|
||||||
|
<Actions
|
||||||
|
secret={secret}
|
||||||
|
copiedEnv={copiedEnv}
|
||||||
|
onGenerate={handleGenerate}
|
||||||
|
onCopyEnv={() => copyToClipboard(`NEXTAUTH_SECRET=${secret}`, setCopiedEnv, ".env")}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SecretCard
|
||||||
|
secret={secret}
|
||||||
|
copied={copied}
|
||||||
|
onCopy={() => copyToClipboard(secret, setCopied, "Secret")}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<p className="mt-3 text-center text-xs text-neutral-400">
|
||||||
|
Copies: secret only or the full{" "}
|
||||||
|
<code className="rounded bg-white/10 px-1 py-0.5">NEXTAUTH_SECRET=...</code> line.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<SecurityNotice />
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<Toaster position="bottom-center" />
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { Check, Copy, RefreshCw } from "lucide-react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
secret: string;
|
||||||
|
copiedEnv: boolean;
|
||||||
|
onGenerate: () => void;
|
||||||
|
onCopyEnv: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Actions({ secret, copiedEnv, onGenerate, onCopyEnv }: Props) {
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
className="mt-8 flex flex-col items-stretch gap-3 sm:flex-row sm:items-center sm:justify-center"
|
||||||
|
initial={{ opacity: 0, scale: 0.97 }}
|
||||||
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
transition={{ delay: 0.3, duration: 0.5, ease: "easeOut" }}
|
||||||
|
>
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.03 }}
|
||||||
|
whileTap={{ scale: 0.97 }}
|
||||||
|
transition={{ type: "spring", stiffness: 300, damping: 20 }}
|
||||||
|
onClick={onGenerate}
|
||||||
|
className="group inline-flex justify-center items-center gap-2 rounded-2xl border border-sky-500/30 bg-sky-500/10 px-5 py-2.5 text-sm font-semibold text-sky-300 ring-sky-500/40 transition hover:bg-sky-500/15 focus:outline-none focus:ring-4"
|
||||||
|
>
|
||||||
|
<RefreshCw className="h-4 w-4 transition group-hover:rotate-90" />
|
||||||
|
Generate
|
||||||
|
</motion.button>
|
||||||
|
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.03 }}
|
||||||
|
whileTap={{ scale: 0.97 }}
|
||||||
|
transition={{ type: "spring", stiffness: 300, damping: 20 }}
|
||||||
|
onClick={onCopyEnv}
|
||||||
|
className="inline-flex justify-center items-center gap-2 rounded-2xl border border-white/10 bg-white/10 px-5 py-2.5 text-sm font-medium shadow-sm transition hover:bg-white/15"
|
||||||
|
aria-label="Copy .env line"
|
||||||
|
title={copiedEnv ? "Copied" : "Copy .env"}
|
||||||
|
>
|
||||||
|
<span className="font-semibold">.env</span>
|
||||||
|
{copiedEnv ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
|
||||||
|
</motion.button>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
export default function Background() {
|
||||||
|
return (
|
||||||
|
<div className="absolute inset-0 -z-10">
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-br from-[#0f172a] via-[#1e293b] to-[#0f172a]" />
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 bg-[url('data:image/svg+xml;utf8,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%2240%22 height=%2240%22 fill=%22none%22 stroke=%22%233459a1%22 stroke-width=%220.4%22><path d=%22M0 .5H40M.5 0V40%22/></svg>')] opacity-[0.05]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { FaHeart } from "react-icons/fa";
|
||||||
|
|
||||||
|
export default function Footer() {
|
||||||
|
return (
|
||||||
|
<footer className="fixed bottom-4 left-1/2 -translate-x-1/2 text-xs text-neutral-400 flex items-center gap-1">
|
||||||
|
<span>Made with</span>
|
||||||
|
<FaHeart className="text-red-500 animate-pulse" />
|
||||||
|
<span>by</span>
|
||||||
|
<a
|
||||||
|
href="https://ultralion.xyz"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="font-medium text-white hover:text-sky-400 transition"
|
||||||
|
>
|
||||||
|
UltraLion
|
||||||
|
</a>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { FaGithub } from "react-icons/fa";
|
||||||
|
|
||||||
|
export default function GithubLink() {
|
||||||
|
const [hover, setHover] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed top-4 right-4 z-50">
|
||||||
|
<Link
|
||||||
|
href="https://github.com/UltraLionfr/nasg"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
onMouseEnter={() => setHover(true)}
|
||||||
|
onMouseLeave={() => setHover(false)}
|
||||||
|
className="group relative flex items-center justify-center"
|
||||||
|
>
|
||||||
|
{/* Icône GitHub */}
|
||||||
|
<motion.div
|
||||||
|
whileHover={{ scale: 1.1, rotate: 5 }}
|
||||||
|
whileTap={{ scale: 0.95 }}
|
||||||
|
className="rounded-full border border-white/10 bg-black/60 p-2 backdrop-blur transition-colors group-hover:bg-black/80"
|
||||||
|
>
|
||||||
|
<FaGithub className="h-5 w-5 text-white" />
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Tooltip au survol */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 10 }}
|
||||||
|
animate={hover ? { opacity: 1, y: 0 } : { opacity: 0, y: 10 }}
|
||||||
|
transition={{ duration: 0.2 }}
|
||||||
|
className="absolute right-10 top-1/2 -translate-y-1/2 whitespace-nowrap rounded-lg border border-white/10 bg-black/80 px-3 py-1.5 text-xs text-white shadow-lg backdrop-blur"
|
||||||
|
>
|
||||||
|
View on GitHub
|
||||||
|
</motion.div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { Shield } from "lucide-react";
|
||||||
|
|
||||||
|
export default function Header() {
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
className="text-center"
|
||||||
|
initial={{ opacity: 0, y: -15 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: 0.2, duration: 0.5, ease: "easeOut" }}
|
||||||
|
>
|
||||||
|
<div className="mx-auto mb-3 inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/10 px-3 py-1 text-xs font-medium text-neutral-200 backdrop-blur-sm">
|
||||||
|
<Shield className="h-3.5 w-3.5" />
|
||||||
|
secure • 32 bytes Base64
|
||||||
|
</div>
|
||||||
|
<h1 className="text-3xl md:text-5xl font-extrabold tracking-tight text-white">
|
||||||
|
Auth secret generator
|
||||||
|
</h1>
|
||||||
|
<p className="mt-2 text-sm text-neutral-300">
|
||||||
|
Generate a cryptographically-secure secret for your Next.js apps.
|
||||||
|
</p>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
|
import { Check, Copy } from "lucide-react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
secret: string;
|
||||||
|
copied: boolean;
|
||||||
|
onCopy: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function SecretCard({ secret, copied, onCopy }: Props) {
|
||||||
|
return (
|
||||||
|
<div className="mt-6 rounded-2xl border border-white/10 bg-black/40 text-white shadow-md">
|
||||||
|
<div className="flex items-center gap-3 p-4">
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
<motion.div
|
||||||
|
key={secret}
|
||||||
|
className="flex-1 max-h-24 overflow-y-auto break-all font-mono text-sm opacity-90"
|
||||||
|
initial={{ opacity: 0, y: -8 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, y: 8 }}
|
||||||
|
transition={{ duration: 0.3, ease: "easeOut" }}
|
||||||
|
>
|
||||||
|
{secret}
|
||||||
|
</motion.div>
|
||||||
|
</AnimatePresence>
|
||||||
|
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.1 }}
|
||||||
|
whileTap={{ scale: 0.9 }}
|
||||||
|
transition={{ type: "spring", stiffness: 300, damping: 20 }}
|
||||||
|
onClick={onCopy}
|
||||||
|
className="inline-flex items-center justify-center rounded-xl border border-white/10 bg-white/5 p-2 transition hover:bg-white/10"
|
||||||
|
aria-label="Copy secret"
|
||||||
|
title={copied ? "Copied" : "Copy"}
|
||||||
|
>
|
||||||
|
{copied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
|
||||||
|
</motion.button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { Lock } from "lucide-react";
|
||||||
|
|
||||||
|
export default function SecurityNotice() {
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
className="mt-4 flex items-start gap-2 rounded-xl border border-sky-500/30 bg-black/50 p-3 font-mono text-xs text-sky-300"
|
||||||
|
initial={{ opacity: 0, y: 15 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: 0.4, duration: 0.5, ease: "easeOut" }}
|
||||||
|
>
|
||||||
|
<Lock className="h-4 w-4 mt-0.5 shrink-0 text-sky-400" />
|
||||||
|
<p>
|
||||||
|
No secrets generated are saved or transmitted.
|
||||||
|
Everything is generated <span className="text-white font-semibold">locally</span> in your browser.
|
||||||
|
</p>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { dirname } from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
import { FlatCompat } from "@eslint/eslintrc";
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
});
|
||||||
|
|
||||||
|
const eslintConfig = [
|
||||||
|
...compat.extends("next/core-web-vitals", "next/typescript"),
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
"node_modules/**",
|
||||||
|
".next/**",
|
||||||
|
"out/**",
|
||||||
|
"build/**",
|
||||||
|
"next-env.d.ts",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default eslintConfig;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import js from "@eslint/js";
|
||||||
|
import globals from "globals";
|
||||||
|
import tseslint from "typescript-eslint";
|
||||||
|
import pluginReact from "eslint-plugin-react";
|
||||||
|
import { defineConfig } from "eslint/config";
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
{ files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], plugins: { js }, extends: ["js/recommended"], languageOptions: { globals: globals.browser } },
|
||||||
|
tseslint.configs.recommended,
|
||||||
|
pluginReact.configs.flat.recommended,
|
||||||
|
]);
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { toast } from "react-hot-toast";
|
||||||
|
|
||||||
|
export async function copyToClipboard(
|
||||||
|
value: string,
|
||||||
|
setFlag: (b: boolean) => void,
|
||||||
|
label?: string
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(value);
|
||||||
|
setFlag(true);
|
||||||
|
toast.success(label ? `${label} copied!` : "Copied!", {
|
||||||
|
id: label ? `copy-${label}` : "copy",
|
||||||
|
duration: 1200,
|
||||||
|
});
|
||||||
|
setTimeout(() => setFlag(false), 1200);
|
||||||
|
} catch {
|
||||||
|
toast.error("Error during copying");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export function generateSecret(bytes = 32): string {
|
||||||
|
const arr = new Uint8Array(bytes);
|
||||||
|
crypto.getRandomValues(arr);
|
||||||
|
let bin = "";
|
||||||
|
arr.forEach((b) => (bin += String.fromCharCode(b)));
|
||||||
|
return btoa(bin);
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import type { Metadata, Viewport } from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Auth Secret Generator",
|
||||||
|
description:
|
||||||
|
"Generate cryptographically secure secrets for your Next.js apps. No data is stored or transmitted.",
|
||||||
|
icons: {
|
||||||
|
icon: "/favicon.png",
|
||||||
|
},
|
||||||
|
openGraph: {
|
||||||
|
title: "Auth Secret Generator",
|
||||||
|
description:
|
||||||
|
"Generate cryptographically secure secrets for your Next.js apps. No data is stored or transmitted.",
|
||||||
|
url: "https://nasg.ultralion.xyz",
|
||||||
|
siteName: "Auth Secret Generator",
|
||||||
|
type: "website",
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
card: "summary",
|
||||||
|
title: "Auth Secret Generator",
|
||||||
|
description:
|
||||||
|
"Generate cryptographically secure secrets for your Next.js apps. No data is stored or transmitted.",
|
||||||
|
},
|
||||||
|
robots: {
|
||||||
|
index: true,
|
||||||
|
follow: true,
|
||||||
|
nocache: false,
|
||||||
|
googleBot: {
|
||||||
|
index: true,
|
||||||
|
follow: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const viewport: Viewport = {
|
||||||
|
themeColor: "#0f172a",
|
||||||
|
};
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
/* config options here */
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
{
|
||||||
|
"name": "nasg",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"description": "Auth Secret Generator — Generate cryptographically secure secrets for your Next.js apps. No data is stored or transmitted.",
|
||||||
|
"author": {
|
||||||
|
"name": "UltraLion",
|
||||||
|
"email": "ultralionfr@gmail.com",
|
||||||
|
"url": "https://ultralion.xyz"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/UltraLionfr/nasg.git"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/UltraLionfr/nasg/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://nasg.ultralion.xyz",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "next lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@tailwindcss/postcss": "^4.1.12",
|
||||||
|
"framer-motion": "^12.23.12",
|
||||||
|
"lucide-react": "^0.540.0",
|
||||||
|
"next": "15.5.0",
|
||||||
|
"postcss": "^8.5.6",
|
||||||
|
"react": "19.1.0",
|
||||||
|
"react-dom": "19.1.0",
|
||||||
|
"react-hot-toast": "^2.6.0",
|
||||||
|
"react-icons": "^5.5.0",
|
||||||
|
"tailwindcss": "^4.1.12"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "^3",
|
||||||
|
"@eslint/js": "^9.33.0",
|
||||||
|
"@types/node": "^20",
|
||||||
|
"@types/react": "^19",
|
||||||
|
"@types/react-dom": "^19",
|
||||||
|
"eslint": "^9.33.0",
|
||||||
|
"eslint-config-next": "15.5.0",
|
||||||
|
"eslint-plugin-react": "^7.37.5",
|
||||||
|
"globals": "^16.3.0",
|
||||||
|
"tw-animate-css": "^1.3.7",
|
||||||
|
"typescript": "^5",
|
||||||
|
"typescript-eslint": "^8.40.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.18.0",
|
||||||
|
"pnpm": ">=9.0.0"
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@9.12.3"
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
const config = {
|
||||||
|
plugins: {
|
||||||
|
"@tailwindcss/postcss": {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
export default config;
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2017",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"incremental": true,
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user