@ghatak/slash-ui 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +36 -0
  2. package/__registry__/index.ts +493 -0
  3. package/app/(auth)/layout.tsx +18 -0
  4. package/app/(auth)/login/page.tsx +152 -0
  5. package/app/(protected)/component/[id]/page.tsx +48 -0
  6. package/app/(protected)/component/page.tsx +151 -0
  7. package/app/(protected)/docs/page.tsx +222 -0
  8. package/app/account/page.tsx +109 -0
  9. package/app/api/me/route.ts +24 -0
  10. package/app/globals.css +68 -0
  11. package/app/icon.png +0 -0
  12. package/app/layout.tsx +43 -0
  13. package/app/page.tsx +22 -0
  14. package/app/pricing/page.tsx +12 -0
  15. package/bin/intex.ts +19 -0
  16. package/components/smooth-scroll.tsx +26 -0
  17. package/components/toast.tsx +101 -0
  18. package/components/ui/IndustryProof.tsx +159 -0
  19. package/components/ui/ShowcaseContainer.tsx +497 -0
  20. package/components/ui/dot-cursor.tsx +108 -0
  21. package/components/ui/featuredComponents.tsx +126 -0
  22. package/components/ui/footer.tsx +59 -0
  23. package/components/ui/hero.tsx +85 -0
  24. package/components/ui/navbar.tsx +337 -0
  25. package/components/ui/pricing.tsx +163 -0
  26. package/eslint.config.mjs +18 -0
  27. package/hooks/use-component-search.tsx +52 -0
  28. package/lib/actions/auth.action.ts +88 -0
  29. package/lib/auth.ts +18 -0
  30. package/lib/email.ts +46 -0
  31. package/lib/prisma.ts +14 -0
  32. package/lib/registry.ts +17 -0
  33. package/lib/utils.ts +6 -0
  34. package/middleware/middleware.ts +21 -0
  35. package/next.config.ts +7 -0
  36. package/package.json +61 -0
  37. package/postcss.config.mjs +7 -0
  38. package/prisma/migrations/20260303172729_init/migration.sql +21 -0
  39. package/prisma/migrations/migration_lock.toml +3 -0
  40. package/prisma/schema.prisma +22 -0
  41. package/prisma.config.ts +14 -0
  42. package/public/compVideos/neubrutal-button.mp4 +0 -0
  43. package/public/fonts/BeVietnamPro-ExtraBold.otf +0 -0
  44. package/public/fonts/CartographCF-Regular.ttf +0 -0
  45. package/public/fonts/Hoshiko-Satsuki.ttf +0 -0
  46. package/public/fonts/Switzer-Regular.otf +0 -0
  47. package/public/images/PricingSlash.svg +58 -0
  48. package/public/images/slash_1.svg +59 -0
  49. package/public/images/slash_2.svg +18 -0
  50. package/public/video/hero_video.mp4 +0 -0
  51. package/registry/details/buttons/neubrutal-button-details.tsx +146 -0
  52. package/registry/details/cursor/dot-cursor-details.tsx +11 -0
  53. package/registry/details/navbar/floating-navbar-details.tsx +11 -0
  54. package/registry/details/scrollbars/minimal-scrollbar-details.tsx +0 -0
  55. package/registry/index.ts +35 -0
  56. package/registry/ui/buttons/neubrutal-button.tsx +33 -0
  57. package/registry/ui/cursors/dot-cursor.tsx +108 -0
  58. package/registry/ui/navbars/floating-navbar.tsx +99 -0
  59. package/registry/ui/scrollbars/minimal-scrollbar.tsx +203 -0
  60. package/scripts/build-registry.ts +60 -0
  61. package/src/commands/add.ts +40 -0
  62. package/src/commands/init.ts +75 -0
  63. package/src/commands/list.ts +44 -0
  64. package/src/index.ts +35 -0
  65. package/src/utils/get-pkg-manager.ts +7 -0
  66. package/tsconfig.json +34 -0
@@ -0,0 +1,163 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+ import Link from 'next/link';
5
+ import { Check, Zap } from 'lucide-react';
6
+
7
+ const PricingCard = ({
8
+
9
+ tier,
10
+ price,
11
+ description,
12
+ features,
13
+ href,
14
+ isHighlighted = false,
15
+ }: {
16
+ tier: string;
17
+ price: string;
18
+ description: string;
19
+ features: string[];
20
+ href: string;
21
+ isHighlighted?: boolean;
22
+ }) => {
23
+ return (
24
+ <div
25
+ className={`relative flex flex-col p-8 rounded-3xl border transition-all duration-500 overflow-hidden h-full ${
26
+ isHighlighted
27
+ ? 'border-white bg-neutral-900 shadow-[0_0_50px_-12px_rgba(255,255,255,0.2)]'
28
+ : 'border-white/10 bg-neutral-900 text-zinc-400'
29
+ }`}
30
+ >
31
+ {isHighlighted && (
32
+ <>
33
+ <div className='absolute -top-[20%] -right-[20%] w-[70%] h-[70%] bg-white/10 blur-[100px] pointer-events-none' />
34
+
35
+ <div className='absolute inset-0 pointer-events-none z-0'>
36
+ <img
37
+ src='/images/PricingSlash.svg'
38
+ alt=''
39
+ className='w-full h-full object-cover scale-150 rotate-[-5deg] opacity-90'
40
+ />
41
+ </div>
42
+
43
+ <div className='absolute inset-0 rounded-3xl border border-white/20 animate-pulse pointer-events-none' />
44
+ </>
45
+ )}
46
+
47
+ <div
48
+ className={`relative flex flex-col h-full ${isHighlighted ? 'z-10 mix-blend-difference' : 'z-10'}`}
49
+ >
50
+ <div className='mb-8'>
51
+ <h3
52
+ className={`text-xs uppercase font-mono tracking-widest ${isHighlighted ? 'text-white' : 'text-zinc-500'}`}
53
+ >
54
+ {tier}
55
+ </h3>
56
+ <p
57
+ className={`text-sm mt-3 font-medium leading-relaxed ${isHighlighted ? 'text-white' : 'text-zinc-400'}`}
58
+ >
59
+ {description}
60
+ </p>
61
+ </div>
62
+
63
+ <div className='mb-8 flex items-baseline gap-1'>
64
+ <span
65
+ className={`text-6xl font-black tracking-tighter ${isHighlighted ? 'text-white' : 'text-zinc-100'}`}
66
+ >
67
+ {price}
68
+ </span>
69
+ <span className='text-zinc-500 text-[10px] uppercase tracking-widest ml-2'>
70
+ Lifetime Access
71
+ </span>
72
+ </div>
73
+
74
+ <div className='space-y-4 mb-10 flex-grow'>
75
+ {features.map((feature, index) => (
76
+ <div
77
+ key={index}
78
+ className='flex items-center gap-3 text-[13px] font-cartographCF'
79
+ >
80
+ <Check
81
+ size={14}
82
+ className={isHighlighted ? 'text-white' : 'text-zinc-600'}
83
+ />
84
+ <span className={isHighlighted ? 'text-white' : 'text-zinc-500'}>
85
+ {feature}
86
+ </span>
87
+ </div>
88
+ ))}
89
+ </div>
90
+
91
+ <div className='relative z-20'>
92
+ <Link
93
+ href={'https://app.archway.finance/payment-requests/HDGAY/public'}
94
+ className={`w-full py-4 rounded-xl font-beVietnamPro text-xs transition-all duration-500 flex items-center justify-center cursor-pointer ${
95
+ isHighlighted
96
+ ? 'bg-white text-black hover:shadow-[0_0_20px_rgba(255,255,255,0.4)] hover:bg-zinc-100'
97
+ : 'bg-transparent text-white border border-white/20 hover:border-white/60'
98
+ }`}
99
+ >
100
+ Select {tier}
101
+ </Link>
102
+ </div>
103
+ </div>
104
+ </div>
105
+ );
106
+ };
107
+
108
+ const Pricing = () => {
109
+ return (
110
+ <section className='w-full py-32 px-8 '>
111
+ <div className='max-w-4xl mx-auto'>
112
+ <div className='flex flex-col items-center text-center mb-20'>
113
+ <div className='flex items-center gap-2 px-3 py-1 rounded-full border border-white/10 bg-zinc-950 mb-6'>
114
+ <Zap size={12} className='text-white' />
115
+ <span className='text-[10px] font-bold text-zinc-400 uppercase tracking-widest'>
116
+ Pricing Plans
117
+ </span>
118
+ </div>
119
+ <h2 className='text-5xl font-black text-white uppercase tracking-tighter mb-6'>
120
+ Unlock the Full Library
121
+ </h2>
122
+ <p className='text-zinc-500 text-sm max-w-lg leading-relaxed'>
123
+ Professional-grade UI components for Next.js and Tailwind. Choose
124
+ the pack that fits your scale.
125
+ </p>
126
+ </div>
127
+
128
+ <div className='grid grid-cols-1 md:grid-cols-2 gap-6 items-stretch'>
129
+ <PricingCard
130
+ tier='Standard'
131
+ price='$50'
132
+ href='https://app.archway.finance/payment-requests/HDGAY/public'
133
+ description='Perfect for individual projects and hobbyists.'
134
+ features={[
135
+ '30+ Open Source Components',
136
+ 'React / Tailwind Templates',
137
+ 'Community Support',
138
+ 'Lifetime Access',
139
+ ]}
140
+ />
141
+
142
+ <PricingCard
143
+
144
+ tier='Premium'
145
+ isHighlighted={true}
146
+ price='$129'
147
+ href='https://app.archway.finance/payment-requests/HDGAY/public'
148
+ description='Full access for professionals and agency work.'
149
+ features={[
150
+ '100+ Premium Components',
151
+ 'Agency/Commercial License',
152
+ 'Priority Feature Requests',
153
+ 'Figma Design Files',
154
+ 'Private Discord Access',
155
+ ]}
156
+ />
157
+ </div>
158
+ </div>
159
+ </section>
160
+ );
161
+ };
162
+
163
+ export default Pricing;
@@ -0,0 +1,18 @@
1
+ import { defineConfig, globalIgnores } from "eslint/config";
2
+ import nextVitals from "eslint-config-next/core-web-vitals";
3
+ import nextTs from "eslint-config-next/typescript";
4
+
5
+ const eslintConfig = defineConfig([
6
+ ...nextVitals,
7
+ ...nextTs,
8
+ // Override default ignores of eslint-config-next.
9
+ globalIgnores([
10
+ // Default ignores of eslint-config-next:
11
+ ".next/**",
12
+ "out/**",
13
+ "build/**",
14
+ "next-env.d.ts",
15
+ ]),
16
+ ]);
17
+
18
+ export default eslintConfig;
@@ -0,0 +1,52 @@
1
+ 'use client';
2
+
3
+ import React, { createContext, useContext, useState, useMemo } from 'react';
4
+ import { Home, Box } from 'lucide-react';
5
+ import { Index } from '@/__registry__';
6
+
7
+ const SearchContext = createContext<any>(undefined);
8
+
9
+ export function SearchProvider({ children }: { children: React.ReactNode }) {
10
+ const [searchQuery, setSearchQuery] = useState('');
11
+
12
+ const staticPages = useMemo(() => [
13
+ { icon: Home, label: 'Home', category: 'Pages', path: '/' },
14
+ ], []);
15
+
16
+ const componentsList = useMemo(() => {
17
+ return Object.values(Index['default'] || {}).map((comp: any) => ({
18
+ icon: Box,
19
+ label: comp.name,
20
+ category: 'Components',
21
+ path: `/component/${comp.name}`,
22
+ }));
23
+ }, []);
24
+
25
+ const allItems = useMemo(() => [...staticPages, ...componentsList], [staticPages, componentsList]);
26
+
27
+ const filteredItems = useMemo(() => {
28
+ return allItems.filter((item) =>
29
+ item.label.toLowerCase().includes(searchQuery.toLowerCase())
30
+ );
31
+ }, [searchQuery, allItems]);
32
+
33
+ return (
34
+ <SearchContext.Provider
35
+ value={{
36
+ searchQuery,
37
+ setSearchQuery,
38
+ filteredItems,
39
+ staticPages,
40
+ totalComponents: componentsList.length,
41
+ }}
42
+ >
43
+ {children}
44
+ </SearchContext.Provider>
45
+ );
46
+ }
47
+
48
+ export const useSearch = () => {
49
+ const context = useContext(SearchContext);
50
+ if (!context) throw new Error('useSearch must be used within a SearchProvider');
51
+ return context;
52
+ };
@@ -0,0 +1,88 @@
1
+ 'use server';
2
+
3
+ import bcrypt from 'bcryptjs';
4
+ import jwt from 'jsonwebtoken';
5
+ import { prisma } from '@/lib/prisma';
6
+ import { randomInt } from 'crypto';
7
+ import { sendOtpEmail } from '../email';
8
+ import { cookies } from 'next/headers';
9
+ import { redirect } from 'next/navigation';
10
+
11
+ export async function sendOtp(formData: FormData) {
12
+ const email = formData.get('email') as string;
13
+
14
+ const rawOtp = randomInt(100000, 999999).toString();
15
+ const hashedOtp = await bcrypt.hash(rawOtp, 10);
16
+
17
+ await prisma.otp.create({
18
+ data: {
19
+ email,
20
+ code: hashedOtp,
21
+ expiresAt: new Date(Date.now() + 5 * 60 * 1000),
22
+ },
23
+ });
24
+
25
+ await sendOtpEmail(email, rawOtp);
26
+
27
+ return { success: true };
28
+ }
29
+
30
+ export async function verifyOtp(formData: FormData) {
31
+ const email = formData.get('email') as string;
32
+ const otp = formData.get('otp') as string;
33
+
34
+ try {
35
+ const record = await prisma.otp.findFirst({
36
+ where: {
37
+ email,
38
+ expiresAt: { gt: new Date() },
39
+ },
40
+ orderBy: { createdAt: 'desc' },
41
+ });
42
+
43
+ if (!record) {
44
+ return { success: false, message: 'OTP expired' };
45
+ }
46
+
47
+ const valid = await bcrypt.compare(otp, record.code);
48
+
49
+ if (!valid) {
50
+ return { success: false, message: 'Invalid OTP' };
51
+ }
52
+
53
+ await prisma.otp.deleteMany({ where: { email } });
54
+
55
+ let user = await prisma.user.findUnique({ where: { email } });
56
+
57
+ if (!user) {
58
+ user = await prisma.user.create({ data: { email } });
59
+ }
60
+
61
+ const token = jwt.sign(
62
+ { email: user.email, id: user.id },
63
+ process.env.JWT_SECRET!,
64
+ { expiresIn: '7d' },
65
+ );
66
+
67
+ const cookieStore = await cookies();
68
+
69
+ cookieStore.set('access_token', token, {
70
+ httpOnly: true,
71
+ secure: process.env.NODE_ENV === 'production',
72
+ sameSite: 'lax',
73
+ path: '/',
74
+ maxAge: 60 * 60 * 24 * 7,
75
+ });
76
+ } catch (err) {
77
+ console.error(err);
78
+ return { success: false, message: 'Server error' };
79
+ }
80
+
81
+ redirect('/account');
82
+ }
83
+
84
+ export async function logout() {
85
+ const cookieStore = await cookies();
86
+ cookieStore.delete('access_token');
87
+ redirect('/');
88
+ }
package/lib/auth.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { cookies } from "next/headers";
2
+ import jwt from "jsonwebtoken";
3
+
4
+ export async function getCurrentUserEmail() {
5
+ const token = (await cookies()).get("access_token")?.value;
6
+
7
+ if (!token) return null;
8
+
9
+ try {
10
+ const decoded = jwt.verify(token, process.env.JWT_SECRET!) as {
11
+ email: string;
12
+ };
13
+
14
+ return decoded.email;
15
+ } catch {
16
+ return null;
17
+ }
18
+ }
package/lib/email.ts ADDED
@@ -0,0 +1,46 @@
1
+ import nodemailer from 'nodemailer';
2
+
3
+ export async function sendOtpEmail(email: string, otp: string) {
4
+ const transporter = nodemailer.createTransport({
5
+ service: 'gmail',
6
+ auth: {
7
+ user: process.env.EMAIL_USER,
8
+ pass: process.env.EMAIL_APP_PASSWORD,
9
+ },
10
+ });
11
+
12
+ const response = await transporter.sendMail({
13
+ from: `"Slash/UI" <${process.env.EMAIL_USER}>`,
14
+ to: email,
15
+ subject: 'Your verification code',
16
+ html: `
17
+ <div style="font-family: 'Helvetica Neue', Helvetica, sans-serif; max-width: 600px; margin: 0 auto; padding: 40px 20px; color: #1a1a1a;">
18
+ <div style="margin-bottom: 32px;">
19
+ <div style="display: flex; align-items: center; gap: 8px; font-weight: 600; letter-spacing: -0.02em;">
20
+ <span>SLASH/UI</span>
21
+ </div>
22
+ </div>
23
+
24
+ <h2 style="font-size: 24px; font-weight: 600; margin-bottom: 8px; color: #000;">Your verification code</h2>
25
+ <p style="font-size: 16px; color: #666; margin-bottom: 32px;">Use the one-time code below to continue.</p>
26
+
27
+ <div style="background-color: #f7f7f7; border-radius: 12px; padding: 24px; text-align: center; margin-bottom: 32px;">
28
+ <span style="font-size: 32px; font-weight: 500; letter-spacing: 12px; color: #000; font-family: monospace;">${otp}</span>
29
+ </div>
30
+
31
+ <p style="font-size: 14px; color: #888; line-height: 1.5; margin-bottom: 24px;">
32
+ This code will expire in 5 minutes and can only be used once.<br />
33
+ For your security, do not share this code with anyone.
34
+ </p>
35
+
36
+ <div style="font-size: 14px; color: #666;">
37
+ Have fun,<br />
38
+ <strong style="color: #000;">The Slash/UI Team</strong>
39
+ </div>
40
+ </div>
41
+ `,
42
+ });
43
+
44
+ return response;
45
+ }
46
+
package/lib/prisma.ts ADDED
@@ -0,0 +1,14 @@
1
+ import { PrismaClient } from "@prisma/client";
2
+
3
+ const globalForPrisma = global as unknown as {
4
+ prisma: PrismaClient | undefined;
5
+ };
6
+
7
+ export const prisma =
8
+ globalForPrisma.prisma ??
9
+ new PrismaClient({
10
+ log: ["query"],
11
+ });
12
+
13
+ if (process.env.NODE_ENV !== "production")
14
+ globalForPrisma.prisma = prisma;
@@ -0,0 +1,17 @@
1
+ 'use server';
2
+
3
+ import fs from 'fs/promises';
4
+ import path from 'path';
5
+
6
+ export async function getComponentSource(filePaths: string[] | undefined) {
7
+ if (!filePaths || filePaths.length === 0) return
8
+
9
+ try {
10
+ const fullPath = path.join(process.cwd(), filePaths[0]);
11
+ const source = await fs.readFile(fullPath, 'utf-8');
12
+ return source;
13
+ } catch (error) {
14
+ console.error('Failed to read source code:', error);
15
+ return
16
+ }
17
+ }
package/lib/utils.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx"
2
+ import { twMerge } from "tailwind-merge"
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }
@@ -0,0 +1,21 @@
1
+ import { NextResponse } from 'next/server';
2
+ import jwt from 'jsonwebtoken';
3
+
4
+ export function middleware(req: any) {
5
+ const token = req.cookies.get('access_token')?.value;
6
+
7
+ if (!token) {
8
+ return NextResponse.redirect(new URL('/login', req.url));
9
+ }
10
+
11
+ try {
12
+ jwt.verify(token, process.env.JWT_SECRET!);
13
+ return NextResponse.next();
14
+ } catch {
15
+ return NextResponse.redirect(new URL('/login', req.url));
16
+ }
17
+ }
18
+
19
+ export const config = {
20
+ matcher: ['/account/:path*', '/docs/:path*', '/components/:path*'],
21
+ };
package/next.config.ts ADDED
@@ -0,0 +1,7 @@
1
+ import type { NextConfig } from "next";
2
+
3
+ const nextConfig: NextConfig = {
4
+ /* config options here */
5
+ };
6
+
7
+ export default nextConfig;
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@ghatak/slash-ui",
3
+ "version": "1.0.0",
4
+ "private": false,
5
+ "bin": {
6
+ "slash-ui": "./bin/index.ts"
7
+ },
8
+ "scripts": {
9
+ "dev": "next dev",
10
+ "build": "tsc",
11
+ "start": "next start",
12
+ "lint": "eslint",
13
+ "build:registry": "tsx scripts/build-registry.ts",
14
+ "postinstall": "prisma generate"
15
+ },
16
+ "dependencies": {
17
+ "@prisma/client": "^6.19.2",
18
+ "@tailwindcss/typography": "^0.5.19",
19
+ "axios": "^1.13.6",
20
+ "bcrypt": "^6.0.0",
21
+ "bcryptjs": "^3.0.3",
22
+ "clsx": "^2.1.1",
23
+ "commander": "^14.0.3",
24
+ "fs-extra": "^11.3.4",
25
+ "hamburger-react": "^2.5.2",
26
+ "jsonwebtoken": "^9.0.3",
27
+ "lenis": "^1.3.18",
28
+ "lucide-react": "^0.563.0",
29
+ "motion": "^12.31.0",
30
+ "next": "16.1.6",
31
+ "nodemailer": "^8.0.1",
32
+ "prisma": "^6.19.2",
33
+ "prompts": "^2.4.2",
34
+ "react": "19.2.3",
35
+ "react-dom": "19.2.3",
36
+ "react-hot-toast": "^2.6.0",
37
+ "react-markdown": "^10.1.0",
38
+ "react-syntax-highlighter": "^16.1.0",
39
+ "remark-gfm": "^4.0.1",
40
+ "resend": "^6.9.3",
41
+ "tailwind-merge": "^3.4.0"
42
+ },
43
+ "devDependencies": {
44
+ "@tailwindcss/postcss": "^4",
45
+ "@types/bcrypt": "^6.0.0",
46
+ "@types/bcryptjs": "^2.4.6",
47
+ "@types/fs-extra": "^11.0.4",
48
+ "@types/jsonwebtoken": "^9.0.10",
49
+ "@types/node": "^20.19.37",
50
+ "@types/nodemailer": "^7.0.11",
51
+ "@types/prompts": "^2.4.9",
52
+ "@types/react": "^19",
53
+ "@types/react-dom": "^19",
54
+ "@types/react-syntax-highlighter": "^15.5.13",
55
+ "eslint": "^9",
56
+ "eslint-config-next": "16.1.6",
57
+ "tailwindcss": "^4",
58
+ "tsx": "^4.21.0",
59
+ "typescript": "^5.9.3"
60
+ }
61
+ }
@@ -0,0 +1,7 @@
1
+ const config = {
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ },
5
+ };
6
+
7
+ export default config;
@@ -0,0 +1,21 @@
1
+ -- CreateTable
2
+ CREATE TABLE "User" (
3
+ "id" TEXT NOT NULL,
4
+ "email" TEXT NOT NULL,
5
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
6
+
7
+ CONSTRAINT "User_pkey" PRIMARY KEY ("id")
8
+ );
9
+
10
+ -- CreateTable
11
+ CREATE TABLE "Otp" (
12
+ "id" TEXT NOT NULL,
13
+ "email" TEXT NOT NULL,
14
+ "code" TEXT NOT NULL,
15
+ "expiresAt" TIMESTAMP(3) NOT NULL,
16
+
17
+ CONSTRAINT "Otp_pkey" PRIMARY KEY ("id")
18
+ );
19
+
20
+ -- CreateIndex
21
+ CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
@@ -0,0 +1,3 @@
1
+ # Please do not edit this file manually
2
+ # It should be added in your version-control system (i.e. Git)
3
+ provider = "postgresql"
@@ -0,0 +1,22 @@
1
+ generator client {
2
+ provider = "prisma-client-js"
3
+ }
4
+
5
+ datasource db {
6
+ provider = "postgresql"
7
+ url = env("DATABASE_URL")
8
+ }
9
+
10
+ model User {
11
+ id String @id @default(uuid())
12
+ email String @unique
13
+ createdAt DateTime @default(now())
14
+ }
15
+
16
+ model Otp {
17
+ id String @id @default(uuid())
18
+ email String
19
+ code String
20
+ expiresAt DateTime
21
+ createdAt DateTime @default(now())
22
+ }
@@ -0,0 +1,14 @@
1
+ // This file was generated by Prisma, and assumes you have installed the following:
2
+ // npm install --save-dev prisma dotenv
3
+ import "dotenv/config";
4
+ import { defineConfig } from "prisma/config";
5
+
6
+ export default defineConfig({
7
+ schema: "prisma/schema.prisma",
8
+ migrations: {
9
+ path: "prisma/migrations",
10
+ },
11
+ datasource: {
12
+ url: process.env["DATABASE_URL"]!,
13
+ },
14
+ });
Binary file
Binary file