feat: initial release of NASG
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user