@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.
- package/README.md +36 -0
- package/__registry__/index.ts +493 -0
- package/app/(auth)/layout.tsx +18 -0
- package/app/(auth)/login/page.tsx +152 -0
- package/app/(protected)/component/[id]/page.tsx +48 -0
- package/app/(protected)/component/page.tsx +151 -0
- package/app/(protected)/docs/page.tsx +222 -0
- package/app/account/page.tsx +109 -0
- package/app/api/me/route.ts +24 -0
- package/app/globals.css +68 -0
- package/app/icon.png +0 -0
- package/app/layout.tsx +43 -0
- package/app/page.tsx +22 -0
- package/app/pricing/page.tsx +12 -0
- package/bin/intex.ts +19 -0
- package/components/smooth-scroll.tsx +26 -0
- package/components/toast.tsx +101 -0
- package/components/ui/IndustryProof.tsx +159 -0
- package/components/ui/ShowcaseContainer.tsx +497 -0
- package/components/ui/dot-cursor.tsx +108 -0
- package/components/ui/featuredComponents.tsx +126 -0
- package/components/ui/footer.tsx +59 -0
- package/components/ui/hero.tsx +85 -0
- package/components/ui/navbar.tsx +337 -0
- package/components/ui/pricing.tsx +163 -0
- package/eslint.config.mjs +18 -0
- package/hooks/use-component-search.tsx +52 -0
- package/lib/actions/auth.action.ts +88 -0
- package/lib/auth.ts +18 -0
- package/lib/email.ts +46 -0
- package/lib/prisma.ts +14 -0
- package/lib/registry.ts +17 -0
- package/lib/utils.ts +6 -0
- package/middleware/middleware.ts +21 -0
- package/next.config.ts +7 -0
- package/package.json +61 -0
- package/postcss.config.mjs +7 -0
- package/prisma/migrations/20260303172729_init/migration.sql +21 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +22 -0
- package/prisma.config.ts +14 -0
- package/public/compVideos/neubrutal-button.mp4 +0 -0
- package/public/fonts/BeVietnamPro-ExtraBold.otf +0 -0
- package/public/fonts/CartographCF-Regular.ttf +0 -0
- package/public/fonts/Hoshiko-Satsuki.ttf +0 -0
- package/public/fonts/Switzer-Regular.otf +0 -0
- package/public/images/PricingSlash.svg +58 -0
- package/public/images/slash_1.svg +59 -0
- package/public/images/slash_2.svg +18 -0
- package/public/video/hero_video.mp4 +0 -0
- package/registry/details/buttons/neubrutal-button-details.tsx +146 -0
- package/registry/details/cursor/dot-cursor-details.tsx +11 -0
- package/registry/details/navbar/floating-navbar-details.tsx +11 -0
- package/registry/details/scrollbars/minimal-scrollbar-details.tsx +0 -0
- package/registry/index.ts +35 -0
- package/registry/ui/buttons/neubrutal-button.tsx +33 -0
- package/registry/ui/cursors/dot-cursor.tsx +108 -0
- package/registry/ui/navbars/floating-navbar.tsx +99 -0
- package/registry/ui/scrollbars/minimal-scrollbar.tsx +203 -0
- package/scripts/build-registry.ts +60 -0
- package/src/commands/add.ts +40 -0
- package/src/commands/init.ts +75 -0
- package/src/commands/list.ts +44 -0
- package/src/index.ts +35 -0
- package/src/utils/get-pkg-manager.ts +7 -0
- 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;
|
package/lib/registry.ts
ADDED
|
@@ -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,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
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,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,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
|
+
}
|
package/prisma.config.ts
ADDED
|
@@ -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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|