├── README.md ├── .eslintrc.json ├── public ├── jjk.webp ├── banner.webp ├── dlore.webp ├── frost.webp ├── item1.webp ├── item2.webp ├── bfklore.webp ├── blue-item.webp ├── karalore.webp ├── onecase.webp ├── wildlotus.webp ├── blue-item-2.webp ├── main-page-event-banner-bg.webp └── swagger.yml ├── src ├── app │ ├── 404 │ │ └── page.tsx │ ├── @authModal │ │ ├── default.tsx │ │ ├── (.)sign-in │ │ │ └── page.tsx │ │ └── (.)sign-up │ │ │ └── page.tsx │ ├── favicon.ico │ ├── profile │ │ ├── page.tsx │ │ └── [slug] │ │ │ └── page.tsx │ ├── api │ │ ├── auth │ │ │ └── [...nextauth] │ │ │ │ └── route.ts │ │ ├── case │ │ │ ├── [slug] │ │ │ │ └── route.ts │ │ │ └── route.ts │ │ ├── user │ │ │ └── route.ts │ │ ├── rmrf │ │ │ └── route.ts │ │ ├── items │ │ │ └── route.ts │ │ ├── balance │ │ │ └── route.ts │ │ ├── open │ │ │ └── route.ts │ │ └── item │ │ │ └── route.ts │ ├── create │ │ ├── page.tsx │ │ ├── item │ │ │ └── page.tsx │ │ └── case │ │ │ └── page.tsx │ ├── (auth) │ │ ├── sign-up │ │ │ └── page.tsx │ │ └── sign-in │ │ │ └── page.tsx │ ├── layout.tsx │ ├── balance │ │ └── page.tsx │ ├── page.tsx │ ├── coinflip │ │ └── page.tsx │ ├── roulette │ │ └── page.tsx │ ├── case │ │ └── [slug] │ │ │ └── page.tsx │ └── globals.css ├── middleware.ts ├── lib │ ├── user-model.ts │ ├── fetch-case.ts │ ├── db.ts │ ├── role-check.ts │ ├── global-store.ts │ ├── utils.ts │ └── auth.ts └── components │ ├── ui │ ├── skeleton.tsx │ ├── toaster.tsx │ ├── button.tsx │ ├── accordion.tsx │ ├── pagination.tsx │ ├── dialog.tsx │ ├── use-toast.ts │ └── toast.tsx │ ├── CloseModal.tsx │ ├── Balance.tsx │ ├── SignUp.tsx │ ├── SignIn.tsx │ ├── UserAvatar.tsx │ ├── Navbar.tsx │ ├── Avatar.tsx │ ├── Case.tsx │ ├── UserAccountNav.tsx │ ├── UserAuthForm.tsx │ ├── Button.tsx │ ├── Icons.tsx │ └── DropdownMenu.tsx ├── .vscode └── settings.json ├── postcss.config.js ├── next.config.js ├── .gitignore ├── tsconfig.json ├── package.json ├── prisma └── schema.prisma └── tailwind.config.ts /README.md: -------------------------------------------------------------------------------- 1 | next api documentation 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /public/jjk.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-plus-f4/next-api/HEAD/public/jjk.webp -------------------------------------------------------------------------------- /public/banner.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-plus-f4/next-api/HEAD/public/banner.webp -------------------------------------------------------------------------------- /public/dlore.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-plus-f4/next-api/HEAD/public/dlore.webp -------------------------------------------------------------------------------- /public/frost.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-plus-f4/next-api/HEAD/public/frost.webp -------------------------------------------------------------------------------- /public/item1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-plus-f4/next-api/HEAD/public/item1.webp -------------------------------------------------------------------------------- /public/item2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-plus-f4/next-api/HEAD/public/item2.webp -------------------------------------------------------------------------------- /src/app/@authModal/default.tsx: -------------------------------------------------------------------------------- 1 | export default function Default(){ 2 | return null 3 | } -------------------------------------------------------------------------------- /public/bfklore.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-plus-f4/next-api/HEAD/public/bfklore.webp -------------------------------------------------------------------------------- /public/blue-item.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-plus-f4/next-api/HEAD/public/blue-item.webp -------------------------------------------------------------------------------- /public/karalore.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-plus-f4/next-api/HEAD/public/karalore.webp -------------------------------------------------------------------------------- /public/onecase.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-plus-f4/next-api/HEAD/public/onecase.webp -------------------------------------------------------------------------------- /public/wildlotus.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-plus-f4/next-api/HEAD/public/wildlotus.webp -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-plus-f4/next-api/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /public/blue-item-2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-plus-f4/next-api/HEAD/public/blue-item-2.webp -------------------------------------------------------------------------------- /src/app/profile/page.tsx: -------------------------------------------------------------------------------- 1 | const Page = () => { 2 | return (

PROFILE

); 3 | } 4 | 5 | export default Page; -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "WillLuke.nextjs.addTypesOnSave": true, 3 | "WillLuke.nextjs.hasPrompted": true 4 | } -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/main-page-event-banner-bg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alt-plus-f4/next-api/HEAD/public/main-page-event-banner-bg.webp -------------------------------------------------------------------------------- /src/middleware.ts: -------------------------------------------------------------------------------- 1 | export {default} from "next-auth/middleware"; 2 | 3 | export const config = { matcher: ["/create", "/balance", "/create/case", "/create/item"]} -------------------------------------------------------------------------------- /src/app/api/auth/[...nextauth]/route.ts: -------------------------------------------------------------------------------- 1 | import { authOptions } from "@/lib/auth" 2 | import NextAuth from "next-auth/next" 3 | 4 | const handler = NextAuth(authOptions) 5 | 6 | export {handler as GET, handler as POST} -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | images: { 4 | domains: ['avatars.githubusercontent.com', 'lh3.googleusercontent.com'], 5 | } 6 | } 7 | 8 | module.exports = nextConfig 9 | -------------------------------------------------------------------------------- /src/lib/user-model.ts: -------------------------------------------------------------------------------- 1 | 2 | import { User as NextAuthUser } from 'next-auth'; 3 | 4 | export interface User extends NextAuthUser { 5 | id: string; 6 | name: string; 7 | email: string; 8 | image: string; 9 | balance: number; 10 | } -------------------------------------------------------------------------------- /src/app/404/page.tsx: -------------------------------------------------------------------------------- 1 | const Error404 = () => { 2 | return ( 3 |
4 |

404

5 |
6 | ); 7 | } 8 | 9 | export default Error404; -------------------------------------------------------------------------------- /src/lib/fetch-case.ts: -------------------------------------------------------------------------------- 1 | export async function fetchCase(id: string) { 2 | const res = await fetch(`/api/case/${id}`); 3 | if (res.status === 404) { 4 | return { error: 'Case not found' }; 5 | } 6 | const data = await res.json(); 7 | return data.cases; 8 | } -------------------------------------------------------------------------------- /src/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ) 13 | } 14 | 15 | export { Skeleton } 16 | -------------------------------------------------------------------------------- /src/app/profile/[slug]/page.tsx: -------------------------------------------------------------------------------- 1 | 2 | interface PageProps { 3 | params: { 4 | slug: string 5 | } 6 | } 7 | 8 | const page = async ({params} : PageProps) => { 9 | const { slug } = params; 10 | 11 | return ( 12 |
13 |

Page for profile: {slug}

14 |
15 | ); 16 | }; 17 | 18 | export default page; 19 | -------------------------------------------------------------------------------- /src/app/create/page.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | 3 | const Page = () => { 4 | return ( 5 | <> 6 |

Create Item/Case

7 | 8 |
9 | ITEM 10 | CASE 11 |
12 | ); 13 | } 14 | 15 | export default Page; -------------------------------------------------------------------------------- /src/lib/db.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from '@prisma/client' 2 | import "server-only" 3 | 4 | declare global { 5 | var cachedPrisma: PrismaClient 6 | } 7 | 8 | let prisma: PrismaClient 9 | if (process.env.NODE_ENV === 'production') { 10 | prisma = new PrismaClient() 11 | } else { 12 | if (!global.cachedPrisma) { 13 | global.cachedPrisma = new PrismaClient() 14 | } 15 | prisma = global.cachedPrisma 16 | } 17 | 18 | export const db = prisma -------------------------------------------------------------------------------- /src/components/CloseModal.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { X } from "lucide-react"; 4 | import { Button } from "./Button"; 5 | import { useRouter } from "next/navigation"; 6 | 7 | const CloseModal = () => { 8 | const router = useRouter() 9 | 10 | return ( 11 | 14 | ); 15 | } 16 | 17 | export default CloseModal; -------------------------------------------------------------------------------- /src/lib/role-check.ts: -------------------------------------------------------------------------------- 1 | import { getAuthSession } from "./auth"; 2 | import { db } from "./db"; 3 | 4 | export async function checkSessionAndRole() { 5 | const session = await getAuthSession(); 6 | 7 | if (!session) 8 | return { status: 401, error: 'Unauthorized' } 9 | 10 | const user = await db.user.findUnique({ 11 | where: { email: session?.user?.email || '' }, 12 | select: { role: true }, 13 | }) 14 | 15 | if (user?.role != 1) 16 | return { status: 401, error: 'Unauthorized' } 17 | 18 | return null; 19 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | .env 4 | 5 | # dependencies 6 | /node_modules 7 | /.pnp 8 | .pnp.js 9 | .yarn/install-state.gz 10 | 11 | # testing 12 | /coverage 13 | 14 | # next.js 15 | /.next/ 16 | /out/ 17 | 18 | # production 19 | /build 20 | 21 | # misc 22 | .DS_Store 23 | *.pem 24 | 25 | # debug 26 | npm-debug.log* 27 | yarn-debug.log* 28 | yarn-error.log* 29 | 30 | # local env files 31 | .env*.local 32 | 33 | # vercel 34 | .vercel 35 | 36 | # typescript 37 | *.tsbuildinfo 38 | next-env.d.ts 39 | -------------------------------------------------------------------------------- /src/app/api/case/[slug]/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from 'next/server'; 2 | import { db } from '@/lib/db' 3 | 4 | export async function GET(req : NextRequest) { 5 | const id = req.nextUrl.pathname.split('/').pop(); 6 | 7 | const cases = await db.case.findMany({ 8 | where: { id: Number(id) }, 9 | select:{ id: true, name: true, image: true, price: true, items: true, odds: true}, 10 | }) 11 | 12 | if (!cases || cases.length === 0) 13 | return NextResponse.json({ error: 'Case/s not found' }, { status: 404 }) 14 | 15 | return NextResponse.json({ cases: cases }, { status: 200 }) 16 | } -------------------------------------------------------------------------------- /src/app/api/user/route.ts: -------------------------------------------------------------------------------- 1 | import { getAuthSession } from '@/lib/auth'; 2 | import { NextResponse } from 'next/server'; 3 | import { db } from '@/lib/db' 4 | 5 | export async function GET() { 6 | const session = await getAuthSession(); 7 | 8 | if (!session) { 9 | return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) 10 | } 11 | 12 | const user = await db.user.findUnique({ 13 | where: { email: session?.user?.email || ''}, 14 | }) 15 | 16 | if (!user) { 17 | return NextResponse.json({ error: 'User not found' }, { status: 404 }) 18 | } 19 | 20 | return NextResponse.json({ haha: user}, { status: 200 }) 21 | } 22 | -------------------------------------------------------------------------------- /src/app/@authModal/(.)sign-in/page.tsx: -------------------------------------------------------------------------------- 1 | import CloseModal from '@/components/CloseModal' 2 | import SignIn from '@/components/SignIn' 3 | 4 | const page = () => { 5 | return ( 6 |
7 |
8 |
9 |
10 | 11 |
12 | 13 |
14 |
15 |
16 | ) 17 | } 18 | 19 | export default page -------------------------------------------------------------------------------- /src/app/@authModal/(.)sign-up/page.tsx: -------------------------------------------------------------------------------- 1 | import CloseModal from '@/components/CloseModal' 2 | import SignUp from '@/components/SignUp' 3 | 4 | const page = () => { 5 | return ( 6 |
7 |
8 |
9 |
10 | 11 |
12 | 13 |
14 |
15 |
16 | ) 17 | } 18 | 19 | export default page -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /src/app/(auth)/sign-up/page.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react" 2 | import { cn } from "@/lib/utils"; 3 | import { buttonVariants } from "@/components/Button"; 4 | import Link from "next/link"; 5 | import SignUp from "@/components/SignUp"; 6 | import { ChevronLeft } from "lucide-react"; 7 | 8 | const page: FC = () => { 9 | return
10 |
11 | 12 | 13 | HOME 14 | 15 | 16 | 17 |
18 | 19 |
; 20 | } 21 | 22 | export default page; -------------------------------------------------------------------------------- /src/app/(auth)/sign-in/page.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react" 2 | import { cn } from "@/lib/utils"; 3 | import { buttonVariants } from "@/components/Button"; 4 | import Link from "next/link"; 5 | import SignIn from "@/components/SignIn"; 6 | import { ChevronLeft } from "lucide-react"; 7 | 8 | const page: FC = () => { 9 | return
10 |
11 | 12 | 13 | HOME 14 | 15 | 16 | 17 |
18 | 19 |
; 20 | } 21 | 22 | export default page; -------------------------------------------------------------------------------- /src/app/api/rmrf/route.ts: -------------------------------------------------------------------------------- 1 | import { db } from "@/lib/db"; 2 | import { checkSessionAndRole } from "@/lib/role-check"; 3 | import { NextResponse } from "next/server"; 4 | 5 | export async function DELETE() { 6 | const check = await checkSessionAndRole(); 7 | if (check) return NextResponse.json({ error: check.error }, { status: check.status }); 8 | 9 | const deletedOdds = await db.odds.deleteMany(); 10 | const deletedItems = await db.item.deleteMany(); 11 | const deletedCases = await db.case.deleteMany(); 12 | 13 | await db.$executeRaw`ALTER TABLE \`Item\` AUTO_INCREMENT = 1`; 14 | await db.$executeRaw`ALTER TABLE \`Case\` AUTO_INCREMENT = 1`; 15 | await db.$executeRaw`ALTER TABLE \`Odds\` AUTO_INCREMENT = 1`; 16 | 17 | return NextResponse.json({ item: deletedItems, case: deletedCases, odds: deletedOdds }, { status: 200 }) 18 | } -------------------------------------------------------------------------------- /src/app/api/items/route.ts: -------------------------------------------------------------------------------- 1 | import { db } from "@/lib/db"; 2 | import { checkSessionAndRole } from "@/lib/role-check"; 3 | import { NextRequest, NextResponse } from "next/server"; 4 | 5 | export async function POST(req : NextRequest) { 6 | const check = await checkSessionAndRole(); 7 | if (check) return NextResponse.json({ error: check.error }, { status: check.status }); 8 | 9 | const data = await req.json(); 10 | 11 | if (!Array.isArray(data.items)) 12 | return NextResponse.json({ error: 'Missing required fields' }, { status: 400 }) 13 | 14 | 15 | const newItems = await db.item.createMany({ 16 | data: data.items.map(({ name, rarity, price, imageURL }: { name: string, rarity: number, price: number, imageURL: string }) => ({ 17 | name, rarity, price, imageURL 18 | })), 19 | }) 20 | 21 | return NextResponse.json({ items: newItems }, { status: 201 }) 22 | } -------------------------------------------------------------------------------- /src/components/ui/toaster.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { 4 | Toast, 5 | ToastClose, 6 | ToastDescription, 7 | ToastProvider, 8 | ToastTitle, 9 | ToastViewport, 10 | } from "@/components/ui/toast" 11 | import { useToast } from "@/components/ui/use-toast" 12 | 13 | export function Toaster() { 14 | const { toasts } = useToast() 15 | 16 | return ( 17 | 18 | {toasts.map(function ({ id, title, description, action, ...props }) { 19 | return ( 20 | 21 |
22 | {title && {title}} 23 | {description && ( 24 | {description} 25 | )} 26 |
27 | {action} 28 | 29 |
30 | ) 31 | })} 32 | 33 |
34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /src/components/Balance.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { useEffect } from 'react'; 4 | import Link from "next/link"; 5 | import useStore from '@/lib/global-store'; 6 | import { Skeleton } from "@/components/ui/skeleton"; 7 | 8 | const Balance = () => { 9 | const balance = useStore((state: unknown) => (state as { balance: number }).balance); 10 | const fetchBalance = useStore((state: unknown) => (state as { fetchBalance: () => void }).fetchBalance); 11 | 12 | useEffect(() => { 13 | fetchBalance(); 14 | }, [fetchBalance]); 15 | 16 | return ( 17 | <> 18 | {!balance ? ( 19 | 20 | ) : ( 21 | 22 | {balance % 1 !== 0 ? `$${balance.toFixed(2)} +` : `$${balance} +`} 23 | 24 | )} 25 | 26 | ); 27 | } 28 | 29 | export default Balance; 30 | -------------------------------------------------------------------------------- /src/components/SignUp.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { Icons } from "./Icons"; 3 | import UserAuthForm from "./UserAuthForm"; 4 | 5 | const SignUp = () => { 6 | return
7 |
8 | 9 |

Sign Up

10 |

11 | By continuing, you are setting up a Cirovbet account and agree to our 12 | User Agreement and Privacy Policy. 13 |

14 | 15 | 16 | 17 |

18 | Already in?{' '} 19 | Sign Up 20 |

21 | 22 |
23 |
24 | } 25 | 26 | export default SignUp; -------------------------------------------------------------------------------- /src/components/SignIn.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { Icons } from "./Icons"; 3 | import UserAuthForm from "./UserAuthForm"; 4 | 5 | const SignIn = () => { 6 | return
7 |
8 | 9 |

Welcome back

10 |

11 | By continuing, you are setting up a Cirovbet account and agree to our 12 | User Agreement and Privacy Policy. 13 |

14 | 15 | 16 | 17 |

18 | New to CirovBet?{' '} 19 | Sign Up 20 |

21 | 22 |
23 |
24 | } 25 | 26 | export default SignIn; -------------------------------------------------------------------------------- /src/components/UserAvatar.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react' 2 | import { User } from '@prisma/client' 3 | import { AvatarProps } from '@radix-ui/react-avatar' 4 | import { Icons } from '@/components/Icons' 5 | import { Avatar, AvatarFallback } from '@/components/Avatar' 6 | import Image from 'next/image' 7 | 8 | interface UserAvatarProps extends AvatarProps { 9 | user: Pick 10 | } 11 | 12 | const UserAvatar : FC = ({ user, ...props }) => { 13 | return ( 14 | 15 | {user.image ? ( 16 |
17 | profile picture 24 |
25 | ) : ( 26 | 27 | {user?.name} 28 | 29 | 30 | )} 31 |
32 | ) 33 | } 34 | 35 | export default UserAvatar; -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | import { Inter } from 'next/font/google' 3 | import './globals.css' 4 | import Navbar from '@/components/Navbar' 5 | import { cn } from "@/lib/utils" 6 | import { Toaster } from "@/components/ui/toaster" 7 | 8 | const inter = Inter({ subsets: ['latin'] }) 9 | 10 | export const metadata: Metadata = { 11 | title: 'CirovBet - The Winning Place', 12 | description: 'You can lose only 100% of your money but you can win 1000%..', 13 | } 14 | 15 | export default function RootLayout({ 16 | children, 17 | authModal, 18 | }: { 19 | children: React.ReactNode, 20 | authModal: React.ReactNode 21 | }) { 22 | return ( 23 | 27 | 28 | 29 | 30 | 31 | 32 | {authModal} 33 | {children} 34 | 35 | 36 | 37 |
Gamble irresponsibly
38 | 39 | 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /src/components/Navbar.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { Icons } from "./Icons"; 3 | import { getAuthSession } from "@/lib/auth"; 4 | import UserAccountNav from "./UserAccountNav"; 5 | import Balance from "./Balance"; 6 | 7 | const Navbar = async () => { 8 | 9 | const session = await getAuthSession(); 10 | 11 | return ( 12 |
13 |
14 | 15 | 16 |

Cirovbet

17 | 18 | 19 | {session?.user ? ( 20 |
21 | 22 | 23 |
24 | ): 25 | ( Sign In)} 26 | 27 |
28 |
29 | ) 30 | } 31 | export default Navbar -------------------------------------------------------------------------------- /src/lib/global-store.ts: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand' 2 | 3 | const useStore = create((set) => ({ 4 | balance: 0, 5 | setBalance: (balance: number) => set({ balance }), 6 | fetchBalance: async () => { 7 | try { 8 | const response = await fetch('/api/balance'); 9 | const data = await response.json(); 10 | set({ balance: data.balance }); 11 | } catch (error) { 12 | console.error('Failed to fetch balance:', error); 13 | } 14 | }, 15 | updateBalance: async (amount: number) => { 16 | try { 17 | const response = await fetch('/api/balance', { 18 | method: 'POST', 19 | headers: { 20 | 'Content-Type': 'application/json', 21 | }, 22 | body: JSON.stringify({ balance: amount }), 23 | }); 24 | 25 | const data = await response.json(); 26 | set({ balance: data.balance }); 27 | } catch (error) { 28 | console.error('Failed to update balance:', error); 29 | } 30 | }, 31 | })); 32 | 33 | export default useStore; -------------------------------------------------------------------------------- /src/app/balance/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { Button } from '@/components/Button'; 4 | import useStore from '@/lib/global-store'; 5 | 6 | const BalancePage = () => { 7 | const balance = useStore((state: { balance: number } | unknown) => (state as { balance: number }).balance); 8 | const updateBalance = useStore((state: unknown) => (state as { updateBalance: (amount: number) => Promise }).updateBalance); 9 | 10 | const handleAddFunds = async () => { 11 | try { 12 | const str = prompt('Enter the amount to add:'); 13 | const amount = str ? parseFloat(str) : null; 14 | 15 | if (amount !== null) { 16 | await updateBalance(amount); 17 | } 18 | } catch (error) { 19 | console.error('Error adding funds:', error); 20 | } 21 | }; 22 | 23 | return ( 24 |
25 |

Balance Page

26 | {balance === null ? ( 27 |

Loading...

28 | ) : ( 29 | <> 30 | 31 | 32 | )} 33 |
34 | ); 35 | }; 36 | 37 | export default BalancePage; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-api", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "prisma generate && next build", 8 | "start": "cross-env NODE_OPTIONS='--inspect' next start", 9 | "lint": "next lint", 10 | "postinstall": "prisma generate" 11 | }, 12 | "dependencies": { 13 | "@next-auth/prisma-adapter": "^1.0.7", 14 | "@prisma/client": "^5.7.0", 15 | "@radix-ui/react-accordion": "^1.1.2", 16 | "@radix-ui/react-avatar": "^1.0.4", 17 | "@radix-ui/react-dialog": "^1.0.5", 18 | "@radix-ui/react-dropdown-menu": "^2.0.6", 19 | "@radix-ui/react-slot": "^1.0.2", 20 | "@radix-ui/react-toast": "^1.1.5", 21 | "class-variance-authority": "^0.7.0", 22 | "date-fns": "^2.30.0", 23 | "lucide-react": "^0.297.0", 24 | "next": "14.0.4", 25 | "next-auth": "^4.24.5", 26 | "react": "^18", 27 | "react-dom": "^18", 28 | "react-icons": "^4.12.0", 29 | "react-infinite-scroll-component": "^6.1.0", 30 | "react-loading-skeleton": "^3.3.1", 31 | "tailwind-merge": "^2.1.0", 32 | "zustand": "^4.4.7" 33 | }, 34 | "devDependencies": { 35 | "@types/node": "^20", 36 | "@types/react": "^18.2.46", 37 | "@types/react-dom": "^18", 38 | "autoprefixer": "^10.0.1", 39 | "cross-env": "^7.0.3", 40 | "eslint": "^8", 41 | "eslint-config-next": "14.0.4", 42 | "postcss": "^8", 43 | "prisma": "^5.7.0", 44 | "tailwindcss": "^3.3.0", 45 | "typescript": "^5" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { ClassValue, clsx } from 'clsx' 2 | import { twMerge } from 'tailwind-merge' 3 | import { formatDistanceToNowStrict } from 'date-fns' 4 | import locale from 'date-fns/locale/en-US' 5 | 6 | export function cn(...inputs: ClassValue[]) { 7 | return twMerge(clsx(inputs)) 8 | } 9 | 10 | const formatDistanceLocale = { 11 | lessThanXSeconds: 'just now', 12 | xSeconds: 'just now', 13 | halfAMinute: 'just now', 14 | lessThanXMinutes: '{{count}}m', 15 | xMinutes: '{{count}}m', 16 | aboutXHours: '{{count}}h', 17 | xHours: '{{count}}h', 18 | xDays: '{{count}}d', 19 | aboutXWeeks: '{{count}}w', 20 | xWeeks: '{{count}}w', 21 | aboutXMonths: '{{count}}m', 22 | xMonths: '{{count}}m', 23 | aboutXYears: '{{count}}y', 24 | xYears: '{{count}}y', 25 | overXYears: '{{count}}y', 26 | almostXYears: '{{count}}y', 27 | } 28 | 29 | function formatDistance(token: string, count: number, options?: any): string { 30 | options = options || {} 31 | 32 | const result = formatDistanceLocale[ 33 | token as keyof typeof formatDistanceLocale 34 | ].replace('{{count}}', count.toString()) 35 | 36 | if (options.addSuffix) { 37 | if (options.comparison > 0) { 38 | return 'in ' + result 39 | } else { 40 | if (result === 'just now') return result 41 | return result + ' ago' 42 | } 43 | } 44 | 45 | return result 46 | } 47 | 48 | export function formatTimeToNow(date: Date): string { 49 | return formatDistanceToNowStrict(date, { 50 | addSuffix: true, 51 | locale: { 52 | ...locale, 53 | formatDistance, 54 | }, 55 | }) 56 | } 57 | -------------------------------------------------------------------------------- /src/app/api/balance/route.ts: -------------------------------------------------------------------------------- 1 | import { getAuthSession } from '@/lib/auth'; 2 | import { NextRequest, NextResponse } from 'next/server'; 3 | import { db } from '@/lib/db' 4 | 5 | export async function GET(req : NextRequest) { 6 | const session = await getAuthSession(); 7 | 8 | if (!session) { 9 | return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) 10 | } 11 | 12 | const user = await db.user.findUnique({ 13 | where: { email: session?.user?.email || ''}, 14 | select: { balance: true }, 15 | }) 16 | 17 | if (!user) { 18 | return NextResponse.json({ error: 'User not found' }, { status: 404 }) 19 | } 20 | 21 | return NextResponse.json({ balance: user.balance }, { status: 200 }) 22 | } 23 | 24 | export async function POST(req : NextRequest) { 25 | const session = await getAuthSession() 26 | 27 | if (!session) { 28 | return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) 29 | } 30 | 31 | const user = await db.user.findUnique({ 32 | where: { email: session?.user?.email || ''}, 33 | }) 34 | 35 | if (!user) { 36 | return NextResponse.json({ error: 'User not found' }, { status: 404 }) 37 | } 38 | 39 | const { balance } = await req.json(); 40 | const parsedBalance = parseFloat(balance); 41 | 42 | if (isNaN(parsedBalance)) { 43 | return NextResponse.json({ error: 'Invalid balance' }, { status: 400 }) 44 | } 45 | 46 | const updatedUser = await db.user.update({ 47 | where: { email: user.email }, 48 | data: { balance }, 49 | }) 50 | 51 | return NextResponse.json({ balance: updatedUser.balance }, { status: 200 }) 52 | } -------------------------------------------------------------------------------- /src/components/Avatar.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AvatarPrimitive from "@radix-ui/react-avatar" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Avatar = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | )) 21 | Avatar.displayName = AvatarPrimitive.Root.displayName 22 | 23 | const AvatarImage = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, ...props }, ref) => ( 27 | 32 | )) 33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName 34 | 35 | const AvatarFallback = React.forwardRef< 36 | React.ElementRef, 37 | React.ComponentPropsWithoutRef 38 | >(({ className, ...props }, ref) => ( 39 | 47 | )) 48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName 49 | 50 | export { Avatar, AvatarImage, AvatarFallback } 51 | -------------------------------------------------------------------------------- /src/app/api/open/route.ts: -------------------------------------------------------------------------------- 1 | import { db } from "@/lib/db"; 2 | import { NextRequest, NextResponse } from "next/server"; 3 | import { getAuthSession } from '@/lib/auth'; 4 | 5 | export async function POST(req: NextRequest) { 6 | const session = await getAuthSession(); 7 | if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) 8 | 9 | const user = await db.user.findUnique({ 10 | where: { email: session?.user?.email || ''}, 11 | select: { balance: true }, 12 | }) 13 | 14 | if (!user) return NextResponse.json({ error: 'User not found' }, { status: 404 }) 15 | const body = await req.json(); 16 | 17 | if (!body || !body.caseId) return NextResponse.json({ error: 'caseId is required' }, { status: 400 }); 18 | const { caseId } = body; 19 | 20 | const caseData = await db.case.findUnique({ 21 | where: { id: caseId }, 22 | include: { 23 | odds: true 24 | }, 25 | }); 26 | 27 | if (!caseData) return NextResponse.json({ error: 'Case not found' }, { status: 404 }); 28 | if (user.balance < caseData.price) return NextResponse.json({ error: 'Not enough balance' }, { status: 400 }); 29 | 30 | const leftBalance = user.balance - caseData.price; 31 | 32 | const totalOdds = caseData.odds.reduce((total, item) => total + item.Odds, 0); 33 | const randomNumber = Math.random() * totalOdds; 34 | 35 | let sum = 0; 36 | const selectedOdds = caseData.odds.find(item => { 37 | sum += item.Odds; 38 | return randomNumber <= sum; 39 | }); 40 | 41 | const selectedItem = await db.item.findFirst({ 42 | where: { id: selectedOdds?.itemId }, 43 | select: { 44 | id: true 45 | } 46 | }); 47 | 48 | return NextResponse.json({ item: selectedItem, leftBalance: leftBalance }, { status: 200 }); 49 | } 50 | -------------------------------------------------------------------------------- /src/components/Case.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import React, { useState } from 'react'; 4 | import Image from 'next/image'; 5 | import Link from 'next/link'; 6 | interface Item { 7 | name: string; 8 | image: string; 9 | price: number; 10 | } 11 | 12 | interface CaseProps { 13 | id: number; 14 | caseName: string; 15 | items: Item[]; 16 | price: number; 17 | image: string; 18 | } 19 | 20 | const Case: React.FC = ({ id, caseName, items, price, image }) => { 21 | const [openedItems, setOpenedItems] = useState([]); 22 | 23 | const openCase = async () => { 24 | try { 25 | const response = await fetch('/api/open', { 26 | method: 'POST', 27 | headers: { 28 | 'Content-Type': 'application/json', 29 | }, 30 | body: JSON.stringify({ 31 | caseName, 32 | }), 33 | }); 34 | 35 | if (!response.ok) { 36 | throw new Error('Failed to send API request'); 37 | } 38 | 39 | const data = await response.json(); 40 | console.log(data); 41 | 42 | if (data.success) { 43 | const randomIndex = Math.floor(Math.random() * items.length); 44 | const newItem = items[randomIndex]; 45 | setOpenedItems((prevItems) => [...prevItems, newItem]); 46 | } else { 47 | console.error('Failed to open case:', data.error); 48 | } 49 | } catch (error) { 50 | console.error('Error sending API request', (error as Error).message); 51 | } 52 | }; 53 | 54 | return ( 55 | 56 |
57 | CasePic 58 |

{caseName}

59 |

${price.toFixed(2)}

60 |
61 | 62 | ); 63 | }; 64 | 65 | export default Case; 66 | -------------------------------------------------------------------------------- /src/components/UserAccountNav.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { User } from "next-auth"; 4 | import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger, DropdownMenuSeparator, DropdownMenuItem } from "./DropdownMenu"; 5 | import UserAvatar from '@/components/UserAvatar' 6 | import Link from "next/link"; 7 | import { signOut } from "next-auth/react"; 8 | 9 | interface UserAccountNavProps extends React.HTMLAttributes { 10 | user: Pick 11 | } 12 | 13 | export function UserAccountNav({ user }: UserAccountNavProps) { 14 | return ( 15 | 16 | 17 | 23 | 24 | 25 | 26 |
27 |
28 | {user.name &&

{user.name}

} 29 | {/* {user.email && ( 30 |

31 | {user.email} 32 |

33 | )} */} 34 |
35 |
36 | 37 | 38 | 39 | My Profile 40 | 41 | 42 | 43 | Add Balance 44 | 45 | 46 | 47 | { 48 | event.preventDefault(); 49 | signOut({ 50 | callbackUrl: `${window.location.origin}/`, 51 | }) 52 | }} className="dropDownItem">Sign Out 53 | 54 |
55 |
56 | ); 57 | } 58 | 59 | export default UserAccountNav; -------------------------------------------------------------------------------- /src/lib/auth.ts: -------------------------------------------------------------------------------- 1 | import { User } from './user-model'; 2 | import { db } from '@/lib/db' 3 | import { PrismaAdapter } from '@next-auth/prisma-adapter' 4 | import { NextAuthOptions, getServerSession } from 'next-auth' 5 | import GoogleProvider from 'next-auth/providers/google' 6 | import GitHubProvider from 'next-auth/providers/github'; 7 | 8 | export const authOptions: NextAuthOptions = { 9 | adapter: PrismaAdapter(db), 10 | session: { 11 | strategy: 'jwt', 12 | }, 13 | pages: { 14 | signIn: '/sign-in', 15 | }, 16 | providers: [ 17 | GoogleProvider({ 18 | clientId: process.env.GOOGLE_ID!, 19 | clientSecret: process.env.GOOGLE_SECRET!, 20 | }), 21 | GitHubProvider({ 22 | clientId: process.env.GITHUB_ID!, 23 | clientSecret: process.env.GITHUB_SECRET!, 24 | }), 25 | ], 26 | callbacks: { 27 | async session({ token, session }) { 28 | if (token) { 29 | session.user = { 30 | id: token.id || '', 31 | name: token.name, 32 | email: token.email, 33 | image: token.picture, 34 | } as User; 35 | } 36 | 37 | return session 38 | }, 39 | 40 | async jwt({ token, user }) { 41 | const dbUser = await db.user.findFirst({ 42 | where: { 43 | email: token.email || '', 44 | }, 45 | }) 46 | 47 | if (user && !dbUser) { 48 | token.id = user.id || '' 49 | return token 50 | } 51 | 52 | if (dbUser && !dbUser.name) { 53 | await db.user.update({ 54 | where: { 55 | id: dbUser.id, 56 | }, 57 | data: { 58 | name: "Sukuna", 59 | }, 60 | }) 61 | } 62 | 63 | return dbUser ? { 64 | id: dbUser.id, 65 | name: dbUser.name, 66 | email: dbUser.email, 67 | picture: dbUser.image, 68 | } : token 69 | }, 70 | redirect() { 71 | return '/' 72 | }, 73 | }, 74 | } 75 | 76 | export const getAuthSession = () => getServerSession(authOptions) -------------------------------------------------------------------------------- /src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 13 | destructive: 14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90", 15 | outline: 16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground", 17 | secondary: 18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80", 19 | ghost: "hover:bg-accent hover:text-accent-foreground", 20 | link: "text-primary underline-offset-4 hover:underline", 21 | }, 22 | size: { 23 | default: "h-10 px-4 py-2", 24 | sm: "h-9 rounded-md px-3", 25 | lg: "h-11 rounded-md px-8", 26 | icon: "h-10 w-10", 27 | }, 28 | }, 29 | defaultVariants: { 30 | variant: "default", 31 | size: "default", 32 | }, 33 | } 34 | ) 35 | 36 | export interface ButtonProps 37 | extends React.ButtonHTMLAttributes, 38 | VariantProps { 39 | asChild?: boolean 40 | } 41 | 42 | const Button = React.forwardRef( 43 | ({ className, variant, size, asChild = false, ...props }, ref) => { 44 | const Comp = asChild ? Slot : "button" 45 | return ( 46 | 51 | ) 52 | } 53 | ) 54 | Button.displayName = "Button" 55 | 56 | export { Button, buttonVariants } 57 | -------------------------------------------------------------------------------- /src/components/UserAuthForm.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { FC, useState } from "react"; 4 | import { cn } from "@/lib/utils"; 5 | import { Button } from "./Button"; 6 | import { signIn } from "next-auth/react" 7 | import { Icons } from "./Icons"; 8 | import { useToast } from "./ui/use-toast"; 9 | 10 | interface UserAuthFormProps extends React.HTMLAttributes{} 11 | 12 | const UserAuthForm: FC = ({className, ...props}) => { 13 | 14 | const [isloading, setIsloading] = useState(false) 15 | const { toast } = useToast(); 16 | 17 | const loginWithGoogle = async () => { 18 | setIsloading(true) 19 | 20 | try{ 21 | await signIn('google') 22 | } catch(error){ 23 | toast({ 24 | title: 'There was a problem.', 25 | description: 'There was an error loggin in with Google', 26 | variant: 'destructive', 27 | }) 28 | } finally{ 29 | setIsloading(false) 30 | } 31 | 32 | } 33 | 34 | const loginWithGithub = async () => { 35 | setIsloading(true) 36 | 37 | try{ 38 | await signIn('github') 39 | } catch(error){ 40 | toast({ 41 | title: 'There was a problem.', 42 | description: 'There was an error loggin in with Github', 43 | variant: 'destructive', 44 | }) 45 | } finally{ 46 | setIsloading(false) 47 | } 48 | 49 | } 50 | 51 | return ( 52 |
53 | 57 | 61 |
62 | ) 63 | } 64 | export default UserAuthForm; -------------------------------------------------------------------------------- /src/components/ui/accordion.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AccordionPrimitive from "@radix-ui/react-accordion" 5 | import { ChevronDown } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const Accordion = AccordionPrimitive.Root 10 | 11 | const AccordionItem = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef 14 | >(({ className, ...props }, ref) => ( 15 | 20 | )) 21 | AccordionItem.displayName = "AccordionItem" 22 | 23 | const AccordionTrigger = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, children, ...props }, ref) => ( 27 | 28 | svg]:rotate-180", 32 | className 33 | )} 34 | {...props} 35 | > 36 | {children} 37 | 38 | 39 | 40 | )) 41 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName 42 | 43 | const AccordionContent = React.forwardRef< 44 | React.ElementRef, 45 | React.ComponentPropsWithoutRef 46 | >(({ className, children, ...props }, ref) => ( 47 | 52 |
{children}
53 |
54 | )) 55 | 56 | AccordionContent.displayName = AccordionPrimitive.Content.displayName 57 | 58 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } 59 | -------------------------------------------------------------------------------- /src/components/Button.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/lib/utils' 2 | import { cva, VariantProps } from 'class-variance-authority' 3 | import { Loader2 } from 'lucide-react' 4 | import * as React from 'react' 5 | 6 | const buttonVariants = cva( 7 | 'active:scale-95 inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:opacity-50 dark:focus:ring-slate-400 disabled:pointer-events-none dark:focus:ring-offset-slate-900', 8 | { 9 | variants: { 10 | variant: { 11 | poligon: 12 | 'button-poligon', 13 | default: 14 | 'bg-zinc-900 text-zinc-100 hover:bg-zinc-800', 15 | destructive: 'text-white hover:bg-red-600 dark:hover:bg-red-600', 16 | outline: 17 | 'bg-zinc-100 text-zinc-900 hover:bg-zinc-200 outline outline-1 outline-zinc-300', 18 | subtle: 19 | 'hover:bg-zinc-200 bg-zinc-100 text-zinc-900', 20 | ghost: 21 | 'bg-transparent hover:bg-zinc-100 text-zinc-800 data-[state=open]:bg-transparent data-[state=open]:bg-transparent', 22 | link: 'bg-transparent dark:bg-transparent underline-offset-4 hover:underline text-slate-900 dark:text-slate-100 hover:bg-transparent dark:hover:bg-transparent', 23 | }, 24 | size: { 25 | default: 'h-10 py-2 px-4', 26 | sm: 'h-9 px-2 rounded-md', 27 | xs: 'h-8 px-1.5 rounded-sm', 28 | lg: 'h-11 px-8 rounded-md', 29 | }, 30 | }, 31 | defaultVariants: { 32 | variant: 'default', 33 | size: 'default', 34 | }, 35 | } 36 | ) 37 | 38 | export interface ButtonProps 39 | extends React.ButtonHTMLAttributes, 40 | VariantProps { 41 | isLoading?: boolean 42 | } 43 | 44 | const Button = React.forwardRef( 45 | ({ className, children, variant, isLoading, size, ...props }, ref) => { 46 | return ( 47 | 55 | ) 56 | } 57 | ) 58 | Button.displayName = 'Button' 59 | 60 | export { Button, buttonVariants } -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | generator client { 2 | provider = "prisma-client-js" 3 | previewFeatures = ["driverAdapters"] 4 | } 5 | 6 | datasource db { 7 | provider = "postgresql" 8 | url = env("DATABASE_URL") 9 | relationMode = "prisma" 10 | } 11 | 12 | model Account { 13 | id String @id @default(cuid()) 14 | userId String 15 | type String 16 | provider String 17 | providerAccountId String 18 | refresh_token String? @db.Text 19 | access_token String? @db.Text 20 | expires_at Int? 21 | token_type String? 22 | scope String? 23 | id_token String? @db.Text 24 | session_state String? 25 | user User @relation(fields: [userId], references: [id], onDelete: Cascade) 26 | 27 | @@unique([provider, providerAccountId]) 28 | } 29 | 30 | model Session { 31 | id String @id @default(cuid()) 32 | sessionToken String @unique 33 | userId String 34 | expires DateTime 35 | user User @relation(fields: [userId], references: [id], onDelete: Cascade) 36 | } 37 | 38 | model User { 39 | id String @id @default(cuid()) 40 | email String @unique 41 | name String? 42 | image String? 43 | balance Float @default(0) 44 | emailVerified DateTime? @map("email_verified") 45 | role Int @default(0) 46 | history History[] 47 | Account Account[] 48 | Session Session[] 49 | } 50 | 51 | model History { 52 | id Int @id @default(autoincrement()) 53 | caseId Int 54 | itemId Int 55 | userId String 56 | user User @relation(fields: [userId], references: [id]) 57 | case Case @relation(fields: [caseId], references: [id]) 58 | item Item @relation(fields: [itemId], references: [id]) 59 | } 60 | 61 | model Case { 62 | id Int @id @default(autoincrement()) 63 | name String 64 | image String 65 | price Float 66 | items Item[] @relation("CaseToItem") 67 | odds Odds[] 68 | history History[] 69 | } 70 | model Odds { 71 | id Int @id @default(autoincrement()) 72 | caseId Int 73 | itemId Int 74 | case Case @relation(fields: [caseId], references: [id]) 75 | item Item @relation(fields: [itemId], references: [id]) 76 | Odds Float 77 | } 78 | 79 | model Item { 80 | id Int @id @default(autoincrement()) 81 | name String 82 | rarity Int 83 | price Float 84 | imageURL String 85 | cases Case[] @relation("CaseToItem") 86 | history History[] 87 | odds Odds[] 88 | } 89 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'tailwindcss' 2 | 3 | const config: Config = { 4 | content: [ 5 | './src/pages/**/*.{js,ts,jsx,tsx,mdx}', 6 | './src/components/**/*.{js,ts,jsx,tsx,mdx}', 7 | './src/app/**/*.{js,ts,jsx,tsx,mdx}', 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 13 | 'gradient-conic': 14 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', 15 | }, 16 | keyframes: { 17 | "accordion-down": { 18 | from: { height: "0" }, 19 | to: { height: "var(--radix-accordion-content-height)" }, 20 | }, 21 | "accordion-up": { 22 | from: { height: "var(--radix-accordion-content-height)" }, 23 | to: { height: "0" }, 24 | }, 25 | }, 26 | animation: { 27 | "accordion-down": "accordion-down 0.2s ease-out", 28 | "accordion-up": "accordion-up 0.2s ease-out", 29 | }, 30 | colors: { 31 | border: "hsl(var(--border))", 32 | input: "hsl(var(--input))", 33 | ring: "hsl(var(--ring))", 34 | background: "hsl(var(--background))", 35 | foreground: "hsl(var(--foreground))", 36 | primary: { 37 | DEFAULT: "hsl(var(--primary))", 38 | foreground: "hsl(var(--primary-foreground))", 39 | }, 40 | secondary: { 41 | DEFAULT: "hsl(var(--secondary))", 42 | foreground: "hsl(var(--secondary-foreground))", 43 | }, 44 | destructive: { 45 | DEFAULT: "hsl(var(--destructive))", 46 | foreground: "hsl(var(--destructive-foreground))", 47 | }, 48 | sucess: { 49 | DEFAULT: "hsl(var(--sucess))", 50 | foreground: "hsl(var(--sucess-foreground))", 51 | }, 52 | muted: { 53 | DEFAULT: "hsl(var(--muted))", 54 | foreground: "hsl(var(--muted-foreground))", 55 | }, 56 | accent: { 57 | DEFAULT: "hsl(var(--accent))", 58 | foreground: "hsl(var(--accent-foreground))", 59 | }, 60 | popover: { 61 | DEFAULT: "hsl(var(--popover))", 62 | foreground: "hsl(var(--popover-foreground))", 63 | }, 64 | card: { 65 | DEFAULT: "hsl(var(--card))", 66 | foreground: "hsl(var(--card-foreground))", 67 | }, 68 | }, 69 | borderRadius: { 70 | lg: `var(--radius)`, 71 | md: `calc(var(--radius) - 2px)`, 72 | sm: "calc(var(--radius) - 4px)", 73 | }, 74 | }, 75 | }, 76 | plugins: [], 77 | } 78 | export default config 79 | -------------------------------------------------------------------------------- /src/components/ui/pagination.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react" 3 | 4 | import { cn } from "@/lib/utils" 5 | import { ButtonProps, buttonVariants } from "@/components/ui/button" 6 | 7 | const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => ( 8 |