Skip to content

Commit 8d9536f

Browse files
Ranveer SoniRizonFTW
andauthored
feat(add): introduce new components, hooks, and API routes; update existing files and configurations (#2)
* feat(add): introduce new components, hooks, and API routes; update existing files and configurations * shift every single thing to react-icons and more bug fixes * shift every single thing to react-icons and more bug fixes * shift every single thing to react-icons and more bug fixes * everything works! * chore: update change logs * fix: dashboard navbar text color and hover * feat: update database from PostgreSQL to MySQL (wordings not like db db), add system status API, and enhance footer with status display * feat(add): new nsfw warning * refactor: improve user authentication logic and enhance API response handling * chore: update changelog * feat: implement NSFW warning modal with age verification for links containing adult content --------- Co-authored-by: RizonFTW <rizon@nodebyte.co.uk> Co-authored-by: Rizon <kye021@hotmail.co.uk>
1 parent d4b500d commit 8d9536f

File tree

172 files changed

+4744
-3140
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

172 files changed

+4744
-3140
lines changed

.env.template

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Application
2+
NODE_ENV=development
3+
NEXTAUTH_URL=http://localhost:3000
4+
NEXTAUTH_SECRET=replace-with-strong-secret
5+
NEXT_PUBLIC_BASE_URL=http://localhost:3000
6+
NEXT_PUBLIC_SITE_DOMAIN=localhost
7+
NEXT_PUBLIC_APP_URL=http://localhost:3000
8+
9+
# Auth Providers (optional)
10+
GOOGLE_CLIENT_ID=
11+
GOOGLE_CLIENT_SECRET=
12+
GITHUB_CLIENT_ID=
13+
GITHUB_CLIENT_SECRET=
14+
DISCORD_CLIENT_ID=
15+
DISCORD_CLIENT_SECRET=
16+
17+
# Internal
18+
INTERNAL_API_KEY=internal-api-access
19+
GITHUB_TOKEN=
20+
NEXT_PUBLIC_GIT_HASH=dev
21+
22+
# Databases
23+
# MySQL Prisma connection URL (target)
24+
# Example: mysql://USER:PASSWORD@HOST:PORT/DATABASE
25+
DATABASE_URL=
26+
27+
# Source MongoDB URL for migration only
28+
# Example: mongodb://USER:PASSWORD@localhost:27017/lynkr
29+
MONGODB_URL=

CHANGELOG.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
- Bulk link import/export
1717
- Link scheduling
1818

19+
## [0.3.0] - 2025-08-08
20+
21+
### 🚀 New Features
22+
23+
- ✨ Update the codebase to use TypeScript
24+
- ✨ Formating changes
25+
- ✨ Added New CLI to convert Mongodb data to MySQL
26+
27+
### ⚡ Improvements
28+
29+
- ⚡ Everything uses React-Icons or Lucide-React insted of SVGs to provide consistency.
30+
- ⚡ Added Username and Bio to onboarding (Optional)
31+
- ⚡ Added a wanring when clicking on "NSFW" links
32+
33+
### 🔧 Bug Fixes
34+
35+
- 🐛 Fixed the og
36+
- 🐛 Fixed API praising bugs
37+
1938
## [0.2.0] - 2025-08-07
2039

2140
### 🚀 New Features
@@ -168,7 +187,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
168187

169188
- **Technical Features**
170189
- Server-side rendering with Next.js 13
171-
- PostgreSQL database with Prisma ORM
190+
- MySQL database with Prisma ORM
172191
- Responsive design with Tailwind CSS
173192
- Component library with Radix UI
174193
- Smooth animations with Framer Motion

components/core/account-linking/account-linking.jsx renamed to components/core/account-linking/account-linking.tsx

Lines changed: 113 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import { useState, useEffect } from 'react';
1+
import { useState, useEffect, useCallback } from 'react';
22
import { signIn, useSession } from 'next-auth/react';
33
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
44
import axios from 'axios';
55
import toast from 'react-hot-toast';
66
import LoadingDots from '@/components/utils/loading-dots';
7-
import GoogleIcon from '@/components/utils/google-icon';
8-
import GitHubIcon from '@/components/utils/github-icon';
9-
import DiscordIcon from '@/components/utils/discord-icon';
7+
import { FaDiscord, FaGithub, FaGoogle } from 'react-icons/fa';
8+
import React from 'react';
109
import ReAuthModal from '@/components/shared/modals/reauth-modal';
1110
import { CheckCircle, X, Link as LinkIcon } from 'lucide-react';
11+
import type { IconType } from 'react-icons';
1212

1313
const AccountLinking = () => {
1414
const { data: session } = useSession();
@@ -18,24 +18,81 @@ const AccountLinking = () => {
1818
const [providerToLink, setProviderToLink] = useState(null);
1919

2020
// Fetch linked accounts
21-
const { data: linkedAccounts, isLoading, refetch } = useQuery({
21+
const {
22+
data: linkedAccounts,
23+
isLoading,
24+
refetch,
25+
} = useQuery({
2226
queryKey: ['linked-accounts'],
2327
queryFn: async () => {
2428
const response = await axios.get('/api/auth/linked-accounts');
2529
return response.data;
2630
},
27-
enabled: !!session?.user?.id,
31+
enabled: !!session?.user,
2832
});
2933

34+
// Handle re-authentication completion
35+
const handleReAuthCompletion = useCallback(async (token, linkProvider) => {
36+
try {
37+
console.log(
38+
'Re-auth completed, will start linking in 2 seconds...',
39+
linkProvider
40+
);
41+
setLoadingProvider(linkProvider);
42+
43+
// Add a delay to ensure the previous OAuth flow has fully completed
44+
// This prevents OAuth state conflicts between GitHub reauth and Discord linking
45+
setTimeout(() => {
46+
console.log('Starting Discord OAuth flow for account linking');
47+
signIn(linkProvider, {
48+
callbackUrl: `/admin/settings?tab=accounts&action=complete&token=${token}`,
49+
});
50+
}, 2000); // 2 second delay to ensure OAuth state is cleared
51+
} catch (error) {
52+
console.error('Error completing re-auth:', error);
53+
toast.error('Failed to complete account linking');
54+
setLoadingProvider(null);
55+
}
56+
}, []);
57+
58+
// Handle account linking completion
59+
const handleLinkCompletion = useCallback(
60+
async (token) => {
61+
try {
62+
console.log('Processing account link completion with token:', token);
63+
64+
const response = await axios.post('/api/auth/process-link', { token });
65+
66+
if (response.status === 200) {
67+
toast.success('Account linked successfully!');
68+
refetch(); // Refresh the linked accounts list
69+
setLoadingProvider(null);
70+
}
71+
} catch (error) {
72+
console.error('Error completing account link:', error);
73+
const message = axios.isAxiosError(error)
74+
? (error.response?.data as any)?.message
75+
: undefined;
76+
toast.error(message || 'Failed to link account');
77+
setLoadingProvider(null);
78+
}
79+
},
80+
[refetch]
81+
);
82+
3083
// Check URL parameters and handle re-authentication flow
3184
useEffect(() => {
3285
const urlParams = new URLSearchParams(window.location.search);
3386
const action = urlParams.get('action');
3487
const token = urlParams.get('token');
3588
const linkProvider = urlParams.get('linkProvider');
36-
37-
console.log('AccountLinking useEffect triggered with:', { action, token, linkProvider });
38-
89+
90+
console.log('AccountLinking useEffect triggered with:', {
91+
action,
92+
token,
93+
linkProvider,
94+
});
95+
3996
if (action === 'link') {
4097
// Refresh the linked accounts data after regular sign-in
4198
refetch();
@@ -44,63 +101,26 @@ const AccountLinking = () => {
44101
window.history.replaceState({}, '', '/admin/settings?tab=accounts');
45102
} else if (action === 'reauth' && token && linkProvider) {
46103
// Handle re-authentication completion
47-
console.log('Re-authentication completed, preparing to link:', linkProvider);
48-
104+
console.log(
105+
'Re-authentication completed, preparing to link:',
106+
linkProvider
107+
);
108+
49109
// Clear the URL parameters first to prevent re-triggering
50110
const newUrl = window.location.pathname + '?tab=accounts';
51111
window.history.replaceState({}, '', newUrl);
52-
112+
53113
// Add a delay and then trigger the Discord linking
54114
handleReAuthCompletion(token, linkProvider);
55115
} else if (action === 'complete' && token) {
56116
// Handle account linking completion
57117
console.log('CALLING handleLinkCompletion with token:', token);
58118
handleLinkCompletion(token);
59-
119+
60120
// Clear URL immediately to prevent duplicate calls
61121
window.history.replaceState({}, '', '/admin/settings?tab=accounts');
62122
}
63-
}, []); // Remove refetch dependency to prevent infinite re-runs
64-
65-
// Handle re-authentication completion
66-
const handleReAuthCompletion = async (token, linkProvider) => {
67-
try {
68-
console.log('Re-auth completed, will start linking in 2 seconds...', linkProvider);
69-
setLoadingProvider(linkProvider);
70-
71-
// Add a delay to ensure the previous OAuth flow has fully completed
72-
// This prevents OAuth state conflicts between GitHub reauth and Discord linking
73-
setTimeout(() => {
74-
console.log('Starting Discord OAuth flow for account linking');
75-
signIn(linkProvider, {
76-
callbackUrl: `/admin/settings?tab=accounts&action=complete&token=${token}`,
77-
});
78-
}, 2000); // 2 second delay to ensure OAuth state is cleared
79-
} catch (error) {
80-
console.error('Error completing re-auth:', error);
81-
toast.error('Failed to complete account linking');
82-
setLoadingProvider(null);
83-
}
84-
};
85-
86-
// Handle account linking completion
87-
const handleLinkCompletion = async (token) => {
88-
try {
89-
console.log('Processing account link completion with token:', token);
90-
91-
const response = await axios.post('/api/auth/process-link', { token });
92-
93-
if (response.status === 200) {
94-
toast.success('Account linked successfully!');
95-
refetch(); // Refresh the linked accounts list
96-
setLoadingProvider(null);
97-
}
98-
} catch (error) {
99-
console.error('Error completing account link:', error);
100-
toast.error(error.response?.data?.message || 'Failed to link account');
101-
setLoadingProvider(null);
102-
}
103-
};
123+
}, [refetch, handleLinkCompletion, handleReAuthCompletion]);
104124

105125
// Unlink account mutation
106126
const unlinkMutation = useMutation({
@@ -111,31 +131,34 @@ const AccountLinking = () => {
111131
queryClient.invalidateQueries(['linked-accounts']);
112132
toast.success('Account unlinked successfully');
113133
},
114-
onError: (error) => {
115-
toast.error(error.response?.data?.message || 'Failed to unlink account');
134+
onError: (error: unknown) => {
135+
const message = axios.isAxiosError(error)
136+
? (error.response?.data as any)?.message
137+
: undefined;
138+
toast.error(message || 'Failed to unlink account');
116139
},
117140
});
118141

119142
const providers = [
120143
{
121144
id: 'google',
122145
name: 'Google',
123-
icon: GoogleIcon,
146+
icon: FaGoogle,
124147
color: 'border-red-500 text-red-500 hover:bg-red-50',
125148
},
126149
{
127150
id: 'github',
128151
name: 'GitHub',
129-
icon: GitHubIcon,
152+
icon: FaGithub,
130153
color: 'border-gray-800 text-gray-800 hover:bg-gray-50',
131154
},
132155
{
133156
id: 'discord',
134157
name: 'Discord',
135-
icon: DiscordIcon,
158+
icon: FaDiscord,
136159
color: 'border-indigo-600 text-indigo-600 hover:bg-indigo-50',
137160
},
138-
];
161+
] as const;
139162

140163
const handleLinkAccount = (providerId) => {
141164
setProviderToLink(providerId);
@@ -161,7 +184,7 @@ const AccountLinking = () => {
161184
};
162185

163186
const isLinked = (providerId) => {
164-
return linkedAccounts?.some(account => account.provider === providerId);
187+
return linkedAccounts?.some((account) => account.provider === providerId);
165188
};
166189

167190
if (isLoading) {
@@ -177,18 +200,22 @@ const AccountLinking = () => {
177200
<div className="p-6 bg-white border rounded-2xl">
178201
<div className="flex items-center gap-3 mb-6">
179202
<LinkIcon className="w-5 h-5 text-gray-600" />
180-
<h3 className="text-lg font-semibold text-gray-900">Linked Accounts</h3>
203+
<h3 className="text-lg font-semibold text-gray-900">
204+
Linked Accounts
205+
</h3>
181206
</div>
182-
207+
183208
<p className="mb-6 text-sm text-gray-600">
184209
Link multiple accounts to sign in with any of them.
185210
</p>
186211

187212
<div className="space-y-4">
188213
{providers.map((provider) => {
189214
const linked = isLinked(provider.id);
190-
const IconComponent = provider.icon;
191-
215+
const IconComponent = provider.icon as React.ComponentType<
216+
React.SVGProps<SVGSVGElement>
217+
>;
218+
192219
return (
193220
<div
194221
key={provider.id}
@@ -197,22 +224,24 @@ const AccountLinking = () => {
197224
<div className="flex items-center gap-3">
198225
<IconComponent />
199226
<div>
200-
<h4 className="font-medium text-gray-900">{provider.name}</h4>
227+
<h4 className="font-medium text-gray-900">
228+
{provider.name}
229+
</h4>
201230
<p className="text-sm text-gray-500">
202231
{linked ? 'Connected' : 'Not connected'}
203232
</p>
204233
</div>
205234
</div>
206235

207236
<div className="flex items-center gap-2">
208-
{linked && (
209-
<CheckCircle className="w-5 h-5 text-green-500" />
210-
)}
211-
237+
{linked && <CheckCircle className="w-5 h-5 text-green-500" />}
238+
212239
{linked ? (
213240
<button
214241
onClick={() => handleUnlinkAccount(provider.id)}
215-
disabled={unlinkMutation.isLoading || linkedAccounts?.length <= 1}
242+
disabled={
243+
unlinkMutation.isLoading || linkedAccounts?.length <= 1
244+
}
216245
className={`px-3 py-1.5 text-sm border rounded-md transition-colors ${
217246
linkedAccounts?.length <= 1
218247
? 'border-gray-200 text-gray-400 cursor-not-allowed'
@@ -253,15 +282,24 @@ const AccountLinking = () => {
253282
<div className="p-4 mt-6 rounded-lg bg-blue-50">
254283
<div className="flex items-start gap-3">
255284
<div className="flex-shrink-0">
256-
<svg className="w-5 h-5 text-blue-400" viewBox="0 0 20 20" fill="currentColor">
257-
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clipRule="evenodd" />
285+
<svg
286+
className="w-5 h-5 text-blue-400"
287+
viewBox="0 0 20 20"
288+
fill="currentColor"
289+
>
290+
<path
291+
fillRule="evenodd"
292+
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
293+
clipRule="evenodd"
294+
/>
258295
</svg>
259296
</div>
260297
<div className="flex-1 min-w-0 text-sm text-blue-700">
261298
<p className="font-medium">Security First</p>
262299
<p className="mt-1">
263-
For your security, you'll need to re-authenticate with your original account
264-
before linking any new accounts. This prevents unauthorized account linking.
300+
For your security, you'll need to re-authenticate with your
301+
original account before linking any new accounts. This prevents
302+
unauthorized account linking.
265303
</p>
266304
</div>
267305
</div>
File renamed without changes.

0 commit comments

Comments
 (0)