mirror of
https://gitea.gofwd.group/dstrawsb/ballistic-builder.git
synced 2025-12-06 02:36:44 -05:00
working
This commit is contained in:
10
.env
10
.env
@@ -11,12 +11,12 @@ DATABASE_URL="postgresql://postgres:cul8rman@portainer.dev.gofwd.group:5433/ball
|
|||||||
|
|
||||||
# DATABASE_URL='postgresql://postgres:cul8rman@r710.gofwd.group:5433/luciatest'
|
# DATABASE_URL='postgresql://postgres:cul8rman@r710.gofwd.group:5433/luciatest'
|
||||||
NEXT_PUBLIC_APP_URL='http://localhost:3000'
|
NEXT_PUBLIC_APP_URL='http://localhost:3000'
|
||||||
MOCK_SEND_EMAIL=true
|
MOCK_SEND_EMAIL=false
|
||||||
|
|
||||||
SMTP_HOST='smtp.example-host.com'
|
SMTP_HOST='smtp-relay.brevo.com'
|
||||||
SMTP_PORT=25
|
SMTP_PORT=587
|
||||||
SMTP_USER='smtp_example_username'
|
SMTP_USER='79f6e8001@smtp-brevo.com'
|
||||||
SMTP_PASSWORD='smtp_example_password'
|
SMTP_PASSWORD='RWg5dz6x1kVAtEnS'
|
||||||
|
|
||||||
DISCORD_CLIENT_ID='discord_client_id'
|
DISCORD_CLIENT_ID='discord_client_id'
|
||||||
DISCORD_CLIENT_SECRET='discord_client_secret'
|
DISCORD_CLIENT_SECRET='discord_client_secret'
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
[2025-01-28T04:58:56.488Z] [INFO] 📨 Email sent to: don@strawsburg.com with template: EmailVerification and props: {"code":"70365595"}
|
[2025-01-28T04:58:56.488Z] [INFO] 📨 Email sent to: don@strawsburg.com with template: EmailVerification and props: {"code":"70365595"}
|
||||||
|
[2025-01-28T22:29:40.567Z] [INFO] 📨 Email sent to: dstrawsb@gmail.com with template: EmailVerification and props: {"code":"36553870"}
|
||||||
|
|||||||
@@ -30,12 +30,12 @@
|
|||||||
"@react-email/render": "^1.0.4",
|
"@react-email/render": "^1.0.4",
|
||||||
"@t3-oss/env-nextjs": "^0.12.0",
|
"@t3-oss/env-nextjs": "^0.12.0",
|
||||||
"@tanstack/react-query": "^4.35.3",
|
"@tanstack/react-query": "^4.35.3",
|
||||||
|
|
||||||
"@trpc/react-query": "^10.45.2",
|
|
||||||
"@trpc/server": "^10.45.2",
|
|
||||||
"@trpc/client": "^10.45.2",
|
"@trpc/client": "^10.45.2",
|
||||||
"@trpc/next": "^10.45.2",
|
"@trpc/next": "^10.45.2",
|
||||||
|
"@trpc/react-query": "^10.45.2",
|
||||||
|
"@trpc/server": "^10.45.2",
|
||||||
"@types/bcryptjs": "^2.4.6",
|
"@types/bcryptjs": "^2.4.6",
|
||||||
|
"@types/js-cookie": "^3.0.6",
|
||||||
"arctic": "^3.2.1",
|
"arctic": "^3.2.1",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
@@ -43,6 +43,7 @@
|
|||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"fontsource-roboto": "^4.0.0",
|
"fontsource-roboto": "^4.0.0",
|
||||||
"framer-motion": "^11.18.0",
|
"framer-motion": "^11.18.0",
|
||||||
|
"js-cookies": "^1.0.4",
|
||||||
"lucia": "^3.2.2",
|
"lucia": "^3.2.2",
|
||||||
"lucide-react": "^0.460.0",
|
"lucide-react": "^0.460.0",
|
||||||
"next": "15.1.0",
|
"next": "15.1.0",
|
||||||
@@ -69,6 +70,7 @@
|
|||||||
"@auth/drizzle-adapter": "^1.7.4",
|
"@auth/drizzle-adapter": "^1.7.4",
|
||||||
"@types/bun": "^1.1.13",
|
"@types/bun": "^1.1.13",
|
||||||
"@types/node": "^20.17.10",
|
"@types/node": "^20.17.10",
|
||||||
|
"@types/nodemailer": "^6.4.17",
|
||||||
"@types/pg": "^8.11.10",
|
"@types/pg": "^8.11.10",
|
||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
|
|||||||
26
pnpm-lock.yaml
generated
26
pnpm-lock.yaml
generated
@@ -86,6 +86,9 @@ importers:
|
|||||||
'@types/bcryptjs':
|
'@types/bcryptjs':
|
||||||
specifier: ^2.4.6
|
specifier: ^2.4.6
|
||||||
version: 2.4.6
|
version: 2.4.6
|
||||||
|
'@types/js-cookie':
|
||||||
|
specifier: ^3.0.6
|
||||||
|
version: 3.0.6
|
||||||
arctic:
|
arctic:
|
||||||
specifier: ^3.2.1
|
specifier: ^3.2.1
|
||||||
version: 3.2.1
|
version: 3.2.1
|
||||||
@@ -107,6 +110,9 @@ importers:
|
|||||||
framer-motion:
|
framer-motion:
|
||||||
specifier: ^11.18.0
|
specifier: ^11.18.0
|
||||||
version: 11.18.2(@emotion/is-prop-valid@1.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
version: 11.18.2(@emotion/is-prop-valid@1.3.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||||
|
js-cookies:
|
||||||
|
specifier: ^1.0.4
|
||||||
|
version: 1.0.4
|
||||||
lucia:
|
lucia:
|
||||||
specifier: ^3.2.2
|
specifier: ^3.2.2
|
||||||
version: 3.2.2
|
version: 3.2.2
|
||||||
@@ -180,6 +186,9 @@ importers:
|
|||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^20.17.10
|
specifier: ^20.17.10
|
||||||
version: 20.17.16
|
version: 20.17.16
|
||||||
|
'@types/nodemailer':
|
||||||
|
specifier: ^6.4.17
|
||||||
|
version: 6.4.17
|
||||||
'@types/pg':
|
'@types/pg':
|
||||||
specifier: ^8.11.10
|
specifier: ^8.11.10
|
||||||
version: 8.11.11
|
version: 8.11.11
|
||||||
@@ -2072,12 +2081,18 @@ packages:
|
|||||||
'@types/cookie@0.6.0':
|
'@types/cookie@0.6.0':
|
||||||
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
|
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
|
||||||
|
|
||||||
|
'@types/js-cookie@3.0.6':
|
||||||
|
resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==}
|
||||||
|
|
||||||
'@types/json5@0.0.29':
|
'@types/json5@0.0.29':
|
||||||
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
||||||
|
|
||||||
'@types/node@20.17.16':
|
'@types/node@20.17.16':
|
||||||
resolution: {integrity: sha512-vOTpLduLkZXePLxHiHsBLp98mHGnl8RptV4YAO3HfKO5UHjDvySGbxKtpYfy8Sx5+WKcgc45qNreJJRVM3L6mw==}
|
resolution: {integrity: sha512-vOTpLduLkZXePLxHiHsBLp98mHGnl8RptV4YAO3HfKO5UHjDvySGbxKtpYfy8Sx5+WKcgc45qNreJJRVM3L6mw==}
|
||||||
|
|
||||||
|
'@types/nodemailer@6.4.17':
|
||||||
|
resolution: {integrity: sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==}
|
||||||
|
|
||||||
'@types/parse-json@4.0.2':
|
'@types/parse-json@4.0.2':
|
||||||
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
|
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
|
||||||
|
|
||||||
@@ -3240,6 +3255,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
|
resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
|
js-cookies@1.0.4:
|
||||||
|
resolution: {integrity: sha512-cO1SHDH7zJsi8FihHmDtcWx90mWmrfGOrcLKPeaEX6tLyuTK2wnzgdmNa34Q6rNAd6VhQUgjDt5Eyl90VI/Fpg==}
|
||||||
|
|
||||||
js-tokens@4.0.0:
|
js-tokens@4.0.0:
|
||||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||||
|
|
||||||
@@ -5918,12 +5936,18 @@ snapshots:
|
|||||||
|
|
||||||
'@types/cookie@0.6.0': {}
|
'@types/cookie@0.6.0': {}
|
||||||
|
|
||||||
|
'@types/js-cookie@3.0.6': {}
|
||||||
|
|
||||||
'@types/json5@0.0.29': {}
|
'@types/json5@0.0.29': {}
|
||||||
|
|
||||||
'@types/node@20.17.16':
|
'@types/node@20.17.16':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 6.19.8
|
undici-types: 6.19.8
|
||||||
|
|
||||||
|
'@types/nodemailer@6.4.17':
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 20.17.16
|
||||||
|
|
||||||
'@types/parse-json@4.0.2': {}
|
'@types/parse-json@4.0.2': {}
|
||||||
|
|
||||||
'@types/pg@8.11.11':
|
'@types/pg@8.11.11':
|
||||||
@@ -7299,6 +7323,8 @@ snapshots:
|
|||||||
|
|
||||||
js-cookie@3.0.5: {}
|
js-cookie@3.0.5: {}
|
||||||
|
|
||||||
|
js-cookies@1.0.4: {}
|
||||||
|
|
||||||
js-tokens@4.0.0: {}
|
js-tokens@4.0.0: {}
|
||||||
|
|
||||||
js-yaml@4.1.0:
|
js-yaml@4.1.0:
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export async function GET(): Promise<Response> {
|
|||||||
scopes: ["identify", "email"],
|
scopes: ["identify", "email"],
|
||||||
});
|
});
|
||||||
|
|
||||||
cookies().set("discord_oauth_state", state, {
|
(await cookies()).set("discord_oauth_state", state, {
|
||||||
path: "/",
|
path: "/",
|
||||||
secure: env.NODE_ENV === "production",
|
secure: env.NODE_ENV === "production",
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useFormState } from "react-dom";
|
import { useActionState } from "react";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
@@ -13,7 +13,7 @@ import { Label } from "@/components/ui/label";
|
|||||||
import { SubmitButton } from "@/components/submit-button";
|
import { SubmitButton } from "@/components/submit-button";
|
||||||
|
|
||||||
export function Login() {
|
export function Login() {
|
||||||
const [state, formAction] = useFormState(login, null);
|
const [state, formAction] = useActionState(login, null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="w-full max-w-md">
|
<Card className="w-full max-w-md">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useFormState } from "react-dom";
|
import { useActionState } from "react-dom";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { ExclamationTriangleIcon } from "@/components/icons";
|
import { ExclamationTriangleIcon } from "@/components/icons";
|
||||||
import { SubmitButton } from "@/components/submit-button";
|
import { SubmitButton } from "@/components/submit-button";
|
||||||
@@ -10,7 +10,7 @@ import { Label } from "@/components/ui/label";
|
|||||||
import { resetPassword } from "@/lib/auth/actions";
|
import { resetPassword } from "@/lib/auth/actions";
|
||||||
|
|
||||||
export function ResetPassword({ token }: { token: string }) {
|
export function ResetPassword({ token }: { token: string }) {
|
||||||
const [state, formAction] = useFormState(resetPassword, null);
|
const [state, formAction] = useActionState(resetPassword, null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (state?.error) {
|
if (state?.error) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useFormState } from "react-dom";
|
import { useActionState } from "react-dom";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@@ -14,7 +14,7 @@ import { ExclamationTriangleIcon } from "@/components/icons";
|
|||||||
import { Paths } from "@/lib/constants";
|
import { Paths } from "@/lib/constants";
|
||||||
|
|
||||||
export function SendResetEmail() {
|
export function SendResetEmail() {
|
||||||
const [state, formAction] = useFormState(sendPasswordResetLink, null);
|
const [state, formAction] = useActionState(sendPasswordResetLink, null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useFormState } from "react-dom";
|
import { useActionState } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { PasswordInput } from "@/components/password-input";
|
import { PasswordInput } from "@/components/password-input";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -13,7 +13,7 @@ import { signup } from "@/lib/auth/actions";
|
|||||||
import { SubmitButton } from "@/components/submit-button";
|
import { SubmitButton } from "@/components/submit-button";
|
||||||
|
|
||||||
export function Signup() {
|
export function Signup() {
|
||||||
const [state, formAction] = useFormState(signup, null);
|
const [state, formAction] = useActionState(signup, null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="w-full max-w-md">
|
<Card className="w-full max-w-md">
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@radix-ui/react-label";
|
import { Label } from "@radix-ui/react-label";
|
||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { useFormState } from "react-dom";
|
import { useActionState } from "react-dom";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { ExclamationTriangleIcon } from "@/components/icons";
|
import { ExclamationTriangleIcon } from "@/components/icons";
|
||||||
import { logout, verifyEmail, resendVerificationEmail as resendEmail } from "@/lib/auth/actions";
|
import { logout, verifyEmail, resendVerificationEmail as resendEmail } from "@/lib/auth/actions";
|
||||||
import { SubmitButton } from "@/components/submit-button";
|
import { SubmitButton } from "@/components/submit-button";
|
||||||
|
|
||||||
export const VerifyCode = () => {
|
export const VerifyCode = () => {
|
||||||
const [verifyEmailState, verifyEmailAction] = useFormState(verifyEmail, null);
|
const [verifyEmailState, verifyEmailAction] = useActionState(verifyEmail, null);
|
||||||
const [resendState, resendAction] = useFormState(resendEmail, null);
|
const [resendState, resendAction] = useActionState(resendEmail, null);
|
||||||
const codeFormRef = useRef<HTMLFormElement>(null);
|
const codeFormRef = useRef<HTMLFormElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -20,7 +20,10 @@ export async function POST(request: Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Compare the provided password with the stored hashed password
|
// Compare the provided password with the stored hashed password
|
||||||
const isPasswordValid = await bcrypt.compare(password, user.password_hash);
|
if (!user.hashedPassword) {
|
||||||
|
return NextResponse.json({ error: 'Invalid email or password' }, { status: 401 });
|
||||||
|
}
|
||||||
|
const isPasswordValid = await bcrypt.compare(password, user.hashedPassword);
|
||||||
|
|
||||||
if (!isPasswordValid) {
|
if (!isPasswordValid) {
|
||||||
return NextResponse.json({ error: 'Invalid email or password' }, { status: 401 });
|
return NextResponse.json({ error: 'Invalid email or password' }, { status: 401 });
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { NextResponse } from "next/server";
|
|||||||
|
|
||||||
export async function GET(request: Request) {
|
export async function GET(request: Request) {
|
||||||
try {
|
try {
|
||||||
// const { email, password } = await request.json();
|
const { email, password } = await request.json();
|
||||||
|
|
||||||
// Fetch the user from the database
|
// Fetch the user from the database
|
||||||
const data = await db.select().from(users)
|
const data = await db.select().from(users)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
import { Fragment, useEffect, useState } from "react";
|
||||||
import { Fragment, useState } from "react";
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogBackdrop,
|
DialogBackdrop,
|
||||||
@@ -21,6 +20,9 @@ import {
|
|||||||
ShoppingBagIcon,
|
ShoppingBagIcon,
|
||||||
XMarkIcon,
|
XMarkIcon,
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
|
import { validateRequest } from "@/lib/auth/validate-request";
|
||||||
|
import { User } from "lucia";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
const navigation = {
|
const navigation = {
|
||||||
categories: [
|
categories: [
|
||||||
@@ -87,7 +89,17 @@ const navigation = {
|
|||||||
|
|
||||||
export default function PopNav() {
|
export default function PopNav() {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
const [user, setUser] = useState<User | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchUser = async () => {
|
||||||
|
const result = null; //(await validateRequest());
|
||||||
|
/* if (result.user) {
|
||||||
|
setUser(result.user);
|
||||||
|
} */
|
||||||
|
};
|
||||||
|
fetchUser();
|
||||||
|
}, []);
|
||||||
return (
|
return (
|
||||||
<div className="bg-white">
|
<div className="bg-white">
|
||||||
{/* Mobile menu */}
|
{/* Mobile menu */}
|
||||||
99
src/app/components/PopNav/page.tsx
Normal file
99
src/app/components/PopNav/page.tsx
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import { Fragment, } from "react";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogBackdrop,
|
||||||
|
DialogPanel,
|
||||||
|
Popover,
|
||||||
|
PopoverButton,
|
||||||
|
PopoverGroup,
|
||||||
|
PopoverPanel,
|
||||||
|
Tab,
|
||||||
|
TabGroup,
|
||||||
|
TabList,
|
||||||
|
TabPanel,
|
||||||
|
TabPanels,
|
||||||
|
} from "@headlessui/react";
|
||||||
|
import {
|
||||||
|
Bars3Icon,
|
||||||
|
MagnifyingGlassIcon,
|
||||||
|
ShoppingBagIcon,
|
||||||
|
XMarkIcon,
|
||||||
|
} from "@heroicons/react/24/outline";
|
||||||
|
import { validateRequest } from "@/lib/auth/validate-request";
|
||||||
|
import { User } from "lucia";
|
||||||
|
import {cookies} from 'next/headers';
|
||||||
|
import PopNavDialog from "../PopNavDialog/page";
|
||||||
|
|
||||||
|
const navigation = {
|
||||||
|
categories: [
|
||||||
|
{
|
||||||
|
id: "armory",
|
||||||
|
name: "Armory",
|
||||||
|
featured: [
|
||||||
|
{
|
||||||
|
name: "Build Alpha",
|
||||||
|
href: "#",
|
||||||
|
imageSrc:
|
||||||
|
"https://images.unsplash.com/photo-1700774607099-8c4631ee9764?q=80&w=2940&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
|
||||||
|
imageAlt: "Rad AR15.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Build Beta",
|
||||||
|
href: "#",
|
||||||
|
imageSrc:
|
||||||
|
"https://images.unsplash.com/photo-1669489890884-baff10f74b49?q=80&w=2899&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
|
||||||
|
imageAlt: "Rad AR15.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
id: "lower-parts",
|
||||||
|
name: "Lower Parts",
|
||||||
|
items: [
|
||||||
|
{ name: "Lower Receivers", href: "/Products/lowers" },
|
||||||
|
{ name: "Grips", href: "/Products/grips" },
|
||||||
|
{ name: "Magazines", href: "/Products/magazines" },
|
||||||
|
{ name: "Stocks", href: "/Products/stocks" },
|
||||||
|
{ name: "Triggers", href: "/Products/triggers" },
|
||||||
|
{ name: "Parts", href: "/Products/parts" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "upper-parts",
|
||||||
|
name: "Upper Parts",
|
||||||
|
items: [
|
||||||
|
{ name: "Upper Receiver", href: "/Products/uppers" },
|
||||||
|
{ name: "Barrels", href: "/Products/barrels" },
|
||||||
|
{ name: "Handguards", href: "/Products/handguards" },
|
||||||
|
{ name: "Muzzle Devices", href: "/Products/muzzle-devices" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "brands",
|
||||||
|
name: "Top Selling Brands",
|
||||||
|
items: [
|
||||||
|
{ name: "Radian Weapons", href: "#" },
|
||||||
|
{ name: "Noveske", href: "#" },
|
||||||
|
{ name: "Aero Precision", href: "#" },
|
||||||
|
{ name: "Primary Arms", href: "#" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pages: [
|
||||||
|
|
||||||
|
{ name: "Single Product", href: "/product" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function PopNav() {
|
||||||
|
const cookieStore = await cookies();
|
||||||
|
const session = cookieStore.get('session');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-white">{session?.value}
|
||||||
|
<PopNavDialog sessionCookie={session}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
400
src/app/components/PopNavDialog/page.tsx
Normal file
400
src/app/components/PopNavDialog/page.tsx
Normal file
@@ -0,0 +1,400 @@
|
|||||||
|
"use client";
|
||||||
|
import { Fragment, useEffect, useState } from "react";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogBackdrop,
|
||||||
|
DialogPanel,
|
||||||
|
Popover,
|
||||||
|
PopoverButton,
|
||||||
|
PopoverGroup,
|
||||||
|
PopoverPanel,
|
||||||
|
Tab,
|
||||||
|
TabGroup,
|
||||||
|
TabList,
|
||||||
|
TabPanel,
|
||||||
|
TabPanels,
|
||||||
|
} from "@headlessui/react";
|
||||||
|
import {
|
||||||
|
Bars3Icon,
|
||||||
|
MagnifyingGlassIcon,
|
||||||
|
ShoppingBagIcon,
|
||||||
|
XMarkIcon,
|
||||||
|
} from "@heroicons/react/24/outline";
|
||||||
|
import { validateRequest } from "@/lib/auth/validate-request";
|
||||||
|
import { User } from "lucia";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
|
const navigation = {
|
||||||
|
categories: [
|
||||||
|
{
|
||||||
|
id: "armory",
|
||||||
|
name: "Armory",
|
||||||
|
featured: [
|
||||||
|
{
|
||||||
|
name: "Build Alpha",
|
||||||
|
href: "#",
|
||||||
|
imageSrc:
|
||||||
|
"https://images.unsplash.com/photo-1700774607099-8c4631ee9764?q=80&w=2940&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
|
||||||
|
imageAlt: "Rad AR15.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Build Beta",
|
||||||
|
href: "#",
|
||||||
|
imageSrc:
|
||||||
|
"https://images.unsplash.com/photo-1669489890884-baff10f74b49?q=80&w=2899&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
|
||||||
|
imageAlt: "Rad AR15.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
id: "lower-parts",
|
||||||
|
name: "Lower Parts",
|
||||||
|
items: [
|
||||||
|
{ name: "Lower Receivers", href: "/Products/lowers" },
|
||||||
|
{ name: "Grips", href: "/Products/grips" },
|
||||||
|
{ name: "Magazines", href: "/Products/magazines" },
|
||||||
|
{ name: "Stocks", href: "/Products/stocks" },
|
||||||
|
{ name: "Triggers", href: "/Products/triggers" },
|
||||||
|
{ name: "Parts", href: "/Products/parts" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "upper-parts",
|
||||||
|
name: "Upper Parts",
|
||||||
|
items: [
|
||||||
|
{ name: "Upper Receiver", href: "/Products/uppers" },
|
||||||
|
{ name: "Barrels", href: "/Products/barrels" },
|
||||||
|
{ name: "Handguards", href: "/Products/handguards" },
|
||||||
|
{ name: "Muzzle Devices", href: "/Products/muzzle-devices" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "brands",
|
||||||
|
name: "Top Selling Brands",
|
||||||
|
items: [
|
||||||
|
{ name: "Radian Weapons", href: "#" },
|
||||||
|
{ name: "Noveske", href: "#" },
|
||||||
|
{ name: "Aero Precision", href: "#" },
|
||||||
|
{ name: "Primary Arms", href: "#" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pages: [
|
||||||
|
|
||||||
|
{ name: "Single Product", href: "/product" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function PopNavDialog(props:any) {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [user, setUser] = useState<User | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchUser = async () => {
|
||||||
|
const result = null; //(await validateRequest());
|
||||||
|
/* if (result.user) {
|
||||||
|
setUser(result.user);
|
||||||
|
} */
|
||||||
|
};
|
||||||
|
fetchUser();
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* Mobile menu */}
|
||||||
|
<Dialog open={open} onClose={setOpen} className="relative z-40 lg:hidden">
|
||||||
|
<DialogBackdrop
|
||||||
|
transition
|
||||||
|
className="fixed inset-0 bg-black/25 transition-opacity duration-300 ease-linear data-[closed]:opacity-0"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="fixed inset-0 z-40 flex">
|
||||||
|
<DialogPanel
|
||||||
|
transition
|
||||||
|
className="relative flex w-full max-w-xs transform flex-col overflow-y-auto bg-white pb-12 shadow-xl transition duration-300 ease-in-out data-[closed]:-translate-x-full"
|
||||||
|
>
|
||||||
|
<div className="flex px-4 pb-2 pt-5">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setOpen(false)}
|
||||||
|
className="relative -m-2 inline-flex items-center justify-center rounded-md p-2 text-gray-400"
|
||||||
|
>
|
||||||
|
<span className="absolute -inset-0.5" />
|
||||||
|
<span className="sr-only">Close menu</span>
|
||||||
|
<XMarkIcon aria-hidden="true" className="size-6" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Links */}
|
||||||
|
<TabGroup className="mt-2">
|
||||||
|
<div className="border-b border-gray-200">
|
||||||
|
<TabList className="-mb-px flex space-x-8 px-4">
|
||||||
|
{navigation.categories.map((category) => (
|
||||||
|
<Tab
|
||||||
|
key={category.name}
|
||||||
|
className="flex-1 whitespace-nowrap border-b-2 border-transparent px-1 py-4 text-base font-medium text-gray-900 data-[selected]:border-indigo-600 data-[selected]:text-indigo-600"
|
||||||
|
>
|
||||||
|
{category.name}
|
||||||
|
</Tab>
|
||||||
|
))}
|
||||||
|
</TabList>
|
||||||
|
</div>
|
||||||
|
<TabPanels as={Fragment}>
|
||||||
|
{navigation.categories.map((category) => (
|
||||||
|
<TabPanel
|
||||||
|
key={category.name}
|
||||||
|
className="space-y-10 px-4 pb-8 pt-10"
|
||||||
|
>
|
||||||
|
<div className="grid grid-cols-2 gap-x-4">
|
||||||
|
{category.featured.map((item) => (
|
||||||
|
<div key={item.name} className="group relative text-sm">
|
||||||
|
<img
|
||||||
|
alt={item.imageAlt}
|
||||||
|
src={item.imageSrc}
|
||||||
|
className="aspect-square w-full rounded-lg bg-gray-100 object-cover group-hover:opacity-75"
|
||||||
|
/>
|
||||||
|
<a
|
||||||
|
href={item.href}
|
||||||
|
className="mt-6 block font-medium text-gray-900"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
className="absolute inset-0 z-10"
|
||||||
|
/>
|
||||||
|
{item.name}
|
||||||
|
</a>
|
||||||
|
<p aria-hidden="true" className="mt-1">
|
||||||
|
See Build
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{category.sections.map((section) => (
|
||||||
|
<div key={section.name}>
|
||||||
|
<p
|
||||||
|
id={`${category.id}-${section.id}-heading-mobile`}
|
||||||
|
className="font-medium text-gray-900"
|
||||||
|
>
|
||||||
|
{section.name}
|
||||||
|
</p>
|
||||||
|
<ul
|
||||||
|
role="list"
|
||||||
|
aria-labelledby={`${category.id}-${section.id}-heading-mobile`}
|
||||||
|
className="mt-6 flex flex-col space-y-6"
|
||||||
|
>
|
||||||
|
{section.items.map((item) => (
|
||||||
|
<li key={item.name} className="flow-root">
|
||||||
|
<a
|
||||||
|
href={item.href}
|
||||||
|
className="-m-2 block p-2 text-gray-500"
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</TabPanel>
|
||||||
|
))}
|
||||||
|
</TabPanels>
|
||||||
|
</TabGroup>
|
||||||
|
|
||||||
|
<div className="space-y-6 border-t border-gray-200 px-4 py-6">
|
||||||
|
{navigation.pages.map((page) => (
|
||||||
|
<div key={page.name} className="flow-root">
|
||||||
|
<a
|
||||||
|
href={page.href}
|
||||||
|
className="-m-2 block p-2 font-medium text-gray-900"
|
||||||
|
>
|
||||||
|
{page.name}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-6 border-t border-gray-200 px-4 py-6">
|
||||||
|
<div className="flow-root">
|
||||||
|
<a
|
||||||
|
href="/login"
|
||||||
|
className="-m-2 block p-2 font-medium text-gray-900"
|
||||||
|
>
|
||||||
|
Sign In
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="flow-root">
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="-m-2 block p-2 font-medium text-gray-900"
|
||||||
|
>
|
||||||
|
Create account
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="border-t border-gray-200 px-4 py-6">
|
||||||
|
<a href="#" className="-m-2 flex items-center p-2">
|
||||||
|
<img
|
||||||
|
alt=""
|
||||||
|
src="https://tailwindui.com/plus/img/flags/flag-canada.svg"
|
||||||
|
className="block h-auto w-5 shrink-0"
|
||||||
|
/>
|
||||||
|
<span className="ml-3 block text-base font-medium text-gray-900">
|
||||||
|
CAD
|
||||||
|
</span>
|
||||||
|
<span className="sr-only">, change currency</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</DialogPanel>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<header className="relative bg-white">
|
||||||
|
{/* <p className="flex h-10 items-center justify-center bg-green-900 px-4 text-sm font-medium text-white sm:px-6 lg:px-8">
|
||||||
|
Get free delivery on orders over $100
|
||||||
|
</p> */}
|
||||||
|
|
||||||
|
<nav
|
||||||
|
aria-label="Top"
|
||||||
|
className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8"
|
||||||
|
>
|
||||||
|
<div className="border-b border-gray-200">
|
||||||
|
<div className="flex h-16 items-center">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setOpen(true)}
|
||||||
|
className="relative rounded-md bg-white p-2 text-gray-400 lg:hidden"
|
||||||
|
>
|
||||||
|
<span className="absolute -inset-0.5" />
|
||||||
|
<span className="sr-only">Open menu</span>
|
||||||
|
<Bars3Icon aria-hidden="true" className="size-6" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Flyout menus */}
|
||||||
|
<PopoverGroup className="hidden lg:ml-8 lg:block lg:self-stretch z-20">
|
||||||
|
<div className="flex h-full space-x-8">
|
||||||
|
{navigation.categories.map((category) => (
|
||||||
|
<Popover key={category.name} className="flex">
|
||||||
|
<div className="relative flex">
|
||||||
|
<PopoverButton className="relative z-10 -mb-px flex items-center border-b-2 border-transparent pt-px text-sm font-medium text-gray-700 transition-colors duration-200 ease-out hover:text-gray-800 data-[open]:border-indigo-600 data-[open]:text-indigo-600">
|
||||||
|
{category.name}
|
||||||
|
</PopoverButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<PopoverPanel
|
||||||
|
transition
|
||||||
|
className="absolute inset-x-0 top-full text-sm text-gray-500 transition data-[closed]:opacity-0 data-[enter]:duration-200 data-[leave]:duration-150 data-[enter]:ease-out data-[leave]:ease-in"
|
||||||
|
>
|
||||||
|
{/* Presentational element used to render the bottom shadow, if we put the shadow on the actual panel it pokes out the top, so we use this shorter element to hide the top of the shadow */}
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
className="absolute inset-0 top-1/2 bg-white shadow"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="relative bg-white">
|
||||||
|
<div className="mx-auto max-w-7xl px-8">
|
||||||
|
<div className="grid grid-cols-2 gap-x-8 gap-y-10 py-16">
|
||||||
|
<div className="col-start-2 grid grid-cols-2 gap-x-8">
|
||||||
|
{category.featured.map((item) => (
|
||||||
|
<div
|
||||||
|
key={item.name}
|
||||||
|
className="group relative text-base sm:text-sm"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt={item.imageAlt}
|
||||||
|
src={item.imageSrc}
|
||||||
|
className="aspect-square w-full rounded-lg bg-gray-100 object-cover group-hover:opacity-75"
|
||||||
|
/>
|
||||||
|
<a
|
||||||
|
href={item.href}
|
||||||
|
className="mt-6 block font-medium text-gray-900"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
className="absolute inset-0 z-10"
|
||||||
|
/>
|
||||||
|
{item.name}
|
||||||
|
</a>
|
||||||
|
<p aria-hidden="true" className="mt-1">
|
||||||
|
See Build
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="row-start-1 grid grid-cols-3 gap-x-8 gap-y-10 text-sm">
|
||||||
|
{category.sections.map((section) => (
|
||||||
|
<div key={section.name}>
|
||||||
|
<p
|
||||||
|
id={`${section.name}-heading`}
|
||||||
|
className="font-medium text-gray-900"
|
||||||
|
>
|
||||||
|
{section.name}
|
||||||
|
</p>
|
||||||
|
<ul
|
||||||
|
role="list"
|
||||||
|
aria-labelledby={`${section.name}-heading`}
|
||||||
|
className="mt-6 space-y-6 sm:mt-4 sm:space-y-4"
|
||||||
|
>
|
||||||
|
{section.items.map((item) => (
|
||||||
|
<li key={item.name} className="flex">
|
||||||
|
<a
|
||||||
|
href={item.href}
|
||||||
|
className="hover:text-gray-800"
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PopoverPanel>
|
||||||
|
</Popover>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{navigation.pages.map((page) => (
|
||||||
|
<a
|
||||||
|
key={page.name}
|
||||||
|
href={page.href}
|
||||||
|
className="flex items-center text-sm font-medium text-gray-700 hover:text-gray-800"
|
||||||
|
>
|
||||||
|
{page.name}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</PopoverGroup>
|
||||||
|
|
||||||
|
<div className="ml-auto flex items-center">
|
||||||
|
<div className="hidden lg:flex lg:flex-1 lg:items-center lg:justify-end lg:space-x-6">
|
||||||
|
<a
|
||||||
|
href="/login"
|
||||||
|
className="text-sm font-medium text-gray-700 hover:text-gray-800"
|
||||||
|
>
|
||||||
|
Sign In
|
||||||
|
</a>
|
||||||
|
<span aria-hidden="true" className="h-6 w-px bg-gray-200" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Search */}
|
||||||
|
<div className="flex lg:ml-6">
|
||||||
|
<a href="#" className="p-2 text-gray-400 hover:text-gray-500">
|
||||||
|
<span className="sr-only">Search</span>
|
||||||
|
<MagnifyingGlassIcon
|
||||||
|
aria-hidden="true"
|
||||||
|
className="size-6"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
import { forwardRef, type SVGProps } from "react";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
|
|
||||||
const AnimatedSpinner = forwardRef<SVGSVGElement, SVGProps<SVGSVGElement>>(
|
|
||||||
({ className, ...props }, ref) => (
|
|
||||||
<svg
|
|
||||||
ref={ref}
|
|
||||||
{...props}
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="currentColor"
|
|
||||||
className={cn(className)}
|
|
||||||
>
|
|
||||||
<g className="animated-spinner">
|
|
||||||
<rect x="11" y="1" width="2" height="5" opacity=".14" />
|
|
||||||
<rect
|
|
||||||
x="11"
|
|
||||||
y="1"
|
|
||||||
width="2"
|
|
||||||
height="5"
|
|
||||||
transform="rotate(30 12 12)"
|
|
||||||
opacity=".29"
|
|
||||||
/>
|
|
||||||
<rect
|
|
||||||
x="11"
|
|
||||||
y="1"
|
|
||||||
width="2"
|
|
||||||
height="5"
|
|
||||||
transform="rotate(60 12 12)"
|
|
||||||
opacity=".43"
|
|
||||||
/>
|
|
||||||
<rect
|
|
||||||
x="11"
|
|
||||||
y="1"
|
|
||||||
width="2"
|
|
||||||
height="5"
|
|
||||||
transform="rotate(90 12 12)"
|
|
||||||
opacity=".57"
|
|
||||||
/>
|
|
||||||
<rect
|
|
||||||
x="11"
|
|
||||||
y="1"
|
|
||||||
width="2"
|
|
||||||
height="5"
|
|
||||||
transform="rotate(120 12 12)"
|
|
||||||
opacity=".71"
|
|
||||||
/>
|
|
||||||
<rect
|
|
||||||
x="11"
|
|
||||||
y="1"
|
|
||||||
width="2"
|
|
||||||
height="5"
|
|
||||||
transform="rotate(150 12 12)"
|
|
||||||
opacity=".86"
|
|
||||||
/>
|
|
||||||
<rect x="11" y="1" width="2" height="5" transform="rotate(180 12 12)" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
),
|
|
||||||
);
|
|
||||||
AnimatedSpinner.displayName = "AnimatedSpinner";
|
|
||||||
|
|
||||||
const CreditCard = forwardRef<SVGSVGElement, SVGProps<SVGSVGElement>>(
|
|
||||||
({ className, ...props }, ref) => (
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
ref={ref}
|
|
||||||
{...props}
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
className={cn(className)}
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="2"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
>
|
|
||||||
<rect x="2" y="5" width="20" height="14" rx="2"></rect>
|
|
||||||
<line x1="2" y1="10" x2="22" y2="10"></line>
|
|
||||||
</svg>
|
|
||||||
),
|
|
||||||
);
|
|
||||||
CreditCard.displayName = "CreditCard";
|
|
||||||
|
|
||||||
export { AnimatedSpinner, CreditCard };
|
|
||||||
|
|
||||||
export {
|
|
||||||
EyeOpenIcon,
|
|
||||||
EyeNoneIcon as EyeCloseIcon,
|
|
||||||
SunIcon,
|
|
||||||
MoonIcon,
|
|
||||||
ExclamationTriangleIcon,
|
|
||||||
ExitIcon,
|
|
||||||
EnterIcon,
|
|
||||||
GearIcon,
|
|
||||||
RocketIcon,
|
|
||||||
PlusIcon,
|
|
||||||
HamburgerMenuIcon,
|
|
||||||
Pencil2Icon,
|
|
||||||
UpdateIcon,
|
|
||||||
CheckCircledIcon,
|
|
||||||
PlayIcon,
|
|
||||||
TrashIcon,
|
|
||||||
ArchiveIcon,
|
|
||||||
ResetIcon,
|
|
||||||
DiscordLogoIcon,
|
|
||||||
FileTextIcon,
|
|
||||||
IdCardIcon,
|
|
||||||
PlusCircledIcon,
|
|
||||||
FilePlusIcon,
|
|
||||||
CheckIcon,
|
|
||||||
ChevronLeftIcon,
|
|
||||||
ChevronRightIcon,
|
|
||||||
DotsHorizontalIcon,
|
|
||||||
ArrowLeftIcon,
|
|
||||||
} from "@radix-ui/react-icons";
|
|
||||||
@@ -19,19 +19,19 @@ export type PropsMap = {
|
|||||||
[EmailTemplate.PasswordReset]: ComponentProps<typeof ResetPasswordTemplate>;
|
[EmailTemplate.PasswordReset]: ComponentProps<typeof ResetPasswordTemplate>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getEmailTemplate = <T extends EmailTemplate>(template: T, props: PropsMap[NoInfer<T>]) => {
|
const getEmailTemplate = async <T extends EmailTemplate>(template: T, props: PropsMap[NoInfer<T>]) => {
|
||||||
switch (template) {
|
switch (template) {
|
||||||
case EmailTemplate.EmailVerification:
|
case EmailTemplate.EmailVerification:
|
||||||
return {
|
return {
|
||||||
subject: "Verify your email address",
|
subject: "Verify your email address",
|
||||||
body: render(
|
body: await render(
|
||||||
<EmailVerificationTemplate {...(props as PropsMap[EmailTemplate.EmailVerification])} />,
|
<EmailVerificationTemplate {...(props as PropsMap[EmailTemplate.EmailVerification])} />,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
case EmailTemplate.PasswordReset:
|
case EmailTemplate.PasswordReset:
|
||||||
return {
|
return {
|
||||||
subject: "Reset your password",
|
subject: "Reset your password",
|
||||||
body: render(
|
body: await render(
|
||||||
<ResetPasswordTemplate {...(props as PropsMap[EmailTemplate.PasswordReset])} />,
|
<ResetPasswordTemplate {...(props as PropsMap[EmailTemplate.PasswordReset])} />,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
@@ -61,7 +61,7 @@ export const sendMail = async <T extends EmailTemplate>(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { subject, body } = getEmailTemplate(template, props);
|
const { subject, body } = await getEmailTemplate(template, props);
|
||||||
|
|
||||||
return transporter.sendMail({ from: EMAIL_SENDER, to, subject, html: body });
|
return transporter.sendMail({ from: EMAIL_SENDER, to, subject, html: body });
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user